Feat: bilibili user cache

pull/44/head
zijiren233 1 year ago
parent b66cb798e2
commit 2d90978c4b

@ -29,7 +29,7 @@ require (
github.com/sirupsen/logrus v1.9.3
github.com/soheilhy/cmux v0.1.5
github.com/spf13/cobra v1.8.0
github.com/synctv-org/vendors v0.1.1-0.20231213112456-9743b943a97c
github.com/synctv-org/vendors v0.1.1-0.20231217064743-ec33fdec2e09
github.com/ulule/limiter/v3 v3.11.2
github.com/zencoder/go-dash/v3 v3.0.3
github.com/zijiren233/gencontainer v0.0.0-20231213075414-f7f4c8261dca

@ -350,6 +350,8 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/synctv-org/vendors v0.1.1-0.20231213112456-9743b943a97c h1:FhIqTq7CKtqn3xvdDAxM6Po2ImcXDw2KaO/Um34KNAs=
github.com/synctv-org/vendors v0.1.1-0.20231213112456-9743b943a97c/go.mod h1:C0ZGPeF8nYsx60gePPhSrgrjrONj6F72XFHW9Mh8+vU=
github.com/synctv-org/vendors v0.1.1-0.20231217064743-ec33fdec2e09 h1:enRcXa6g7/LC8EcwUP5ccOC+2vX1TLDNHNQSrCuvFUI=
github.com/synctv-org/vendors v0.1.1-0.20231217064743-ec33fdec2e09/go.mod h1:C0ZGPeF8nYsx60gePPhSrgrjrONj6F72XFHW9Mh8+vU=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=

@ -12,74 +12,77 @@ import (
"github.com/zijiren233/gencontainer/refreshcache"
)
type AlistUserCache = refreshcache.RefreshCache[*AlistUserCacheData, string]
type AlistUserCache = refreshcache.RefreshCache[*AlistUserCacheData, struct{}]
type AlistUserCacheData struct {
Host string
Token string
Host string
Token string
Backend string
}
func NewAlistCache(userID string) *AlistUserCache {
return refreshcache.NewRefreshCache[*AlistUserCacheData](func(ctx context.Context, args ...string) (*AlistUserCacheData, error) {
var backend string
if len(args) == 1 {
backend = args[0]
func NewAlistUserCache(userID string) *AlistUserCache {
f := AlistAuthorizationCacheWithUserIDInitFunc(userID)
return refreshcache.NewRefreshCache(func(ctx context.Context, args ...struct{}) (*AlistUserCacheData, error) {
return f(ctx)
}, 0)
}
func AlistAuthorizationCacheWithUserIDInitFunc(userID string) func(ctx context.Context, args ...struct{}) (*AlistUserCacheData, error) {
return func(ctx context.Context, args ...struct{}) (*AlistUserCacheData, error) {
v, err := db.GetAlistVendor(userID)
if err != nil {
return nil, err
}
return AlistAuthorizationCacheWithUserIDInitFunc(userID, backend)(ctx)
}, time.Hour*24)
return AlistAuthorizationCacheWithConfigInitFunc(v)(ctx)
}
}
func AlistAuthorizationCacheWithConfigInitFunc(host, username, password, backend string) func(ctx context.Context, args ...string) (*AlistUserCacheData, error) {
return func(ctx context.Context, args ...string) (*AlistUserCacheData, error) {
cli := vendor.LoadAlistClient(backend)
if username == "" {
func AlistAuthorizationCacheWithConfigInitFunc(v *model.AlistVendor) func(ctx context.Context, args ...struct{}) (*AlistUserCacheData, error) {
return func(ctx context.Context, args ...struct{}) (*AlistUserCacheData, error) {
cli := vendor.LoadAlistClient(v.Backend)
if v.Username == "" {
_, err := cli.Me(ctx, &alist.MeReq{
Host: host,
Host: v.Host,
})
return &AlistUserCacheData{
Host: host,
Host: v.Host,
Backend: v.Backend,
}, err
} else {
resp, err := cli.Login(ctx, &alist.LoginReq{
Host: host,
Username: username,
Password: password,
Host: v.Host,
Username: v.UserID,
Password: string(v.HashedPassword),
Hashed: true,
})
if err != nil {
return nil, err
}
return &AlistUserCacheData{
Host: host,
Token: resp.Token,
Host: v.Host,
Token: resp.Token,
Backend: v.Backend,
}, nil
}
}
}
func AlistAuthorizationCacheWithUserIDInitFunc(userID string, backend string) func(ctx context.Context, args ...any) (*AlistUserCacheData, error) {
return func(ctx context.Context, args ...any) (*AlistUserCacheData, error) {
v, err := db.GetAlistVendor(userID)
if err != nil {
return nil, err
}
type AlistMovieCache = refreshcache.RefreshCache[*AlistMovieCacheData, *AlistUserCache]
return AlistAuthorizationCacheWithConfigInitFunc(v.Host, v.Username, v.Password, backend)(ctx)
}
}
type AlistMovieCache = refreshcache.RefreshCache[*AlistMovieCacheData, string]
func NewAlistMovieCache(user *AlistUserCache, movie *model.Movie) *AlistMovieCache {
return refreshcache.NewRefreshCache[*AlistMovieCacheData, string](NewAlistMovieCacheInitFunc(user, movie), time.Hour)
func NewAlistMovieCache(movie *model.Movie) *AlistMovieCache {
return refreshcache.NewRefreshCache(NewAlistMovieCacheInitFunc(movie), time.Hour)
}
type AlistMovieCacheData struct {
URL string
}
func NewAlistMovieCacheInitFunc(user *AlistUserCache, movie *model.Movie) func(ctx context.Context, args ...string) (*AlistMovieCacheData, error) {
return func(ctx context.Context, args ...string) (*AlistMovieCacheData, error) {
aucd, err := user.Get(ctx)
func NewAlistMovieCacheInitFunc(movie *model.Movie) func(ctx context.Context, args ...*AlistUserCache) (*AlistMovieCacheData, error) {
return func(ctx context.Context, args ...*AlistUserCache) (*AlistMovieCacheData, error) {
if len(args) == 0 {
return nil, errors.New("need alist user cache")
}
aucd, err := args[0].Get(ctx)
if err != nil {
return nil, err
}
@ -106,8 +109,8 @@ func NewAlistMovieCacheInitFunc(user *AlistUserCache, movie *model.Movie) func(c
}
if fg.Provider == "AliyundriveOpen" {
fo, err := cli.FsOther(ctx, &alist.FsOtherReq{
// Host: v.Host,
// Token: v.Authorization,
Host: aucd.Host,
Token: aucd.Token,
Path: movie.Base.VendorInfo.Alist.Path,
Password: movie.Base.VendorInfo.Alist.Password,
Method: "video_preview",

@ -30,21 +30,24 @@ type BilibiliSubtitleCache map[string]*struct {
Srt *refreshcache.RefreshCache[[]byte, struct{}]
}
func NewBilibiliSharedMpdCacheInitFunc(movie *model.Movie) func(ctx context.Context, args ...struct{}) (*BilibiliMpdCache, error) {
return func(ctx context.Context, args ...struct{}) (*BilibiliMpdCache, error) {
func NewBilibiliSharedMpdCacheInitFunc(movie *model.Movie) func(ctx context.Context, args ...*BilibiliUserCache) (*BilibiliMpdCache, error) {
return func(ctx context.Context, args ...*BilibiliUserCache) (*BilibiliMpdCache, error) {
return BilibiliSharedMpdCacheInitFunc(ctx, movie, args...)
}
}
func BilibiliSharedMpdCacheInitFunc(ctx context.Context, movie *model.Movie, args ...struct{}) (*BilibiliMpdCache, error) {
func BilibiliSharedMpdCacheInitFunc(ctx context.Context, movie *model.Movie, args ...*BilibiliUserCache) (*BilibiliMpdCache, error) {
if len(args) == 0 {
return nil, errors.New("no bilibili user cache data")
}
var cookies []*http.Cookie
vendorInfo, err := db.GetBilibiliVendor(movie.CreatorID)
vendorInfo, err := args[0].Get(ctx)
if err != nil {
if !errors.Is(err, db.ErrNotFound("vendor")) {
return nil, err
}
} else {
cookies = utils.MapToHttpCookie(vendorInfo.Cookies)
cookies = vendorInfo.Cookies
}
cli := vendor.LoadBilibiliClient(movie.Base.VendorInfo.Backend)
var m, hevcM *mpd.MPD
@ -127,21 +130,24 @@ func BilibiliSharedMpdCacheInitFunc(ctx context.Context, movie *model.Movie, arg
}, nil
}
func NewBilibiliNoSharedMovieCacheInitFunc(movie *model.Movie) func(ctx context.Context, id string, args ...struct{}) (string, error) {
return func(ctx context.Context, id string, args ...struct{}) (string, error) {
return BilibiliNoSharedMovieCacheInitFunc(ctx, id, movie, args...)
func NewBilibiliNoSharedMovieCacheInitFunc(movie *model.Movie) func(ctx context.Context, args ...*BilibiliUserCache) (string, error) {
return func(ctx context.Context, args ...*BilibiliUserCache) (string, error) {
return BilibiliNoSharedMovieCacheInitFunc(ctx, movie, args...)
}
}
func BilibiliNoSharedMovieCacheInitFunc(ctx context.Context, id string, movie *model.Movie, args ...struct{}) (string, error) {
func BilibiliNoSharedMovieCacheInitFunc(ctx context.Context, movie *model.Movie, args ...*BilibiliUserCache) (string, error) {
if len(args) == 0 {
return "", errors.New("no bilibili user cache data")
}
var cookies []*http.Cookie
vendorInfo, err := db.GetBilibiliVendor(id)
vendorInfo, err := args[0].Get(ctx)
if err != nil {
if !errors.Is(err, db.ErrNotFound("vendor")) {
return "", err
}
} else {
cookies = utils.MapToHttpCookie(vendorInfo.Cookies)
cookies = vendorInfo.Cookies
}
cli := vendor.LoadBilibiliClient(movie.Base.VendorInfo.Backend)
var u string
@ -194,31 +200,33 @@ type bilibiliSubtitleResp struct {
} `json:"body"`
}
func NewBilibiliSubtitleCacheInitFunc(movie *model.Movie) func(ctx context.Context, args ...struct{}) (BilibiliSubtitleCache, error) {
return func(ctx context.Context, args ...struct{}) (BilibiliSubtitleCache, error) {
return BilibiliSubtitleCacheInitFunc(ctx, movie)
func NewBilibiliSubtitleCacheInitFunc(movie *model.Movie) func(ctx context.Context, args ...*BilibiliUserCache) (BilibiliSubtitleCache, error) {
return func(ctx context.Context, args ...*BilibiliUserCache) (BilibiliSubtitleCache, error) {
return BilibiliSubtitleCacheInitFunc(ctx, movie, args...)
}
}
func BilibiliSubtitleCacheInitFunc(ctx context.Context, movie *model.Movie, args ...struct{}) (BilibiliSubtitleCache, error) {
return initBilibiliSubtitleCache(ctx, movie, args...)
}
func BilibiliSubtitleCacheInitFunc(ctx context.Context, movie *model.Movie, args ...*BilibiliUserCache) (BilibiliSubtitleCache, error) {
if len(args) == 0 {
return nil, errors.New("no bilibili user cache data")
}
func initBilibiliSubtitleCache(ctx context.Context, movie *model.Movie, args ...struct{}) (BilibiliSubtitleCache, error) {
biliInfo := movie.Base.VendorInfo.Bilibili
if biliInfo.Bvid == "" || biliInfo.Cid == 0 {
return nil, errors.New("bvid or cid is empty")
}
// must login
var cookies []*http.Cookie
vendorInfo, err := db.GetBilibiliVendor(movie.CreatorID)
vendorInfo, err := args[0].Get(ctx)
if err != nil {
if !errors.Is(err, db.ErrNotFound("vendor")) {
return nil, err
if errors.Is(err, db.ErrNotFound("vendor")) {
return nil, nil
}
} else {
cookies = utils.MapToHttpCookie(vendorInfo.Cookies)
cookies = vendorInfo.Cookies
}
cli := vendor.LoadBilibiliClient(movie.Base.VendorInfo.Backend)
resp, err := cli.GetSubtitles(ctx, &bilibili.GetSubtitlesReq{
Cookies: utils.HttpCookieToMap(cookies),
@ -289,15 +297,42 @@ func translateBilibiliSubtitleToSrt(ctx context.Context, url string) ([]byte, er
}
type BilibiliMovieCache struct {
NoSharedMovie *MapCache[string, struct{}]
SharedMpd *refreshcache.RefreshCache[*BilibiliMpdCache, struct{}]
Subtitle *refreshcache.RefreshCache[BilibiliSubtitleCache, struct{}]
NoSharedMovie *MapCache[string, *BilibiliUserCache]
SharedMpd *refreshcache.RefreshCache[*BilibiliMpdCache, *BilibiliUserCache]
Subtitle *refreshcache.RefreshCache[BilibiliSubtitleCache, *BilibiliUserCache]
}
func NewBilibiliMovieCache(movie *model.Movie) *BilibiliMovieCache {
return &BilibiliMovieCache{
NoSharedMovie: newMapCache[string, struct{}](NewBilibiliNoSharedMovieCacheInitFunc(movie), time.Minute*115),
SharedMpd: refreshcache.NewRefreshCache[*BilibiliMpdCache, struct{}](NewBilibiliSharedMpdCacheInitFunc(movie), time.Minute*115),
Subtitle: refreshcache.NewRefreshCache[BilibiliSubtitleCache, struct{}](NewBilibiliSubtitleCacheInitFunc(movie), time.Minute*60),
NoSharedMovie: newMapCache(NewBilibiliNoSharedMovieCacheInitFunc(movie), time.Minute*115),
SharedMpd: refreshcache.NewRefreshCache(NewBilibiliSharedMpdCacheInitFunc(movie), time.Minute*115),
Subtitle: refreshcache.NewRefreshCache(NewBilibiliSubtitleCacheInitFunc(movie), time.Minute*60),
}
}
type BilibiliUserCache = refreshcache.RefreshCache[*BilibiliUserCacheData, struct{}]
type BilibiliUserCacheData struct {
Cookies []*http.Cookie
Backend string
}
func NewBilibiliCache(userID string) *BilibiliUserCache {
f := BilibiliAuthorizationCacheWithUserIDInitFunc(userID)
return refreshcache.NewRefreshCache(func(ctx context.Context, args ...struct{}) (*BilibiliUserCacheData, error) {
return f(ctx)
}, 0)
}
func BilibiliAuthorizationCacheWithUserIDInitFunc(userID string) func(ctx context.Context, args ...struct{}) (*BilibiliUserCacheData, error) {
return func(ctx context.Context, args ...struct{}) (*BilibiliUserCacheData, error) {
v, err := db.GetBilibiliVendor(userID)
if err != nil {
return nil, err
}
return &BilibiliUserCacheData{
Cookies: utils.MapToHttpCookie(v.Cookies),
Backend: v.Backend,
}, nil
}
}

@ -9,7 +9,7 @@ import (
"golang.org/x/exp/maps"
)
type MapRefreshFunc[T any, A any] func(ctx context.Context, id string, args ...A) (T, error)
type MapRefreshFunc[T any, A any] func(ctx context.Context, args ...A) (T, error)
type MapCache[T any, A any] struct {
lock sync.RWMutex
@ -36,102 +36,82 @@ func (b *MapCache[T, A]) clear() {
maps.Clear(b.cache)
}
func (b *MapCache[T, A]) LoadOrStore(ctx context.Context, id string) (T, error) {
func (b *MapCache[T, A]) LoadOrStore(ctx context.Context, id string, args ...A) (T, error) {
b.lock.RLock()
c, loaded := b.cache[id]
if loaded {
b.lock.RUnlock()
return c.Get(ctx)
return c.Get(ctx, args...)
}
b.lock.RUnlock()
b.lock.Lock()
c, loaded = b.cache[id]
if loaded {
b.lock.Unlock()
return c.Get(ctx)
return c.Get(ctx, args...)
}
c = refreshcache.NewRefreshCache[T, A](func(ctx context.Context, args ...A) (T, error) {
return b.refreshFunc(ctx, id, args...)
}, b.maxAge)
c = refreshcache.NewRefreshCache[T, A](refreshcache.RefreshFunc[T, A](b.refreshFunc), b.maxAge)
b.cache[id] = c
b.lock.Unlock()
return c.Get(ctx)
return c.Get(ctx, args...)
}
func (b *MapCache[T, A]) StoreOrRefresh(ctx context.Context, id string) (T, error) {
func (b *MapCache[T, A]) StoreOrRefresh(ctx context.Context, id string, args ...A) (T, error) {
b.lock.RLock()
c, ok := b.cache[id]
if ok {
b.lock.RUnlock()
return c.Refresh(ctx)
return c.Refresh(ctx, args...)
}
b.lock.RUnlock()
b.lock.Lock()
c, ok = b.cache[id]
if ok {
b.lock.Unlock()
return c.Refresh(ctx)
return c.Refresh(ctx, args...)
}
c = refreshcache.NewRefreshCache[T, A](func(ctx context.Context, args ...A) (T, error) {
return b.refreshFunc(ctx, id, args...)
}, b.maxAge)
c = refreshcache.NewRefreshCache[T, A](refreshcache.RefreshFunc[T, A](b.refreshFunc), b.maxAge)
b.cache[id] = c
b.lock.Unlock()
return c.Refresh(ctx)
return c.Refresh(ctx, args...)
}
func (b *MapCache[T, A]) LoadOrStoreWithDynamicFunc(ctx context.Context, id string, refreshFunc MapRefreshFunc[T, A]) (T, error) {
func (b *MapCache[T, A]) LoadOrStoreWithDynamicFunc(ctx context.Context, id string, refreshFunc MapRefreshFunc[T, A], args ...A) (T, error) {
b.lock.RLock()
c, loaded := b.cache[id]
if loaded {
b.lock.RUnlock()
return c.Data().Get(ctx, func(ctx context.Context, args ...A) (T, error) {
return refreshFunc(ctx, id, args...)
})
return c.Data().Get(ctx, refreshcache.RefreshFunc[T, A](refreshFunc), args...)
}
b.lock.RUnlock()
b.lock.Lock()
c, loaded = b.cache[id]
if loaded {
b.lock.Unlock()
return c.Data().Get(ctx, func(ctx context.Context, args ...A) (T, error) {
return refreshFunc(ctx, id, args...)
})
return c.Data().Get(ctx, refreshcache.RefreshFunc[T, A](refreshFunc), args...)
}
c = refreshcache.NewRefreshCache[T, A](func(ctx context.Context, args ...A) (T, error) {
return b.refreshFunc(ctx, id, args...)
}, b.maxAge)
c = refreshcache.NewRefreshCache[T, A](refreshcache.RefreshFunc[T, A](b.refreshFunc), b.maxAge)
b.cache[id] = c
b.lock.Unlock()
return c.Data().Get(ctx, func(ctx context.Context, args ...A) (T, error) {
return refreshFunc(ctx, id, args...)
})
return c.Data().Get(ctx, refreshcache.RefreshFunc[T, A](refreshFunc), args...)
}
func (b *MapCache[T, A]) StoreOrRefreshWithDynamicFunc(ctx context.Context, id string, refreshFunc MapRefreshFunc[T, A]) (T, error) {
func (b *MapCache[T, A]) StoreOrRefreshWithDynamicFunc(ctx context.Context, id string, refreshFunc MapRefreshFunc[T, A], args ...A) (T, error) {
b.lock.RLock()
c, ok := b.cache[id]
if ok {
b.lock.RUnlock()
return c.Data().Refresh(ctx, func(ctx context.Context, args ...A) (T, error) {
return refreshFunc(ctx, id, args...)
})
return c.Data().Refresh(ctx, refreshcache.RefreshFunc[T, A](refreshFunc), args...)
}
b.lock.RUnlock()
b.lock.Lock()
c, ok = b.cache[id]
if ok {
b.lock.Unlock()
return c.Data().Refresh(ctx, func(ctx context.Context, args ...A) (T, error) {
return refreshFunc(ctx, id, args...)
})
return c.Data().Refresh(ctx, refreshcache.RefreshFunc[T, A](refreshFunc), args...)
}
c = refreshcache.NewRefreshCache[T, A](func(ctx context.Context, args ...A) (T, error) {
return b.refreshFunc(ctx, id, args...)
}, b.maxAge)
c = refreshcache.NewRefreshCache[T, A](refreshcache.RefreshFunc[T, A](b.refreshFunc), b.maxAge)
b.cache[id] = c
b.lock.Unlock()
return c.Data().Refresh(ctx, func(ctx context.Context, args ...A) (T, error) {
return refreshFunc(ctx, id, args...)
})
return c.Data().Refresh(ctx, refreshcache.RefreshFunc[T, A](refreshFunc), args...)
}

@ -52,6 +52,7 @@ type User struct {
Movies []Movie `gorm:"foreignKey:CreatorID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL"`
BilibiliVendor *BilibiliVendor `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
AlistVendor *AlistVendor `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
EmbyVendor *EmbyVendor `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
}
func (u *User) CheckPassword(password string) bool {

@ -6,7 +6,8 @@ import (
)
type BilibiliVendor struct {
UserID string `gorm:"primaryKey"`
UserID string `gorm:"primaryKey"`
Backend string
Cookies map[string]string `gorm:"serializer:fastjson"`
}
@ -35,10 +36,11 @@ func (b *BilibiliVendor) AfterFind(tx *gorm.DB) error {
}
type AlistVendor struct {
UserID string `gorm:"primaryKey"`
Host string `gorm:"serializer:fastjson"`
Username string `gorm:"serializer:fastjson"`
Password string `gorm:"serializer:fastjson"`
UserID string `gorm:"primaryKey"`
Backend string
Host string
Username string
HashedPassword []byte
}
func (a *AlistVendor) BeforeSave(tx *gorm.DB) error {
@ -50,7 +52,8 @@ func (a *AlistVendor) BeforeSave(tx *gorm.DB) error {
if a.Username, err = utils.CryptoToBase64([]byte(a.Username), key); err != nil {
return err
}
if a.Password, err = utils.CryptoToBase64([]byte(a.Password), key); err != nil {
if a.HashedPassword, err = utils.Crypto(a.HashedPassword, key); err != nil {
return err
}
return nil
@ -68,10 +71,62 @@ func (a *AlistVendor) AfterFind(tx *gorm.DB) error {
} else {
a.Username = string(v)
}
if v, err := utils.DecryptoFromBase64(a.Password, key); err != nil {
if v, err := utils.Decrypto(a.HashedPassword, key); err != nil {
return err
} else {
a.HashedPassword = v
}
return nil
}
type EmbyVendor struct {
UserID string `gorm:"primaryKey"`
Backend string
Host string
Username string
Password string
ApiKey string
}
func (e *EmbyVendor) BeforeSave(tx *gorm.DB) error {
key := []byte(e.UserID)
var err error
if e.Host, err = utils.CryptoToBase64([]byte(e.Host), key); err != nil {
return err
}
if e.Username, err = utils.CryptoToBase64([]byte(e.Username), key); err != nil {
return err
}
if e.Password, err = utils.CryptoToBase64([]byte(e.Password), key); err != nil {
return err
}
if e.ApiKey, err = utils.CryptoToBase64([]byte(e.ApiKey), key); err != nil {
return err
}
return nil
}
func (e *EmbyVendor) AfterFind(tx *gorm.DB) error {
key := []byte(e.UserID)
if v, err := utils.DecryptoFromBase64(e.Host, key); err != nil {
return err
} else {
e.Host = string(v)
}
if v, err := utils.DecryptoFromBase64(e.Username, key); err != nil {
return err
} else {
e.Username = string(v)
}
if v, err := utils.DecryptoFromBase64(e.Password, key); err != nil {
return err
} else {
e.Password = string(v)
}
if v, err := utils.DecryptoFromBase64(e.ApiKey, key); err != nil {
return err
} else {
a.Password = string(v)
e.ApiKey = string(v)
}
return nil
}

@ -28,7 +28,7 @@ type Movie struct {
alistCache atomic.Pointer[cache.AlistMovieCache]
}
func (m *Movie) BilibiliCache() (*cache.BilibiliMovieCache, error) {
func (m *Movie) BilibiliCache() *cache.BilibiliMovieCache {
c := m.bilibiliCache.Load()
if c == nil {
c = cache.NewBilibiliMovieCache(&m.Movie)
@ -36,22 +36,18 @@ func (m *Movie) BilibiliCache() (*cache.BilibiliMovieCache, error) {
return m.BilibiliCache()
}
}
return c, nil
return c
}
func (m *Movie) AlistCache() (*cache.AlistMovieCache, error) {
func (m *Movie) AlistCache() *cache.AlistMovieCache {
c := m.alistCache.Load()
if c == nil {
u, err := LoadOrInitUserByID(m.Movie.CreatorID)
if err != nil {
return nil, err
}
c = cache.NewAlistMovieCache(u.AlistCache(), &m.Movie)
c = cache.NewAlistMovieCache(&m.Movie)
if !m.alistCache.CompareAndSwap(nil, c) {
return m.AlistCache()
}
}
return c, nil
return c
}
func (m *Movie) Channel() (*rtmps.Channel, error) {

@ -16,14 +16,19 @@ import (
type User struct {
model.User
version uint32
alistCache *cache.AlistUserCache
version uint32
alistCache *cache.AlistUserCache
bilibiliCache *cache.BilibiliUserCache
}
func (u *User) AlistCache() *cache.AlistUserCache {
return u.alistCache
}
func (u *User) BilibiliCache() *cache.BilibiliUserCache {
return u.bilibiliCache
}
func (u *User) Version() uint32 {
return atomic.LoadUint32(&u.version)
}

@ -27,9 +27,10 @@ func LoadOrInitUser(u *model.User) (*User, error) {
return nil, ErrUserPending
}
i, _ := userCache.LoadOrStore(u.ID, &User{
User: *u,
version: crc32.ChecksumIEEE(u.HashedPassword),
alistCache: cache.NewAlistCache(u.ID),
User: *u,
version: crc32.ChecksumIEEE(u.HashedPassword),
alistCache: cache.NewAlistUserCache(u.ID),
bilibiliCache: cache.NewBilibiliCache(u.ID),
}, time.Hour)
return i.Value(), nil
}

@ -211,6 +211,8 @@ func Init(e *gin.Engine) {
alist.POST("/login", vendorAlist.Login)
alist.POST("/logout", vendorAlist.Logout)
alist.POST("/list", vendorAlist.List)
alist.GET("/me", vendorAlist.Me)

@ -654,13 +654,12 @@ func proxyVendorMovie(ctx *gin.Context, movie *op.Movie) {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("not support movie proxy"))
return
}
bmc, err := movie.BilibiliCache()
u, err := op.LoadOrInitUserByID(movie.Movie.CreatorID)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
mpdC, err := bmc.SharedMpd.Get(ctx)
mpdC, err := movie.BilibiliCache().SharedMpd.Get(ctx, u.BilibiliCache())
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
@ -701,12 +700,12 @@ func proxyVendorMovie(ctx *gin.Context, movie *op.Movie) {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("n is empty"))
return
}
bmc, err := movie.BilibiliCache()
u, err := op.LoadOrInitUserByID(movie.Movie.CreatorID)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
srtI, err := bmc.Subtitle.Get(ctx)
srtI, err := movie.BilibiliCache().Subtitle.Get(ctx, u.BilibiliCache())
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
@ -738,16 +737,19 @@ func parse2VendorMovie(ctx context.Context, user *op.User, room *op.Room, movie
if err != nil {
return err
}
bmc := opM.BilibiliCache()
if !movie.Base.Proxy {
bmc, err := opM.BilibiliCache()
if err != nil {
return err
}
userID := user.ID
var s string
if movie.Base.VendorInfo.Bilibili.Shared {
userID = movie.CreatorID
var u *op.User
u, err = op.LoadOrInitUserByID(movie.CreatorID)
if err != nil {
return err
}
s, err = opM.BilibiliCache().NoSharedMovie.LoadOrStore(ctx, movie.CreatorID, u.BilibiliCache())
} else {
s, err = opM.BilibiliCache().NoSharedMovie.LoadOrStore(ctx, user.ID, user.BilibiliCache())
}
s, err := bmc.NoSharedMovie.LoadOrStore(ctx, userID)
if err != nil {
return err
}
@ -756,11 +758,7 @@ func parse2VendorMovie(ctx context.Context, user *op.User, room *op.Room, movie
} else {
movie.Base.Type = "mpd"
}
bmc, err := opM.BilibiliCache()
if err != nil {
return err
}
srt, err := bmc.Subtitle.Get(ctx)
srt, err := bmc.Subtitle.Get(ctx, user.BilibiliCache())
if err != nil {
return err
}
@ -780,12 +778,12 @@ func parse2VendorMovie(ctx context.Context, user *op.User, room *op.Room, movie
if err != nil {
return err
}
rc, err := opM.AlistCache()
u, err := op.LoadOrInitUserByID(movie.CreatorID)
if err != nil {
return err
}
data, err := rc.Get(ctx)
data, err := opM.AlistCache().Get(ctx, u.AlistCache())
if err != nil {
return err
}

@ -17,9 +17,9 @@ import (
)
type LoginReq struct {
Host string `json:"host"`
Username string `json:"username"`
Password string `json:"password"`
Host string `json:"host"`
Username string `json:"username"`
HashedPassword string `json:"hashedPassword"`
}
func (r *LoginReq) Validate() error {
@ -52,7 +52,7 @@ func Login(ctx *gin.Context) {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
_, err = user.AlistCache().Data().Refresh(ctx, func(ctx context.Context, args ...string) (*cache.AlistUserCacheData, error) {
_, err = user.AlistCache().Data().Refresh(ctx, func(ctx context.Context, args ...struct{}) (*cache.AlistUserCacheData, error) {
return &cache.AlistUserCacheData{
Host: req.Host,
}, nil
@ -65,14 +65,15 @@ func Login(ctx *gin.Context) {
resp, err := cli.Login(ctx, &alist.LoginReq{
Host: req.Host,
Username: req.Username,
Password: req.Password,
Password: req.HashedPassword,
Hashed: true,
})
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
_, err = user.AlistCache().Data().Refresh(ctx, func(ctx context.Context, args ...string) (*cache.AlistUserCacheData, error) {
_, err = user.AlistCache().Data().Refresh(ctx, func(ctx context.Context, args ...struct{}) (*cache.AlistUserCacheData, error) {
return &cache.AlistUserCacheData{
Host: req.Host,
Token: resp.Token,
@ -85,9 +86,9 @@ func Login(ctx *gin.Context) {
}
_, err := db.CreateOrSaveAlistVendor(user.ID, &dbModel.AlistVendor{
Host: req.Host,
Username: req.Username,
Password: req.Password,
Host: req.Host,
Username: req.Username,
HashedPassword: []byte(req.HashedPassword),
})
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
@ -96,3 +97,17 @@ func Login(ctx *gin.Context) {
ctx.Status(http.StatusNoContent)
}
func Logout(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.User)
err := db.DeleteAlistVendor(user.ID)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
user.AlistCache().Clear()
ctx.Status(http.StatusNoContent)
}

@ -17,9 +17,7 @@ type AlistMeResp = model.VendorMeResp[*alist.MeResp]
func Me(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.User)
cli := vendor.LoadAlistClient(ctx.Query("backend"))
aucd, err := user.AlistCache().Get(ctx, ctx.Query("backend"))
aucd, err := user.AlistCache().Get(ctx)
if err != nil {
if errors.Is(err, db.ErrNotFound("vendor")) {
ctx.JSON(http.StatusOK, model.NewApiDataResp(&AlistMeResp{
@ -31,6 +29,8 @@ func Me(ctx *gin.Context) {
return
}
cli := vendor.LoadAlistClient(ctx.Query("backend"))
resp, err := cli.Me(ctx, &alist.MeReq{
Host: aucd.Host,
Token: aucd.Token,
@ -41,7 +41,7 @@ func Me(ctx *gin.Context) {
}
ctx.JSON(http.StatusOK, model.NewApiDataResp(&AlistMeResp{
IsLogin: false,
IsLogin: true,
Info: resp,
}))

@ -39,19 +39,17 @@ func List(ctx *gin.Context) {
return
}
var cli = vendor.LoadAlistClient(ctx.Query("backend"))
aucd, err := user.AlistCache().Get(ctx, ctx.Query("backend"))
aucd, err := user.AlistCache().Get(ctx)
if err != nil {
if errors.Is(err, db.ErrNotFound("vendor")) {
ctx.JSON(http.StatusOK, model.NewApiDataResp(&AlistMeResp{
IsLogin: false,
}))
ctx.JSON(http.StatusBadRequest, model.NewApiErrorStringResp("alist not login"))
return
}
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
var cli = vendor.LoadAlistClient(ctx.Query("backend"))
resp, err := cli.FsList(ctx, &alist.FsListReq{
Token: aucd.Token,
Password: req.Password,

@ -49,15 +49,16 @@ func Parse(ctx *gin.Context) {
return
}
// can be no login
var cookies []*http.Cookie
vendorInfo, err := db.GetBilibiliVendor(user.ID)
bucd, err := user.BilibiliCache().Get(ctx)
if err != nil {
if !errors.Is(err, db.ErrNotFound("vendor")) {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
} else {
cookies = utils.MapToHttpCookie(vendorInfo.Cookies)
cookies = bucd.Cookies
}
switch resp.Type {

@ -203,5 +203,6 @@ func Logout(ctx *gin.Context) {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
user.BilibiliCache().Clear()
ctx.Status(http.StatusNoContent)
}

@ -9,6 +9,7 @@ import (
"github.com/synctv-org/synctv/internal/op"
"github.com/synctv-org/synctv/internal/vendor"
"github.com/synctv-org/synctv/server/model"
"github.com/synctv-org/synctv/utils"
"github.com/synctv-org/vendors/api/bilibili"
)
@ -16,7 +17,8 @@ type BilibiliMeResp = model.VendorMeResp[*bilibili.UserInfoResp]
func Me(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.User)
v, err := db.GetBilibiliVendor(user.ID)
bucd, err := user.BilibiliCache().Get(ctx)
if err != nil {
if errors.Is(err, db.ErrNotFound("vendor")) {
ctx.JSON(http.StatusOK, model.NewApiDataResp(&BilibiliMeResp{
@ -27,14 +29,14 @@ func Me(ctx *gin.Context) {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
if len(v.Cookies) == 0 {
if len(bucd.Cookies) == 0 {
ctx.JSON(http.StatusOK, model.NewApiDataResp(&BilibiliMeResp{
IsLogin: false,
}))
return
}
resp, err := vendor.LoadBilibiliClient("").UserInfo(ctx, &bilibili.UserInfoReq{
Cookies: v.Cookies,
resp, err := vendor.LoadBilibiliClient(bucd.Backend).UserInfo(ctx, &bilibili.UserInfoReq{
Cookies: utils.HttpCookieToMap(bucd.Cookies),
})
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))

Loading…
Cancel
Save