mirror of https://github.com/synctv-org/synctv.git
Feat: bilibili parser
This commit is contained in:
parent
16bd51fa81
commit
18836ff1bf
|
@ -3,7 +3,6 @@ package admin
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/synctv-org/synctv/internal/bootstrap"
|
||||
|
@ -25,11 +24,7 @@ var AddCmd = &cobra.Command{
|
|||
if len(args) == 0 {
|
||||
return errors.New("missing user id")
|
||||
}
|
||||
id, err := strconv.Atoi(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid user id: %s", args[0])
|
||||
}
|
||||
u, err := db.GetUserByID(uint(id))
|
||||
u, err := db.GetUserByID(args[0])
|
||||
if err != nil {
|
||||
fmt.Printf("get user failed: %s", err)
|
||||
return nil
|
||||
|
|
|
@ -3,7 +3,6 @@ package admin
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/synctv-org/synctv/internal/bootstrap"
|
||||
|
@ -25,11 +24,7 @@ var RemoveCmd = &cobra.Command{
|
|||
if len(args) == 0 {
|
||||
return errors.New("missing user id")
|
||||
}
|
||||
id, err := strconv.Atoi(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid user id: %s", args[0])
|
||||
}
|
||||
u, err := db.GetUserByID(uint(id))
|
||||
u, err := db.GetUserByID(args[0])
|
||||
if err != nil {
|
||||
fmt.Printf("get user failed: %s", err)
|
||||
return nil
|
||||
|
|
|
@ -22,7 +22,7 @@ var ShowCmd = &cobra.Command{
|
|||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
admins := db.GetAdmins()
|
||||
for _, admin := range admins {
|
||||
fmt.Printf("id: %d\tusername: %s\n", admin.ID, admin.Username)
|
||||
fmt.Printf("id: %s\tusername: %s\n", admin.ID, admin.Username)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
|
|
@ -3,7 +3,6 @@ package root
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/synctv-org/synctv/internal/bootstrap"
|
||||
|
@ -25,11 +24,7 @@ var AddCmd = &cobra.Command{
|
|||
if len(args) == 0 {
|
||||
return errors.New("missing user id")
|
||||
}
|
||||
id, err := strconv.Atoi(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid user id: %s", args[0])
|
||||
}
|
||||
u, err := db.GetUserByID(uint(id))
|
||||
u, err := db.GetUserByID(args[0])
|
||||
if err != nil {
|
||||
fmt.Printf("get user failed: %s", err)
|
||||
return nil
|
||||
|
|
|
@ -3,7 +3,6 @@ package root
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/synctv-org/synctv/internal/bootstrap"
|
||||
|
@ -25,11 +24,7 @@ var RemoveCmd = &cobra.Command{
|
|||
if len(args) == 0 {
|
||||
return errors.New("missing user id")
|
||||
}
|
||||
id, err := strconv.Atoi(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid user id: %s", args[0])
|
||||
}
|
||||
u, err := db.GetUserByID(uint(id))
|
||||
u, err := db.GetUserByID(args[0])
|
||||
if err != nil {
|
||||
fmt.Printf("get user failed: %s", err)
|
||||
return nil
|
||||
|
|
|
@ -22,7 +22,7 @@ var ShowCmd = &cobra.Command{
|
|||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
roots := db.GetRoots()
|
||||
for _, root := range roots {
|
||||
fmt.Printf("id: %d\tusername: %s\n", root.ID, root.Username)
|
||||
fmt.Printf("id: %s\tusername: %s\n", root.ID, root.Username)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
|
|
@ -3,7 +3,6 @@ package user
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/synctv-org/synctv/internal/bootstrap"
|
||||
|
@ -25,11 +24,7 @@ var BanCmd = &cobra.Command{
|
|||
if len(args) == 0 {
|
||||
return errors.New("missing user id")
|
||||
}
|
||||
id, err := strconv.Atoi(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid user id: %s", args[0])
|
||||
}
|
||||
u, err := db.GetUserByID(uint(id))
|
||||
u, err := db.GetUserByID(args[0])
|
||||
if err != nil {
|
||||
fmt.Printf("get user failed: %s\n", err)
|
||||
return nil
|
||||
|
|
|
@ -3,7 +3,6 @@ package user
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/synctv-org/synctv/internal/bootstrap"
|
||||
|
@ -25,11 +24,7 @@ var DeleteCmd = &cobra.Command{
|
|||
if len(args) == 0 {
|
||||
return errors.New("missing user id")
|
||||
}
|
||||
id, err := strconv.Atoi(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid user id: %s", args[0])
|
||||
}
|
||||
u, err := db.LoadAndDeleteUserByID(uint(id))
|
||||
u, err := db.LoadAndDeleteUserByID(args[0])
|
||||
if err != nil {
|
||||
fmt.Printf("delete user failed: %s\n", err)
|
||||
return nil
|
||||
|
|
|
@ -33,7 +33,7 @@ var SearchCmd = &cobra.Command{
|
|||
return nil
|
||||
}
|
||||
for _, u := range us {
|
||||
fmt.Printf("id: %d\tusername: %s\tcreated_at: %s\trole: %s\n", u.ID, u.Username, u.CreatedAt, u.Role)
|
||||
fmt.Printf("id: %s\tusername: %s\tcreated_at: %s\trole: %s\n", u.ID, u.Username, u.CreatedAt, u.Role)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
|
|
@ -3,7 +3,6 @@ package user
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/synctv-org/synctv/internal/bootstrap"
|
||||
|
@ -25,11 +24,7 @@ var UnbanCmd = &cobra.Command{
|
|||
if len(args) == 0 {
|
||||
return errors.New("missing user id")
|
||||
}
|
||||
id, err := strconv.Atoi(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid user id: %s", args[0])
|
||||
}
|
||||
u, err := db.GetUserByID(uint(id))
|
||||
u, err := db.GetUserByID(args[0])
|
||||
if err != nil {
|
||||
fmt.Printf("get user failed: %s\n", err)
|
||||
return nil
|
||||
|
|
2
go.mod
2
go.mod
|
@ -71,7 +71,7 @@ require (
|
|||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/maruel/natural v1.1.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.4 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
|
|
5
go.sum
5
go.sum
|
@ -123,11 +123,13 @@ github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
|||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/maruel/natural v1.1.0 h1:2z1NgP/Vae+gYrtC0VuvrTJ6U35OuyUqDdfluLqMWuQ=
|
||||
github.com/maruel/natural v1.1.0/go.mod h1:eFVhYCcUOfZFxXoDZam8Ktya72wa79fNC3lc/leA0DQ=
|
||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
|
@ -249,6 +251,7 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
|
|
@ -3,7 +3,6 @@ package bootstrap
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/synctv-org/synctv/internal/conf"
|
||||
|
@ -27,12 +26,7 @@ func auth(ReqAppName, ReqChannelName string, IsPublisher bool) (*rtmps.Channel,
|
|||
return nil, err
|
||||
}
|
||||
log.Infof("rtmp: publisher login success: %s/%s", ReqAppName, channelName)
|
||||
id, err := strconv.Atoi(ReqAppName)
|
||||
if err != nil {
|
||||
log.Errorf("rtmp: parse channel name to id error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
r, err := op.LoadOrInitRoomByID(uint(id))
|
||||
r, err := op.LoadOrInitRoomByID(ReqAppName)
|
||||
if err != nil {
|
||||
log.Errorf("rtmp: get room by id error: %v", err)
|
||||
return nil, err
|
||||
|
@ -44,12 +38,7 @@ func auth(ReqAppName, ReqChannelName string, IsPublisher bool) (*rtmps.Channel,
|
|||
log.Warnf("rtmp: dial to %s/%s error: %s", ReqAppName, ReqChannelName, "rtmp player is not enabled")
|
||||
return nil, fmt.Errorf("rtmp: dial to %s/%s error: %s", ReqAppName, ReqChannelName, "rtmp player is not enabled")
|
||||
}
|
||||
id, err := strconv.Atoi(ReqAppName)
|
||||
if err != nil {
|
||||
log.Errorf("rtmp: parse channel name to id error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
r, err := op.LoadOrInitRoomByID(uint(id))
|
||||
r, err := op.LoadOrInitRoomByID(ReqAppName)
|
||||
if err != nil {
|
||||
log.Errorf("rtmp: get room by id error: %v", err)
|
||||
return nil, err
|
||||
|
|
|
@ -19,7 +19,7 @@ var (
|
|||
func Init(d *gorm.DB, t conf.DatabaseType) error {
|
||||
db = d
|
||||
dbType = t
|
||||
return AutoMigrate(new(model.Movie), new(model.Room), new(model.User), new(model.RoomUserRelation), new(model.UserProvider), new(model.Setting))
|
||||
return AutoMigrate(new(model.Movie), new(model.Room), new(model.User), new(model.RoomUserRelation), new(model.UserProvider), new(model.Setting), new(model.StreamingVendorInfo))
|
||||
}
|
||||
|
||||
func AutoMigrate(dst ...any) error {
|
||||
|
@ -103,19 +103,19 @@ func WithUserAndProvider(db *gorm.DB) *gorm.DB {
|
|||
return db.Preload("User").Preload("User.Provider")
|
||||
}
|
||||
|
||||
func WhereRoomID(roomID uint) func(db *gorm.DB) *gorm.DB {
|
||||
func WhereRoomID(roomID string) func(db *gorm.DB) *gorm.DB {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("room_id = ?", roomID)
|
||||
}
|
||||
}
|
||||
|
||||
func WhereUserID(userID uint) func(db *gorm.DB) *gorm.DB {
|
||||
func WhereUserID(userID string) func(db *gorm.DB) *gorm.DB {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("user_id = ?", userID)
|
||||
}
|
||||
}
|
||||
|
||||
func WhereCreatorID(creatorID uint) func(db *gorm.DB) *gorm.DB {
|
||||
func WhereCreatorID(creatorID string) func(db *gorm.DB) *gorm.DB {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("creator_id = ?", creatorID)
|
||||
}
|
||||
|
@ -140,7 +140,7 @@ func WhereLike(column string, value string) func(db *gorm.DB) *gorm.DB {
|
|||
}
|
||||
}
|
||||
|
||||
func WhereRoomNameLikeOrCreatorIn(name string, ids []uint) func(db *gorm.DB) *gorm.DB {
|
||||
func WhereRoomNameLikeOrCreatorIn(name string, ids []string) func(db *gorm.DB) *gorm.DB {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
switch dbType {
|
||||
case conf.DatabaseTypePostgres:
|
||||
|
@ -173,7 +173,7 @@ func WhereUserNameLike(name string) func(db *gorm.DB) *gorm.DB {
|
|||
}
|
||||
}
|
||||
|
||||
func WhereCreatorIDIn(ids []uint) func(db *gorm.DB) *gorm.DB {
|
||||
func WhereCreatorIDIn(ids []string) func(db *gorm.DB) *gorm.DB {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("creator_id IN ?", ids)
|
||||
}
|
||||
|
|
|
@ -13,13 +13,13 @@ func CreateMovie(movie *model.Movie) error {
|
|||
return db.Create(movie).Error
|
||||
}
|
||||
|
||||
func GetAllMoviesByRoomID(roomID uint) []*model.Movie {
|
||||
func GetAllMoviesByRoomID(roomID string) []*model.Movie {
|
||||
movies := []*model.Movie{}
|
||||
db.Where("room_id = ?", roomID).Order("position ASC").Find(&movies)
|
||||
return movies
|
||||
}
|
||||
|
||||
func DeleteMovieByID(roomID, id uint) error {
|
||||
func DeleteMovieByID(roomID, id string) error {
|
||||
err := db.Unscoped().Where("room_id = ? AND id = ?", roomID, id).Delete(&model.Movie{}).Error
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("room or movie not found")
|
||||
|
@ -27,7 +27,7 @@ func DeleteMovieByID(roomID, id uint) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func LoadAndDeleteMovieByID(roomID, id uint, columns ...clause.Column) (*model.Movie, error) {
|
||||
func LoadAndDeleteMovieByID(roomID, id string, columns ...clause.Column) (*model.Movie, error) {
|
||||
movie := &model.Movie{}
|
||||
err := db.Unscoped().Clauses(clause.Returning{Columns: columns}).Where("room_id = ? AND id = ?", roomID, id).Delete(movie).Error
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
|
@ -36,7 +36,7 @@ func LoadAndDeleteMovieByID(roomID, id uint, columns ...clause.Column) (*model.M
|
|||
return movie, err
|
||||
}
|
||||
|
||||
func DeleteMoviesByRoomID(roomID uint) error {
|
||||
func DeleteMoviesByRoomID(roomID string) error {
|
||||
err := db.Unscoped().Where("room_id = ?", roomID).Delete(&model.Movie{}).Error
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("room not found")
|
||||
|
@ -44,7 +44,7 @@ func DeleteMoviesByRoomID(roomID uint) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func LoadAndDeleteMoviesByRoomID(roomID uint, columns ...clause.Column) ([]*model.Movie, error) {
|
||||
func LoadAndDeleteMoviesByRoomID(roomID string, columns ...clause.Column) ([]*model.Movie, error) {
|
||||
movies := []*model.Movie{}
|
||||
err := db.Unscoped().Clauses(clause.Returning{Columns: columns}).Where("room_id = ?", roomID).Delete(&movies).Error
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
|
@ -69,7 +69,7 @@ func SaveMovie(movie *model.Movie, columns ...clause.Column) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func SwapMoviePositions(roomID uint, movie1ID uint, movie2ID uint) (err error) {
|
||||
func SwapMoviePositions(roomID, movie1ID, movie2ID string) (err error) {
|
||||
tx := db.Begin()
|
||||
defer func() {
|
||||
if err != nil {
|
||||
|
@ -83,14 +83,14 @@ func SwapMoviePositions(roomID uint, movie1ID uint, movie2ID uint) (err error) {
|
|||
err = tx.Select("position").Where("room_id = ? AND id = ?", roomID, movie1ID).First(movie1).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
err = fmt.Errorf("movie with id %d not found", movie1ID)
|
||||
err = fmt.Errorf("movie with id %s not found", movie1ID)
|
||||
}
|
||||
return
|
||||
}
|
||||
err = tx.Select("position").Where("room_id = ? AND id = ?", roomID, movie2ID).First(movie2).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
err = fmt.Errorf("movie with id %d not found", movie2ID)
|
||||
err = fmt.Errorf("movie with id %s not found", movie2ID)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func GetRoomUserRelation(roomID, userID uint) (*model.RoomUserRelation, error) {
|
||||
func GetRoomUserRelation(roomID, userID string) (*model.RoomUserRelation, error) {
|
||||
roomUserRelation := &model.RoomUserRelation{}
|
||||
err := db.Where("room_id = ? AND user_id = ?", roomID, userID).Attrs(&model.RoomUserRelation{
|
||||
RoomID: roomID,
|
||||
|
@ -21,7 +21,7 @@ func GetRoomUserRelation(roomID, userID uint) (*model.RoomUserRelation, error) {
|
|||
return roomUserRelation, err
|
||||
}
|
||||
|
||||
func CreateRoomUserRelation(roomID, userID uint, role model.RoomRole, permissions model.Permission) (*model.RoomUserRelation, error) {
|
||||
func CreateRoomUserRelation(roomID, userID string, role model.RoomRole, permissions model.Permission) (*model.RoomUserRelation, error) {
|
||||
roomUserRelation := &model.RoomUserRelation{
|
||||
RoomID: roomID,
|
||||
UserID: userID,
|
||||
|
@ -32,7 +32,7 @@ func CreateRoomUserRelation(roomID, userID uint, role model.RoomRole, permission
|
|||
return roomUserRelation, err
|
||||
}
|
||||
|
||||
func SetUserRole(roomID uint, userID uint, role model.RoomRole) error {
|
||||
func SetUserRole(roomID string, userID string, role model.RoomRole) error {
|
||||
err := db.Model(&model.RoomUserRelation{}).Where("room_id = ? AND user_id = ?", roomID, userID).Update("role", role).Error
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("room or user not found")
|
||||
|
@ -40,7 +40,7 @@ func SetUserRole(roomID uint, userID uint, role model.RoomRole) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func SetUserPermission(roomID uint, userID uint, permission model.Permission) error {
|
||||
func SetUserPermission(roomID string, userID string, permission model.Permission) error {
|
||||
err := db.Model(&model.RoomUserRelation{}).Where("room_id = ? AND user_id = ?", roomID, userID).Update("permissions", permission).Error
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("room or user not found")
|
||||
|
@ -48,7 +48,7 @@ func SetUserPermission(roomID uint, userID uint, permission model.Permission) er
|
|||
return err
|
||||
}
|
||||
|
||||
func AddUserPermission(roomID uint, userID uint, permission model.Permission) error {
|
||||
func AddUserPermission(roomID string, userID string, permission model.Permission) error {
|
||||
err := db.Model(&model.RoomUserRelation{}).Where("room_id = ? AND user_id = ?", roomID, userID).Update("permissions", db.Raw("permissions | ?", permission)).Error
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("room or user not found")
|
||||
|
@ -56,7 +56,7 @@ func AddUserPermission(roomID uint, userID uint, permission model.Permission) er
|
|||
return err
|
||||
}
|
||||
|
||||
func RemoveUserPermission(roomID uint, userID uint, permission model.Permission) error {
|
||||
func RemoveUserPermission(roomID string, userID string, permission model.Permission) error {
|
||||
err := db.Model(&model.RoomUserRelation{}).Where("room_id = ? AND user_id = ?", roomID, userID).Update("permissions", db.Raw("permissions & ?", ^permission)).Error
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("room or user not found")
|
||||
|
@ -64,7 +64,7 @@ func RemoveUserPermission(roomID uint, userID uint, permission model.Permission)
|
|||
return err
|
||||
}
|
||||
|
||||
func DeleteUserPermission(roomID uint, userID uint) error {
|
||||
func DeleteUserPermission(roomID string, userID string) error {
|
||||
err := db.Unscoped().Where("room_id = ? AND user_id = ?", roomID, userID).Delete(&model.RoomUserRelation{}).Error
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("room or user not found")
|
||||
|
|
|
@ -59,7 +59,10 @@ func CreateRoom(name, password string, conf ...CreateRoomConfig) (*model.Room, e
|
|||
return r, err
|
||||
}
|
||||
|
||||
func GetRoomByID(id uint) (*model.Room, error) {
|
||||
func GetRoomByID(id string) (*model.Room, error) {
|
||||
if len(id) != 36 {
|
||||
return nil, errors.New("room id is not 32 bit")
|
||||
}
|
||||
r := &model.Room{}
|
||||
err := db.Where("id = ?", id).First(r).Error
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
|
@ -68,7 +71,7 @@ func GetRoomByID(id uint) (*model.Room, error) {
|
|||
return r, err
|
||||
}
|
||||
|
||||
func GetRoomAndCreatorByID(id uint) (*model.Room, error) {
|
||||
func GetRoomAndCreatorByID(id string) (*model.Room, error) {
|
||||
r := &model.Room{}
|
||||
err := db.Preload("Creator").Where("id = ?", id).First(r).Error
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
|
@ -77,7 +80,7 @@ func GetRoomAndCreatorByID(id uint) (*model.Room, error) {
|
|||
return r, err
|
||||
}
|
||||
|
||||
func ChangeRoomSetting(roomID uint, setting model.Settings) error {
|
||||
func ChangeRoomSetting(roomID string, setting model.Settings) error {
|
||||
err := db.Model(&model.Room{}).Where("id = ?", roomID).Update("setting", setting).Error
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("room not found")
|
||||
|
@ -85,7 +88,7 @@ func ChangeRoomSetting(roomID uint, setting model.Settings) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func ChangeUserPermission(roomID uint, userID uint, permission model.Permission) error {
|
||||
func ChangeUserPermission(roomID, userID string, permission model.Permission) error {
|
||||
err := db.Model(&model.RoomUserRelation{}).Where("room_id = ? AND user_id = ?", roomID, userID).Update("permissions", permission).Error
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("room or user not found")
|
||||
|
@ -93,7 +96,7 @@ func ChangeUserPermission(roomID uint, userID uint, permission model.Permission)
|
|||
return err
|
||||
}
|
||||
|
||||
func HasPermission(roomID uint, userID uint, permission model.Permission) (bool, error) {
|
||||
func HasPermission(roomID, userID string, permission model.Permission) (bool, error) {
|
||||
ur := &model.RoomUserRelation{}
|
||||
err := db.Where("room_id = ? AND user_id = ?", roomID, userID).First(ur).Error
|
||||
if err != nil {
|
||||
|
@ -105,7 +108,7 @@ func HasPermission(roomID uint, userID uint, permission model.Permission) (bool,
|
|||
return ur.Permissions.Has(permission), nil
|
||||
}
|
||||
|
||||
func DeleteRoomByID(roomID uint) error {
|
||||
func DeleteRoomByID(roomID string) error {
|
||||
err := db.Unscoped().Delete(&model.Room{}, roomID).Error
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("room not found")
|
||||
|
@ -113,7 +116,7 @@ func DeleteRoomByID(roomID uint) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func HasRoom(roomID uint) (bool, error) {
|
||||
func HasRoom(roomID string) (bool, error) {
|
||||
r := &model.Room{}
|
||||
err := db.Where("id = ?", roomID).First(r).Error
|
||||
if err != nil {
|
||||
|
@ -137,7 +140,7 @@ func HasRoomByName(name string) (bool, error) {
|
|||
return true, nil
|
||||
}
|
||||
|
||||
func SetRoomPassword(roomID uint, password string) error {
|
||||
func SetRoomPassword(roomID, password string) error {
|
||||
var hashedPassword []byte
|
||||
if password != "" {
|
||||
var err error
|
||||
|
@ -149,7 +152,7 @@ func SetRoomPassword(roomID uint, password string) error {
|
|||
return SetRoomHashedPassword(roomID, hashedPassword)
|
||||
}
|
||||
|
||||
func SetRoomHashedPassword(roomID uint, hashedPassword []byte) error {
|
||||
func SetRoomHashedPassword(roomID string, hashedPassword []byte) error {
|
||||
err := db.Model(&model.Room{}).Where("id = ?", roomID).Update("hashed_password", hashedPassword).Error
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("room not found")
|
||||
|
@ -181,7 +184,7 @@ func GetAllRoomsAndCreator(scopes ...func(*gorm.DB) *gorm.DB) []*model.Room {
|
|||
return rooms
|
||||
}
|
||||
|
||||
func GetAllRoomsByUserID(userID uint) []*model.Room {
|
||||
func GetAllRoomsByUserID(userID string) []*model.Room {
|
||||
rooms := []*model.Room{}
|
||||
db.Where("creator_id = ?", userID).Find(&rooms)
|
||||
return rooms
|
||||
|
|
|
@ -59,19 +59,19 @@ func CreateOrLoadUser(username string, p provider.OAuth2Provider, puid uint, con
|
|||
return &user, nil
|
||||
}
|
||||
|
||||
func GetProviderUserID(p provider.OAuth2Provider, puid uint) (uint, error) {
|
||||
func GetProviderUserID(p provider.OAuth2Provider, puid uint) (string, error) {
|
||||
var userProvider model.UserProvider
|
||||
if err := db.Where("provider = ? AND provider_user_id = ?", p, puid).First(&userProvider).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return 0, errors.New("user not found")
|
||||
return "", errors.New("user not found")
|
||||
} else {
|
||||
return 0, err
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return userProvider.UserID, nil
|
||||
}
|
||||
|
||||
func AddUserToRoom(userID uint, roomID uint, role model.RoomRole, permission model.Permission) error {
|
||||
func AddUserToRoom(userID, roomID string, role model.RoomRole, permission model.Permission) error {
|
||||
ur := &model.RoomUserRelation{
|
||||
UserID: userID,
|
||||
RoomID: roomID,
|
||||
|
@ -100,8 +100,8 @@ func GetUserByUsernameLike(username string, scopes ...func(*gorm.DB) *gorm.DB) [
|
|||
return users
|
||||
}
|
||||
|
||||
func GerUsersIDByUsernameLike(username string, scopes ...func(*gorm.DB) *gorm.DB) []uint {
|
||||
var ids []uint
|
||||
func GerUsersIDByUsernameLike(username string, scopes ...func(*gorm.DB) *gorm.DB) []string {
|
||||
var ids []string
|
||||
db.Model(&model.User{}).Where(`username LIKE ?`, fmt.Sprintf("%%%s%%", username)).Scopes(scopes...).Pluck("id", &ids)
|
||||
return ids
|
||||
}
|
||||
|
@ -115,7 +115,10 @@ func GetUserByIDOrUsernameLike(idOrUsername string, scopes ...func(*gorm.DB) *go
|
|||
return users, err
|
||||
}
|
||||
|
||||
func GetUserByID(id uint) (*model.User, error) {
|
||||
func GetUserByID(id string) (*model.User, error) {
|
||||
if len(id) != 36 {
|
||||
return nil, errors.New("user id is not 32 bit")
|
||||
}
|
||||
u := &model.User{}
|
||||
err := db.Where("id = ?", id).First(u).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
|
@ -124,7 +127,7 @@ func GetUserByID(id uint) (*model.User, error) {
|
|||
return u, err
|
||||
}
|
||||
|
||||
func GetUsersByRoomID(roomID uint, scopes ...func(*gorm.DB) *gorm.DB) ([]model.User, error) {
|
||||
func GetUsersByRoomID(roomID string, scopes ...func(*gorm.DB) *gorm.DB) ([]model.User, error) {
|
||||
users := []model.User{}
|
||||
err := db.Model(&model.RoomUserRelation{}).Where("room_id = ?", roomID).Scopes(scopes...).Find(&users).Error
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
|
@ -141,7 +144,7 @@ func BanUser(u *model.User) error {
|
|||
return SaveUser(u)
|
||||
}
|
||||
|
||||
func BanUserByID(userID uint) error {
|
||||
func BanUserByID(userID string) error {
|
||||
err := db.Model(&model.User{}).Where("id = ?", userID).Update("role", model.RoleBanned).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("user not found")
|
||||
|
@ -157,7 +160,7 @@ func UnbanUser(u *model.User) error {
|
|||
return SaveUser(u)
|
||||
}
|
||||
|
||||
func UnbanUserByID(userID uint) error {
|
||||
func UnbanUserByID(userID string) error {
|
||||
err := db.Model(&model.User{}).Where("id = ?", userID).Update("role", model.RoleUser).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("user not found")
|
||||
|
@ -165,7 +168,7 @@ func UnbanUserByID(userID uint) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func DeleteUserByID(userID uint) error {
|
||||
func DeleteUserByID(userID string) error {
|
||||
err := db.Unscoped().Delete(&model.User{}, userID).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("user not found")
|
||||
|
@ -173,7 +176,7 @@ func DeleteUserByID(userID uint) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func LoadAndDeleteUserByID(userID uint, columns ...clause.Column) (*model.User, error) {
|
||||
func LoadAndDeleteUserByID(userID string, columns ...clause.Column) (*model.User, error) {
|
||||
u := &model.User{}
|
||||
if db.Unscoped().
|
||||
Clauses(clause.Returning{Columns: columns}).
|
||||
|
@ -210,7 +213,7 @@ func GetAdmins() []*model.User {
|
|||
return users
|
||||
}
|
||||
|
||||
func AddAdminByID(userID uint) error {
|
||||
func AddAdminByID(userID string) error {
|
||||
err := db.Model(&model.User{}).Where("id = ?", userID).Update("role", model.RoleAdmin).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("user not found")
|
||||
|
@ -218,7 +221,7 @@ func AddAdminByID(userID uint) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func RemoveAdminByID(userID uint) error {
|
||||
func RemoveAdminByID(userID string) error {
|
||||
err := db.Model(&model.User{}).Where("id = ?", userID).Update("role", model.RoleUser).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("user not found")
|
||||
|
@ -242,7 +245,7 @@ func RemoveRoot(u *model.User) error {
|
|||
return SaveUser(u)
|
||||
}
|
||||
|
||||
func AddRootByID(userID uint) error {
|
||||
func AddRootByID(userID string) error {
|
||||
err := db.Model(&model.User{}).Where("id = ?", userID).Update("role", model.RoleRoot).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("user not found")
|
||||
|
@ -250,7 +253,7 @@ func AddRootByID(userID uint) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func RemoveRootByID(userID uint) error {
|
||||
func RemoveRootByID(userID string) error {
|
||||
err := db.Model(&model.User{}).Where("id = ?", userID).Update("role", model.RoleUser).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("user not found")
|
||||
|
@ -269,7 +272,7 @@ func SetRole(u *model.User, role model.Role) error {
|
|||
return SaveUser(u)
|
||||
}
|
||||
|
||||
func SetRoleByID(userID uint, role model.Role) error {
|
||||
func SetRoleByID(userID string, role model.Role) error {
|
||||
err := db.Model(&model.User{}).Where("id = ?", userID).Update("role", role).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("user not found")
|
||||
|
@ -289,7 +292,7 @@ func GetAllUserCountWithRole(role model.Role, scopes ...func(*gorm.DB) *gorm.DB)
|
|||
return count
|
||||
}
|
||||
|
||||
func SetUsernameByID(userID uint, username string) error {
|
||||
func SetUsernameByID(userID string, username string) error {
|
||||
err := db.Model(&model.User{}).Where("id = ?", userID).Update("username", username).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("user not found")
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/synctv-org/synctv/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
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 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
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("vendor not found")
|
||||
}
|
||||
return &vendorInfo, err
|
||||
}
|
||||
|
||||
type CreateVendorConfig func(*model.StreamingVendorInfo)
|
||||
|
||||
func WithCookie(cookie []*http.Cookie) CreateVendorConfig {
|
||||
return func(vendor *model.StreamingVendorInfo) {
|
||||
vendor.Cookies = cookie
|
||||
}
|
||||
}
|
||||
|
||||
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 AssignFirstOrCreateVendorByUserIDAndVendor(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).Assign(
|
||||
v,
|
||||
).FirstOrCreate(&vendorInfo).Error
|
||||
return &vendorInfo, err
|
||||
}
|
|
@ -56,8 +56,8 @@ func TestAddUserToRoom(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
ur := model.RoomUserRelation{
|
||||
UserID: 1,
|
||||
RoomID: 1,
|
||||
UserID: "1",
|
||||
RoomID: "1",
|
||||
Role: model.RoomRoleUser,
|
||||
Permissions: model.DefaultPermissions,
|
||||
}
|
||||
|
|
|
@ -1,23 +1,30 @@
|
|||
package model
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Movie struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" json:"id"`
|
||||
CreatedAt time.Time `json:"-"`
|
||||
UpdatedAt time.Time `json:"-"`
|
||||
Position uint `gorm:"not null" json:"-"`
|
||||
RoomID uint `gorm:"not null;index" json:"-"`
|
||||
CreatorID uint `gorm:"not null;index" json:"creatorId"`
|
||||
MovieInfo
|
||||
RoomID string `gorm:"not null;index" json:"-"`
|
||||
CreatorID string `gorm:"not null;index" json:"creatorId"`
|
||||
Base BaseMovie `gorm:"embedded;embeddedPrefix:base_" json:"base"`
|
||||
}
|
||||
|
||||
type MovieInfo struct {
|
||||
Base BaseMovieInfo `gorm:"embedded;embeddedPrefix:base_" json:"base"`
|
||||
PullKey string `json:"pullKey"`
|
||||
func (m *Movie) BeforeCreate(tx *gorm.DB) error {
|
||||
if m.ID == "" {
|
||||
m.ID = uuid.NewString()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type BaseMovieInfo struct {
|
||||
type BaseMovie struct {
|
||||
Url string `json:"url"`
|
||||
Name string `gorm:"not null" json:"name"`
|
||||
Live bool `json:"live"`
|
||||
|
@ -25,4 +32,10 @@ type BaseMovieInfo struct {
|
|||
RtmpSource bool `json:"rtmpSource"`
|
||||
Type string `json:"type"`
|
||||
Headers map[string]string `gorm:"serializer:fastjson" json:"headers"`
|
||||
VendorInfo `gorm:"embedded;embeddedPrefix:vendor_info_" json:"vendorInfo"`
|
||||
}
|
||||
|
||||
type VendorInfo struct {
|
||||
Vendor StreamingVendor `json:"vendor"`
|
||||
Info map[string]any `gorm:"serializer:fastjson" json:"info"`
|
||||
}
|
||||
|
|
|
@ -7,9 +7,9 @@ import (
|
|||
)
|
||||
|
||||
type UserProvider struct {
|
||||
Provider provider.OAuth2Provider `gorm:"primarykey"`
|
||||
ProviderUserID uint `gorm:"primarykey;autoIncrement:false"`
|
||||
Provider provider.OAuth2Provider `gorm:"not null;primarykey"`
|
||||
ProviderUserID uint `gorm:"not null;primarykey;autoIncrement:false"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
UserID uint `gorm:"not null"`
|
||||
UserID string `gorm:"not null;index"`
|
||||
}
|
||||
|
|
|
@ -38,11 +38,10 @@ func (p Permission) Has(permission Permission) bool {
|
|||
}
|
||||
|
||||
type RoomUserRelation struct {
|
||||
ID uint `gorm:"primarykey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
UserID uint `gorm:"not null;uniqueIndex:idx_user_room"`
|
||||
RoomID uint `gorm:"not null;uniqueIndex:idx_user_room"`
|
||||
UserID string `gorm:"not null;primarykey"`
|
||||
RoomID string `gorm:"not null;primarykey"`
|
||||
Role RoomRole `gorm:"not null"`
|
||||
Permissions Permission
|
||||
}
|
||||
|
|
|
@ -3,22 +3,31 @@ package model
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/zijiren233/stream"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Room struct {
|
||||
ID uint `gorm:"primarykey"`
|
||||
ID string `gorm:"not null;primaryKey;type:varchar(36)" json:"id"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Name string `gorm:"not null;uniqueIndex"`
|
||||
Settings Settings `gorm:"embedded;embeddedPrefix:settings_"`
|
||||
CreatorID uint `gorm:"index"`
|
||||
CreatorID string `gorm:"index"`
|
||||
HashedPassword []byte
|
||||
GroupUserRelations []RoomUserRelation `gorm:"foreignKey:RoomID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
|
||||
Movies []Movie `gorm:"foreignKey:RoomID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
|
||||
}
|
||||
|
||||
func (r *Room) BeforeCreate(tx *gorm.DB) error {
|
||||
if r.ID == "" {
|
||||
r.ID = uuid.NewString()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Settings struct {
|
||||
Hidden bool `json:"hidden"`
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
|
@ -19,15 +20,16 @@ const (
|
|||
)
|
||||
|
||||
type User struct {
|
||||
ID uint `gorm:"primarykey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Providers []UserProvider `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
|
||||
Username string `gorm:"not null;uniqueIndex"`
|
||||
Role Role `gorm:"not null;default:user"`
|
||||
GroupUserRelations []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"`
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" json:"id"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Providers []UserProvider `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
|
||||
Username string `gorm:"not null;uniqueIndex"`
|
||||
Role Role `gorm:"not null;default:user"`
|
||||
GroupUserRelations []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"`
|
||||
}
|
||||
|
||||
func (u *User) BeforeCreate(tx *gorm.DB) error {
|
||||
|
@ -36,6 +38,9 @@ func (u *User) BeforeCreate(tx *gorm.DB) error {
|
|||
if err == nil {
|
||||
u.Username = fmt.Sprintf("%s#%d", u.Username, rand.Intn(9999))
|
||||
}
|
||||
if u.ID == "" {
|
||||
u.ID = uuid.NewString()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type StreamingVendor string
|
||||
|
||||
const (
|
||||
StreamingVendorBilibili StreamingVendor = "bilibili"
|
||||
)
|
||||
|
||||
type StreamingVendorInfo struct {
|
||||
UserID string `gorm:"not null;primarykey"`
|
||||
Vendor StreamingVendor `gorm:"not null;primarykey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
VendorToken
|
||||
}
|
||||
|
||||
type VendorToken struct {
|
||||
Cookies []*http.Cookie `gorm:"serializer:fastjson"`
|
||||
}
|
|
@ -88,7 +88,7 @@ func (c *current) SetSeekRate(seek, rate, timeDiff float64) Status {
|
|||
func (c *Current) Proto() *pb.Current {
|
||||
return &pb.Current{
|
||||
Movie: &pb.MovieInfo{
|
||||
Id: uint64(c.Movie.ID),
|
||||
Id: c.Movie.ID,
|
||||
Base: &pb.BaseMovieInfo{
|
||||
Url: c.Movie.Base.Url,
|
||||
Name: c.Movie.Base.Name,
|
||||
|
@ -98,7 +98,6 @@ func (c *Current) Proto() *pb.Current {
|
|||
Type: c.Movie.Base.Type,
|
||||
Headers: c.Movie.Base.Headers,
|
||||
},
|
||||
PullKey: c.Movie.PullKey,
|
||||
CreatedAt: c.Movie.CreatedAt.UnixMilli(),
|
||||
Creator: GetUserName(c.Movie.CreatorID),
|
||||
},
|
||||
|
|
|
@ -15,8 +15,8 @@ import (
|
|||
)
|
||||
|
||||
type Hub struct {
|
||||
id uint
|
||||
clients rwmap.RWMap[uint, *Client]
|
||||
id string
|
||||
clients rwmap.RWMap[string, *Client]
|
||||
broadcast chan *broadcastMessage
|
||||
exit chan struct{}
|
||||
closed uint32
|
||||
|
@ -52,7 +52,7 @@ func WithIgnoreId(id ...string) BroadcastConf {
|
|||
}
|
||||
}
|
||||
|
||||
func newHub(id uint) *Hub {
|
||||
func newHub(id string) *Hub {
|
||||
return &Hub{
|
||||
id: id,
|
||||
broadcast: make(chan *broadcastMessage, 128),
|
||||
|
@ -73,7 +73,7 @@ func (h *Hub) serve() error {
|
|||
select {
|
||||
case message := <-h.broadcast:
|
||||
h.devMessage(message.data)
|
||||
h.clients.Range(func(_ uint, cli *Client) bool {
|
||||
h.clients.Range(func(_ string, cli *Client) bool {
|
||||
if !message.sendToSelf {
|
||||
if cli.u.Username == message.sender {
|
||||
return true
|
||||
|
@ -83,13 +83,13 @@ func (h *Hub) serve() error {
|
|||
return true
|
||||
}
|
||||
if err := cli.Send(message.data); err != nil {
|
||||
log.Debugf("hub: %d, write to client err: %s\nmessage: %+v", h.id, err, message)
|
||||
log.Debugf("hub: %s, write to client err: %s\nmessage: %+v", h.id, err, message)
|
||||
cli.Close()
|
||||
}
|
||||
return true
|
||||
})
|
||||
case <-h.exit:
|
||||
log.Debugf("hub: %d, closed", h.id)
|
||||
log.Debugf("hub: %s, closed", h.id)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -127,7 +127,7 @@ func (h *Hub) ping() {
|
|||
func (h *Hub) devMessage(msg Message) {
|
||||
switch msg.MessageType() {
|
||||
case websocket.TextMessage:
|
||||
log.Debugf("hub: %d, broadcast:\nmessage: %+v", h.id, msg.String())
|
||||
log.Debugf("hub: %s, broadcast:\nmessage: %+v", h.id, msg.String())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -144,7 +144,7 @@ func (h *Hub) Close() error {
|
|||
return ErrAlreadyClosed
|
||||
}
|
||||
close(h.exit)
|
||||
h.clients.Range(func(_ uint, client *Client) bool {
|
||||
h.clients.Range(func(_ string, client *Client) bool {
|
||||
h.clients.Delete(client.u.ID)
|
||||
client.Close()
|
||||
return true
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/google/uuid"
|
||||
"github.com/synctv-org/synctv/internal/conf"
|
||||
"github.com/synctv-org/synctv/internal/model"
|
||||
"github.com/synctv-org/synctv/utils"
|
||||
|
@ -38,9 +37,6 @@ func (m *movie) init() (err error) {
|
|||
if !conf.Conf.Rtmp.Enable {
|
||||
return errors.New("rtmp is not enabled")
|
||||
}
|
||||
if m.PullKey == "" {
|
||||
m.PullKey = uuid.NewString()
|
||||
}
|
||||
if m.channel == nil {
|
||||
m.channel = rtmps.NewChannel()
|
||||
m.channel.InitHlsPlayer()
|
||||
|
@ -58,7 +54,6 @@ func (m *movie) init() (err error) {
|
|||
}
|
||||
switch u.Scheme {
|
||||
case "rtmp":
|
||||
m.PullKey = uuid.NewMD5(uuid.NameSpaceURL, []byte(m.Base.Url)).String()
|
||||
if m.channel == nil {
|
||||
m.channel = rtmps.NewChannel()
|
||||
m.channel.InitHlsPlayer()
|
||||
|
@ -84,7 +79,6 @@ func (m *movie) init() (err error) {
|
|||
if m.Base.Type != "flv" {
|
||||
return errors.New("only flv is supported")
|
||||
}
|
||||
m.PullKey = uuid.NewMD5(uuid.NameSpaceURL, []byte(m.Base.Url)).String()
|
||||
if m.channel == nil {
|
||||
m.channel = rtmps.NewChannel()
|
||||
m.channel.InitHlsPlayer()
|
||||
|
@ -119,6 +113,9 @@ func (m *movie) init() (err error) {
|
|||
if !conf.Conf.Proxy.MovieProxy {
|
||||
return errors.New("movie proxy is not enabled")
|
||||
}
|
||||
if m.Base.VendorInfo.Vendor != "" {
|
||||
return errors.New("vendor movie info is not supported in movie proxy mode")
|
||||
}
|
||||
u, err := url.Parse(m.Base.Url)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -129,16 +126,16 @@ func (m *movie) init() (err error) {
|
|||
if u.Scheme != "http" && u.Scheme != "https" {
|
||||
return errors.New("unsupported scheme")
|
||||
}
|
||||
m.PullKey = uuid.NewMD5(uuid.NameSpaceURL, []byte(m.Base.Url)).String()
|
||||
case !m.Base.Live && !m.Base.Proxy, m.Base.Live && !m.Base.Proxy && !m.Base.RtmpSource:
|
||||
u, err := url.Parse(m.Base.Url)
|
||||
if err != nil {
|
||||
return err
|
||||
if m.Base.VendorInfo.Vendor == "" {
|
||||
u, err := url.Parse(m.Base.Url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if u.Scheme != "http" && u.Scheme != "https" {
|
||||
return errors.New("unsupported scheme")
|
||||
}
|
||||
}
|
||||
if u.Scheme != "http" && u.Scheme != "https" {
|
||||
return errors.New("unsupported scheme")
|
||||
}
|
||||
m.PullKey = ""
|
||||
default:
|
||||
return errors.New("unknown error")
|
||||
}
|
||||
|
@ -158,7 +155,7 @@ func (m *movie) terminate() {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *movie) Update(movie model.BaseMovieInfo) error {
|
||||
func (m *movie) Update(movie model.BaseMovie) error {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
m.terminate()
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
)
|
||||
|
||||
type movies struct {
|
||||
roomID uint
|
||||
roomID string
|
||||
lock sync.RWMutex
|
||||
list dllist.Dllist[*movie]
|
||||
once sync.Once
|
||||
|
@ -45,7 +45,6 @@ func (m *movies) Add(mo *model.Movie) error {
|
|||
Movie: mo,
|
||||
}
|
||||
|
||||
// You need to init to get the pullKey first, and then create it into the database.
|
||||
err := movie.init()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -61,22 +60,22 @@ func (m *movies) Add(mo *model.Movie) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *movies) GetChannel(channelName string) (*rtmps.Channel, error) {
|
||||
if channelName == "" {
|
||||
func (m *movies) GetChannel(id string) (*rtmps.Channel, error) {
|
||||
if id == "" {
|
||||
return nil, errors.New("channel name is nil")
|
||||
}
|
||||
m.lock.RLock()
|
||||
defer m.lock.RUnlock()
|
||||
m.init()
|
||||
for e := m.list.Front(); e != nil; e = e.Next() {
|
||||
if e.Value.PullKey == channelName {
|
||||
if e.Value.ID == id {
|
||||
return e.Value.Channel()
|
||||
}
|
||||
}
|
||||
return nil, errors.New("channel not found")
|
||||
}
|
||||
|
||||
func (m *movies) Update(movieId uint, movie model.BaseMovieInfo) error {
|
||||
func (m *movies) Update(movieId string, movie model.BaseMovie) error {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
m.init()
|
||||
|
@ -116,7 +115,7 @@ func (m *movies) Close() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *movies) DeleteMovieByID(id uint) error {
|
||||
func (m *movies) DeleteMovieByID(id string) error {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
m.init()
|
||||
|
@ -135,13 +134,13 @@ func (m *movies) DeleteMovieByID(id uint) error {
|
|||
return errors.New("movie not found")
|
||||
}
|
||||
|
||||
func (m *movies) GetMovieByID(id uint) (*movie, error) {
|
||||
func (m *movies) GetMovieByID(id string) (*movie, error) {
|
||||
m.lock.RLock()
|
||||
defer m.lock.RUnlock()
|
||||
return m.getMovieByID(id)
|
||||
}
|
||||
|
||||
func (m *movies) getMovieByID(id uint) (*movie, error) {
|
||||
func (m *movies) getMovieByID(id string) (*movie, error) {
|
||||
m.init()
|
||||
for e := m.list.Front(); e != nil; e = e.Next() {
|
||||
if e.Value.ID == id {
|
||||
|
@ -151,7 +150,7 @@ func (m *movies) getMovieByID(id uint) (*movie, error) {
|
|||
return nil, errors.New("movie not found")
|
||||
}
|
||||
|
||||
func (m *movies) getMovieElementByID(id uint) (*dllist.Element[*movie], error) {
|
||||
func (m *movies) getMovieElementByID(id string) (*dllist.Element[*movie], error) {
|
||||
m.init()
|
||||
for e := m.list.Front(); e != nil; e = e.Next() {
|
||||
if e.Value.ID == id {
|
||||
|
@ -161,20 +160,7 @@ func (m *movies) getMovieElementByID(id uint) (*dllist.Element[*movie], error) {
|
|||
return nil, errors.New("movie not found")
|
||||
}
|
||||
|
||||
func (m *movies) GetMovieWithPullKey(pullKey string) (*movie, error) {
|
||||
m.lock.RLock()
|
||||
defer m.lock.RUnlock()
|
||||
m.init()
|
||||
|
||||
for e := m.list.Front(); e != nil; e = e.Next() {
|
||||
if e.Value.PullKey == pullKey {
|
||||
return e.Value, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("movie not found")
|
||||
}
|
||||
|
||||
func (m *movies) SwapMoviePositions(id1, id2 uint) error {
|
||||
func (m *movies) SwapMoviePositions(id1, id2 string) error {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
m.init()
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
func Init(size int, ttl time.Duration) error {
|
||||
roomTTL = ttl
|
||||
roomCache = synccache.NewSyncCache[uint, *Room](time.Minute*5, synccache.WithDeletedCallback[uint, *Room](func(v *Room) {
|
||||
roomCache = synccache.NewSyncCache[string, *Room](time.Minute*5, synccache.WithDeletedCallback[string, *Room](func(v *Room) {
|
||||
v.close()
|
||||
}))
|
||||
userCache = gcache.New(size).
|
||||
|
|
|
@ -62,7 +62,7 @@ func (r *Room) CheckVersion(version uint32) bool {
|
|||
return atomic.LoadUint32(&r.version) == version
|
||||
}
|
||||
|
||||
func (r *Room) UpdateMovie(movieId uint, movie model.BaseMovieInfo) error {
|
||||
func (r *Room) UpdateMovie(movieId string, movie model.BaseMovie) error {
|
||||
return r.movies.Update(movieId, movie)
|
||||
}
|
||||
|
||||
|
@ -71,7 +71,7 @@ func (r *Room) AddMovie(m model.Movie) error {
|
|||
return r.movies.Add(&m)
|
||||
}
|
||||
|
||||
func (r *Room) HasPermission(userID uint, permission model.Permission) bool {
|
||||
func (r *Room) HasPermission(userID string, permission model.Permission) bool {
|
||||
ur, err := db.GetRoomUserRelation(r.ID, userID)
|
||||
if err != nil {
|
||||
return false
|
||||
|
@ -100,23 +100,23 @@ func (r *Room) SetPassword(password string) error {
|
|||
return db.SetRoomHashedPassword(r.ID, hashedPassword)
|
||||
}
|
||||
|
||||
func (r *Room) SetUserRole(userID uint, role model.RoomRole) error {
|
||||
func (r *Room) SetUserRole(userID string, role model.RoomRole) error {
|
||||
return db.SetUserRole(r.ID, userID, role)
|
||||
}
|
||||
|
||||
func (r *Room) SetUserPermission(userID uint, permission model.Permission) error {
|
||||
func (r *Room) SetUserPermission(userID string, permission model.Permission) error {
|
||||
return db.SetUserPermission(r.ID, userID, permission)
|
||||
}
|
||||
|
||||
func (r *Room) AddUserPermission(userID uint, permission model.Permission) error {
|
||||
func (r *Room) AddUserPermission(userID string, permission model.Permission) error {
|
||||
return db.AddUserPermission(r.ID, userID, permission)
|
||||
}
|
||||
|
||||
func (r *Room) RemoveUserPermission(userID uint, permission model.Permission) error {
|
||||
func (r *Room) RemoveUserPermission(userID string, permission model.Permission) error {
|
||||
return db.RemoveUserPermission(r.ID, userID, permission)
|
||||
}
|
||||
|
||||
func (r *Room) DeleteUserPermission(userID uint) error {
|
||||
func (r *Room) DeleteUserPermission(userID string) error {
|
||||
return db.DeleteUserPermission(r.ID, userID)
|
||||
}
|
||||
|
||||
|
@ -124,7 +124,7 @@ func (r *Room) GetMoviesCount() int {
|
|||
return r.movies.Len()
|
||||
}
|
||||
|
||||
func (r *Room) DeleteMovieByID(id uint) error {
|
||||
func (r *Room) DeleteMovieByID(id string) error {
|
||||
return r.movies.DeleteMovieByID(id)
|
||||
}
|
||||
|
||||
|
@ -132,7 +132,7 @@ func (r *Room) ClearMovies() error {
|
|||
return r.movies.Clear()
|
||||
}
|
||||
|
||||
func (r *Room) GetMovieByID(id uint) (*movie, error) {
|
||||
func (r *Room) GetMovieByID(id string) (*movie, error) {
|
||||
return r.movies.GetMovieByID(id)
|
||||
}
|
||||
|
||||
|
@ -141,7 +141,7 @@ func (r *Room) Current() *Current {
|
|||
return &c
|
||||
}
|
||||
|
||||
func (r *Room) ChangeCurrentMovie(id uint) error {
|
||||
func (r *Room) ChangeCurrentMovie(id string) error {
|
||||
m, err := r.movies.GetMovieByID(id)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -150,14 +150,10 @@ func (r *Room) ChangeCurrentMovie(id uint) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (r *Room) SwapMoviePositions(id1, id2 uint) error {
|
||||
func (r *Room) SwapMoviePositions(id1, id2 string) error {
|
||||
return r.movies.SwapMoviePositions(id1, id2)
|
||||
}
|
||||
|
||||
func (r *Room) GetMovieWithPullKey(pullKey string) (*movie, error) {
|
||||
return r.movies.GetMovieWithPullKey(pullKey)
|
||||
}
|
||||
|
||||
func (r *Room) GetMoviesWithPage(page, pageSize int) []*movie {
|
||||
return r.movies.GetMoviesWithPage(page, pageSize)
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
)
|
||||
|
||||
var roomTTL = time.Hour * 24 * 2
|
||||
var roomCache *synccache.SyncCache[uint, *Room]
|
||||
var roomCache *synccache.SyncCache[string, *Room]
|
||||
|
||||
func CreateRoom(name, password string, conf ...db.CreateRoomConfig) (*Room, error) {
|
||||
r, err := db.CreateRoom(name, password, conf...)
|
||||
|
@ -53,7 +53,7 @@ func LoadOrInitRoom(room *model.Room) (*Room, bool) {
|
|||
return i.Value(), loaded
|
||||
}
|
||||
|
||||
func DeleteRoom(roomID uint) error {
|
||||
func DeleteRoom(roomID string) error {
|
||||
err := db.DeleteRoomByID(roomID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -61,7 +61,7 @@ func DeleteRoom(roomID uint) error {
|
|||
return CloseRoom(roomID)
|
||||
}
|
||||
|
||||
func CloseRoom(roomID uint) error {
|
||||
func CloseRoom(roomID string) error {
|
||||
r, loaded := roomCache.LoadAndDelete(roomID)
|
||||
if loaded {
|
||||
r.Value().close()
|
||||
|
@ -69,7 +69,7 @@ func CloseRoom(roomID uint) error {
|
|||
return errors.New("room not found in cache")
|
||||
}
|
||||
|
||||
func LoadRoomByID(id uint) (*Room, error) {
|
||||
func LoadRoomByID(id string) (*Room, error) {
|
||||
r2, loaded := roomCache.Load(id)
|
||||
if loaded {
|
||||
r2.SetExpiration(time.Now().Add(roomTTL))
|
||||
|
@ -78,7 +78,10 @@ func LoadRoomByID(id uint) (*Room, error) {
|
|||
return nil, errors.New("room not found")
|
||||
}
|
||||
|
||||
func LoadOrInitRoomByID(id uint) (*Room, error) {
|
||||
func LoadOrInitRoomByID(id string) (*Room, error) {
|
||||
if len(id) != 36 {
|
||||
return nil, errors.New("room id is not 32 bit")
|
||||
}
|
||||
i, loaded := roomCache.Load(id)
|
||||
if loaded {
|
||||
i.SetExpiration(time.Now().Add(roomTTL))
|
||||
|
@ -92,7 +95,7 @@ func LoadOrInitRoomByID(id uint) (*Room, error) {
|
|||
return r, nil
|
||||
}
|
||||
|
||||
func ClientNum(roomID uint) int64 {
|
||||
func ClientNum(roomID string) int64 {
|
||||
r, loaded := roomCache.Load(roomID)
|
||||
if loaded {
|
||||
return r.Value().ClientNum()
|
||||
|
@ -100,7 +103,7 @@ func ClientNum(roomID uint) int64 {
|
|||
return 0
|
||||
}
|
||||
|
||||
func HasRoom(roomID uint) bool {
|
||||
func HasRoom(roomID string) bool {
|
||||
_, ok := roomCache.Load(roomID)
|
||||
if ok {
|
||||
return true
|
||||
|
@ -120,7 +123,7 @@ func HasRoomByName(name string) bool {
|
|||
return ok
|
||||
}
|
||||
|
||||
func SetRoomPassword(roomID uint, password string) error {
|
||||
func SetRoomPassword(roomID, password string) error {
|
||||
r, err := LoadOrInitRoomByID(roomID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -130,7 +133,7 @@ func SetRoomPassword(roomID uint, password string) error {
|
|||
|
||||
func GetAllRoomsInCacheWithNoNeedPassword() []*Room {
|
||||
rooms := make([]*Room, 0)
|
||||
roomCache.Range(func(key uint, value *synccache.Entry[*Room]) bool {
|
||||
roomCache.Range(func(key string, value *synccache.Entry[*Room]) bool {
|
||||
v := value.Value()
|
||||
if !v.NeedPassword() {
|
||||
rooms = append(rooms, v)
|
||||
|
@ -142,7 +145,7 @@ func GetAllRoomsInCacheWithNoNeedPassword() []*Room {
|
|||
|
||||
func GetAllRoomsInCacheWithoutHidden() []*Room {
|
||||
rooms := make([]*Room, 0)
|
||||
roomCache.Range(func(key uint, value *synccache.Entry[*Room]) bool {
|
||||
roomCache.Range(func(key string, value *synccache.Entry[*Room]) bool {
|
||||
v := value.Value()
|
||||
if !v.Settings.Hidden {
|
||||
rooms = append(rooms, v)
|
||||
|
@ -153,11 +156,11 @@ func GetAllRoomsInCacheWithoutHidden() []*Room {
|
|||
}
|
||||
|
||||
type RoomHeapItem struct {
|
||||
ID uint
|
||||
ID string
|
||||
RoomName string
|
||||
ClientNum int64
|
||||
NeedPassword bool
|
||||
CreatorID uint
|
||||
CreatorID string
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
|
@ -189,7 +192,7 @@ func (h *RoomHeap) Pop() *RoomHeapItem {
|
|||
|
||||
func GetRoomHeapInCacheWithoutHidden() RoomHeap {
|
||||
rooms := make(RoomHeap, 0)
|
||||
roomCache.Range(func(key uint, value *synccache.Entry[*Room]) bool {
|
||||
roomCache.Range(func(key string, value *synccache.Entry[*Room]) bool {
|
||||
v := value.Value()
|
||||
if !v.Settings.Hidden {
|
||||
heap.Push[*RoomHeapItem](&rooms, &RoomHeapItem{
|
||||
|
|
|
@ -15,9 +15,9 @@ func (u *User) CreateRoom(name, password string, conf ...db.CreateRoomConfig) (*
|
|||
return db.CreateRoom(name, password, append(conf, db.WithCreator(&u.User))...)
|
||||
}
|
||||
|
||||
func (u *User) NewMovie(movie model.MovieInfo) model.Movie {
|
||||
func (u *User) NewMovie(movie model.BaseMovie) model.Movie {
|
||||
return model.Movie{
|
||||
MovieInfo: movie,
|
||||
Base: movie,
|
||||
CreatorID: u.ID,
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ func (u *User) IsPending() bool {
|
|||
return u.Role == model.RolePending
|
||||
}
|
||||
|
||||
func (u *User) HasPermission(roomID uint, permission model.Permission) bool {
|
||||
func (u *User) HasPermission(roomID string, permission model.Permission) bool {
|
||||
if u.Role >= model.RoleAdmin {
|
||||
return true
|
||||
}
|
||||
|
@ -49,14 +49,14 @@ func (u *User) HasPermission(roomID uint, permission model.Permission) bool {
|
|||
return ur.HasPermission(permission)
|
||||
}
|
||||
|
||||
func (u *User) DeleteRoom(roomID uint) error {
|
||||
func (u *User) DeleteRoom(roomID string) error {
|
||||
if !u.HasPermission(roomID, model.CanDeleteRoom) {
|
||||
return errors.New("no permission")
|
||||
}
|
||||
return DeleteRoom(roomID)
|
||||
}
|
||||
|
||||
func (u *User) SetRoomPassword(roomID uint, password string) error {
|
||||
func (u *User) SetRoomPassword(roomID, password string) error {
|
||||
if !u.HasPermission(roomID, model.CanSetRoomPassword) {
|
||||
return errors.New("no permission")
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
|
||||
var userCache gcache.Cache
|
||||
|
||||
func GetUserById(id uint) (*User, error) {
|
||||
func GetUserById(id string) (*User, error) {
|
||||
i, err := userCache.Get(id)
|
||||
if err == nil {
|
||||
return i.(*User), nil
|
||||
|
@ -72,14 +72,14 @@ func GetUserByProvider(p provider.OAuth2Provider, pid uint) (*User, error) {
|
|||
return GetUserById(uid)
|
||||
}
|
||||
|
||||
func DeleteUserByID(userID uint) error {
|
||||
func DeleteUserByID(userID string) error {
|
||||
err := db.DeleteUserByID(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userCache.Remove(userID)
|
||||
|
||||
roomCache.Range(func(key uint, value *synccache.Entry[*Room]) bool {
|
||||
roomCache.Range(func(key string, value *synccache.Entry[*Room]) bool {
|
||||
v := value.Value()
|
||||
if v.CreatorID == userID {
|
||||
roomCache.CompareAndDelete(key, value)
|
||||
|
@ -95,7 +95,7 @@ func SaveUser(u *model.User) error {
|
|||
return db.SaveUser(u)
|
||||
}
|
||||
|
||||
func GetUserName(userID uint) string {
|
||||
func GetUserName(userID string) string {
|
||||
u, err := GetUserById(userID)
|
||||
if err != nil {
|
||||
return ""
|
||||
|
@ -103,7 +103,7 @@ func GetUserName(userID uint) string {
|
|||
return u.Username
|
||||
}
|
||||
|
||||
func SetRoleByID(userID uint, role model.Role) error {
|
||||
func SetRoleByID(userID string, role model.Role) error {
|
||||
err := db.SetRoleByID(userID, role)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -14,11 +14,11 @@ import (
|
|||
var s *rtmps.Server
|
||||
|
||||
type RtmpClaims struct {
|
||||
PullKey string `json:"p"`
|
||||
MovieID string `json:"m"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
func AuthRtmpPublish(Authorization string) (channelName string, err error) {
|
||||
func AuthRtmpPublish(Authorization string) (movieID string, err error) {
|
||||
t, err := jwt.ParseWithClaims(strings.TrimPrefix(Authorization, `Bearer `), &RtmpClaims{}, func(token *jwt.Token) (any, error) {
|
||||
return stream.StringToBytes(conf.Conf.Jwt.Secret), nil
|
||||
})
|
||||
|
@ -29,12 +29,12 @@ func AuthRtmpPublish(Authorization string) (channelName string, err error) {
|
|||
if !ok {
|
||||
return "", errors.New("auth failed")
|
||||
}
|
||||
return claims.PullKey, nil
|
||||
return claims.MovieID, nil
|
||||
}
|
||||
|
||||
func NewRtmpAuthorization(channelName string) (string, error) {
|
||||
func NewRtmpAuthorization(movieID string) (string, error) {
|
||||
claims := &RtmpClaims{
|
||||
PullKey: channelName,
|
||||
MovieID: movieID,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
NotBefore: jwt.NewNumericDate(time.Now()),
|
||||
},
|
||||
|
|
|
@ -199,11 +199,10 @@ type MovieInfo struct {
|
|||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
Base *BaseMovieInfo `protobuf:"bytes,2,opt,name=base,proto3" json:"base,omitempty"`
|
||||
PullKey string `protobuf:"bytes,3,opt,name=pullKey,proto3" json:"pullKey,omitempty"`
|
||||
CreatedAt int64 `protobuf:"varint,4,opt,name=createdAt,proto3" json:"createdAt,omitempty"`
|
||||
Creator string `protobuf:"bytes,5,opt,name=creator,proto3" json:"creator,omitempty"`
|
||||
CreatedAt int64 `protobuf:"varint,3,opt,name=createdAt,proto3" json:"createdAt,omitempty"`
|
||||
Creator string `protobuf:"bytes,4,opt,name=creator,proto3" json:"creator,omitempty"`
|
||||
}
|
||||
|
||||
func (x *MovieInfo) Reset() {
|
||||
|
@ -238,11 +237,11 @@ func (*MovieInfo) Descriptor() ([]byte, []int) {
|
|||
return file_proto_message_message_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *MovieInfo) GetId() uint64 {
|
||||
func (x *MovieInfo) GetId() string {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return 0
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *MovieInfo) GetBase() *BaseMovieInfo {
|
||||
|
@ -252,13 +251,6 @@ func (x *MovieInfo) GetBase() *BaseMovieInfo {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (x *MovieInfo) GetPullKey() string {
|
||||
if x != nil {
|
||||
return x.PullKey
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *MovieInfo) GetCreatedAt() int64 {
|
||||
if x != nil {
|
||||
return x.CreatedAt
|
||||
|
@ -516,58 +508,56 @@ var file_proto_message_message_proto_rawDesc = []byte{
|
|||
0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c,
|
||||
0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a,
|
||||
0x02, 0x38, 0x01, 0x22, 0x97, 0x01, 0x0a, 0x09, 0x4d, 0x6f, 0x76, 0x69, 0x65, 0x49, 0x6e, 0x66,
|
||||
0x6f, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x69,
|
||||
0x64, 0x12, 0x28, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||
0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x42, 0x61, 0x73, 0x65, 0x4d, 0x6f, 0x76, 0x69,
|
||||
0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70,
|
||||
0x75, 0x6c, 0x6c, 0x4b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x75,
|
||||
0x6c, 0x6c, 0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64,
|
||||
0x41, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65,
|
||||
0x64, 0x41, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x05,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x22, 0x4a, 0x0a,
|
||||
0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x65, 0x65, 0x6b, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x04, 0x73, 0x65, 0x65, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x72,
|
||||
0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x04, 0x72, 0x61, 0x74, 0x65, 0x12,
|
||||
0x18, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x79, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08,
|
||||
0x52, 0x07, 0x70, 0x6c, 0x61, 0x79, 0x69, 0x6e, 0x67, 0x22, 0x58, 0x0a, 0x07, 0x43, 0x75, 0x72,
|
||||
0x72, 0x65, 0x6e, 0x74, 0x12, 0x26, 0x0a, 0x05, 0x6d, 0x6f, 0x76, 0x69, 0x65, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4d, 0x6f, 0x76, 0x69,
|
||||
0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x6d, 0x6f, 0x76, 0x69, 0x65, 0x12, 0x25, 0x0a, 0x06,
|
||||
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61,
|
||||
0x74, 0x75, 0x73, 0x22, 0x86, 0x02, 0x0a, 0x0e, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x4d,
|
||||
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2d, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6c, 0x65,
|
||||
0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52,
|
||||
0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a,
|
||||
0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
|
||||
0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x61, 0x74, 0x65, 0x18,
|
||||
0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x04, 0x72, 0x61, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73,
|
||||
0x65, 0x65, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x04, 0x73, 0x65, 0x65, 0x6b, 0x12,
|
||||
0x2d, 0x0a, 0x07, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74,
|
||||
0x48, 0x00, 0x52, 0x07, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x88, 0x01, 0x01, 0x12, 0x1c,
|
||||
0x0a, 0x09, 0x70, 0x65, 0x6f, 0x70, 0x6c, 0x65, 0x4e, 0x75, 0x6d, 0x18, 0x07, 0x20, 0x01, 0x28,
|
||||
0x03, 0x52, 0x09, 0x70, 0x65, 0x6f, 0x70, 0x6c, 0x65, 0x4e, 0x75, 0x6d, 0x12, 0x12, 0x0a, 0x04,
|
||||
0x74, 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65,
|
||||
0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2a, 0xdb, 0x01, 0x0a,
|
||||
0x12, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54,
|
||||
0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00,
|
||||
0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x43,
|
||||
0x48, 0x41, 0x54, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x02, 0x12, 0x08, 0x0a,
|
||||
0x04, 0x50, 0x4c, 0x41, 0x59, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x41, 0x55, 0x53, 0x45,
|
||||
0x10, 0x04, 0x12, 0x0e, 0x0a, 0x0a, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x5f, 0x53, 0x45, 0x45, 0x4b,
|
||||
0x10, 0x05, 0x12, 0x0c, 0x0a, 0x08, 0x54, 0x4f, 0x4f, 0x5f, 0x46, 0x41, 0x53, 0x54, 0x10, 0x06,
|
||||
0x12, 0x0c, 0x0a, 0x08, 0x54, 0x4f, 0x4f, 0x5f, 0x53, 0x4c, 0x4f, 0x57, 0x10, 0x07, 0x12, 0x0f,
|
||||
0x0a, 0x0b, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x52, 0x41, 0x54, 0x45, 0x10, 0x08, 0x12,
|
||||
0x0f, 0x0a, 0x0b, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x53, 0x45, 0x45, 0x4b, 0x10, 0x09,
|
||||
0x12, 0x12, 0x0a, 0x0e, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x43, 0x55, 0x52, 0x52, 0x45,
|
||||
0x4e, 0x54, 0x10, 0x0a, 0x12, 0x11, 0x0a, 0x0d, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x4d,
|
||||
0x4f, 0x56, 0x49, 0x45, 0x53, 0x10, 0x0b, 0x12, 0x11, 0x0a, 0x0d, 0x43, 0x48, 0x41, 0x4e, 0x47,
|
||||
0x45, 0x5f, 0x50, 0x45, 0x4f, 0x50, 0x4c, 0x45, 0x10, 0x0c, 0x42, 0x06, 0x5a, 0x04, 0x2e, 0x3b,
|
||||
0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x02, 0x38, 0x01, 0x22, 0x7d, 0x0a, 0x09, 0x4d, 0x6f, 0x76, 0x69, 0x65, 0x49, 0x6e, 0x66, 0x6f,
|
||||
0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64,
|
||||
0x12, 0x28, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x42, 0x61, 0x73, 0x65, 0x4d, 0x6f, 0x76, 0x69, 0x65,
|
||||
0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x72,
|
||||
0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63,
|
||||
0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x61,
|
||||
0x74, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74,
|
||||
0x6f, 0x72, 0x22, 0x4a, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04,
|
||||
0x73, 0x65, 0x65, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x04, 0x73, 0x65, 0x65, 0x6b,
|
||||
0x12, 0x12, 0x0a, 0x04, 0x72, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x04,
|
||||
0x72, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x79, 0x69, 0x6e, 0x67, 0x18,
|
||||
0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x6c, 0x61, 0x79, 0x69, 0x6e, 0x67, 0x22, 0x58,
|
||||
0x0a, 0x07, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x12, 0x26, 0x0a, 0x05, 0x6d, 0x6f, 0x76,
|
||||
0x69, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x2e, 0x4d, 0x6f, 0x76, 0x69, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x6d, 0x6f, 0x76, 0x69,
|
||||
0x65, 0x12, 0x25, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x0b, 0x32, 0x0d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
|
||||
0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x86, 0x02, 0x0a, 0x0e, 0x45, 0x6c, 0x65,
|
||||
0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2d, 0x0a, 0x04, 0x74,
|
||||
0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x2e, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
|
||||
0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65,
|
||||
0x6e, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64,
|
||||
0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04,
|
||||
0x72, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x04, 0x72, 0x61, 0x74, 0x65,
|
||||
0x12, 0x12, 0x0a, 0x04, 0x73, 0x65, 0x65, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x04,
|
||||
0x73, 0x65, 0x65, 0x6b, 0x12, 0x2d, 0x0a, 0x07, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x18,
|
||||
0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x75,
|
||||
0x72, 0x72, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x07, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74,
|
||||
0x88, 0x01, 0x01, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x65, 0x6f, 0x70, 0x6c, 0x65, 0x4e, 0x75, 0x6d,
|
||||
0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x70, 0x65, 0x6f, 0x70, 0x6c, 0x65, 0x4e, 0x75,
|
||||
0x6d, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52,
|
||||
0x04, 0x74, 0x69, 0x6d, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e,
|
||||
0x74, 0x2a, 0xdb, 0x01, 0x0a, 0x12, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x73,
|
||||
0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e,
|
||||
0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x01,
|
||||
0x12, 0x10, 0x0a, 0x0c, 0x43, 0x48, 0x41, 0x54, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45,
|
||||
0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x4c, 0x41, 0x59, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05,
|
||||
0x50, 0x41, 0x55, 0x53, 0x45, 0x10, 0x04, 0x12, 0x0e, 0x0a, 0x0a, 0x43, 0x48, 0x45, 0x43, 0x4b,
|
||||
0x5f, 0x53, 0x45, 0x45, 0x4b, 0x10, 0x05, 0x12, 0x0c, 0x0a, 0x08, 0x54, 0x4f, 0x4f, 0x5f, 0x46,
|
||||
0x41, 0x53, 0x54, 0x10, 0x06, 0x12, 0x0c, 0x0a, 0x08, 0x54, 0x4f, 0x4f, 0x5f, 0x53, 0x4c, 0x4f,
|
||||
0x57, 0x10, 0x07, 0x12, 0x0f, 0x0a, 0x0b, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x52, 0x41,
|
||||
0x54, 0x45, 0x10, 0x08, 0x12, 0x0f, 0x0a, 0x0b, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x53,
|
||||
0x45, 0x45, 0x4b, 0x10, 0x09, 0x12, 0x12, 0x0a, 0x0e, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f,
|
||||
0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x54, 0x10, 0x0a, 0x12, 0x11, 0x0a, 0x0d, 0x43, 0x48, 0x41,
|
||||
0x4e, 0x47, 0x45, 0x5f, 0x4d, 0x4f, 0x56, 0x49, 0x45, 0x53, 0x10, 0x0b, 0x12, 0x11, 0x0a, 0x0d,
|
||||
0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x50, 0x45, 0x4f, 0x50, 0x4c, 0x45, 0x10, 0x0c, 0x42,
|
||||
0x06, 0x5a, 0x04, 0x2e, 0x3b, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
|
@ -30,11 +30,10 @@ message BaseMovieInfo {
|
|||
}
|
||||
|
||||
message MovieInfo {
|
||||
uint64 id = 1;
|
||||
string id = 1;
|
||||
BaseMovieInfo base = 2;
|
||||
string pullKey = 3;
|
||||
int64 createdAt = 4;
|
||||
string creator = 5;
|
||||
int64 createdAt = 3;
|
||||
string creator = 4;
|
||||
}
|
||||
|
||||
message Status {
|
||||
|
|
|
@ -31,6 +31,9 @@ func WithHeaders(headers map[string]string) HttpReadSeekerConf {
|
|||
|
||||
func WithAppendHeaders(headers map[string]string) HttpReadSeekerConf {
|
||||
return func(h *HttpReadSeeker) {
|
||||
if h.headers == nil {
|
||||
h.headers = make(map[string]string)
|
||||
}
|
||||
for k, v := range headers {
|
||||
h.headers[k] = v
|
||||
}
|
||||
|
|
|
@ -177,7 +177,7 @@ func PendingUsers(ctx *gin.Context) {
|
|||
}))
|
||||
}
|
||||
|
||||
func ApprovePendingUser(Authorization string, userID uint) error {
|
||||
func ApprovePendingUser(Authorization, userID string) error {
|
||||
user, err := op.GetUserById(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/synctv-org/synctv/public"
|
||||
Vbilibili "github.com/synctv-org/synctv/server/handlers/vendors/bilibili"
|
||||
"github.com/synctv-org/synctv/server/middlewares"
|
||||
"github.com/synctv-org/synctv/utils"
|
||||
)
|
||||
|
@ -106,16 +107,16 @@ func Init(e *gin.Engine) {
|
|||
|
||||
needAuthMovie.POST("/clear", ClearMovies)
|
||||
|
||||
movie.HEAD("/proxy/:roomId/:pullKey", ProxyMovie)
|
||||
movie.HEAD("/proxy/:roomId/:movieId", ProxyMovie)
|
||||
|
||||
movie.GET("/proxy/:roomId/:pullKey", ProxyMovie)
|
||||
movie.GET("/proxy/:roomId/:movieId", ProxyMovie)
|
||||
|
||||
{
|
||||
live := needAuthMovie.Group("/live")
|
||||
|
||||
live.POST("/publishKey", NewPublishKey)
|
||||
|
||||
live.GET("/*pullKey", JoinLive)
|
||||
live.GET("/*movieId", JoinLive)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,5 +132,19 @@ func Init(e *gin.Engine) {
|
|||
|
||||
needAuthUser.POST("/username", SetUsername)
|
||||
}
|
||||
|
||||
{
|
||||
vendor := needAuthUserApi.Group("/vendor")
|
||||
|
||||
{
|
||||
bilibili := vendor.Group("/bilibili")
|
||||
|
||||
bilibili.GET("/qr", Vbilibili.QRCode)
|
||||
|
||||
bilibili.POST("/login", Vbilibili.Login)
|
||||
|
||||
bilibili.POST("/parse", Vbilibili.Parse)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/synctv-org/synctv/internal/conf"
|
||||
"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/rtmp"
|
||||
|
@ -20,6 +21,7 @@ import (
|
|||
"github.com/synctv-org/synctv/proxy"
|
||||
"github.com/synctv-org/synctv/server/model"
|
||||
"github.com/synctv-org/synctv/utils"
|
||||
"github.com/synctv-org/synctv/vendors/bilibili"
|
||||
"github.com/zijiren233/livelib/protocol/hls"
|
||||
"github.com/zijiren233/livelib/protocol/httpflv"
|
||||
)
|
||||
|
@ -62,34 +64,54 @@ func MovieList(ctx *gin.Context) {
|
|||
mresp[i] = model.MoviesResp{
|
||||
Id: v.ID,
|
||||
Base: m[i].Base,
|
||||
PullKey: v.PullKey,
|
||||
Creater: op.GetUserName(v.CreatorID),
|
||||
Creator: op.GetUserName(v.CreatorID),
|
||||
}
|
||||
}
|
||||
|
||||
current := room.Current()
|
||||
current, err := genCurrent(room)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{
|
||||
"current": &model.CurrentMovieResp{
|
||||
Status: current.Status,
|
||||
Movie: model.MoviesResp{
|
||||
Id: current.Movie.ID,
|
||||
Base: current.Movie.Base,
|
||||
PullKey: current.Movie.PullKey,
|
||||
Creater: op.GetUserName(current.Movie.CreatorID),
|
||||
},
|
||||
},
|
||||
"total": room.GetMoviesCount(),
|
||||
"movies": mresp,
|
||||
"current": genCurrentResp(current),
|
||||
"total": room.GetMoviesCount(),
|
||||
"movies": mresp,
|
||||
}))
|
||||
}
|
||||
|
||||
func genCurrent(room *op.Room) (*op.Current, error) {
|
||||
current := room.Current()
|
||||
if current.Movie.Base.Vendor != "" {
|
||||
return current, parse2VendorMovie(¤t.Movie)
|
||||
}
|
||||
return current, nil
|
||||
}
|
||||
|
||||
func genCurrentResp(current *op.Current) *model.CurrentMovieResp {
|
||||
return &model.CurrentMovieResp{
|
||||
Status: current.Status,
|
||||
Movie: model.MoviesResp{
|
||||
Id: current.Movie.ID,
|
||||
Base: current.Movie.Base,
|
||||
Creator: op.GetUserName(current.Movie.CreatorID),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func CurrentMovie(ctx *gin.Context) {
|
||||
room := ctx.MustGet("room").(*op.Room)
|
||||
// user := ctx.MustGet("user").(*op.User)
|
||||
|
||||
current, err := genCurrent(room)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{
|
||||
"current": room.Current(),
|
||||
"current": genCurrentResp(current),
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -110,8 +132,7 @@ func Movies(ctx *gin.Context) {
|
|||
mresp[i] = model.MoviesResp{
|
||||
Id: v.ID,
|
||||
Base: m[i].Base,
|
||||
PullKey: v.PullKey,
|
||||
Creater: op.GetUserName(v.CreatorID),
|
||||
Creator: op.GetUserName(v.CreatorID),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,9 +152,7 @@ func PushMovie(ctx *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
mi := user.NewMovie(dbModel.MovieInfo{
|
||||
Base: dbModel.BaseMovieInfo(req),
|
||||
})
|
||||
mi := user.NewMovie(dbModel.BaseMovie(req))
|
||||
|
||||
err := room.AddMovie(mi)
|
||||
if err != nil {
|
||||
|
@ -179,12 +198,7 @@ func NewPublishKey(ctx *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if movie.PullKey == "" {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorStringResp("pull key is empty"))
|
||||
return
|
||||
}
|
||||
|
||||
token, err := rtmp.NewRtmpAuthorization(movie.PullKey)
|
||||
token, err := rtmp.NewRtmpAuthorization(movie.ID)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
return
|
||||
|
@ -212,7 +226,7 @@ func EditMovie(ctx *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if err := room.UpdateMovie(req.Id, dbModel.BaseMovieInfo(req.PushMovieReq)); err != nil {
|
||||
if err := room.UpdateMovie(req.Id, dbModel.BaseMovie(req.PushMovieReq)); err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
|
||||
return
|
||||
}
|
||||
|
@ -345,29 +359,27 @@ var allowedProxyMovieContentType = map[string]struct{}{
|
|||
"video/webm": {},
|
||||
}
|
||||
|
||||
const UserAgent = `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.40`
|
||||
|
||||
func ProxyMovie(ctx *gin.Context) {
|
||||
roomId := ctx.Param("roomId")
|
||||
if roomId == "" {
|
||||
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("roomId is empty"))
|
||||
return
|
||||
}
|
||||
id, err := strconv.ParseUint(roomId, 10, 64)
|
||||
|
||||
room, err := op.LoadOrInitRoomByID(roomId)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
|
||||
return
|
||||
}
|
||||
|
||||
room, err := op.LoadOrInitRoomByID(uint(id))
|
||||
m, err := room.GetMovieByID(ctx.Param("movieId"))
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
|
||||
return
|
||||
}
|
||||
|
||||
m, err := room.GetMovieWithPullKey(ctx.Param("pullKey"))
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
|
||||
if m.Base.VendorInfo.Vendor != "" {
|
||||
ProxyVendorMovie(ctx, m.Movie)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -412,7 +424,6 @@ func ProxyMovie(ctx *gin.Context) {
|
|||
hrs := proxy.NewBufferedHttpReadSeeker(128*1024, m.Base.Url,
|
||||
proxy.WithContext(ctx),
|
||||
proxy.WithHeaders(m.Base.Headers),
|
||||
proxy.WithContext(ctx),
|
||||
proxy.WithContentLength(length),
|
||||
)
|
||||
name := resp.Header().Get("Content-Disposition")
|
||||
|
@ -438,16 +449,11 @@ func JoinLive(ctx *gin.Context) {
|
|||
room := ctx.MustGet("room").(*op.Room)
|
||||
// user := ctx.MustGet("user").(*op.User)
|
||||
|
||||
pullKey := strings.Trim(ctx.Param("pullKey"), "/")
|
||||
pullKeySplitd := strings.Split(pullKey, "/")
|
||||
fileName := pullKeySplitd[0]
|
||||
fileExt := path.Ext(pullKey)
|
||||
movieId := strings.Trim(ctx.Param("movieId"), "/")
|
||||
movieIdSplitd := strings.Split(movieId, "/")
|
||||
fileName := movieIdSplitd[0]
|
||||
fileExt := path.Ext(movieId)
|
||||
channelName := strings.TrimSuffix(fileName, fileExt)
|
||||
// m, err := room.GetMovieWithPullKey(channelName)
|
||||
// if err != nil {
|
||||
// ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
|
||||
// return
|
||||
// }
|
||||
channel, err := room.GetChannel(channelName)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
|
||||
|
@ -469,7 +475,7 @@ func JoinLive(ctx *gin.Context) {
|
|||
}
|
||||
ctx.Data(http.StatusOK, hls.M3U8ContentType, b.Bytes())
|
||||
case ".ts":
|
||||
b, err := channel.GetTsFile(pullKeySplitd[1])
|
||||
b, err := channel.GetTsFile(movieIdSplitd[1])
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
|
||||
return
|
||||
|
@ -481,3 +487,159 @@ func JoinLive(ctx *gin.Context) {
|
|||
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(FormatErrNotSupportFileType(fileExt)))
|
||||
}
|
||||
}
|
||||
|
||||
func ProxyVendorMovie(ctx *gin.Context, m *dbModel.Movie) {
|
||||
if m.Base.VendorInfo.Vendor == "" {
|
||||
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("vendor is empty"))
|
||||
return
|
||||
}
|
||||
|
||||
switch m.Base.VendorInfo.Vendor {
|
||||
case dbModel.StreamingVendorBilibili:
|
||||
bvidI := m.Base.VendorInfo.Info["bvid"]
|
||||
epIdI := m.Base.VendorInfo.Info["epId"]
|
||||
if bvidI != nil && epIdI != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp(fmt.Sprintf("bvid(%v) and epId(%v) can't be used at the same time", bvidI, epIdI)))
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
bvid string
|
||||
epId float64
|
||||
cid float64
|
||||
ok bool
|
||||
)
|
||||
if bvidI != nil {
|
||||
bvid, ok = bvidI.(string)
|
||||
if !ok {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorStringResp("bvid is not string"))
|
||||
return
|
||||
}
|
||||
} else if epIdI != nil {
|
||||
epId, ok = epIdI.(float64)
|
||||
if !ok {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorStringResp("epId is not number"))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorStringResp("bvid and epId is empty"))
|
||||
return
|
||||
}
|
||||
|
||||
cidI := m.Base.VendorInfo.Info["cid"]
|
||||
if cidI == nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("cid is empty"))
|
||||
return
|
||||
}
|
||||
cid, ok = cidI.(float64)
|
||||
if !ok {
|
||||
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("cid is not number"))
|
||||
return
|
||||
}
|
||||
|
||||
vendor, err := db.AssignFirstOrCreateVendorByUserIDAndVendor(m.CreatorID, dbModel.StreamingVendorBilibili)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
return
|
||||
}
|
||||
cli := bilibili.NewClient(vendor.Cookies)
|
||||
|
||||
if bvid != "" {
|
||||
mu, err := cli.GetVideoURL(0, bvid, uint(cid))
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
return
|
||||
}
|
||||
// s, err := cli.GetSubtitles(0, bvid, uint(cid))
|
||||
// if err != nil {
|
||||
// ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
// return
|
||||
// }
|
||||
ctx.Redirect(http.StatusFound, mu.URL)
|
||||
return
|
||||
} else {
|
||||
mu, err := cli.GetPGCURL(uint(epId), uint(cid))
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
return
|
||||
}
|
||||
hrs := proxy.NewBufferedHttpReadSeeker(128*1024, mu.URL,
|
||||
proxy.WithContext(ctx),
|
||||
proxy.WithAppendHeaders(map[string]string{
|
||||
"Referer": "https://www.bilibili.com/",
|
||||
"User-Agent": utils.UA,
|
||||
}),
|
||||
)
|
||||
http.ServeContent(ctx.Writer, ctx.Request, mu.URL, time.Now(), hrs)
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("vendor not support"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func parse2VendorMovie(movie *dbModel.Movie) error {
|
||||
switch movie.Base.VendorInfo.Vendor {
|
||||
case dbModel.StreamingVendorBilibili:
|
||||
bvidI := movie.Base.VendorInfo.Info["bvid"]
|
||||
epIdI := movie.Base.VendorInfo.Info["epId"]
|
||||
if bvidI != nil && epIdI != nil {
|
||||
return fmt.Errorf("bvid(%v) and epId(%v) can't be used at the same time", bvidI, epIdI)
|
||||
}
|
||||
|
||||
var (
|
||||
bvid string
|
||||
// epId float64
|
||||
cid float64
|
||||
ok bool
|
||||
)
|
||||
if bvidI != nil {
|
||||
bvid, ok = bvidI.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("bvid is not string")
|
||||
}
|
||||
} else if epIdI != nil {
|
||||
// epId, ok = epIdI.(float64)
|
||||
// if !ok {
|
||||
// return fmt.Errorf("epId is not number")
|
||||
// }
|
||||
} else {
|
||||
return fmt.Errorf("bvid and epId is empty")
|
||||
}
|
||||
|
||||
cidI := movie.Base.VendorInfo.Info["cid"]
|
||||
if cidI == nil {
|
||||
return fmt.Errorf("cid is empty")
|
||||
}
|
||||
cid, ok = cidI.(float64)
|
||||
if !ok {
|
||||
return fmt.Errorf("cid is not number")
|
||||
}
|
||||
|
||||
vendor, err := db.AssignFirstOrCreateVendorByUserIDAndVendor(movie.CreatorID, dbModel.StreamingVendorBilibili)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cli := bilibili.NewClient(vendor.Cookies)
|
||||
|
||||
if bvid != "" {
|
||||
mu, err := cli.GetVideoURL(0, bvid, uint(cid))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
movie.Base.Url = mu.URL
|
||||
return nil
|
||||
} else {
|
||||
// mu, err := cli.GetPGCURL(uint(epId), uint(cid))
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
return nil
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("vendor not support")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/synctv-org/synctv/internal/db"
|
||||
|
@ -190,13 +189,7 @@ func genRoomListResp(scopes ...func(db *gorm.DB) *gorm.DB) []*model.RoomListResp
|
|||
}
|
||||
|
||||
func CheckRoom(ctx *gin.Context) {
|
||||
id, err := strconv.Atoi(ctx.Query("roomId"))
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
|
||||
return
|
||||
}
|
||||
|
||||
r, err := db.GetRoomByID(uint(id))
|
||||
r, err := db.GetRoomByID(ctx.Query("roomId"))
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
|
||||
return
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
package Vbilibili
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"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/server/model"
|
||||
"github.com/synctv-org/synctv/vendors/bilibili"
|
||||
)
|
||||
|
||||
func QRCode(ctx *gin.Context) {
|
||||
r, err := bilibili.NewQRCode()
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, model.NewApiDataResp(r))
|
||||
}
|
||||
|
||||
type LoginReq struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
func (r *LoginReq) Validate() error {
|
||||
if r.Key == "" {
|
||||
return errors.New("key is empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *LoginReq) Decode(ctx *gin.Context) error {
|
||||
return json.NewDecoder(ctx.Request.Body).Decode(r)
|
||||
}
|
||||
|
||||
func Login(ctx *gin.Context) {
|
||||
user := ctx.MustGet("user").(*op.User)
|
||||
|
||||
req := LoginReq{}
|
||||
if err := model.Decode(ctx, &req); err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
|
||||
return
|
||||
}
|
||||
|
||||
cookie, err := bilibili.Login(req.Key)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
|
||||
return
|
||||
}
|
||||
|
||||
_, err = db.AssignFirstOrCreateVendorByUserIDAndVendor(user.ID, dbModel.StreamingVendorBilibili, db.WithCookie([]*http.Cookie{cookie}))
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
type ParseReq struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
func (r *ParseReq) Validate() error {
|
||||
if r.URL == "" {
|
||||
return errors.New("url is empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ParseReq) Decode(ctx *gin.Context) error {
|
||||
return json.NewDecoder(ctx.Request.Body).Decode(r)
|
||||
}
|
||||
|
||||
func Parse(ctx *gin.Context) {
|
||||
user := ctx.MustGet("user").(*op.User)
|
||||
|
||||
req := ParseReq{}
|
||||
if err := model.Decode(ctx, &req); err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
|
||||
return
|
||||
}
|
||||
|
||||
matchType, id, err := bilibili.Match(req.URL)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
|
||||
return
|
||||
}
|
||||
|
||||
vendor, err := db.FirstOrCreateVendorByUserIDAndVendor(user.ID, dbModel.StreamingVendorBilibili)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
return
|
||||
}
|
||||
cli := bilibili.NewClient(vendor.Cookies)
|
||||
|
||||
switch matchType {
|
||||
case "bv":
|
||||
mpis, err := cli.ParseVideoPage(0, id)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, model.NewApiDataResp(mpis))
|
||||
case "av":
|
||||
aid, err := strconv.Atoi(id)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
return
|
||||
}
|
||||
mpis, err := cli.ParseVideoPage(uint(aid), "")
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, model.NewApiDataResp(mpis))
|
||||
case "ep":
|
||||
epId, err := strconv.Atoi(id)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
return
|
||||
}
|
||||
mpis, err := cli.ParsePGCPage(uint(epId), 0)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, model.NewApiDataResp(mpis))
|
||||
case "ss":
|
||||
seasonId, err := strconv.Atoi(id)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
return
|
||||
}
|
||||
mpis, err := cli.ParsePGCPage(0, uint(seasonId))
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, model.NewApiDataResp(mpis))
|
||||
default:
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorStringResp("unknown match type"))
|
||||
return
|
||||
}
|
||||
}
|
|
@ -20,13 +20,13 @@ var (
|
|||
)
|
||||
|
||||
type AuthClaims struct {
|
||||
UserId uint `json:"u"`
|
||||
UserId string `json:"u"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
type AuthRoomClaims struct {
|
||||
AuthClaims
|
||||
RoomId uint `json:"r"`
|
||||
RoomId string `json:"r"`
|
||||
Version uint32 `json:"rv"`
|
||||
}
|
||||
|
||||
|
@ -64,11 +64,11 @@ func AuthRoom(Authorization string) (*op.User, *op.Room, error) {
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
if claims.RoomId == 0 {
|
||||
if len(claims.RoomId) != 36 {
|
||||
return nil, nil, ErrAuthFailed
|
||||
}
|
||||
|
||||
if claims.UserId == 0 {
|
||||
if len(claims.UserId) != 36 {
|
||||
return nil, nil, ErrAuthFailed
|
||||
}
|
||||
|
||||
|
@ -100,7 +100,7 @@ func AuthUser(Authorization string) (*op.User, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if claims.UserId == 0 {
|
||||
if len(claims.UserId) != 36 {
|
||||
return nil, ErrAuthFailed
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ var (
|
|||
ErrEmptyIds = errors.New("empty ids")
|
||||
)
|
||||
|
||||
type PushMovieReq model.BaseMovieInfo
|
||||
type PushMovieReq model.BaseMovie
|
||||
|
||||
func (p *PushMovieReq) Decode(ctx *gin.Context) error {
|
||||
return json.NewDecoder(ctx.Request.Body).Decode(p)
|
||||
|
@ -41,11 +41,15 @@ func (p *PushMovieReq) Validate() error {
|
|||
return ErrTypeTooLong
|
||||
}
|
||||
|
||||
if p.VendorInfo.Vendor != "" && p.VendorInfo.Info == nil {
|
||||
return errors.New("vendor info is empty")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type IdReq struct {
|
||||
Id uint `json:"id"`
|
||||
Id string `json:"id"`
|
||||
}
|
||||
|
||||
func (i *IdReq) Decode(ctx *gin.Context) error {
|
||||
|
@ -53,7 +57,7 @@ func (i *IdReq) Decode(ctx *gin.Context) error {
|
|||
}
|
||||
|
||||
func (i *IdReq) Validate() error {
|
||||
if i.Id <= 0 {
|
||||
if len(i.Id) != 36 {
|
||||
return ErrId
|
||||
}
|
||||
return nil
|
||||
|
@ -79,7 +83,7 @@ func (e *EditMovieReq) Validate() error {
|
|||
}
|
||||
|
||||
type IdsReq struct {
|
||||
Ids []uint `json:"ids"`
|
||||
Ids []string `json:"ids"`
|
||||
}
|
||||
|
||||
func (i *IdsReq) Decode(ctx *gin.Context) error {
|
||||
|
@ -90,12 +94,17 @@ func (i *IdsReq) Validate() error {
|
|||
if len(i.Ids) == 0 {
|
||||
return ErrEmptyIds
|
||||
}
|
||||
for _, v := range i.Ids {
|
||||
if len(v) != 36 {
|
||||
return ErrId
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SwapMovieReq struct {
|
||||
Id1 uint `json:"id1"`
|
||||
Id2 uint `json:"id2"`
|
||||
Id1 string `json:"id1"`
|
||||
Id2 string `json:"id2"`
|
||||
}
|
||||
|
||||
func (s *SwapMovieReq) Decode(ctx *gin.Context) error {
|
||||
|
@ -103,17 +112,16 @@ func (s *SwapMovieReq) Decode(ctx *gin.Context) error {
|
|||
}
|
||||
|
||||
func (s *SwapMovieReq) Validate() error {
|
||||
if s.Id1 <= 0 || s.Id2 <= 0 {
|
||||
if len(s.Id1) != 36 || len(s.Id2) != 36 {
|
||||
return ErrId
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type MoviesResp struct {
|
||||
Id uint `json:"id"`
|
||||
Base model.BaseMovieInfo `json:"base"`
|
||||
PullKey string `json:"pullKey"`
|
||||
Creater string `json:"creater"`
|
||||
Id string `json:"id"`
|
||||
Base model.BaseMovie `json:"base"`
|
||||
Creator string `json:"creator"`
|
||||
}
|
||||
|
||||
type CurrentMovieResp struct {
|
||||
|
|
|
@ -71,7 +71,7 @@ func (c *CreateRoomReq) Validate() error {
|
|||
}
|
||||
|
||||
type RoomListResp struct {
|
||||
RoomId uint `json:"roomId"`
|
||||
RoomId string `json:"roomId"`
|
||||
RoomName string `json:"roomName"`
|
||||
PeopleNum int64 `json:"peopleNum"`
|
||||
NeedPassword bool `json:"needPassword"`
|
||||
|
@ -80,7 +80,7 @@ type RoomListResp struct {
|
|||
}
|
||||
|
||||
type LoginRoomReq struct {
|
||||
RoomId uint `json:"roomId"`
|
||||
RoomId string `json:"roomId"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
|
@ -89,7 +89,7 @@ func (l *LoginRoomReq) Decode(ctx *gin.Context) error {
|
|||
}
|
||||
|
||||
func (l *LoginRoomReq) Validate() error {
|
||||
if l.RoomId == 0 {
|
||||
if len(l.RoomId) != 36 {
|
||||
return ErrEmptyRoomName
|
||||
}
|
||||
|
||||
|
@ -114,7 +114,7 @@ func (s *SetRoomPasswordReq) Validate() error {
|
|||
}
|
||||
|
||||
type UserIdReq struct {
|
||||
UserId uint `json:"userId"`
|
||||
UserId string `json:"userId"`
|
||||
}
|
||||
|
||||
func (u *UserIdReq) Decode(ctx *gin.Context) error {
|
||||
|
@ -122,7 +122,7 @@ func (u *UserIdReq) Decode(ctx *gin.Context) error {
|
|||
}
|
||||
|
||||
func (u *UserIdReq) Validate() error {
|
||||
if u.UserId == 0 {
|
||||
if len(u.UserId) != 36 {
|
||||
return ErrEmptyUserId
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -52,7 +52,7 @@ func (l *LoginUserReq) Validate() error {
|
|||
}
|
||||
|
||||
type UserInfoResp struct {
|
||||
ID uint `json:"id"`
|
||||
ID string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Role dbModel.Role `json:"role"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
|
@ -78,7 +78,7 @@ func (s *SetUsernameReq) Decode(ctx *gin.Context) error {
|
|||
}
|
||||
|
||||
type UserIDReq struct {
|
||||
ID uint `json:"id"`
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
func (u *UserIDReq) Decode(ctx *gin.Context) error {
|
||||
|
@ -86,7 +86,7 @@ func (u *UserIDReq) Decode(ctx *gin.Context) error {
|
|||
}
|
||||
|
||||
func (u *UserIDReq) Validate() error {
|
||||
if u.ID == 0 {
|
||||
if len(u.ID) != 36 {
|
||||
return errors.New("id is required")
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -19,6 +19,10 @@ import (
|
|||
|
||||
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
|
||||
const (
|
||||
UA = `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 Edg/118.0.2088.69`
|
||||
)
|
||||
|
||||
func RandString(n int) string {
|
||||
b := make([]rune, n)
|
||||
for i := range b {
|
||||
|
|
|
@ -0,0 +1,814 @@
|
|||
package bilibili
|
||||
|
||||
type qrcodeResp struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
TTL int `json:"ttl"`
|
||||
Data struct {
|
||||
URL string `json:"url"`
|
||||
QrcodeKey string `json:"qrcode_key"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type videoPageInfo struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
TTL int `json:"ttl"`
|
||||
Data struct {
|
||||
Bvid string `json:"bvid"`
|
||||
Aid uint `json:"aid"`
|
||||
Videos int `json:"videos"`
|
||||
Tid int `json:"tid"`
|
||||
Tname string `json:"tname"`
|
||||
Copyright int `json:"copyright"`
|
||||
Pic string `json:"pic"`
|
||||
Title string `json:"title"`
|
||||
Pubdate int `json:"pubdate"`
|
||||
Ctime int `json:"ctime"`
|
||||
Desc string `json:"desc"`
|
||||
DescV2 []struct {
|
||||
RawText string `json:"raw_text"`
|
||||
Type int `json:"type"`
|
||||
BizID int `json:"biz_id"`
|
||||
} `json:"desc_v2"`
|
||||
State int `json:"state"`
|
||||
Duration int `json:"duration"`
|
||||
Rights struct {
|
||||
Bp int `json:"bp"`
|
||||
Elec int `json:"elec"`
|
||||
Download int `json:"download"`
|
||||
Movie int `json:"movie"`
|
||||
Pay int `json:"pay"`
|
||||
Hd5 int `json:"hd5"`
|
||||
NoReprint int `json:"no_reprint"`
|
||||
Autoplay int `json:"autoplay"`
|
||||
UgcPay int `json:"ugc_pay"`
|
||||
IsCooperation int `json:"is_cooperation"`
|
||||
UgcPayPreview int `json:"ugc_pay_preview"`
|
||||
NoBackground int `json:"no_background"`
|
||||
CleanMode int `json:"clean_mode"`
|
||||
IsSteinGate int `json:"is_stein_gate"`
|
||||
Is360 int `json:"is_360"`
|
||||
NoShare int `json:"no_share"`
|
||||
ArcPay int `json:"arc_pay"`
|
||||
FreeWatch int `json:"free_watch"`
|
||||
} `json:"rights"`
|
||||
Owner struct {
|
||||
Mid int `json:"mid"`
|
||||
Name string `json:"name"`
|
||||
Face string `json:"face"`
|
||||
} `json:"owner"`
|
||||
Stat struct {
|
||||
Aid int `json:"aid"`
|
||||
View int `json:"view"`
|
||||
Danmaku int `json:"danmaku"`
|
||||
Reply int `json:"reply"`
|
||||
Favorite int `json:"favorite"`
|
||||
Coin int `json:"coin"`
|
||||
Share int `json:"share"`
|
||||
NowRank int `json:"now_rank"`
|
||||
HisRank int `json:"his_rank"`
|
||||
Like int `json:"like"`
|
||||
Dislike int `json:"dislike"`
|
||||
Evaluation string `json:"evaluation"`
|
||||
ArgueMsg string `json:"argue_msg"`
|
||||
Vt int `json:"vt"`
|
||||
} `json:"stat"`
|
||||
Dynamic string `json:"dynamic"`
|
||||
Cid int `json:"cid"`
|
||||
Dimension struct {
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Rotate int `json:"rotate"`
|
||||
} `json:"dimension"`
|
||||
SeasonID int `json:"season_id"`
|
||||
Premiere interface{} `json:"premiere"`
|
||||
TeenageMode int `json:"teenage_mode"`
|
||||
IsChargeableSeason bool `json:"is_chargeable_season"`
|
||||
IsStory bool `json:"is_story"`
|
||||
IsUpowerExclusive bool `json:"is_upower_exclusive"`
|
||||
IsUpowerPlay bool `json:"is_upower_play"`
|
||||
EnableVt int `json:"enable_vt"`
|
||||
VtDisplay string `json:"vt_display"`
|
||||
NoCache bool `json:"no_cache"`
|
||||
Pages []struct {
|
||||
Cid int `json:"cid"`
|
||||
Page int `json:"page"`
|
||||
From string `json:"from"`
|
||||
Part string `json:"part"`
|
||||
Duration int `json:"duration"`
|
||||
Vid string `json:"vid"`
|
||||
Weblink string `json:"weblink"`
|
||||
Dimension struct {
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Rotate int `json:"rotate"`
|
||||
} `json:"dimension"`
|
||||
FirstFrame string `json:"first_frame"`
|
||||
} `json:"pages"`
|
||||
Subtitle struct {
|
||||
AllowSubmit bool `json:"allow_submit"`
|
||||
List []interface{} `json:"list"`
|
||||
} `json:"subtitle"`
|
||||
UgcSeason struct {
|
||||
ID int `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Cover string `json:"cover"`
|
||||
Mid int `json:"mid"`
|
||||
Intro string `json:"intro"`
|
||||
SignState int `json:"sign_state"`
|
||||
Attribute int `json:"attribute"`
|
||||
Sections []struct {
|
||||
SeasonID int `json:"season_id"`
|
||||
ID int `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Type int `json:"type"`
|
||||
Episodes []struct {
|
||||
SeasonID int `json:"season_id"`
|
||||
SectionID int `json:"section_id"`
|
||||
ID int `json:"id"`
|
||||
Aid int `json:"aid"`
|
||||
Cid int `json:"cid"`
|
||||
Title string `json:"title"`
|
||||
Attribute int `json:"attribute"`
|
||||
Arc struct {
|
||||
Aid int `json:"aid"`
|
||||
Videos int `json:"videos"`
|
||||
TypeID int `json:"type_id"`
|
||||
TypeName string `json:"type_name"`
|
||||
Copyright int `json:"copyright"`
|
||||
Pic string `json:"pic"`
|
||||
Title string `json:"title"`
|
||||
Pubdate int `json:"pubdate"`
|
||||
Ctime int `json:"ctime"`
|
||||
Desc string `json:"desc"`
|
||||
State int `json:"state"`
|
||||
Duration int `json:"duration"`
|
||||
Rights struct {
|
||||
Bp int `json:"bp"`
|
||||
Elec int `json:"elec"`
|
||||
Download int `json:"download"`
|
||||
Movie int `json:"movie"`
|
||||
Pay int `json:"pay"`
|
||||
Hd5 int `json:"hd5"`
|
||||
NoReprint int `json:"no_reprint"`
|
||||
Autoplay int `json:"autoplay"`
|
||||
UgcPay int `json:"ugc_pay"`
|
||||
IsCooperation int `json:"is_cooperation"`
|
||||
UgcPayPreview int `json:"ugc_pay_preview"`
|
||||
ArcPay int `json:"arc_pay"`
|
||||
FreeWatch int `json:"free_watch"`
|
||||
} `json:"rights"`
|
||||
Author struct {
|
||||
Mid int `json:"mid"`
|
||||
Name string `json:"name"`
|
||||
Face string `json:"face"`
|
||||
} `json:"author"`
|
||||
Stat struct {
|
||||
Aid int `json:"aid"`
|
||||
View int `json:"view"`
|
||||
Danmaku int `json:"danmaku"`
|
||||
Reply int `json:"reply"`
|
||||
Fav int `json:"fav"`
|
||||
Coin int `json:"coin"`
|
||||
Share int `json:"share"`
|
||||
NowRank int `json:"now_rank"`
|
||||
HisRank int `json:"his_rank"`
|
||||
Like int `json:"like"`
|
||||
Dislike int `json:"dislike"`
|
||||
Evaluation string `json:"evaluation"`
|
||||
ArgueMsg string `json:"argue_msg"`
|
||||
Vt int `json:"vt"`
|
||||
Vv int `json:"vv"`
|
||||
} `json:"stat"`
|
||||
Dynamic string `json:"dynamic"`
|
||||
Dimension struct {
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Rotate int `json:"rotate"`
|
||||
} `json:"dimension"`
|
||||
DescV2 interface{} `json:"desc_v2"`
|
||||
IsChargeableSeason bool `json:"is_chargeable_season"`
|
||||
IsBlooper bool `json:"is_blooper"`
|
||||
EnableVt int `json:"enable_vt"`
|
||||
VtDisplay string `json:"vt_display"`
|
||||
} `json:"arc"`
|
||||
Page struct {
|
||||
Cid int `json:"cid"`
|
||||
Page int `json:"page"`
|
||||
From string `json:"from"`
|
||||
Part string `json:"part"`
|
||||
Duration int `json:"duration"`
|
||||
Vid string `json:"vid"`
|
||||
Weblink string `json:"weblink"`
|
||||
Dimension struct {
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Rotate int `json:"rotate"`
|
||||
} `json:"dimension"`
|
||||
} `json:"page"`
|
||||
Bvid string `json:"bvid"`
|
||||
} `json:"episodes"`
|
||||
} `json:"sections"`
|
||||
Stat struct {
|
||||
SeasonID int `json:"season_id"`
|
||||
View int `json:"view"`
|
||||
Danmaku int `json:"danmaku"`
|
||||
Reply int `json:"reply"`
|
||||
Fav int `json:"fav"`
|
||||
Coin int `json:"coin"`
|
||||
Share int `json:"share"`
|
||||
NowRank int `json:"now_rank"`
|
||||
HisRank int `json:"his_rank"`
|
||||
Like int `json:"like"`
|
||||
Vt int `json:"vt"`
|
||||
Vv int `json:"vv"`
|
||||
} `json:"stat"`
|
||||
EpCount int `json:"ep_count"`
|
||||
SeasonType int `json:"season_type"`
|
||||
IsPaySeason bool `json:"is_pay_season"`
|
||||
EnableVt int `json:"enable_vt"`
|
||||
} `json:"ugc_season"`
|
||||
IsSeasonDisplay bool `json:"is_season_display"`
|
||||
UserGarb struct {
|
||||
URLImageAniCut string `json:"url_image_ani_cut"`
|
||||
} `json:"user_garb"`
|
||||
HonorReply struct {
|
||||
Honor []struct {
|
||||
Aid int `json:"aid"`
|
||||
Type int `json:"type"`
|
||||
Desc string `json:"desc"`
|
||||
WeeklyRecommendNum int `json:"weekly_recommend_num"`
|
||||
} `json:"honor"`
|
||||
} `json:"honor_reply"`
|
||||
LikeIcon string `json:"like_icon"`
|
||||
NeedJumpBv bool `json:"need_jump_bv"`
|
||||
DisableShowUpInfo bool `json:"disable_show_up_info"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type videoInfo struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
TTL int `json:"ttl"`
|
||||
Data struct {
|
||||
From string `json:"from"`
|
||||
Result string `json:"result"`
|
||||
Message string `json:"message"`
|
||||
Quality uint `json:"quality"`
|
||||
Format string `json:"format"`
|
||||
Timelength int `json:"timelength"`
|
||||
AcceptFormat string `json:"accept_format"`
|
||||
AcceptDescription []string `json:"accept_description"`
|
||||
AcceptQuality []uint `json:"accept_quality"`
|
||||
VideoCodecid int `json:"video_codecid"`
|
||||
SeekParam string `json:"seek_param"`
|
||||
SeekType string `json:"seek_type"`
|
||||
Durl []struct {
|
||||
Order int `json:"order"`
|
||||
Length int `json:"length"`
|
||||
Size int `json:"size"`
|
||||
Ahead string `json:"ahead"`
|
||||
Vhead string `json:"vhead"`
|
||||
URL string `json:"url"`
|
||||
BackupURL interface{} `json:"backup_url"`
|
||||
} `json:"durl"`
|
||||
SupportFormats []struct {
|
||||
Quality int `json:"quality"`
|
||||
Format string `json:"format"`
|
||||
NewDescription string `json:"new_description"`
|
||||
DisplayDesc string `json:"display_desc"`
|
||||
Superscript string `json:"superscript"`
|
||||
Codecs interface{} `json:"codecs"`
|
||||
} `json:"support_formats"`
|
||||
HighFormat interface{} `json:"high_format"`
|
||||
LastPlayTime int `json:"last_play_time"`
|
||||
LastPlayCid int `json:"last_play_cid"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type playerV2Info struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
TTL int `json:"ttl"`
|
||||
Data struct {
|
||||
Aid int `json:"aid"`
|
||||
Bvid string `json:"bvid"`
|
||||
AllowBp bool `json:"allow_bp"`
|
||||
NoShare bool `json:"no_share"`
|
||||
Cid int `json:"cid"`
|
||||
MaxLimit int `json:"max_limit"`
|
||||
PageNo int `json:"page_no"`
|
||||
HasNext bool `json:"has_next"`
|
||||
IPInfo struct {
|
||||
IP string `json:"ip"`
|
||||
ZoneIP string `json:"zone_ip"`
|
||||
ZoneID int `json:"zone_id"`
|
||||
Country string `json:"country"`
|
||||
Province string `json:"province"`
|
||||
City string `json:"city"`
|
||||
} `json:"ip_info"`
|
||||
LoginMid int `json:"login_mid"`
|
||||
LoginMidHash string `json:"login_mid_hash"`
|
||||
IsOwner bool `json:"is_owner"`
|
||||
Name string `json:"name"`
|
||||
Permission string `json:"permission"`
|
||||
LevelInfo struct {
|
||||
CurrentLevel int `json:"current_level"`
|
||||
CurrentMin int `json:"current_min"`
|
||||
CurrentExp int `json:"current_exp"`
|
||||
NextExp int `json:"next_exp"`
|
||||
LevelUp int64 `json:"level_up"`
|
||||
} `json:"level_info"`
|
||||
Vip struct {
|
||||
Type int `json:"type"`
|
||||
Status int `json:"status"`
|
||||
DueDate int64 `json:"due_date"`
|
||||
VipPayType int `json:"vip_pay_type"`
|
||||
ThemeType int `json:"theme_type"`
|
||||
Label struct {
|
||||
Path string `json:"path"`
|
||||
Text string `json:"text"`
|
||||
LabelTheme string `json:"label_theme"`
|
||||
TextColor string `json:"text_color"`
|
||||
BgStyle int `json:"bg_style"`
|
||||
BgColor string `json:"bg_color"`
|
||||
BorderColor string `json:"border_color"`
|
||||
UseImgLabel bool `json:"use_img_label"`
|
||||
ImgLabelURIHans string `json:"img_label_uri_hans"`
|
||||
ImgLabelURIHant string `json:"img_label_uri_hant"`
|
||||
ImgLabelURIHansStatic string `json:"img_label_uri_hans_static"`
|
||||
ImgLabelURIHantStatic string `json:"img_label_uri_hant_static"`
|
||||
} `json:"label"`
|
||||
AvatarSubscript int `json:"avatar_subscript"`
|
||||
NicknameColor string `json:"nickname_color"`
|
||||
Role int `json:"role"`
|
||||
AvatarSubscriptURL string `json:"avatar_subscript_url"`
|
||||
TvVipStatus int `json:"tv_vip_status"`
|
||||
TvVipPayType int `json:"tv_vip_pay_type"`
|
||||
TvDueDate int `json:"tv_due_date"`
|
||||
} `json:"vip"`
|
||||
AnswerStatus int `json:"answer_status"`
|
||||
BlockTime int `json:"block_time"`
|
||||
Role string `json:"role"`
|
||||
LastPlayTime int `json:"last_play_time"`
|
||||
LastPlayCid int `json:"last_play_cid"`
|
||||
NowTime int `json:"now_time"`
|
||||
OnlineCount int `json:"online_count"`
|
||||
NeedLoginSubtitle bool `json:"need_login_subtitle"`
|
||||
Subtitle struct {
|
||||
AllowSubmit bool `json:"allow_submit"`
|
||||
Lan string `json:"lan"`
|
||||
LanDoc string `json:"lan_doc"`
|
||||
Subtitles []struct {
|
||||
ID int64 `json:"id"`
|
||||
Lan string `json:"lan"`
|
||||
LanDoc string `json:"lan_doc"`
|
||||
IsLock bool `json:"is_lock"`
|
||||
SubtitleURL string `json:"subtitle_url"`
|
||||
Type int `json:"type"`
|
||||
IDStr string `json:"id_str"`
|
||||
AiType int `json:"ai_type"`
|
||||
AiStatus int `json:"ai_status"`
|
||||
} `json:"subtitles"`
|
||||
} `json:"subtitle"`
|
||||
PlayerIcon struct {
|
||||
URL1 string `json:"url1"`
|
||||
Hash1 string `json:"hash1"`
|
||||
URL2 string `json:"url2"`
|
||||
Hash2 string `json:"hash2"`
|
||||
Ctime int `json:"ctime"`
|
||||
} `json:"player_icon"`
|
||||
ViewPoints []interface{} `json:"view_points"`
|
||||
IsUgcPayPreview bool `json:"is_ugc_pay_preview"`
|
||||
PreviewToast string `json:"preview_toast"`
|
||||
Options struct {
|
||||
Is360 bool `json:"is_360"`
|
||||
WithoutVip bool `json:"without_vip"`
|
||||
} `json:"options"`
|
||||
GuideAttention []interface{} `json:"guide_attention"`
|
||||
JumpCard []interface{} `json:"jump_card"`
|
||||
OperationCard []interface{} `json:"operation_card"`
|
||||
OnlineSwitch struct {
|
||||
EnableGrayDashPlayback string `json:"enable_gray_dash_playback"`
|
||||
NewBroadcast string `json:"new_broadcast"`
|
||||
RealtimeDm string `json:"realtime_dm"`
|
||||
SubtitleSubmitSwitch string `json:"subtitle_submit_switch"`
|
||||
} `json:"online_switch"`
|
||||
Fawkes struct {
|
||||
ConfigVersion int `json:"config_version"`
|
||||
FfVersion int `json:"ff_version"`
|
||||
} `json:"fawkes"`
|
||||
ShowSwitch struct {
|
||||
LongProgress bool `json:"long_progress"`
|
||||
} `json:"show_switch"`
|
||||
BgmInfo interface{} `json:"bgm_info"`
|
||||
ToastBlock bool `json:"toast_block"`
|
||||
IsUpowerExclusive bool `json:"is_upower_exclusive"`
|
||||
IsUpowerPlay bool `json:"is_upower_play"`
|
||||
ElecHighLevel struct {
|
||||
PrivilegeType int `json:"privilege_type"`
|
||||
LevelStr string `json:"level_str"`
|
||||
Title string `json:"title"`
|
||||
Intro string `json:"intro"`
|
||||
} `json:"elec_high_level"`
|
||||
DisableShowUpInfo bool `json:"disable_show_up_info"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type seasonInfo struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Result struct {
|
||||
Activity struct {
|
||||
HeadBgURL string `json:"head_bg_url"`
|
||||
ID int `json:"id"`
|
||||
Title string `json:"title"`
|
||||
} `json:"activity"`
|
||||
Actors string `json:"actors"`
|
||||
Alias string `json:"alias"`
|
||||
Areas []struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
} `json:"areas"`
|
||||
BkgCover string `json:"bkg_cover"`
|
||||
Cover string `json:"cover"`
|
||||
EnableVt bool `json:"enable_vt"`
|
||||
Episodes []struct {
|
||||
Aid int `json:"aid"`
|
||||
Badge string `json:"badge"`
|
||||
BadgeInfo struct {
|
||||
BgColor string `json:"bg_color"`
|
||||
BgColorNight string `json:"bg_color_night"`
|
||||
Text string `json:"text"`
|
||||
} `json:"badge_info"`
|
||||
BadgeType int `json:"badge_type"`
|
||||
Bvid string `json:"bvid"`
|
||||
Cid uint `json:"cid"`
|
||||
Cover string `json:"cover"`
|
||||
Dimension struct {
|
||||
Height int `json:"height"`
|
||||
Rotate int `json:"rotate"`
|
||||
Width int `json:"width"`
|
||||
} `json:"dimension"`
|
||||
Duration int `json:"duration"`
|
||||
EnableVt bool `json:"enable_vt"`
|
||||
EpID uint `json:"ep_id"`
|
||||
From string `json:"from"`
|
||||
ID int `json:"id"`
|
||||
IsViewHide bool `json:"is_view_hide"`
|
||||
Link string `json:"link"`
|
||||
LongTitle string `json:"long_title"`
|
||||
PubTime int `json:"pub_time"`
|
||||
Pv int `json:"pv"`
|
||||
ReleaseDate string `json:"release_date"`
|
||||
Rights struct {
|
||||
AllowDemand int `json:"allow_demand"`
|
||||
AllowDm int `json:"allow_dm"`
|
||||
AllowDownload int `json:"allow_download"`
|
||||
AreaLimit int `json:"area_limit"`
|
||||
} `json:"rights"`
|
||||
ShareCopy string `json:"share_copy"`
|
||||
ShareURL string `json:"share_url"`
|
||||
ShortLink string `json:"short_link"`
|
||||
ShowDrmLoginDialog bool `json:"showDrmLoginDialog"`
|
||||
Skip struct {
|
||||
Ed struct {
|
||||
End int `json:"end"`
|
||||
Start int `json:"start"`
|
||||
} `json:"ed"`
|
||||
Op struct {
|
||||
End int `json:"end"`
|
||||
Start int `json:"start"`
|
||||
} `json:"op"`
|
||||
} `json:"skip"`
|
||||
Status int `json:"status"`
|
||||
Subtitle string `json:"subtitle"`
|
||||
Title string `json:"title"`
|
||||
Vid string `json:"vid"`
|
||||
} `json:"episodes"`
|
||||
Evaluate string `json:"evaluate"`
|
||||
Freya struct {
|
||||
BubbleDesc string `json:"bubble_desc"`
|
||||
BubbleShowCnt int `json:"bubble_show_cnt"`
|
||||
IconShow int `json:"icon_show"`
|
||||
} `json:"freya"`
|
||||
IconFont struct {
|
||||
Name string `json:"name"`
|
||||
Text string `json:"text"`
|
||||
} `json:"icon_font"`
|
||||
JpTitle string `json:"jp_title"`
|
||||
Link string `json:"link"`
|
||||
MediaID int `json:"media_id"`
|
||||
Mode int `json:"mode"`
|
||||
NewEp struct {
|
||||
Desc string `json:"desc"`
|
||||
ID int `json:"id"`
|
||||
IsNew int `json:"is_new"`
|
||||
Title string `json:"title"`
|
||||
} `json:"new_ep"`
|
||||
Payment struct {
|
||||
Discount int `json:"discount"`
|
||||
PayType struct {
|
||||
AllowDiscount int `json:"allow_discount"`
|
||||
AllowPack int `json:"allow_pack"`
|
||||
AllowTicket int `json:"allow_ticket"`
|
||||
AllowTimeLimit int `json:"allow_time_limit"`
|
||||
AllowVipDiscount int `json:"allow_vip_discount"`
|
||||
ForbidBb int `json:"forbid_bb"`
|
||||
} `json:"pay_type"`
|
||||
Price string `json:"price"`
|
||||
Promotion string `json:"promotion"`
|
||||
Tip string `json:"tip"`
|
||||
ViewStartTime int `json:"view_start_time"`
|
||||
VipDiscount int `json:"vip_discount"`
|
||||
VipFirstPromotion string `json:"vip_first_promotion"`
|
||||
VipPrice string `json:"vip_price"`
|
||||
VipPromotion string `json:"vip_promotion"`
|
||||
} `json:"payment"`
|
||||
PlayStrategy struct {
|
||||
Strategies []string `json:"strategies"`
|
||||
} `json:"play_strategy"`
|
||||
Positive struct {
|
||||
ID int `json:"id"`
|
||||
Title string `json:"title"`
|
||||
} `json:"positive"`
|
||||
Publish struct {
|
||||
IsFinish int `json:"is_finish"`
|
||||
IsStarted int `json:"is_started"`
|
||||
PubTime string `json:"pub_time"`
|
||||
PubTimeShow string `json:"pub_time_show"`
|
||||
UnknowPubDate int `json:"unknow_pub_date"`
|
||||
Weekday int `json:"weekday"`
|
||||
} `json:"publish"`
|
||||
Rating struct {
|
||||
Count int `json:"count"`
|
||||
Score float64 `json:"score"`
|
||||
} `json:"rating"`
|
||||
Record string `json:"record"`
|
||||
Rights struct {
|
||||
AllowBp int `json:"allow_bp"`
|
||||
AllowBpRank int `json:"allow_bp_rank"`
|
||||
AllowDownload int `json:"allow_download"`
|
||||
AllowReview int `json:"allow_review"`
|
||||
AreaLimit int `json:"area_limit"`
|
||||
BanAreaShow int `json:"ban_area_show"`
|
||||
CanWatch int `json:"can_watch"`
|
||||
Copyright string `json:"copyright"`
|
||||
ForbidPre int `json:"forbid_pre"`
|
||||
FreyaWhite int `json:"freya_white"`
|
||||
IsCoverShow int `json:"is_cover_show"`
|
||||
IsPreview int `json:"is_preview"`
|
||||
OnlyVipDownload int `json:"only_vip_download"`
|
||||
Resource string `json:"resource"`
|
||||
WatchPlatform int `json:"watch_platform"`
|
||||
} `json:"rights"`
|
||||
SeasonID int `json:"season_id"`
|
||||
SeasonTitle string `json:"season_title"`
|
||||
Seasons []struct {
|
||||
Badge string `json:"badge"`
|
||||
BadgeInfo struct {
|
||||
BgColor string `json:"bg_color"`
|
||||
BgColorNight string `json:"bg_color_night"`
|
||||
Text string `json:"text"`
|
||||
} `json:"badge_info"`
|
||||
BadgeType int `json:"badge_type"`
|
||||
Cover string `json:"cover"`
|
||||
EnableVt bool `json:"enable_vt"`
|
||||
HorizontalCover1610 string `json:"horizontal_cover_1610"`
|
||||
HorizontalCover169 string `json:"horizontal_cover_169"`
|
||||
IconFont struct {
|
||||
Name string `json:"name"`
|
||||
Text string `json:"text"`
|
||||
} `json:"icon_font"`
|
||||
MediaID int `json:"media_id"`
|
||||
NewEp struct {
|
||||
Cover string `json:"cover"`
|
||||
ID int `json:"id"`
|
||||
IndexShow string `json:"index_show"`
|
||||
} `json:"new_ep"`
|
||||
SeasonID int `json:"season_id"`
|
||||
SeasonTitle string `json:"season_title"`
|
||||
SeasonType int `json:"season_type"`
|
||||
Stat struct {
|
||||
Favorites int `json:"favorites"`
|
||||
SeriesFollow int `json:"series_follow"`
|
||||
Views int `json:"views"`
|
||||
Vt int `json:"vt"`
|
||||
} `json:"stat"`
|
||||
} `json:"seasons"`
|
||||
Section []struct {
|
||||
Attr int `json:"attr"`
|
||||
EpisodeID int `json:"episode_id"`
|
||||
EpisodeIds []interface{} `json:"episode_ids"`
|
||||
Episodes []struct {
|
||||
Aid int `json:"aid"`
|
||||
Badge string `json:"badge"`
|
||||
BadgeInfo struct {
|
||||
BgColor string `json:"bg_color"`
|
||||
BgColorNight string `json:"bg_color_night"`
|
||||
Text string `json:"text"`
|
||||
} `json:"badge_info"`
|
||||
BadgeType int `json:"badge_type"`
|
||||
Bvid string `json:"bvid"`
|
||||
Cid int `json:"cid"`
|
||||
Cover string `json:"cover"`
|
||||
Dimension struct {
|
||||
Height int `json:"height"`
|
||||
Rotate int `json:"rotate"`
|
||||
Width int `json:"width"`
|
||||
} `json:"dimension"`
|
||||
Duration int `json:"duration"`
|
||||
EnableVt bool `json:"enable_vt"`
|
||||
EpID int `json:"ep_id"`
|
||||
From string `json:"from"`
|
||||
IconFont struct {
|
||||
Name string `json:"name"`
|
||||
Text string `json:"text"`
|
||||
} `json:"icon_font"`
|
||||
ID int `json:"id"`
|
||||
IsViewHide bool `json:"is_view_hide"`
|
||||
Link string `json:"link"`
|
||||
LongTitle string `json:"long_title"`
|
||||
PubTime int `json:"pub_time"`
|
||||
Pv int `json:"pv"`
|
||||
ReleaseDate string `json:"release_date"`
|
||||
Rights struct {
|
||||
AllowDemand int `json:"allow_demand"`
|
||||
AllowDm int `json:"allow_dm"`
|
||||
AllowDownload int `json:"allow_download"`
|
||||
AreaLimit int `json:"area_limit"`
|
||||
} `json:"rights"`
|
||||
ShareCopy string `json:"share_copy"`
|
||||
ShareURL string `json:"share_url"`
|
||||
ShortLink string `json:"short_link"`
|
||||
ShowDrmLoginDialog bool `json:"showDrmLoginDialog"`
|
||||
Skip struct {
|
||||
Ed struct {
|
||||
End int `json:"end"`
|
||||
Start int `json:"start"`
|
||||
} `json:"ed"`
|
||||
Op struct {
|
||||
End int `json:"end"`
|
||||
Start int `json:"start"`
|
||||
} `json:"op"`
|
||||
} `json:"skip"`
|
||||
Stat struct {
|
||||
Coin int `json:"coin"`
|
||||
Danmakus int `json:"danmakus"`
|
||||
Likes int `json:"likes"`
|
||||
Play int `json:"play"`
|
||||
Reply int `json:"reply"`
|
||||
Vt int `json:"vt"`
|
||||
} `json:"stat"`
|
||||
StatForUnity struct {
|
||||
Coin int `json:"coin"`
|
||||
Danmaku struct {
|
||||
Icon string `json:"icon"`
|
||||
PureText string `json:"pure_text"`
|
||||
Text string `json:"text"`
|
||||
Value int `json:"value"`
|
||||
} `json:"danmaku"`
|
||||
Likes int `json:"likes"`
|
||||
Reply int `json:"reply"`
|
||||
Vt struct {
|
||||
Icon string `json:"icon"`
|
||||
PureText string `json:"pure_text"`
|
||||
Text string `json:"text"`
|
||||
Value int `json:"value"`
|
||||
} `json:"vt"`
|
||||
} `json:"stat_for_unity"`
|
||||
Status int `json:"status"`
|
||||
Subtitle string `json:"subtitle"`
|
||||
Title string `json:"title"`
|
||||
Vid string `json:"vid"`
|
||||
} `json:"episodes"`
|
||||
ID int `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Type int `json:"type"`
|
||||
Type2 int `json:"type2"`
|
||||
} `json:"section"`
|
||||
Series struct {
|
||||
DisplayType int `json:"display_type"`
|
||||
SeriesID int `json:"series_id"`
|
||||
SeriesTitle string `json:"series_title"`
|
||||
} `json:"series"`
|
||||
ShareCopy string `json:"share_copy"`
|
||||
ShareSubTitle string `json:"share_sub_title"`
|
||||
ShareURL string `json:"share_url"`
|
||||
Show struct {
|
||||
WideScreen int `json:"wide_screen"`
|
||||
} `json:"show"`
|
||||
ShowSeasonType int `json:"show_season_type"`
|
||||
SquareCover string `json:"square_cover"`
|
||||
Staff string `json:"staff"`
|
||||
Stat struct {
|
||||
Coins int `json:"coins"`
|
||||
Danmakus int `json:"danmakus"`
|
||||
Favorite int `json:"favorite"`
|
||||
Favorites int `json:"favorites"`
|
||||
FollowText string `json:"follow_text"`
|
||||
Likes int `json:"likes"`
|
||||
Reply int `json:"reply"`
|
||||
Share int `json:"share"`
|
||||
Views int `json:"views"`
|
||||
Vt int `json:"vt"`
|
||||
} `json:"stat"`
|
||||
Status int `json:"status"`
|
||||
Styles []string `json:"styles"`
|
||||
Subtitle string `json:"subtitle"`
|
||||
Title string `json:"title"`
|
||||
Total int `json:"total"`
|
||||
Type int `json:"type"`
|
||||
UpInfo struct {
|
||||
Avatar string `json:"avatar"`
|
||||
AvatarSubscriptURL string `json:"avatar_subscript_url"`
|
||||
Follower int `json:"follower"`
|
||||
IsFollow int `json:"is_follow"`
|
||||
Mid int `json:"mid"`
|
||||
NicknameColor string `json:"nickname_color"`
|
||||
Pendant struct {
|
||||
Image string `json:"image"`
|
||||
Name string `json:"name"`
|
||||
Pid int `json:"pid"`
|
||||
} `json:"pendant"`
|
||||
ThemeType int `json:"theme_type"`
|
||||
Uname string `json:"uname"`
|
||||
VerifyType int `json:"verify_type"`
|
||||
VipLabel struct {
|
||||
BgColor string `json:"bg_color"`
|
||||
BgStyle int `json:"bg_style"`
|
||||
BorderColor string `json:"border_color"`
|
||||
Text string `json:"text"`
|
||||
TextColor string `json:"text_color"`
|
||||
} `json:"vip_label"`
|
||||
VipStatus int `json:"vip_status"`
|
||||
VipType int `json:"vip_type"`
|
||||
} `json:"up_info"`
|
||||
UserStatus struct {
|
||||
AreaLimit int `json:"area_limit"`
|
||||
BanAreaShow int `json:"ban_area_show"`
|
||||
Follow int `json:"follow"`
|
||||
FollowStatus int `json:"follow_status"`
|
||||
Login int `json:"login"`
|
||||
Pay int `json:"pay"`
|
||||
PayPackPaid int `json:"pay_pack_paid"`
|
||||
Sponsor int `json:"sponsor"`
|
||||
} `json:"user_status"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
type pgcURLInfo struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Result struct {
|
||||
AcceptFormat string `json:"accept_format"`
|
||||
Code int `json:"code"`
|
||||
SeekParam string `json:"seek_param"`
|
||||
IsPreview int `json:"is_preview"`
|
||||
Fnval int `json:"fnval"`
|
||||
VideoProject bool `json:"video_project"`
|
||||
Fnver int `json:"fnver"`
|
||||
Type string `json:"type"`
|
||||
Bp int `json:"bp"`
|
||||
Result string `json:"result"`
|
||||
SeekType string `json:"seek_type"`
|
||||
From string `json:"from"`
|
||||
VideoCodecid int `json:"video_codecid"`
|
||||
RecordInfo struct {
|
||||
RecordIcon string `json:"record_icon"`
|
||||
Record string `json:"record"`
|
||||
} `json:"record_info"`
|
||||
Durl []struct {
|
||||
Size int `json:"size"`
|
||||
Ahead string `json:"ahead"`
|
||||
Length int `json:"length"`
|
||||
Vhead string `json:"vhead"`
|
||||
BackupURL []string `json:"backup_url"`
|
||||
URL string `json:"url"`
|
||||
Order int `json:"order"`
|
||||
Md5 string `json:"md5"`
|
||||
} `json:"durl"`
|
||||
IsDrm bool `json:"is_drm"`
|
||||
NoRexcode int `json:"no_rexcode"`
|
||||
Format string `json:"format"`
|
||||
SupportFormats []struct {
|
||||
DisplayDesc string `json:"display_desc"`
|
||||
Superscript string `json:"superscript"`
|
||||
NeedLogin bool `json:"need_login,omitempty"`
|
||||
Codecs []interface{} `json:"codecs"`
|
||||
Format string `json:"format"`
|
||||
Description string `json:"description"`
|
||||
Quality int `json:"quality"`
|
||||
NewDescription string `json:"new_description"`
|
||||
} `json:"support_formats"`
|
||||
Message string `json:"message"`
|
||||
AcceptQuality []uint `json:"accept_quality"`
|
||||
Quality uint `json:"quality"`
|
||||
Timelength int `json:"timelength"`
|
||||
HasPaid bool `json:"has_paid"`
|
||||
ClipInfoList []interface{} `json:"clip_info_list"`
|
||||
AcceptDescription []string `json:"accept_description"`
|
||||
Status int `json:"status"`
|
||||
} `json:"result"`
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package bilibili_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/synctv-org/synctv/vendors/bilibili"
|
||||
)
|
||||
|
||||
func TestMatch(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
url string
|
||||
wantT string
|
||||
wantID string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "bv",
|
||||
url: "https://www.bilibili.com/video/BV1i5411y7fB",
|
||||
wantT: "bv",
|
||||
wantID: "BV1i5411y7fB",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "av",
|
||||
url: "https://www.bilibili.com/video/av1",
|
||||
wantT: "av",
|
||||
wantID: "1",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "ss",
|
||||
url: "https://www.bilibili.com/bangumi/play/ss1",
|
||||
wantT: "ss",
|
||||
wantID: "1",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "ep",
|
||||
url: "https://www.bilibili.com/bangumi/play/ep1",
|
||||
wantT: "ep",
|
||||
wantID: "1",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
gotT, gotID, err := bilibili.Match(tt.url)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Match() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if gotT != tt.wantT {
|
||||
t.Errorf("Match() gotT = %v, want %v", gotT, tt.wantT)
|
||||
}
|
||||
if gotID != tt.wantID {
|
||||
t.Errorf("Match() gotID = %v, want %v", gotID, tt.wantID)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package bilibili
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/synctv-org/synctv/utils"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
httpClient *http.Client
|
||||
cookies []*http.Cookie
|
||||
}
|
||||
|
||||
type ClientConfig func(*Client)
|
||||
|
||||
func WithHttpClient(httpClient *http.Client) ClientConfig {
|
||||
return func(c *Client) {
|
||||
c.httpClient = httpClient
|
||||
}
|
||||
}
|
||||
|
||||
func NewClient(cookies []*http.Cookie, conf ...ClientConfig) *Client {
|
||||
c := &Client{
|
||||
httpClient: http.DefaultClient,
|
||||
cookies: cookies,
|
||||
}
|
||||
for _, v := range conf {
|
||||
v(c)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) NewRequest(method, url string, body io.Reader) (*http.Request, error) {
|
||||
req, err := http.NewRequest(method, url, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, cookie := range c.cookies {
|
||||
req.AddCookie(cookie)
|
||||
}
|
||||
req.Header.Set("User-Agent", utils.UA)
|
||||
req.Header.Set("Referer", "https://www.bilibili.com/")
|
||||
return req, nil
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package bilibili
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
json "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
type RQCode struct {
|
||||
URL string `json:"url"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
func NewQRCode() (*RQCode, error) {
|
||||
resp, err := http.Get("https://passport.bilibili.com/x/passport-login/web/qrcode/generate")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
qr := qrcodeResp{}
|
||||
err = json.NewDecoder(resp.Body).Decode(&qr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: error message
|
||||
return &RQCode{
|
||||
URL: qr.Data.URL,
|
||||
Key: qr.Data.QrcodeKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// return SESSDATA cookie
|
||||
func Login(key string) (*http.Cookie, error) {
|
||||
resp, err := http.Get(fmt.Sprintf("https://passport.bilibili.com/x/passport-login/web/qrcode/poll?qrcode_key=%s", key))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
for _, cookie := range resp.Cookies() {
|
||||
if cookie.Name == "SESSDATA" {
|
||||
return cookie, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("no SESSDATA cookie")
|
||||
}
|
|
@ -0,0 +1,252 @@
|
|||
package bilibili
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
json "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
type VideoPageInfo struct {
|
||||
Bvid string `json:"bvid"`
|
||||
Title string `json:"title"`
|
||||
CoverImage string `json:"coverImage"`
|
||||
VideoInfos []*VideoInfo `json:"videoInfos"`
|
||||
}
|
||||
|
||||
type VideoInfo struct {
|
||||
Cid int `json:"cid"`
|
||||
// 分P
|
||||
Name string `json:"name"`
|
||||
FirstFrame string `json:"firstFrame"`
|
||||
}
|
||||
|
||||
func (c *Client) ParseVideoPage(aid uint, bvid string) (*VideoPageInfo, error) {
|
||||
var url string
|
||||
if aid != 0 {
|
||||
url = fmt.Sprintf("https://api.bilibili.com/x/web-interface/view?aid=%d", aid)
|
||||
} else if bvid != "" {
|
||||
url = fmt.Sprintf("https://api.bilibili.com/x/web-interface/view?bvid=%s", bvid)
|
||||
} else {
|
||||
return nil, fmt.Errorf("aid and bvid are both empty")
|
||||
}
|
||||
req, err := c.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
info := videoPageInfo{}
|
||||
err = json.NewDecoder(resp.Body).Decode(&info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: error message
|
||||
r := &VideoPageInfo{
|
||||
Bvid: info.Data.Bvid,
|
||||
Title: info.Data.Title,
|
||||
CoverImage: info.Data.Pic,
|
||||
}
|
||||
r.VideoInfos = make([]*VideoInfo, 0, len(info.Data.Pages))
|
||||
for _, page := range info.Data.Pages {
|
||||
r.VideoInfos = append(r.VideoInfos, &VideoInfo{
|
||||
Cid: page.Cid,
|
||||
Name: page.Part,
|
||||
FirstFrame: page.FirstFrame,
|
||||
})
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
const (
|
||||
Q240P uint = 6
|
||||
Q360P uint = 16
|
||||
Q480P uint = 32
|
||||
Q720P uint = 64
|
||||
Q1080P uint = 80
|
||||
Q1080PP uint = 112
|
||||
Q1080P60 uint = 116
|
||||
Q4K uint = 120
|
||||
QHDR uint = 124
|
||||
QDOLBY uint = 126
|
||||
Q8K uint = 127
|
||||
)
|
||||
|
||||
type VideoURL struct {
|
||||
AcceptDescription []string `json:"acceptDescription"`
|
||||
AcceptQuality []uint `json:"acceptQuality"`
|
||||
CurrentQuality uint `json:"currentQuality"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type GetVideoURLConf struct {
|
||||
Quality uint
|
||||
}
|
||||
|
||||
type GetVideoURLConfig func(*GetVideoURLConf)
|
||||
|
||||
// https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/video/videostream_url.md
|
||||
func (c *Client) GetVideoURL(aid uint, bvid string, cid uint, conf ...GetVideoURLConfig) (*VideoURL, error) {
|
||||
config := &GetVideoURLConf{
|
||||
Quality: Q1080PP,
|
||||
}
|
||||
for _, v := range conf {
|
||||
v(config)
|
||||
}
|
||||
var url string
|
||||
if aid != 0 {
|
||||
url = fmt.Sprintf("https://api.bilibili.com/x/player/wbi/playurl?aid=%d&cid=%d&qn=%d&platform=html5&high_quality=1", aid, cid, config.Quality)
|
||||
} else if bvid != "" {
|
||||
url = fmt.Sprintf("https://api.bilibili.com/x/player/wbi/playurl?bvid=%s&cid=%d&qn=%d&platform=html5&high_quality=1", bvid, cid, config.Quality)
|
||||
} else {
|
||||
return nil, fmt.Errorf("aid and bvid are both empty")
|
||||
}
|
||||
req, err := c.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
info := videoInfo{}
|
||||
err = json.NewDecoder(resp.Body).Decode(&info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &VideoURL{
|
||||
AcceptDescription: info.Data.AcceptDescription,
|
||||
AcceptQuality: info.Data.AcceptQuality,
|
||||
CurrentQuality: info.Data.Quality,
|
||||
URL: info.Data.Durl[0].URL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type Subtitle struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
func (c *Client) GetSubtitles(aid uint, bvid string, cid uint) ([]*Subtitle, error) {
|
||||
var url string
|
||||
if aid != 0 {
|
||||
url = fmt.Sprintf("https://api.bilibili.com/x/player/v2?aid=%d&cid=%d", aid, cid)
|
||||
} else if bvid != "" {
|
||||
url = fmt.Sprintf("https://api.bilibili.com/x/player/v2?bvid=%s&cid=%d", bvid, cid)
|
||||
} else {
|
||||
return nil, fmt.Errorf("aid and bvid are both empty")
|
||||
}
|
||||
req, err := c.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
info := playerV2Info{}
|
||||
err = json.NewDecoder(resp.Body).Decode(&info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := make([]*Subtitle, len(info.Data.Subtitle.Subtitles))
|
||||
for i, s := range info.Data.Subtitle.Subtitles {
|
||||
r[i] = &Subtitle{
|
||||
Name: s.LanDoc,
|
||||
URL: s.SubtitleURL,
|
||||
}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
type PGCPageInfo struct {
|
||||
Actors string `json:"actors"`
|
||||
CoverImage string `json:"coverImage"`
|
||||
PGCInfos []*PGCInfo `json:"pgcInfos"`
|
||||
}
|
||||
|
||||
type PGCInfo struct {
|
||||
EpId uint `json:"epId"`
|
||||
Cid uint `json:"cid"`
|
||||
Name string `json:"name"`
|
||||
CoverImage string `json:"coverImage"`
|
||||
}
|
||||
|
||||
func (c *Client) ParsePGCPage(epId, season_id uint) (*PGCPageInfo, error) {
|
||||
var url string
|
||||
if epId != 0 {
|
||||
url = fmt.Sprintf("https://api.bilibili.com/pgc/view/web/season?ep_id=%d", epId)
|
||||
} else if season_id != 0 {
|
||||
url = fmt.Sprintf("https://api.bilibili.com/pgc/view/web/season?season_id=%d", season_id)
|
||||
} else {
|
||||
return nil, fmt.Errorf("edId and season_id are both empty")
|
||||
}
|
||||
|
||||
req, err := c.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
info := seasonInfo{}
|
||||
err = json.NewDecoder(resp.Body).Decode(&info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := &PGCPageInfo{
|
||||
Actors: info.Result.Actors,
|
||||
CoverImage: info.Result.Cover,
|
||||
PGCInfos: make([]*PGCInfo, len(info.Result.Episodes)),
|
||||
}
|
||||
|
||||
for i, v := range info.Result.Episodes {
|
||||
r.PGCInfos[i] = &PGCInfo{
|
||||
EpId: v.EpID,
|
||||
Cid: v.Cid,
|
||||
Name: v.ShareCopy,
|
||||
CoverImage: v.Cover,
|
||||
}
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetPGCURL(ep_id, cid uint, conf ...GetVideoURLConfig) (*VideoURL, error) {
|
||||
config := &GetVideoURLConf{
|
||||
Quality: Q1080PP,
|
||||
}
|
||||
for _, v := range conf {
|
||||
v(config)
|
||||
}
|
||||
url := fmt.Sprintf("https://api.bilibili.com/pgc/player/web/playurl?ep_id=%d&cid=%d&qn=%d&fourk=1", ep_id, cid, config.Quality)
|
||||
req, err := c.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
info := pgcURLInfo{}
|
||||
err = json.NewDecoder(resp.Body).Decode(&info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &VideoURL{
|
||||
AcceptDescription: info.Result.AcceptDescription,
|
||||
AcceptQuality: info.Result.AcceptQuality,
|
||||
CurrentQuality: info.Result.Quality,
|
||||
URL: info.Result.Durl[0].URL,
|
||||
}, nil
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package bilibili
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var (
|
||||
BVRegex = regexp.MustCompile(`(?:https://www.bilibili.com/video/)?((?:bv|bV|Bv|BV)\w+)(?:/(\?.*)?)?$`)
|
||||
ARegex = regexp.MustCompile(`(?:https://www.bilibili.com/video/)?(?:av|aV|Av|AV)(\d+)(?:/(\?.*)?)?$`)
|
||||
SSRegex = regexp.MustCompile(`(?:https://www.bilibili.com/bangumi/play/)?(?:ss|sS|Ss|SS)(\d+)(?:\?.*)?$`)
|
||||
EPRegex = regexp.MustCompile(`(?:https://www.bilibili.com/bangumi/play/)?(?:ep|eP|Ep|EP)(\d+)(?:\?.*)?$`)
|
||||
)
|
||||
|
||||
func Match(url string) (t string, id string, err error) {
|
||||
if m := BVRegex.FindStringSubmatch(url); m != nil {
|
||||
return "bv", m[1], nil
|
||||
}
|
||||
if m := ARegex.FindStringSubmatch(url); m != nil {
|
||||
return "av", m[1], nil
|
||||
}
|
||||
if m := SSRegex.FindStringSubmatch(url); m != nil {
|
||||
return "ss", m[1], nil
|
||||
}
|
||||
if m := EPRegex.FindStringSubmatch(url); m != nil {
|
||||
return "ep", m[1], nil
|
||||
}
|
||||
return "", "", errors.New("match failed")
|
||||
}
|
Loading…
Reference in New Issue