Feat: crypto vendor info in db

pull/43/head
zijiren233 2 years ago
parent ae1f6c1407
commit 37094dd2bc

@ -31,7 +31,7 @@ require (
github.com/synctv-org/vendors v0.1.1-0.20231209122754-ebad9251fa7a github.com/synctv-org/vendors v0.1.1-0.20231209122754-ebad9251fa7a
github.com/ulule/limiter/v3 v3.11.2 github.com/ulule/limiter/v3 v3.11.2
github.com/zencoder/go-dash/v3 v3.0.3 github.com/zencoder/go-dash/v3 v3.0.3
github.com/zijiren233/gencontainer v0.0.0-20231209155516-a52fcb19fee5 github.com/zijiren233/gencontainer v0.0.0-20231210091819-97da95d7545c
github.com/zijiren233/go-colorable v0.0.0-20230930131441-997304c961cb github.com/zijiren233/go-colorable v0.0.0-20230930131441-997304c961cb
github.com/zijiren233/ksync v0.2.0 github.com/zijiren233/ksync v0.2.0
github.com/zijiren233/livelib v0.2.3-0.20231103145812-58de2ae7f423 github.com/zijiren233/livelib v0.2.3-0.20231103145812-58de2ae7f423

@ -370,6 +370,8 @@ github.com/zijiren233/gencontainer v0.0.0-20231209055719-473cab2b7931 h1:13Z/zjQ
github.com/zijiren233/gencontainer v0.0.0-20231209055719-473cab2b7931/go.mod h1:V5oL7PrZxgisuLCblFWd89Jg99O8vM1n58llcxZ2hDY= github.com/zijiren233/gencontainer v0.0.0-20231209055719-473cab2b7931/go.mod h1:V5oL7PrZxgisuLCblFWd89Jg99O8vM1n58llcxZ2hDY=
github.com/zijiren233/gencontainer v0.0.0-20231209155516-a52fcb19fee5 h1:OsNDmOre1xXJpRaQUeqet3yYZbkfy8bfEdsXs8PrXSE= github.com/zijiren233/gencontainer v0.0.0-20231209155516-a52fcb19fee5 h1:OsNDmOre1xXJpRaQUeqet3yYZbkfy8bfEdsXs8PrXSE=
github.com/zijiren233/gencontainer v0.0.0-20231209155516-a52fcb19fee5/go.mod h1:V5oL7PrZxgisuLCblFWd89Jg99O8vM1n58llcxZ2hDY= github.com/zijiren233/gencontainer v0.0.0-20231209155516-a52fcb19fee5/go.mod h1:V5oL7PrZxgisuLCblFWd89Jg99O8vM1n58llcxZ2hDY=
github.com/zijiren233/gencontainer v0.0.0-20231210091819-97da95d7545c h1:+up6wZezwJRTzEbxM7E0PRM+kHEJUk7FzifardCdaZs=
github.com/zijiren233/gencontainer v0.0.0-20231210091819-97da95d7545c/go.mod h1:V5oL7PrZxgisuLCblFWd89Jg99O8vM1n58llcxZ2hDY=
github.com/zijiren233/go-colorable v0.0.0-20230930131441-997304c961cb h1:0DyOxf/TbbGodHhOVHNoPk+7v/YBJACs22gKpKlatWw= github.com/zijiren233/go-colorable v0.0.0-20230930131441-997304c961cb h1:0DyOxf/TbbGodHhOVHNoPk+7v/YBJACs22gKpKlatWw=
github.com/zijiren233/go-colorable v0.0.0-20230930131441-997304c961cb/go.mod h1:6TCzjDiQ8+5gWZiwsC3pnA5M0vUy2jV2Y7ciHJh729g= github.com/zijiren233/go-colorable v0.0.0-20230930131441-997304c961cb/go.mod h1:6TCzjDiQ8+5gWZiwsC3pnA5M0vUy2jV2Y7ciHJh729g=
github.com/zijiren233/ksync v0.2.0 h1:OyXVXbVQYFEVfWM13NApt4LMHbLQ3HTs4oYcLmqL6NE= github.com/zijiren233/ksync v0.2.0 h1:OyXVXbVQYFEVfWM13NApt4LMHbLQ3HTs4oYcLmqL6NE=

@ -21,7 +21,16 @@ var (
func Init(d *gorm.DB, t conf.DatabaseType) error { func Init(d *gorm.DB, t conf.DatabaseType) error {
db = d db = d
dbType = t dbType = t
return AutoMigrate(new(model.Setting), new(model.User), new(model.UserProvider), new(model.Room), new(model.RoomUserRelation), new(model.StreamingVendorInfo), new(model.Movie)) return AutoMigrate(
new(model.Setting),
new(model.User),
new(model.UserProvider),
new(model.Room),
new(model.RoomUserRelation),
new(model.Movie),
new(model.BilibiliVendor),
new(model.AlistVendor),
)
} }
func AutoMigrate(dst ...any) error { func AutoMigrate(dst ...any) error {

@ -3,18 +3,19 @@ package db
import ( import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/synctv-org/synctv/internal/model" "github.com/synctv-org/synctv/internal/model"
"gorm.io/gorm"
) )
type dbVersion struct { type dbVersion struct {
NextVersion string NextVersion string
Upgrade func() error Upgrade func(*gorm.DB) error
} }
const CurrentVersion = "0.0.1" const CurrentVersion = "0.0.1"
var dbVersions = map[string]dbVersion{ var dbVersions = map[string]dbVersion{
"0.0.1": { "0.0.1": {
NextVersion: "", NextVersion: "0.0.2-dev",
Upgrade: nil, Upgrade: nil,
}, },
} }
@ -43,7 +44,7 @@ func upgradeDatabase() error {
} }
log.Infof("Upgrading database to version %s", currentVersion) log.Infof("Upgrading database to version %s", currentVersion)
if version.Upgrade != nil { if version.Upgrade != nil {
err = version.Upgrade() err = version.Upgrade(db)
if err != nil { if err != nil {
return err return err
} }

@ -2,88 +2,54 @@ package db
import ( import (
"errors" "errors"
"net/http"
"github.com/synctv-org/synctv/internal/model" "github.com/synctv-org/synctv/internal/model"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/clause"
) )
func GetVendorByUserID(userID string) ([]*model.StreamingVendorInfo, error) { func GetBilibiliVendor(userID string) (*model.BilibiliVendor, error) {
var vendors []*model.StreamingVendorInfo var vendor model.BilibiliVendor
err := db.Where("user_id = ?", userID).Find(&vendors).Error err := db.Where("user_id = ?", userID).Preload(clause.Associations).First(&vendor).Error
if err != nil { return &vendor, HandleNotFound(err, "vendor")
return nil, err
}
return vendors, nil
} }
func GetVendorByUserIDAndVendor(userID string, vendor model.StreamingVendor) (*model.StreamingVendorInfo, error) { func CreateOrSaveBilibiliVendor(userID string, vendorInfo *model.BilibiliVendor) (*model.BilibiliVendor, error) {
var vendorInfo model.StreamingVendorInfo vendorInfo.UserID = userID
err := db.Where("user_id = ? AND vendor = ?", userID, vendor).First(&vendorInfo).Error return vendorInfo, Transactional(func(tx *gorm.DB) error {
return &vendorInfo, HandleNotFound(err, "vendor") if errors.Is(tx.First(&model.BilibiliVendor{
} UserID: userID,
}).Error, gorm.ErrRecordNotFound) {
type CreateVendorConfig func(*model.StreamingVendorInfo) return tx.Create(&vendorInfo).Error
} else {
func WithCookie(cookie []*http.Cookie) CreateVendorConfig { return tx.Save(&vendorInfo).Error
return func(vendor *model.StreamingVendorInfo) { }
vendor.Cookies = cookie })
}
}
func WithAuthorization(authorization string) CreateVendorConfig {
return func(vendor *model.StreamingVendorInfo) {
vendor.Authorization = authorization
}
}
func WithPassword(password string) CreateVendorConfig {
return func(vendor *model.StreamingVendorInfo) {
vendor.Password = password
}
} }
func WithHost(host string) CreateVendorConfig { func DeleteBilibiliVendor(userID string) error {
return func(vendor *model.StreamingVendorInfo) { return db.Where("user_id = ?", userID).Delete(&model.BilibiliVendor{}).Error
vendor.Host = host
}
} }
func FirstOrCreateVendorByUserIDAndVendor(userID string, vendor model.StreamingVendor, conf ...CreateVendorConfig) (*model.StreamingVendorInfo, error) { func GetAlistVendor(userID string) (*model.AlistVendor, error) {
var vendorInfo model.StreamingVendorInfo var vendor model.AlistVendor
v := &model.StreamingVendorInfo{ err := db.Where("user_id = ?", userID).Preload(clause.Associations).First(&vendor).Error
UserID: userID, return &vendor, HandleNotFound(err, "vendor")
Vendor: vendor,
}
for _, c := range conf {
c(v)
}
err := db.Where("user_id = ? AND vendor = ?", userID, vendor).Attrs(
v,
).FirstOrCreate(&vendorInfo).Error
return &vendorInfo, err
} }
func CreateOrSaveVendorByUserIDAndVendor(userID string, vendor model.StreamingVendor, conf ...CreateVendorConfig) (*model.StreamingVendorInfo, error) { func CreateOrSaveAlistVendor(userID string, vendorInfo *model.AlistVendor) (*model.AlistVendor, error) {
vendorInfo := model.StreamingVendorInfo{ vendorInfo.UserID = userID
UserID: userID, return vendorInfo, Transactional(func(tx *gorm.DB) error {
Vendor: vendor, if errors.Is(tx.First(&model.AlistVendor{
} UserID: userID,
return &vendorInfo, Transactional(func(tx *gorm.DB) error { }).Error, gorm.ErrRecordNotFound) {
if errors.Is(tx.First(&vendorInfo).Error, gorm.ErrRecordNotFound) {
for _, c := range conf {
c(&vendorInfo)
}
return tx.Create(&vendorInfo).Error return tx.Create(&vendorInfo).Error
} else { } else {
for _, c := range conf {
c(&vendorInfo)
}
return tx.Save(&vendorInfo).Error return tx.Save(&vendorInfo).Error
} }
}) })
} }
func DeleteVendorByUserIDAndVendor(userID string, vendor model.StreamingVendor) error { func DeleteAlistVendor(userID string) error {
return db.Where("user_id = ? AND vendor = ?", userID, vendor).Delete(&model.StreamingVendorInfo{}).Error return db.Where("user_id = ?", userID).Delete(&model.AlistVendor{}).Error
} }

@ -42,22 +42,29 @@ type Subtitle struct {
Type string `json:"type"` Type string `json:"type"`
} }
type VendorName = string
const (
VendorBilibili VendorName = "bilibili"
VendorAlist VendorName = "alist"
)
type VendorInfo struct { type VendorInfo struct {
Vendor StreamingVendor `json:"vendor"` Vendor VendorName `json:"vendor"`
Backend string `json:"backend"` Backend string `json:"backend"`
Shared bool `gorm:"not null;default:false" json:"shared"` Shared bool `gorm:"not null;default:false" json:"shared"`
Bilibili *BilibiliVendorInfo `gorm:"embedded;embeddedPrefix:bilibili_" json:"bilibili,omitempty"` Bilibili *BilibiliStreamingInfo `gorm:"embedded;embeddedPrefix:bilibili_" json:"bilibili,omitempty"`
Alist *AlistVendorInfo `gorm:"embedded;embeddedPrefix:alist_" json:"alist,omitempty"` Alist *AlistStreamingInfo `gorm:"embedded;embeddedPrefix:alist_" json:"alist,omitempty"`
} }
type BilibiliVendorInfo struct { type BilibiliStreamingInfo struct {
Bvid string `json:"bvid,omitempty"` Bvid string `json:"bvid,omitempty"`
Cid uint64 `json:"cid,omitempty"` Cid uint64 `json:"cid,omitempty"`
Epid uint64 `json:"epid,omitempty"` Epid uint64 `json:"epid,omitempty"`
Quality uint64 `json:"quality,omitempty"` Quality uint64 `json:"quality,omitempty"`
} }
func (b *BilibiliVendorInfo) Validate() error { func (b *BilibiliStreamingInfo) Validate() error {
switch { switch {
// 先判断epid是否为0来确定是否是pgc // 先判断epid是否为0来确定是否是pgc
case b.Epid != 0: case b.Epid != 0:
@ -75,7 +82,7 @@ func (b *BilibiliVendorInfo) Validate() error {
return nil return nil
} }
type AlistVendorInfo struct { type AlistStreamingInfo struct {
Path string `json:"path,omitempty"` Path string `json:"path,omitempty"`
Password string `json:"password,omitempty"` Password string `json:"password,omitempty"`
} }

@ -42,15 +42,16 @@ type User struct {
ID string `gorm:"primaryKey;type:varchar(32)" json:"id"` ID string `gorm:"primaryKey;type:varchar(32)" json:"id"`
CreatedAt time.Time CreatedAt time.Time
UpdatedAt time.Time UpdatedAt time.Time
RegisteredByProvider bool `gorm:"not null;default:false"` RegisteredByProvider bool `gorm:"not null;default:false"`
UserProviders []UserProvider `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"` UserProviders []UserProvider `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
Username string `gorm:"not null;uniqueIndex"` Username string `gorm:"not null;uniqueIndex"`
HashedPassword []byte `gorm:"not null"` HashedPassword []byte `gorm:"not null"`
Role Role `gorm:"not null;default:2"` Role Role `gorm:"not null;default:2"`
RoomUserRelations []RoomUserRelation `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"` RoomUserRelations []RoomUserRelation `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
Rooms []Room `gorm:"foreignKey:CreatorID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"` Rooms []Room `gorm:"foreignKey:CreatorID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
Movies []Movie `gorm:"foreignKey:CreatorID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL"` Movies []Movie `gorm:"foreignKey:CreatorID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL"`
StreamingVendorInfos []StreamingVendorInfo `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"` BilibiliVendor *BilibiliVendor `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
AlistVendor *AlistVendor `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
} }
func (u *User) CheckPassword(password string) bool { func (u *User) CheckPassword(password string) bool {

@ -1,28 +1,77 @@
package model package model
import ( import (
"net/http" "github.com/synctv-org/synctv/utils"
"time" "gorm.io/gorm"
) )
type StreamingVendor string type BilibiliVendor struct {
UserID string `gorm:"primaryKey"`
Cookies map[string]string `gorm:"serializer:fastjson"`
}
const ( func (b *BilibiliVendor) BeforeSave(tx *gorm.DB) error {
StreamingVendorBilibili StreamingVendor = "bilibili" key := []byte(b.UserID)
StreamingVendorAlist StreamingVendor = "alist" for k, v := range b.Cookies {
) value, err := utils.CryptoToBase64([]byte(v), key)
if err != nil {
return err
}
b.Cookies[k] = value
}
return nil
}
func (b *BilibiliVendor) AfterFind(tx *gorm.DB) error {
key := []byte(b.UserID)
for k, v := range b.Cookies {
value, err := utils.DecryptoFromBase64(v, key)
if err != nil {
return err
}
b.Cookies[k] = string(value)
}
return nil
}
type AlistVendor struct {
UserID string `gorm:"primaryKey"`
Host string `gorm:"serializer:fastjson"`
Username string `gorm:"serializer:fastjson"`
Password string `gorm:"serializer:fastjson"`
}
type StreamingVendorInfo struct { func (a *AlistVendor) BeforeSave(tx *gorm.DB) error {
UserID string `gorm:"not null;primarykey"` key := []byte(a.UserID)
Vendor StreamingVendor `gorm:"not null;primarykey"` var err error
CreatedAt time.Time if a.Host, err = utils.CryptoToBase64([]byte(a.Host), key); err != nil {
UpdatedAt time.Time return err
VendorToken }
Host string 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 {
return err
}
return nil
} }
type VendorToken struct { func (a *AlistVendor) AfterFind(tx *gorm.DB) error {
Cookies []*http.Cookie `gorm:"serializer:fastjson"` key := []byte(a.UserID)
Authorization string if v, err := utils.DecryptoFromBase64(a.Host, key); err != nil {
Password string return err
} else {
a.Host = string(v)
}
if v, err := utils.DecryptoFromBase64(a.Username, key); err != nil {
return err
} else {
a.Username = string(v)
}
if v, err := utils.DecryptoFromBase64(a.Password, key); err != nil {
return err
} else {
a.Password = string(v)
}
return nil
} }

@ -0,0 +1,110 @@
package op
import (
"sync"
"time"
"github.com/zijiren233/gencontainer/refreshcache"
"golang.org/x/exp/maps"
)
type Cache struct {
lock sync.RWMutex
cache map[string]*refreshcache.RefreshCache[any]
}
func newBaseCache() *Cache {
return &Cache{
cache: make(map[string]*refreshcache.RefreshCache[any]),
}
}
func (b *Cache) Clear() {
b.lock.Lock()
defer b.lock.Unlock()
b.clear()
}
func (b *Cache) clear() {
maps.Clear(b.cache)
}
func (b *Cache) LoadOrStore(id string, refreshFunc func() (any, error), maxAge time.Duration) (any, error) {
b.lock.RLock()
c, loaded := b.cache[id]
if loaded {
b.lock.RUnlock()
return c.Get()
}
b.lock.RUnlock()
b.lock.Lock()
c, loaded = b.cache[id]
if loaded {
b.lock.Unlock()
return c.Get()
}
c = refreshcache.NewRefreshCache[any](refreshFunc, maxAge)
b.cache[id] = c
b.lock.Unlock()
return c.Get()
}
func (b *Cache) StoreOrRefresh(id string, refreshFunc func() (any, error), maxAge time.Duration) (any, error) {
b.lock.RLock()
c, ok := b.cache[id]
if ok {
b.lock.RUnlock()
return c.Refresh()
}
b.lock.RUnlock()
b.lock.Lock()
c, ok = b.cache[id]
if ok {
b.lock.Unlock()
return c.Refresh()
}
c = refreshcache.NewRefreshCache[any](refreshFunc, maxAge)
b.cache[id] = c
b.lock.Unlock()
return c.Refresh()
}
func (b *Cache) LoadOrStoreWithDynamicFunc(id string, refreshFunc func() (any, error), maxAge time.Duration) (any, error) {
b.lock.RLock()
c, loaded := b.cache[id]
if loaded {
b.lock.RUnlock()
return c.Data().Get(refreshFunc)
}
b.lock.RUnlock()
b.lock.Lock()
c, loaded = b.cache[id]
if loaded {
b.lock.Unlock()
return c.Data().Get(refreshFunc)
}
c = refreshcache.NewRefreshCache[any](refreshFunc, maxAge)
b.cache[id] = c
b.lock.Unlock()
return c.Data().Get(refreshFunc)
}
func (b *Cache) StoreOrRefreshWithDynamicFunc(id string, refreshFunc func() (any, error), maxAge time.Duration) (any, error) {
b.lock.RLock()
c, ok := b.cache[id]
if ok {
b.lock.RUnlock()
return c.Data().Refresh(refreshFunc)
}
b.lock.RUnlock()
b.lock.Lock()
c, ok = b.cache[id]
if ok {
b.lock.Unlock()
return c.Data().Refresh(refreshFunc)
}
c = refreshcache.NewRefreshCache[any](refreshFunc, maxAge)
b.cache[id] = c
b.lock.Unlock()
return c.Data().Refresh(refreshFunc)
}

@ -12,62 +12,19 @@ import (
"github.com/synctv-org/synctv/internal/model" "github.com/synctv-org/synctv/internal/model"
"github.com/synctv-org/synctv/internal/settings" "github.com/synctv-org/synctv/internal/settings"
"github.com/synctv-org/synctv/utils" "github.com/synctv-org/synctv/utils"
"github.com/zijiren233/gencontainer/refreshcache"
"github.com/zijiren233/livelib/av" "github.com/zijiren233/livelib/av"
"github.com/zijiren233/livelib/container/flv" "github.com/zijiren233/livelib/container/flv"
"github.com/zijiren233/livelib/protocol/hls" "github.com/zijiren233/livelib/protocol/hls"
rtmpProto "github.com/zijiren233/livelib/protocol/rtmp" rtmpProto "github.com/zijiren233/livelib/protocol/rtmp"
"github.com/zijiren233/livelib/protocol/rtmp/core" "github.com/zijiren233/livelib/protocol/rtmp/core"
rtmps "github.com/zijiren233/livelib/server" rtmps "github.com/zijiren233/livelib/server"
"golang.org/x/exp/maps"
) )
type Movie struct { type Movie struct {
Movie model.Movie Movie model.Movie
lock *sync.RWMutex lock *sync.RWMutex
channel *rtmps.Channel channel *rtmps.Channel
cache *BaseCache Cache *Cache
}
type BaseCache struct {
lock sync.RWMutex
cache map[string]*refreshcache.RefreshData[any]
}
func newBaseCache() *BaseCache {
return &BaseCache{
cache: make(map[string]*refreshcache.RefreshData[any]),
}
}
func (b *BaseCache) Clear() {
b.lock.Lock()
defer b.lock.Unlock()
b.clear()
}
func (b *BaseCache) clear() {
maps.Clear(b.cache)
}
func (b *BaseCache) LoadOrStore(id string, refreshFunc func() (any, error), maxAge time.Duration) (any, error) {
b.lock.RLock()
c, loaded := b.cache[id]
if loaded {
b.lock.RUnlock()
return c.Get(refreshFunc)
}
b.lock.RUnlock()
b.lock.Lock()
c, loaded = b.cache[id]
if loaded {
b.lock.Unlock()
return c, nil
}
c = refreshcache.NewRefreshData[any](maxAge)
b.cache[id] = c
b.lock.Unlock()
return c.Get(refreshFunc)
} }
func (m *Movie) Channel() (*rtmps.Channel, error) { func (m *Movie) Channel() (*rtmps.Channel, error) {
@ -76,10 +33,6 @@ func (m *Movie) Channel() (*rtmps.Channel, error) {
return m.channel, m.init() return m.channel, m.init()
} }
func (m *Movie) Cache() *BaseCache {
return m.cache
}
func genTsName() string { func genTsName() string {
return utils.SortUUID() return utils.SortUUID()
} }
@ -225,10 +178,11 @@ func (movie *Movie) Validate() error {
func (movie *Movie) validateVendorMovie() error { func (movie *Movie) validateVendorMovie() error {
switch movie.Movie.Base.VendorInfo.Vendor { switch movie.Movie.Base.VendorInfo.Vendor {
case model.StreamingVendorBilibili: case model.VendorBilibili:
return movie.Movie.Base.VendorInfo.Bilibili.Validate() return movie.Movie.Base.VendorInfo.Bilibili.Validate()
case model.StreamingVendorAlist: case model.VendorAlist:
// return movie.Movie.Base.VendorInfo.Alist.Validate()
default: default:
return fmt.Errorf("vendor not support") return fmt.Errorf("vendor not support")
@ -248,7 +202,7 @@ func (m *Movie) terminate() {
m.channel.Close() m.channel.Close()
m.channel = nil m.channel = nil
} }
m.cache.clear() m.Cache.clear()
} }
func (m *Movie) Update(movie *model.BaseMovie) error { func (m *Movie) Update(movie *model.BaseMovie) error {

@ -25,7 +25,7 @@ func (m *movies) init() {
m.list.PushBack(&Movie{ m.list.PushBack(&Movie{
Movie: *m2, Movie: *m2,
lock: new(sync.RWMutex), lock: new(sync.RWMutex),
cache: newBaseCache(), Cache: newBaseCache(),
}) })
} }
}) })
@ -46,7 +46,7 @@ func (m *movies) AddMovie(mo *model.Movie) error {
movie := &Movie{ movie := &Movie{
Movie: *mo, Movie: *mo,
lock: new(sync.RWMutex), lock: new(sync.RWMutex),
cache: newBaseCache(), Cache: newBaseCache(),
} }
err := movie.init() err := movie.init()
@ -75,7 +75,7 @@ func (m *movies) AddMovies(mos []*model.Movie) error {
movie := &Movie{ movie := &Movie{
Movie: *mo, Movie: *mo,
lock: new(sync.RWMutex), lock: new(sync.RWMutex),
cache: newBaseCache(), Cache: newBaseCache(),
} }
err := movie.init() err := movie.init()

@ -16,6 +16,7 @@ import (
type User struct { type User struct {
model.User model.User
version uint32 version uint32
Cache *Cache
} }
func (u *User) Version() uint32 { func (u *User) Version() uint32 {
@ -69,11 +70,11 @@ func (u *User) NewMovie(movie *model.BaseMovie) (*model.Movie, error) {
return nil, errors.New("movie is nil") return nil, errors.New("movie is nil")
} }
switch movie.VendorInfo.Vendor { switch movie.VendorInfo.Vendor {
case model.StreamingVendorBilibili: case model.VendorBilibili:
if movie.VendorInfo.Bilibili == nil { if movie.VendorInfo.Bilibili == nil {
return nil, errors.New("bilibili payload is nil") return nil, errors.New("bilibili payload is nil")
} }
case model.StreamingVendorAlist: case model.VendorAlist:
if movie.VendorInfo.Alist == nil { if movie.VendorInfo.Alist == nil {
return nil, errors.New("alist payload is nil") return nil, errors.New("alist payload is nil")
} }

@ -28,6 +28,7 @@ func LoadOrInitUser(u *model.User) (*User, error) {
i, _ := userCache.LoadOrStore(u.ID, &User{ i, _ := userCache.LoadOrStore(u.ID, &User{
User: *u, User: *u,
version: crc32.ChecksumIEEE(u.HashedPassword), version: crc32.ChecksumIEEE(u.HashedPassword),
Cache: newBaseCache(),
}, time.Hour) }, time.Hour)
return i.Value(), nil return i.Value(), nil
} }

@ -473,7 +473,7 @@ func ProxyMovie(ctx *gin.Context) {
switch m.Movie.Base.Type { switch m.Movie.Base.Type {
case "mpd": case "mpd":
mpdI, err := m.Cache().LoadOrStore("", initDashCache(ctx, &m.Movie), time.Minute*5) mpdI, err := m.Cache.LoadOrStoreWithDynamicFunc("", initDashCache(ctx, &m.Movie), time.Minute*5)
if err != nil { if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return return
@ -673,13 +673,13 @@ type bilibiliCache struct {
func initBilibiliMPDCache(ctx context.Context, movie dbModel.Movie) func() (any, error) { func initBilibiliMPDCache(ctx context.Context, movie dbModel.Movie) func() (any, error) {
return func() (any, error) { return func() (any, error) {
var cookies []*http.Cookie var cookies []*http.Cookie
vendorInfo, err := db.GetVendorByUserIDAndVendor(movie.CreatorID, dbModel.StreamingVendorBilibili) vendorInfo, err := db.GetBilibiliVendor(movie.CreatorID)
if err != nil { if err != nil {
if !errors.Is(err, db.ErrNotFound("vendor")) { if !errors.Is(err, db.ErrNotFound("vendor")) {
return nil, err return nil, err
} }
} else { } else {
cookies = vendorInfo.Cookies cookies = utils.MapToHttpCookie(vendorInfo.Cookies)
} }
cli := vendor.BilibiliClient(movie.Base.VendorInfo.Backend) cli := vendor.BilibiliClient(movie.Base.VendorInfo.Backend)
var m, hevcM *mpd.MPD var m, hevcM *mpd.MPD
@ -766,13 +766,13 @@ func initBilibiliMPDCache(ctx context.Context, movie dbModel.Movie) func() (any,
func initBilibiliCache(ctx context.Context, movie dbModel.Movie, cookieUserID string) func() (any, error) { func initBilibiliCache(ctx context.Context, movie dbModel.Movie, cookieUserID string) func() (any, error) {
return func() (any, error) { return func() (any, error) {
var cookies []*http.Cookie var cookies []*http.Cookie
vendorInfo, err := db.GetVendorByUserIDAndVendor(cookieUserID, dbModel.StreamingVendorBilibili) vendorInfo, err := db.GetBilibiliVendor(cookieUserID)
if err != nil { if err != nil {
if !errors.Is(err, db.ErrNotFound("vendor")) { if !errors.Is(err, db.ErrNotFound("vendor")) {
return nil, err return nil, err
} }
} else { } else {
cookies = vendorInfo.Cookies cookies = utils.MapToHttpCookie(vendorInfo.Cookies)
} }
cli := vendor.BilibiliClient(movie.Base.VendorInfo.Backend) cli := vendor.BilibiliClient(movie.Base.VendorInfo.Backend)
var u string var u string
@ -839,13 +839,13 @@ func initBilibiliSubtitleCache(ctx context.Context, movie dbModel.Movie) func()
} }
var cookies []*http.Cookie var cookies []*http.Cookie
vendorInfo, err := db.GetVendorByUserIDAndVendor(movie.CreatorID, dbModel.StreamingVendorBilibili) vendorInfo, err := db.GetBilibiliVendor(movie.CreatorID)
if err != nil { if err != nil {
if !errors.Is(err, db.ErrNotFound("vendor")) { if !errors.Is(err, db.ErrNotFound("vendor")) {
return nil, err return nil, err
} }
} else { } else {
cookies = vendorInfo.Cookies cookies = utils.MapToHttpCookie(vendorInfo.Cookies)
} }
cli := vendor.BilibiliClient(movie.Base.VendorInfo.Backend) cli := vendor.BilibiliClient(movie.Base.VendorInfo.Backend)
resp, err := cli.GetSubtitles(ctx, &bilibili.GetSubtitlesReq{ resp, err := cli.GetSubtitles(ctx, &bilibili.GetSubtitlesReq{
@ -921,17 +921,17 @@ type alistCache struct {
func initAlistCache(ctx context.Context, movie dbModel.Movie) func() (any, error) { func initAlistCache(ctx context.Context, movie dbModel.Movie) func() (any, error) {
return func() (any, error) { return func() (any, error) {
v, err := db.GetVendorByUserIDAndVendor(movie.CreatorID, dbModel.StreamingVendorAlist) // v, err := db.GetVendorByUserIDAndVendor(movie.CreatorID, dbModel.StreamingVendorAlist)
if err != nil { // if err != nil {
return nil, err // return nil, err
} // }
if v.Host == "" { // if v.Host == "" {
return nil, errors.New("not bind alist vendor") // return nil, errors.New("not bind alist vendor")
} // }
cli := vendor.AlistClient(movie.Base.VendorInfo.Backend) cli := vendor.AlistClient(movie.Base.VendorInfo.Backend)
fg, err := cli.FsGet(ctx, &alist.FsGetReq{ fg, err := cli.FsGet(ctx, &alist.FsGetReq{
Host: v.Host, // Host: v.Host,
Token: v.Authorization, // Token: v.Authorization,
Path: movie.Base.VendorInfo.Alist.Path, Path: movie.Base.VendorInfo.Alist.Path,
Password: movie.Base.VendorInfo.Alist.Password, Password: movie.Base.VendorInfo.Alist.Password,
}) })
@ -948,8 +948,8 @@ func initAlistCache(ctx context.Context, movie dbModel.Movie) func() (any, error
} }
if fg.Provider == "AliyundriveOpen" { if fg.Provider == "AliyundriveOpen" {
fo, err := cli.FsOther(ctx, &alist.FsOtherReq{ fo, err := cli.FsOther(ctx, &alist.FsOtherReq{
Host: v.Host, // Host: v.Host,
Token: v.Authorization, // Token: v.Authorization,
Path: movie.Base.VendorInfo.Alist.Path, Path: movie.Base.VendorInfo.Alist.Path,
Password: movie.Base.VendorInfo.Alist.Password, Password: movie.Base.VendorInfo.Alist.Password,
Method: "video_preview", Method: "video_preview",
@ -965,7 +965,7 @@ func initAlistCache(ctx context.Context, movie dbModel.Movie) func() (any, error
func proxyVendorMovie(ctx *gin.Context, movie *op.Movie) { func proxyVendorMovie(ctx *gin.Context, movie *op.Movie) {
switch movie.Movie.Base.VendorInfo.Vendor { switch movie.Movie.Base.VendorInfo.Vendor {
case dbModel.StreamingVendorBilibili: case dbModel.VendorBilibili:
t := ctx.Query("t") t := ctx.Query("t")
switch t { switch t {
case "", "hevc": case "", "hevc":
@ -974,7 +974,7 @@ func proxyVendorMovie(ctx *gin.Context, movie *op.Movie) {
return return
} }
mpdI, err := movie.Cache().LoadOrStore(t, initBilibiliMPDCache(ctx, movie.Movie), time.Minute*119) mpdI, err := movie.Cache.LoadOrStoreWithDynamicFunc(t, initBilibiliMPDCache(ctx, movie.Movie), time.Minute*119)
if err != nil { if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return return
@ -1020,7 +1020,7 @@ func proxyVendorMovie(ctx *gin.Context, movie *op.Movie) {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("n is empty")) ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("n is empty"))
return return
} }
srtI, err := movie.Cache().LoadOrStore("subtitle", initBilibiliSubtitleCache(ctx, movie.Movie), time.Minute*15) srtI, err := movie.Cache.LoadOrStoreWithDynamicFunc("subtitle", initBilibiliSubtitleCache(ctx, movie.Movie), time.Minute*15)
if err != nil { if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return return
@ -1058,9 +1058,9 @@ func parse2VendorMovie(ctx context.Context, userID string, movie *op.Movie) (err
} }
switch movie.Movie.Base.VendorInfo.Vendor { switch movie.Movie.Base.VendorInfo.Vendor {
case dbModel.StreamingVendorBilibili: case dbModel.VendorBilibili:
if !movie.Movie.Base.Proxy { if !movie.Movie.Base.Proxy {
dataI, err := movie.Cache().LoadOrStore(userID, initBilibiliCache(ctx, movie.Movie, userID), time.Minute*119) dataI, err := movie.Cache.LoadOrStoreWithDynamicFunc(userID, initBilibiliCache(ctx, movie.Movie, userID), time.Minute*119)
if err != nil { if err != nil {
return err return err
} }
@ -1074,7 +1074,7 @@ func parse2VendorMovie(ctx context.Context, userID string, movie *op.Movie) (err
} else { } else {
movie.Movie.Base.Type = "mpd" movie.Movie.Base.Type = "mpd"
} }
srtI, err := movie.Cache().LoadOrStore("subtitle", initBilibiliSubtitleCache(ctx, movie.Movie), time.Minute*15) srtI, err := movie.Cache.LoadOrStoreWithDynamicFunc("subtitle", initBilibiliSubtitleCache(ctx, movie.Movie), time.Minute*15)
if err != nil { if err != nil {
return err return err
} }
@ -1093,8 +1093,8 @@ func parse2VendorMovie(ctx context.Context, userID string, movie *op.Movie) (err
} }
return nil return nil
case dbModel.StreamingVendorAlist: case dbModel.VendorAlist:
dataI, err := movie.Cache().LoadOrStore("", initAlistCache(ctx, movie.Movie), time.Minute*15) dataI, err := movie.Cache.LoadOrStoreWithDynamicFunc("", initAlistCache(ctx, movie.Movie), time.Minute*15)
if err != nil { if err != nil {
return err return err
} }

@ -1,8 +1,10 @@
package vendorAlist package vendorAlist
import ( import (
"context"
"errors" "errors"
"net/http" "net/http"
"time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
json "github.com/json-iterator/go" json "github.com/json-iterator/go"
@ -31,6 +33,48 @@ func (r *LoginReq) Decode(ctx *gin.Context) error {
return json.NewDecoder(ctx.Request.Body).Decode(r) return json.NewDecoder(ctx.Request.Body).Decode(r)
} }
type alistCache struct {
Host string
Token string
}
func initAlistAuthorizationCacheWithConfig(ctx context.Context, cli alist.AlistHTTPServer, host, username, password string) func() (any, error) {
return func() (any, error) {
if username == "" {
_, err := cli.Me(ctx, &alist.MeReq{
Host: host,
})
return &alistCache{
Host: host,
}, err
} else {
resp, err := cli.Login(ctx, &alist.LoginReq{
Host: host,
Username: username,
Password: password,
})
if err != nil {
return nil, err
}
return &alistCache{
Host: host,
Token: resp.Token,
}, nil
}
}
}
func initAlistAuthorizationCacheWithUserID(ctx context.Context, cli alist.AlistHTTPServer, userID string) func() (any, error) {
return func() (any, error) {
v, err := db.GetAlistVendor(userID)
if err != nil {
return nil, err
}
return initAlistAuthorizationCacheWithConfig(ctx, cli, v.Host, v.Username, v.Password)()
}
}
func Login(ctx *gin.Context) { func Login(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.User) user := ctx.MustGet("user").(*op.User)
@ -42,20 +86,26 @@ func Login(ctx *gin.Context) {
cli := vendor.AlistClient("") cli := vendor.AlistClient("")
var (
authI any
err error
)
if req.Username == "" { if req.Username == "" {
_, err := cli.Me(ctx, &alist.MeReq{ _, err = cli.Me(ctx, &alist.MeReq{
Host: req.Host, Host: req.Host,
}) })
if err != nil { if err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
} }
_, err = db.CreateOrSaveVendorByUserIDAndVendor(user.ID, dbModel.StreamingVendorAlist, db.WithHost(req.Host), db.WithAuthorization(""))
if err != nil { authI, err = user.Cache.StoreOrRefreshWithDynamicFunc("alist_authorization", func() (any, error) {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) return &alistCache{
return Host: req.Host,
} }, nil
}, time.Hour*24)
} else { } else {
resp, err := cli.Login(ctx, &alist.LoginReq{ var resp *alist.LoginResp
resp, err = cli.Login(ctx, &alist.LoginReq{
Host: req.Host, Host: req.Host,
Username: req.Username, Username: req.Username,
Password: req.Password, Password: req.Password,
@ -65,11 +115,32 @@ func Login(ctx *gin.Context) {
return return
} }
_, err = db.CreateOrSaveVendorByUserIDAndVendor(user.ID, dbModel.StreamingVendorAlist, db.WithAuthorization(resp.Token), db.WithHost(req.Host)) authI, err = user.Cache.StoreOrRefreshWithDynamicFunc("alist_authorization", func() (any, error) {
if err != nil { return &alistCache{
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) Host: req.Host,
return Token: resp.Token,
} }, nil
}, time.Hour*24)
}
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
_, ok := authI.(*alistCache)
if !ok {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
_, err = db.CreateOrSaveAlistVendor(user.ID, &dbModel.AlistVendor{
Host: req.Host,
Username: req.Username,
Password: req.Password,
})
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
} }
ctx.Status(http.StatusNoContent) ctx.Status(http.StatusNoContent)

@ -3,10 +3,10 @@ package vendorAlist
import ( import (
"errors" "errors"
"net/http" "net/http"
"time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/synctv-org/synctv/internal/db" "github.com/synctv-org/synctv/internal/db"
dbModel "github.com/synctv-org/synctv/internal/model"
"github.com/synctv-org/synctv/internal/op" "github.com/synctv-org/synctv/internal/op"
"github.com/synctv-org/synctv/internal/vendor" "github.com/synctv-org/synctv/internal/vendor"
"github.com/synctv-org/synctv/server/model" "github.com/synctv-org/synctv/server/model"
@ -17,7 +17,10 @@ type AlistMeResp = model.VendorMeResp[*alist.MeResp]
func Me(ctx *gin.Context) { func Me(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.User) user := ctx.MustGet("user").(*op.User)
v, err := db.GetVendorByUserIDAndVendor(user.ID, dbModel.StreamingVendorAlist)
cli := vendor.AlistClient(ctx.Query("backend"))
authorizationI, err := user.Cache.LoadOrStoreWithDynamicFunc("alist_authorization", initAlistAuthorizationCacheWithUserID(ctx, cli, user.ID), time.Hour*24)
if err != nil { if err != nil {
if errors.Is(err, db.ErrNotFound("vendor")) { if errors.Is(err, db.ErrNotFound("vendor")) {
ctx.JSON(http.StatusOK, model.NewApiDataResp(&AlistMeResp{ ctx.JSON(http.StatusOK, model.NewApiDataResp(&AlistMeResp{
@ -28,10 +31,15 @@ func Me(ctx *gin.Context) {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return return
} }
cache, ok := authorizationI.(*alistCache)
if !ok {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
resp, err := vendor.AlistClient("").Me(ctx, &alist.MeReq{ resp, err := cli.Me(ctx, &alist.MeReq{
Host: v.Host, Host: cache.Host,
Token: v.Authorization, Token: cache.Token,
}) })
if err != nil { if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
@ -42,4 +50,5 @@ func Me(ctx *gin.Context) {
IsLogin: false, IsLogin: false,
Info: resp, Info: resp,
})) }))
} }

@ -1,13 +1,13 @@
package vendorAlist package vendorAlist
import ( import (
"fmt" "errors"
"net/http" "net/http"
"time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
json "github.com/json-iterator/go" json "github.com/json-iterator/go"
"github.com/synctv-org/synctv/internal/db" "github.com/synctv-org/synctv/internal/db"
dbModel "github.com/synctv-org/synctv/internal/model"
"github.com/synctv-org/synctv/internal/op" "github.com/synctv-org/synctv/internal/op"
"github.com/synctv-org/synctv/internal/vendor" "github.com/synctv-org/synctv/internal/vendor"
"github.com/synctv-org/synctv/server/model" "github.com/synctv-org/synctv/server/model"
@ -40,21 +40,30 @@ func List(ctx *gin.Context) {
return return
} }
v, err := db.GetVendorByUserIDAndVendor(user.ID, dbModel.StreamingVendorAlist) var cli = vendor.AlistClient(ctx.Query("backend"))
cacheI, err := user.Cache.LoadOrStoreWithDynamicFunc("alist_authorization", initAlistAuthorizationCacheWithUserID(ctx, cli, user.ID), time.Hour*24)
if err != nil { if err != nil {
if errors.Is(err, db.ErrNotFound("vendor")) {
ctx.JSON(http.StatusOK, model.NewApiDataResp(&AlistMeResp{
IsLogin: false,
}))
return
}
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
cache, ok := cacheI.(*alistCache)
if !ok {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return return
} }
fmt.Printf("v.Authorization: %v\n", v.Authorization)
var cli = vendor.AlistClient(ctx.Query("backend"))
resp, err := cli.FsList(ctx, &alist.FsListReq{ resp, err := cli.FsList(ctx, &alist.FsListReq{
Token: v.Authorization, Token: cache.Token,
Password: req.Password, Password: req.Password,
Path: req.Path, Path: req.Path,
Host: v.Host, Host: cache.Host,
Refresh: req.Refresh, Refresh: req.Refresh,
}) })
if err != nil { if err != nil {

@ -8,7 +8,6 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
json "github.com/json-iterator/go" json "github.com/json-iterator/go"
"github.com/synctv-org/synctv/internal/db" "github.com/synctv-org/synctv/internal/db"
dbModel "github.com/synctv-org/synctv/internal/model"
"github.com/synctv-org/synctv/internal/op" "github.com/synctv-org/synctv/internal/op"
"github.com/synctv-org/synctv/internal/vendor" "github.com/synctv-org/synctv/internal/vendor"
"github.com/synctv-org/synctv/server/model" "github.com/synctv-org/synctv/server/model"
@ -51,14 +50,14 @@ func Parse(ctx *gin.Context) {
} }
var cookies []*http.Cookie var cookies []*http.Cookie
vendorInfo, err := db.GetVendorByUserIDAndVendor(user.ID, dbModel.StreamingVendorBilibili) vendorInfo, err := db.GetBilibiliVendor(user.ID)
if err != nil { if err != nil {
if !errors.Is(err, db.ErrNotFound("vendor")) { if !errors.Is(err, db.ErrNotFound("vendor")) {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return return
} }
} else { } else {
cookies = vendorInfo.Cookies cookies = utils.MapToHttpCookie(vendorInfo.Cookies)
} }
switch resp.Type { switch resp.Type {

@ -11,7 +11,6 @@ import (
"github.com/synctv-org/synctv/internal/op" "github.com/synctv-org/synctv/internal/op"
"github.com/synctv-org/synctv/internal/vendor" "github.com/synctv-org/synctv/internal/vendor"
"github.com/synctv-org/synctv/server/model" "github.com/synctv-org/synctv/server/model"
"github.com/synctv-org/synctv/utils"
"github.com/synctv-org/vendors/api/bilibili" "github.com/synctv-org/vendors/api/bilibili"
) )
@ -73,7 +72,9 @@ func LoginWithQR(ctx *gin.Context) {
})) }))
return return
case bilibili.QRCodeStatus_SUCCESS: case bilibili.QRCodeStatus_SUCCESS:
_, err = db.CreateOrSaveVendorByUserIDAndVendor(user.ID, dbModel.StreamingVendorBilibili, db.WithCookie(utils.MapToHttpCookie(resp.Cookies))) _, err = db.CreateOrSaveBilibiliVendor(user.ID, &dbModel.BilibiliVendor{
Cookies: resp.Cookies,
})
if err != nil { if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return return
@ -185,7 +186,9 @@ func LoginWithSMS(ctx *gin.Context) {
return return
} }
user := ctx.MustGet("user").(*op.User) user := ctx.MustGet("user").(*op.User)
_, err = db.CreateOrSaveVendorByUserIDAndVendor(user.ID, dbModel.StreamingVendorBilibili, db.WithCookie(utils.MapToHttpCookie(c.Cookies))) _, err = db.CreateOrSaveBilibiliVendor(user.ID, &dbModel.BilibiliVendor{
Cookies: c.Cookies,
})
if err != nil { if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return return
@ -195,7 +198,7 @@ func LoginWithSMS(ctx *gin.Context) {
func Logout(ctx *gin.Context) { func Logout(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.User) user := ctx.MustGet("user").(*op.User)
err := db.DeleteVendorByUserIDAndVendor(user.ID, dbModel.StreamingVendorBilibili) err := db.DeleteBilibiliVendor(user.ID)
if err != nil { if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return return

@ -6,11 +6,9 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/synctv-org/synctv/internal/db" "github.com/synctv-org/synctv/internal/db"
dbModel "github.com/synctv-org/synctv/internal/model"
"github.com/synctv-org/synctv/internal/op" "github.com/synctv-org/synctv/internal/op"
"github.com/synctv-org/synctv/internal/vendor" "github.com/synctv-org/synctv/internal/vendor"
"github.com/synctv-org/synctv/server/model" "github.com/synctv-org/synctv/server/model"
"github.com/synctv-org/synctv/utils"
"github.com/synctv-org/vendors/api/bilibili" "github.com/synctv-org/vendors/api/bilibili"
) )
@ -18,7 +16,7 @@ type BilibiliMeResp = model.VendorMeResp[*bilibili.UserInfoResp]
func Me(ctx *gin.Context) { func Me(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.User) user := ctx.MustGet("user").(*op.User)
v, err := db.GetVendorByUserIDAndVendor(user.ID, dbModel.StreamingVendorBilibili) v, err := db.GetBilibiliVendor(user.ID)
if err != nil { if err != nil {
if errors.Is(err, db.ErrNotFound("vendor")) { if errors.Is(err, db.ErrNotFound("vendor")) {
ctx.JSON(http.StatusOK, model.NewApiDataResp(&BilibiliMeResp{ ctx.JSON(http.StatusOK, model.NewApiDataResp(&BilibiliMeResp{
@ -36,7 +34,7 @@ func Me(ctx *gin.Context) {
return return
} }
resp, err := vendor.BilibiliClient("").UserInfo(ctx, &bilibili.UserInfoReq{ resp, err := vendor.BilibiliClient("").UserInfo(ctx, &bilibili.UserInfoReq{
Cookies: utils.HttpCookieToMap(v.Cookies), Cookies: v.Cookies,
}) })
if err != nil { if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))

@ -12,10 +12,10 @@ import (
func Backends(ctx *gin.Context) { func Backends(ctx *gin.Context) {
var backends []string var backends []string
switch dbModel.StreamingVendor(ctx.Param("vendor")) { switch ctx.Param("vendor") {
case dbModel.StreamingVendorBilibili: case dbModel.VendorBilibili:
backends = maps.Keys(vendor.BilibiliClients()) backends = maps.Keys(vendor.BilibiliClients())
case dbModel.StreamingVendorAlist: case dbModel.VendorAlist:
backends = maps.Keys(vendor.AlistClients()) backends = maps.Keys(vendor.AlistClients())
default: default:
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("invalid vendor")) ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("invalid vendor"))

@ -0,0 +1,62 @@
package utils
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"errors"
"io"
)
func Crypto(v []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
ciphertext := make([]byte, aes.BlockSize+len(v))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(ciphertext[aes.BlockSize:], v)
return ciphertext, nil
}
func Decrypto(v []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
if len(v) < aes.BlockSize {
return nil, errors.New("ciphertext too short")
}
iv := v[:aes.BlockSize]
v = v[aes.BlockSize:]
stream := cipher.NewCFBDecrypter(block, iv)
stream.XORKeyStream(v, v)
return v, nil
}
func CryptoToBase64(v []byte, key []byte) (string, error) {
ciphertext, err := Crypto(v, key)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
func DecryptoFromBase64(v string, key []byte) ([]byte, error) {
ciphertext, err := base64.StdEncoding.DecodeString(v)
if err != nil {
return nil, err
}
return Decrypto(ciphertext, key)
}

@ -0,0 +1,22 @@
package utils_test
import (
"testing"
"github.com/synctv-org/synctv/utils"
)
func TestCrypto(t *testing.T) {
m := []byte("hello world")
key := []byte(utils.RandString(32))
m, err := utils.Crypto(m, key)
if err != nil {
t.Fatal(err)
}
t.Log(string(m))
m, err = utils.Decrypto(m, key)
if err != nil {
t.Fatal(err)
}
t.Log(string(m))
}

@ -14,7 +14,7 @@ var json = jsoniter.ConfigCompatibleWithStandardLibrary
type JSONSerializer struct{} type JSONSerializer struct{}
func (*JSONSerializer) Scan(ctx context.Context, field *schema.Field, dst reflect.Value, dbValue interface{}) (err error) { func (*JSONSerializer) Scan(ctx context.Context, field *schema.Field, dst reflect.Value, dbValue any) (err error) {
fieldValue := reflect.New(field.FieldType) fieldValue := reflect.New(field.FieldType)
if dbValue != nil { if dbValue != nil {
@ -35,8 +35,7 @@ func (*JSONSerializer) Scan(ctx context.Context, field *schema.Field, dst reflec
return return
} }
// 实现 Value 方法 func (*JSONSerializer) Value(ctx context.Context, field *schema.Field, dst reflect.Value, fieldValue any) (any, error) {
func (*JSONSerializer) Value(ctx context.Context, field *schema.Field, dst reflect.Value, fieldValue interface{}) (interface{}, error) {
return json.Marshal(fieldValue) return json.Marshal(fieldValue)
} }

Loading…
Cancel
Save