mirror of https://github.com/synctv-org/synctv.git
Feat: crypto vendor info in db
This commit is contained in:
parent
ae1f6c1407
commit
37094dd2bc
2
go.mod
2
go.mod
|
@ -31,7 +31,7 @@ require (
|
|||
github.com/synctv-org/vendors v0.1.1-0.20231209122754-ebad9251fa7a
|
||||
github.com/ulule/limiter/v3 v3.11.2
|
||||
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/ksync v0.2.0
|
||||
github.com/zijiren233/livelib v0.2.3-0.20231103145812-58de2ae7f423
|
||||
|
|
2
go.sum
2
go.sum
|
@ -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-20231209155516-a52fcb19fee5 h1:OsNDmOre1xXJpRaQUeqet3yYZbkfy8bfEdsXs8PrXSE=
|
||||
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/go.mod h1:6TCzjDiQ8+5gWZiwsC3pnA5M0vUy2jV2Y7ciHJh729g=
|
||||
github.com/zijiren233/ksync v0.2.0 h1:OyXVXbVQYFEVfWM13NApt4LMHbLQ3HTs4oYcLmqL6NE=
|
||||
|
|
|
@ -21,7 +21,16 @@ var (
|
|||
func Init(d *gorm.DB, t conf.DatabaseType) error {
|
||||
db = d
|
||||
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 {
|
||||
|
|
|
@ -3,18 +3,19 @@ package db
|
|||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/synctv-org/synctv/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type dbVersion struct {
|
||||
NextVersion string
|
||||
Upgrade func() error
|
||||
Upgrade func(*gorm.DB) error
|
||||
}
|
||||
|
||||
const CurrentVersion = "0.0.1"
|
||||
|
||||
var dbVersions = map[string]dbVersion{
|
||||
"0.0.1": {
|
||||
NextVersion: "",
|
||||
NextVersion: "0.0.2-dev",
|
||||
Upgrade: nil,
|
||||
},
|
||||
}
|
||||
|
@ -43,7 +44,7 @@ func upgradeDatabase() error {
|
|||
}
|
||||
log.Infof("Upgrading database to version %s", currentVersion)
|
||||
if version.Upgrade != nil {
|
||||
err = version.Upgrade()
|
||||
err = version.Upgrade(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -2,88 +2,54 @@ package db
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/synctv-org/synctv/internal/model"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
func GetVendorByUserID(userID string) ([]*model.StreamingVendorInfo, error) {
|
||||
var vendors []*model.StreamingVendorInfo
|
||||
err := db.Where("user_id = ?", userID).Find(&vendors).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return vendors, nil
|
||||
func GetBilibiliVendor(userID string) (*model.BilibiliVendor, error) {
|
||||
var vendor model.BilibiliVendor
|
||||
err := db.Where("user_id = ?", userID).Preload(clause.Associations).First(&vendor).Error
|
||||
return &vendor, HandleNotFound(err, "vendor")
|
||||
}
|
||||
|
||||
func GetVendorByUserIDAndVendor(userID string, vendor model.StreamingVendor) (*model.StreamingVendorInfo, error) {
|
||||
var vendorInfo model.StreamingVendorInfo
|
||||
err := db.Where("user_id = ? AND vendor = ?", userID, vendor).First(&vendorInfo).Error
|
||||
return &vendorInfo, HandleNotFound(err, "vendor")
|
||||
}
|
||||
|
||||
type CreateVendorConfig func(*model.StreamingVendorInfo)
|
||||
|
||||
func WithCookie(cookie []*http.Cookie) CreateVendorConfig {
|
||||
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 {
|
||||
return func(vendor *model.StreamingVendorInfo) {
|
||||
vendor.Host = host
|
||||
}
|
||||
}
|
||||
|
||||
func FirstOrCreateVendorByUserIDAndVendor(userID string, vendor model.StreamingVendor, conf ...CreateVendorConfig) (*model.StreamingVendorInfo, error) {
|
||||
var vendorInfo model.StreamingVendorInfo
|
||||
v := &model.StreamingVendorInfo{
|
||||
UserID: userID,
|
||||
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) {
|
||||
vendorInfo := model.StreamingVendorInfo{
|
||||
UserID: userID,
|
||||
Vendor: vendor,
|
||||
}
|
||||
return &vendorInfo, Transactional(func(tx *gorm.DB) error {
|
||||
if errors.Is(tx.First(&vendorInfo).Error, gorm.ErrRecordNotFound) {
|
||||
for _, c := range conf {
|
||||
c(&vendorInfo)
|
||||
}
|
||||
func CreateOrSaveBilibiliVendor(userID string, vendorInfo *model.BilibiliVendor) (*model.BilibiliVendor, error) {
|
||||
vendorInfo.UserID = userID
|
||||
return vendorInfo, Transactional(func(tx *gorm.DB) error {
|
||||
if errors.Is(tx.First(&model.BilibiliVendor{
|
||||
UserID: userID,
|
||||
}).Error, gorm.ErrRecordNotFound) {
|
||||
return tx.Create(&vendorInfo).Error
|
||||
} else {
|
||||
for _, c := range conf {
|
||||
c(&vendorInfo)
|
||||
}
|
||||
return tx.Save(&vendorInfo).Error
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func DeleteVendorByUserIDAndVendor(userID string, vendor model.StreamingVendor) error {
|
||||
return db.Where("user_id = ? AND vendor = ?", userID, vendor).Delete(&model.StreamingVendorInfo{}).Error
|
||||
func DeleteBilibiliVendor(userID string) error {
|
||||
return db.Where("user_id = ?", userID).Delete(&model.BilibiliVendor{}).Error
|
||||
}
|
||||
|
||||
func GetAlistVendor(userID string) (*model.AlistVendor, error) {
|
||||
var vendor model.AlistVendor
|
||||
err := db.Where("user_id = ?", userID).Preload(clause.Associations).First(&vendor).Error
|
||||
return &vendor, HandleNotFound(err, "vendor")
|
||||
}
|
||||
|
||||
func CreateOrSaveAlistVendor(userID string, vendorInfo *model.AlistVendor) (*model.AlistVendor, error) {
|
||||
vendorInfo.UserID = userID
|
||||
return vendorInfo, Transactional(func(tx *gorm.DB) error {
|
||||
if errors.Is(tx.First(&model.AlistVendor{
|
||||
UserID: userID,
|
||||
}).Error, gorm.ErrRecordNotFound) {
|
||||
return tx.Create(&vendorInfo).Error
|
||||
} else {
|
||||
return tx.Save(&vendorInfo).Error
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func DeleteAlistVendor(userID string) error {
|
||||
return db.Where("user_id = ?", userID).Delete(&model.AlistVendor{}).Error
|
||||
}
|
||||
|
|
|
@ -42,22 +42,29 @@ type Subtitle struct {
|
|||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type VendorName = string
|
||||
|
||||
const (
|
||||
VendorBilibili VendorName = "bilibili"
|
||||
VendorAlist VendorName = "alist"
|
||||
)
|
||||
|
||||
type VendorInfo struct {
|
||||
Vendor StreamingVendor `json:"vendor"`
|
||||
Backend string `json:"backend"`
|
||||
Shared bool `gorm:"not null;default:false" json:"shared"`
|
||||
Bilibili *BilibiliVendorInfo `gorm:"embedded;embeddedPrefix:bilibili_" json:"bilibili,omitempty"`
|
||||
Alist *AlistVendorInfo `gorm:"embedded;embeddedPrefix:alist_" json:"alist,omitempty"`
|
||||
Vendor VendorName `json:"vendor"`
|
||||
Backend string `json:"backend"`
|
||||
Shared bool `gorm:"not null;default:false" json:"shared"`
|
||||
Bilibili *BilibiliStreamingInfo `gorm:"embedded;embeddedPrefix:bilibili_" json:"bilibili,omitempty"`
|
||||
Alist *AlistStreamingInfo `gorm:"embedded;embeddedPrefix:alist_" json:"alist,omitempty"`
|
||||
}
|
||||
|
||||
type BilibiliVendorInfo struct {
|
||||
type BilibiliStreamingInfo struct {
|
||||
Bvid string `json:"bvid,omitempty"`
|
||||
Cid uint64 `json:"cid,omitempty"`
|
||||
Epid uint64 `json:"epid,omitempty"`
|
||||
Quality uint64 `json:"quality,omitempty"`
|
||||
}
|
||||
|
||||
func (b *BilibiliVendorInfo) Validate() error {
|
||||
func (b *BilibiliStreamingInfo) Validate() error {
|
||||
switch {
|
||||
// 先判断epid是否为0来确定是否是pgc
|
||||
case b.Epid != 0:
|
||||
|
@ -75,7 +82,7 @@ func (b *BilibiliVendorInfo) Validate() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
type AlistVendorInfo struct {
|
||||
type AlistStreamingInfo struct {
|
||||
Path string `json:"path,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
}
|
||||
|
|
|
@ -42,15 +42,16 @@ type User struct {
|
|||
ID string `gorm:"primaryKey;type:varchar(32)" json:"id"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
RegisteredByProvider bool `gorm:"not null;default:false"`
|
||||
UserProviders []UserProvider `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
|
||||
Username string `gorm:"not null;uniqueIndex"`
|
||||
HashedPassword []byte `gorm:"not null"`
|
||||
Role Role `gorm:"not null;default:2"`
|
||||
RoomUserRelations []RoomUserRelation `gorm:"foreignKey:UserID;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"`
|
||||
StreamingVendorInfos []StreamingVendorInfo `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
|
||||
RegisteredByProvider bool `gorm:"not null;default:false"`
|
||||
UserProviders []UserProvider `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
|
||||
Username string `gorm:"not null;uniqueIndex"`
|
||||
HashedPassword []byte `gorm:"not null"`
|
||||
Role Role `gorm:"not null;default:2"`
|
||||
RoomUserRelations []RoomUserRelation `gorm:"foreignKey:UserID;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"`
|
||||
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 {
|
||||
|
|
|
@ -1,28 +1,77 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
"github.com/synctv-org/synctv/utils"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type StreamingVendor string
|
||||
|
||||
const (
|
||||
StreamingVendorBilibili StreamingVendor = "bilibili"
|
||||
StreamingVendorAlist StreamingVendor = "alist"
|
||||
)
|
||||
|
||||
type StreamingVendorInfo struct {
|
||||
UserID string `gorm:"not null;primarykey"`
|
||||
Vendor StreamingVendor `gorm:"not null;primarykey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
VendorToken
|
||||
Host string
|
||||
type BilibiliVendor struct {
|
||||
UserID string `gorm:"primaryKey"`
|
||||
Cookies map[string]string `gorm:"serializer:fastjson"`
|
||||
}
|
||||
|
||||
type VendorToken struct {
|
||||
Cookies []*http.Cookie `gorm:"serializer:fastjson"`
|
||||
Authorization string
|
||||
Password string
|
||||
func (b *BilibiliVendor) BeforeSave(tx *gorm.DB) error {
|
||||
key := []byte(b.UserID)
|
||||
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"`
|
||||
}
|
||||
|
||||
func (a *AlistVendor) BeforeSave(tx *gorm.DB) error {
|
||||
key := []byte(a.UserID)
|
||||
var err error
|
||||
if a.Host, err = utils.CryptoToBase64([]byte(a.Host), key); err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func (a *AlistVendor) AfterFind(tx *gorm.DB) error {
|
||||
key := []byte(a.UserID)
|
||||
if v, err := utils.DecryptoFromBase64(a.Host, key); err != nil {
|
||||
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/settings"
|
||||
"github.com/synctv-org/synctv/utils"
|
||||
"github.com/zijiren233/gencontainer/refreshcache"
|
||||
"github.com/zijiren233/livelib/av"
|
||||
"github.com/zijiren233/livelib/container/flv"
|
||||
"github.com/zijiren233/livelib/protocol/hls"
|
||||
rtmpProto "github.com/zijiren233/livelib/protocol/rtmp"
|
||||
"github.com/zijiren233/livelib/protocol/rtmp/core"
|
||||
rtmps "github.com/zijiren233/livelib/server"
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
type Movie struct {
|
||||
Movie model.Movie
|
||||
lock *sync.RWMutex
|
||||
channel *rtmps.Channel
|
||||
cache *BaseCache
|
||||
}
|
||||
|
||||
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)
|
||||
Cache *Cache
|
||||
}
|
||||
|
||||
func (m *Movie) Channel() (*rtmps.Channel, error) {
|
||||
|
@ -76,10 +33,6 @@ func (m *Movie) Channel() (*rtmps.Channel, error) {
|
|||
return m.channel, m.init()
|
||||
}
|
||||
|
||||
func (m *Movie) Cache() *BaseCache {
|
||||
return m.cache
|
||||
}
|
||||
|
||||
func genTsName() string {
|
||||
return utils.SortUUID()
|
||||
}
|
||||
|
@ -225,10 +178,11 @@ func (movie *Movie) Validate() error {
|
|||
|
||||
func (movie *Movie) validateVendorMovie() error {
|
||||
switch movie.Movie.Base.VendorInfo.Vendor {
|
||||
case model.StreamingVendorBilibili:
|
||||
case model.VendorBilibili:
|
||||
return movie.Movie.Base.VendorInfo.Bilibili.Validate()
|
||||
|
||||
case model.StreamingVendorAlist:
|
||||
case model.VendorAlist:
|
||||
// return movie.Movie.Base.VendorInfo.Alist.Validate()
|
||||
|
||||
default:
|
||||
return fmt.Errorf("vendor not support")
|
||||
|
@ -248,7 +202,7 @@ func (m *Movie) terminate() {
|
|||
m.channel.Close()
|
||||
m.channel = nil
|
||||
}
|
||||
m.cache.clear()
|
||||
m.Cache.clear()
|
||||
}
|
||||
|
||||
func (m *Movie) Update(movie *model.BaseMovie) error {
|
||||
|
|
|
@ -25,7 +25,7 @@ func (m *movies) init() {
|
|||
m.list.PushBack(&Movie{
|
||||
Movie: *m2,
|
||||
lock: new(sync.RWMutex),
|
||||
cache: newBaseCache(),
|
||||
Cache: newBaseCache(),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -46,7 +46,7 @@ func (m *movies) AddMovie(mo *model.Movie) error {
|
|||
movie := &Movie{
|
||||
Movie: *mo,
|
||||
lock: new(sync.RWMutex),
|
||||
cache: newBaseCache(),
|
||||
Cache: newBaseCache(),
|
||||
}
|
||||
|
||||
err := movie.init()
|
||||
|
@ -75,7 +75,7 @@ func (m *movies) AddMovies(mos []*model.Movie) error {
|
|||
movie := &Movie{
|
||||
Movie: *mo,
|
||||
lock: new(sync.RWMutex),
|
||||
cache: newBaseCache(),
|
||||
Cache: newBaseCache(),
|
||||
}
|
||||
|
||||
err := movie.init()
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
type User struct {
|
||||
model.User
|
||||
version uint32
|
||||
Cache *Cache
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
switch movie.VendorInfo.Vendor {
|
||||
case model.StreamingVendorBilibili:
|
||||
case model.VendorBilibili:
|
||||
if movie.VendorInfo.Bilibili == nil {
|
||||
return nil, errors.New("bilibili payload is nil")
|
||||
}
|
||||
case model.StreamingVendorAlist:
|
||||
case model.VendorAlist:
|
||||
if movie.VendorInfo.Alist == 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{
|
||||
User: *u,
|
||||
version: crc32.ChecksumIEEE(u.HashedPassword),
|
||||
Cache: newBaseCache(),
|
||||
}, time.Hour)
|
||||
return i.Value(), nil
|
||||
}
|
||||
|
|
|
@ -473,7 +473,7 @@ func ProxyMovie(ctx *gin.Context) {
|
|||
|
||||
switch m.Movie.Base.Type {
|
||||
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 {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
return
|
||||
|
@ -673,13 +673,13 @@ type bilibiliCache struct {
|
|||
func initBilibiliMPDCache(ctx context.Context, movie dbModel.Movie) func() (any, error) {
|
||||
return func() (any, error) {
|
||||
var cookies []*http.Cookie
|
||||
vendorInfo, err := db.GetVendorByUserIDAndVendor(movie.CreatorID, dbModel.StreamingVendorBilibili)
|
||||
vendorInfo, err := db.GetBilibiliVendor(movie.CreatorID)
|
||||
if err != nil {
|
||||
if !errors.Is(err, db.ErrNotFound("vendor")) {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
cookies = vendorInfo.Cookies
|
||||
cookies = utils.MapToHttpCookie(vendorInfo.Cookies)
|
||||
}
|
||||
cli := vendor.BilibiliClient(movie.Base.VendorInfo.Backend)
|
||||
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) {
|
||||
return func() (any, error) {
|
||||
var cookies []*http.Cookie
|
||||
vendorInfo, err := db.GetVendorByUserIDAndVendor(cookieUserID, dbModel.StreamingVendorBilibili)
|
||||
vendorInfo, err := db.GetBilibiliVendor(cookieUserID)
|
||||
if err != nil {
|
||||
if !errors.Is(err, db.ErrNotFound("vendor")) {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
cookies = vendorInfo.Cookies
|
||||
cookies = utils.MapToHttpCookie(vendorInfo.Cookies)
|
||||
}
|
||||
cli := vendor.BilibiliClient(movie.Base.VendorInfo.Backend)
|
||||
var u string
|
||||
|
@ -839,13 +839,13 @@ func initBilibiliSubtitleCache(ctx context.Context, movie dbModel.Movie) func()
|
|||
}
|
||||
|
||||
var cookies []*http.Cookie
|
||||
vendorInfo, err := db.GetVendorByUserIDAndVendor(movie.CreatorID, dbModel.StreamingVendorBilibili)
|
||||
vendorInfo, err := db.GetBilibiliVendor(movie.CreatorID)
|
||||
if err != nil {
|
||||
if !errors.Is(err, db.ErrNotFound("vendor")) {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
cookies = vendorInfo.Cookies
|
||||
cookies = utils.MapToHttpCookie(vendorInfo.Cookies)
|
||||
}
|
||||
cli := vendor.BilibiliClient(movie.Base.VendorInfo.Backend)
|
||||
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) {
|
||||
return func() (any, error) {
|
||||
v, err := db.GetVendorByUserIDAndVendor(movie.CreatorID, dbModel.StreamingVendorAlist)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if v.Host == "" {
|
||||
return nil, errors.New("not bind alist vendor")
|
||||
}
|
||||
// v, err := db.GetVendorByUserIDAndVendor(movie.CreatorID, dbModel.StreamingVendorAlist)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// if v.Host == "" {
|
||||
// return nil, errors.New("not bind alist vendor")
|
||||
// }
|
||||
cli := vendor.AlistClient(movie.Base.VendorInfo.Backend)
|
||||
fg, err := cli.FsGet(ctx, &alist.FsGetReq{
|
||||
Host: v.Host,
|
||||
Token: v.Authorization,
|
||||
// Host: v.Host,
|
||||
// Token: v.Authorization,
|
||||
Path: movie.Base.VendorInfo.Alist.Path,
|
||||
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" {
|
||||
fo, err := cli.FsOther(ctx, &alist.FsOtherReq{
|
||||
Host: v.Host,
|
||||
Token: v.Authorization,
|
||||
// Host: v.Host,
|
||||
// Token: v.Authorization,
|
||||
Path: movie.Base.VendorInfo.Alist.Path,
|
||||
Password: movie.Base.VendorInfo.Alist.Password,
|
||||
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) {
|
||||
switch movie.Movie.Base.VendorInfo.Vendor {
|
||||
case dbModel.StreamingVendorBilibili:
|
||||
case dbModel.VendorBilibili:
|
||||
t := ctx.Query("t")
|
||||
switch t {
|
||||
case "", "hevc":
|
||||
|
@ -974,7 +974,7 @@ func proxyVendorMovie(ctx *gin.Context, movie *op.Movie) {
|
|||
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 {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
return
|
||||
|
@ -1020,7 +1020,7 @@ func proxyVendorMovie(ctx *gin.Context, movie *op.Movie) {
|
|||
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("n is empty"))
|
||||
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 {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
return
|
||||
|
@ -1058,9 +1058,9 @@ func parse2VendorMovie(ctx context.Context, userID string, movie *op.Movie) (err
|
|||
}
|
||||
|
||||
switch movie.Movie.Base.VendorInfo.Vendor {
|
||||
case dbModel.StreamingVendorBilibili:
|
||||
case dbModel.VendorBilibili:
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
@ -1074,7 +1074,7 @@ func parse2VendorMovie(ctx context.Context, userID string, movie *op.Movie) (err
|
|||
} else {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
@ -1093,8 +1093,8 @@ func parse2VendorMovie(ctx context.Context, userID string, movie *op.Movie) (err
|
|||
}
|
||||
return nil
|
||||
|
||||
case dbModel.StreamingVendorAlist:
|
||||
dataI, err := movie.Cache().LoadOrStore("", initAlistCache(ctx, movie.Movie), time.Minute*15)
|
||||
case dbModel.VendorAlist:
|
||||
dataI, err := movie.Cache.LoadOrStoreWithDynamicFunc("", initAlistCache(ctx, movie.Movie), time.Minute*15)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package vendorAlist
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
user := ctx.MustGet("user").(*op.User)
|
||||
|
||||
|
@ -42,20 +86,26 @@ func Login(ctx *gin.Context) {
|
|||
|
||||
cli := vendor.AlistClient("")
|
||||
|
||||
var (
|
||||
authI any
|
||||
err error
|
||||
)
|
||||
if req.Username == "" {
|
||||
_, err := cli.Me(ctx, &alist.MeReq{
|
||||
_, err = cli.Me(ctx, &alist.MeReq{
|
||||
Host: req.Host,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
|
||||
}
|
||||
_, err = db.CreateOrSaveVendorByUserIDAndVendor(user.ID, dbModel.StreamingVendorAlist, db.WithHost(req.Host), db.WithAuthorization(""))
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
return
|
||||
}
|
||||
|
||||
authI, err = user.Cache.StoreOrRefreshWithDynamicFunc("alist_authorization", func() (any, error) {
|
||||
return &alistCache{
|
||||
Host: req.Host,
|
||||
}, nil
|
||||
}, time.Hour*24)
|
||||
} else {
|
||||
resp, err := cli.Login(ctx, &alist.LoginReq{
|
||||
var resp *alist.LoginResp
|
||||
resp, err = cli.Login(ctx, &alist.LoginReq{
|
||||
Host: req.Host,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
|
@ -65,11 +115,32 @@ func Login(ctx *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
_, err = db.CreateOrSaveVendorByUserIDAndVendor(user.ID, dbModel.StreamingVendorAlist, db.WithAuthorization(resp.Token), db.WithHost(req.Host))
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
return
|
||||
}
|
||||
authI, err = user.Cache.StoreOrRefreshWithDynamicFunc("alist_authorization", func() (any, error) {
|
||||
return &alistCache{
|
||||
Host: req.Host,
|
||||
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)
|
||||
|
|
|
@ -3,10 +3,10 @@ package vendorAlist
|
|||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"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/vendor"
|
||||
"github.com/synctv-org/synctv/server/model"
|
||||
|
@ -17,7 +17,10 @@ type AlistMeResp = model.VendorMeResp[*alist.MeResp]
|
|||
|
||||
func Me(ctx *gin.Context) {
|
||||
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 errors.Is(err, db.ErrNotFound("vendor")) {
|
||||
ctx.JSON(http.StatusOK, model.NewApiDataResp(&AlistMeResp{
|
||||
|
@ -28,10 +31,15 @@ func Me(ctx *gin.Context) {
|
|||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
return
|
||||
}
|
||||
cache, ok := authorizationI.(*alistCache)
|
||||
if !ok {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := vendor.AlistClient("").Me(ctx, &alist.MeReq{
|
||||
Host: v.Host,
|
||||
Token: v.Authorization,
|
||||
resp, err := cli.Me(ctx, &alist.MeReq{
|
||||
Host: cache.Host,
|
||||
Token: cache.Token,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
|
@ -42,4 +50,5 @@ func Me(ctx *gin.Context) {
|
|||
IsLogin: false,
|
||||
Info: resp,
|
||||
}))
|
||||
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
package vendorAlist
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
json "github.com/json-iterator/go"
|
||||
"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/vendor"
|
||||
"github.com/synctv-org/synctv/server/model"
|
||||
|
@ -40,21 +40,30 @@ func List(ctx *gin.Context) {
|
|||
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 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))
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("v.Authorization: %v\n", v.Authorization)
|
||||
|
||||
var cli = vendor.AlistClient(ctx.Query("backend"))
|
||||
|
||||
resp, err := cli.FsList(ctx, &alist.FsListReq{
|
||||
Token: v.Authorization,
|
||||
Token: cache.Token,
|
||||
Password: req.Password,
|
||||
Path: req.Path,
|
||||
Host: v.Host,
|
||||
Host: cache.Host,
|
||||
Refresh: req.Refresh,
|
||||
})
|
||||
if err != nil {
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"github.com/gin-gonic/gin"
|
||||
json "github.com/json-iterator/go"
|
||||
"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/vendor"
|
||||
"github.com/synctv-org/synctv/server/model"
|
||||
|
@ -51,14 +50,14 @@ func Parse(ctx *gin.Context) {
|
|||
}
|
||||
|
||||
var cookies []*http.Cookie
|
||||
vendorInfo, err := db.GetVendorByUserIDAndVendor(user.ID, dbModel.StreamingVendorBilibili)
|
||||
vendorInfo, err := db.GetBilibiliVendor(user.ID)
|
||||
if err != nil {
|
||||
if !errors.Is(err, db.ErrNotFound("vendor")) {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
cookies = vendorInfo.Cookies
|
||||
cookies = utils.MapToHttpCookie(vendorInfo.Cookies)
|
||||
}
|
||||
|
||||
switch resp.Type {
|
||||
|
|
|
@ -11,7 +11,6 @@ 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"
|
||||
)
|
||||
|
||||
|
@ -73,7 +72,9 @@ func LoginWithQR(ctx *gin.Context) {
|
|||
}))
|
||||
return
|
||||
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 {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
return
|
||||
|
@ -185,7 +186,9 @@ func LoginWithSMS(ctx *gin.Context) {
|
|||
return
|
||||
}
|
||||
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 {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
return
|
||||
|
@ -195,7 +198,7 @@ func LoginWithSMS(ctx *gin.Context) {
|
|||
|
||||
func Logout(ctx *gin.Context) {
|
||||
user := ctx.MustGet("user").(*op.User)
|
||||
err := db.DeleteVendorByUserIDAndVendor(user.ID, dbModel.StreamingVendorBilibili)
|
||||
err := db.DeleteBilibiliVendor(user.ID)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
return
|
||||
|
|
|
@ -6,11 +6,9 @@ import (
|
|||
|
||||
"github.com/gin-gonic/gin"
|
||||
"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/vendor"
|
||||
"github.com/synctv-org/synctv/server/model"
|
||||
"github.com/synctv-org/synctv/utils"
|
||||
"github.com/synctv-org/vendors/api/bilibili"
|
||||
)
|
||||
|
||||
|
@ -18,7 +16,7 @@ type BilibiliMeResp = model.VendorMeResp[*bilibili.UserInfoResp]
|
|||
|
||||
func Me(ctx *gin.Context) {
|
||||
user := ctx.MustGet("user").(*op.User)
|
||||
v, err := db.GetVendorByUserIDAndVendor(user.ID, dbModel.StreamingVendorBilibili)
|
||||
v, err := db.GetBilibiliVendor(user.ID)
|
||||
if err != nil {
|
||||
if errors.Is(err, db.ErrNotFound("vendor")) {
|
||||
ctx.JSON(http.StatusOK, model.NewApiDataResp(&BilibiliMeResp{
|
||||
|
@ -36,7 +34,7 @@ func Me(ctx *gin.Context) {
|
|||
return
|
||||
}
|
||||
resp, err := vendor.BilibiliClient("").UserInfo(ctx, &bilibili.UserInfoReq{
|
||||
Cookies: utils.HttpCookieToMap(v.Cookies),
|
||||
Cookies: v.Cookies,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
|
|
|
@ -12,10 +12,10 @@ import (
|
|||
|
||||
func Backends(ctx *gin.Context) {
|
||||
var backends []string
|
||||
switch dbModel.StreamingVendor(ctx.Param("vendor")) {
|
||||
case dbModel.StreamingVendorBilibili:
|
||||
switch ctx.Param("vendor") {
|
||||
case dbModel.VendorBilibili:
|
||||
backends = maps.Keys(vendor.BilibiliClients())
|
||||
case dbModel.StreamingVendorAlist:
|
||||
case dbModel.VendorAlist:
|
||||
backends = maps.Keys(vendor.AlistClients())
|
||||
default:
|
||||
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{}
|
||||
|
||||
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)
|
||||
|
||||
if dbValue != nil {
|
||||
|
@ -35,8 +35,7 @@ func (*JSONSerializer) Scan(ctx context.Context, field *schema.Field, dst reflec
|
|||
return
|
||||
}
|
||||
|
||||
// 实现 Value 方法
|
||||
func (*JSONSerializer) Value(ctx context.Context, field *schema.Field, dst reflect.Value, fieldValue interface{}) (interface{}, error) {
|
||||
func (*JSONSerializer) Value(ctx context.Context, field *schema.Field, dst reflect.Value, fieldValue any) (any, error) {
|
||||
return json.Marshal(fieldValue)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue