From 16acae8bf590bf9101c6d2bd924000b5ee2238f9 Mon Sep 17 00:00:00 2001 From: zijiren233 Date: Wed, 1 Nov 2023 22:17:41 +0800 Subject: [PATCH] Feat: push movies --- internal/model/movie.go | 108 +++++++++++++++++++++++++++++++++++++++ internal/op/movie.go | 87 +------------------------------ internal/op/room.go | 4 +- internal/op/user.go | 14 +++-- server/handlers/init.go | 2 + server/handlers/movie.go | 45 +++++++++++++++- server/model/movie.go | 15 ++++++ 7 files changed, 183 insertions(+), 92 deletions(-) diff --git a/internal/model/movie.go b/internal/model/movie.go index b8efba7..a850312 100644 --- a/internal/model/movie.go +++ b/internal/model/movie.go @@ -1,9 +1,14 @@ package model import ( + "errors" + "fmt" + "net/url" "time" "github.com/google/uuid" + "github.com/synctv-org/synctv/internal/conf" + "github.com/synctv-org/synctv/utils" "gorm.io/gorm" ) @@ -24,6 +29,10 @@ func (m *Movie) BeforeCreate(tx *gorm.DB) error { return nil } +func (m *Movie) Validate() error { + return m.Base.Validate() +} + type BaseMovie struct { Url string `json:"url"` Name string `gorm:"not null" json:"name"` @@ -35,6 +44,105 @@ type BaseMovie struct { VendorInfo `gorm:"embedded;embeddedPrefix:vendor_info_" json:"vendorInfo,omitempty"` } +func (m *BaseMovie) Validate() error { + if m.VendorInfo.Vendor != "" { + err := m.validateVendorMovie() + if err != nil { + return err + } + } + switch { + case m.RtmpSource && m.Proxy: + return errors.New("rtmp source and proxy can't be true at the same time") + case m.Live && m.RtmpSource: + if !conf.Conf.Rtmp.Enable { + return errors.New("rtmp is not enabled") + } + case m.Live && m.Proxy: + if !conf.Conf.Proxy.LiveProxy { + return errors.New("live proxy is not enabled") + } + u, err := url.Parse(m.Url) + if err != nil { + return err + } + if utils.IsLocalIP(u.Host) { + return errors.New("local ip is not allowed") + } + switch u.Scheme { + case "rtmp": + case "http", "https": + default: + return errors.New("unsupported scheme") + } + case !m.Live && m.RtmpSource: + return errors.New("rtmp source can't be true when movie is not live") + case !m.Live && m.Proxy: + if !conf.Conf.Proxy.MovieProxy { + return errors.New("movie proxy is not enabled") + } + if m.VendorInfo.Vendor != "" { + return nil + } + u, err := url.Parse(m.Url) + if err != nil { + return err + } + if utils.IsLocalIP(u.Host) { + return errors.New("local ip is not allowed") + } + if u.Scheme != "http" && u.Scheme != "https" { + return errors.New("unsupported scheme") + } + case !m.Live && !m.Proxy, m.Live && !m.Proxy && !m.RtmpSource: + if m.VendorInfo.Vendor == "" { + u, err := url.Parse(m.Url) + if err != nil { + return err + } + if u.Scheme != "http" && u.Scheme != "https" { + return errors.New("unsupported scheme") + } + } + default: + return errors.New("unknown error") + } + return nil +} + +func (m *BaseMovie) validateVendorMovie() error { + switch m.VendorInfo.Vendor { + case StreamingVendorBilibili: + info := m.VendorInfo.Bilibili + if info.Bvid == "" && info.Epid == 0 { + return fmt.Errorf("bvid and epid are empty") + } + + if info.Bvid != "" && info.Epid != 0 { + return fmt.Errorf("bvid and epid can't be set at the same time") + } + + if info.Bvid != "" && info.Cid == 0 { + return fmt.Errorf("cid is empty") + } + + if m.Headers == nil { + m.Headers = map[string]string{ + "Referer": "https://www.bilibili.com", + "User-Agent": utils.UA, + } + } else { + m.Headers["Referer"] = "https://www.bilibili.com" + m.Headers["User-Agent"] = utils.UA + } + + default: + return fmt.Errorf("vendor not support") + } + + return nil +} + type VendorInfo struct { Vendor StreamingVendor `json:"vendor"` Shared bool `gorm:"not null;default:false" json:"shared"` diff --git a/internal/op/movie.go b/internal/op/movie.go index 040611d..1bb24dc 100644 --- a/internal/op/movie.go +++ b/internal/op/movie.go @@ -2,15 +2,12 @@ package op import ( "errors" - "fmt" "net/url" "sync" "time" "github.com/go-resty/resty/v2" - "github.com/synctv-org/synctv/internal/conf" "github.com/synctv-org/synctv/internal/model" - "github.com/synctv-org/synctv/utils" "github.com/zijiren233/livelib/av" "github.com/zijiren233/livelib/container/flv" rtmpProto "github.com/zijiren233/livelib/protocol/rtmp" @@ -31,32 +28,20 @@ func (m *movie) Channel() (*rtmps.Channel, error) { } func (m *movie) init() (err error) { - err = m.validateVendorMovie() - if err != nil { + if err = m.Movie.Validate(); err != nil { return } switch { - case m.Base.RtmpSource && m.Base.Proxy: - return errors.New("rtmp source and proxy can't be true at the same time") case m.Base.Live && m.Base.RtmpSource: - if !conf.Conf.Rtmp.Enable { - return errors.New("rtmp is not enabled") - } if m.channel == nil { m.channel = rtmps.NewChannel() m.channel.InitHlsPlayer() } case m.Base.Live && m.Base.Proxy: - if !conf.Conf.Proxy.LiveProxy { - return errors.New("live proxy is not enabled") - } u, err := url.Parse(m.Base.Url) if err != nil { return err } - if utils.IsLocalIP(u.Host) { - return errors.New("local ip is not allowed") - } switch u.Scheme { case "rtmp": if m.channel == nil { @@ -81,9 +66,6 @@ func (m *movie) init() (err error) { }() } case "http", "https": - if m.Base.Type != "flv" { - return errors.New("only flv is supported") - } if m.channel == nil { m.channel = rtmps.NewChannel() m.channel.InitHlsPlayer() @@ -112,77 +94,10 @@ func (m *movie) init() (err error) { default: return errors.New("unsupported scheme") } - case !m.Base.Live && m.Base.RtmpSource: - return errors.New("rtmp source can't be true when movie is not live") - case !m.Base.Live && m.Base.Proxy: - if !conf.Conf.Proxy.MovieProxy { - return errors.New("movie proxy is not enabled") - } - if m.Base.VendorInfo.Vendor != "" { - return nil - } - u, err := url.Parse(m.Base.Url) - if err != nil { - return err - } - if utils.IsLocalIP(u.Host) { - return errors.New("local ip is not allowed") - } - if u.Scheme != "http" && u.Scheme != "https" { - return errors.New("unsupported scheme") - } - case !m.Base.Live && !m.Base.Proxy, m.Base.Live && !m.Base.Proxy && !m.Base.RtmpSource: - 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") - } - } - default: - return errors.New("unknown error") } return nil } -func (m *movie) validateVendorMovie() error { - if m.Base.VendorInfo.Vendor == "" { - return nil - } - switch m.Base.VendorInfo.Vendor { - case model.StreamingVendorBilibili: - info := m.Base.VendorInfo.Bilibili - if info.Bvid == "" && info.Epid == 0 { - return fmt.Errorf("bvid and epid are empty") - } - - if info.Bvid != "" && info.Epid != 0 { - return fmt.Errorf("bvid and epid can't be set at the same time") - } - - if info.Bvid != "" && info.Cid == 0 { - return fmt.Errorf("cid is empty") - } - - if m.Base.Headers == nil { - m.Base.Headers = map[string]string{ - "Referer": "https://www.bilibili.com", - "User-Agent": utils.UA, - } - } else { - m.Base.Headers["Referer"] = "https://www.bilibili.com" - m.Base.Headers["User-Agent"] = utils.UA - } - - default: - return fmt.Errorf("vendor not support") - } - - return nil -} - func (m *movie) Terminate() { m.lock.Lock() defer m.lock.Unlock() diff --git a/internal/op/room.go b/internal/op/room.go index 443422a..ad9a7b7 100644 --- a/internal/op/room.go +++ b/internal/op/room.go @@ -73,9 +73,9 @@ func (r *Room) UpdateMovie(movieId string, movie model.BaseMovie) error { return r.movies.Update(movieId, movie) } -func (r *Room) AddMovie(m model.Movie) error { +func (r *Room) AddMovie(m *model.Movie) error { m.RoomID = r.ID - return r.movies.Add(&m) + return r.movies.Add(m) } func (r *Room) HasPermission(userID string, permission model.Permission) bool { diff --git a/internal/op/user.go b/internal/op/user.go index 514baf9..2a2a496 100644 --- a/internal/op/user.go +++ b/internal/op/user.go @@ -24,13 +24,21 @@ 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.BaseMovie) model.Movie { - return model.Movie{ - Base: movie, +func (u *User) NewMovie(movie *model.BaseMovie) *model.Movie { + return &model.Movie{ + Base: *movie, CreatorID: u.ID, } } +func (u *User) NewMovies(movies []*model.BaseMovie) []*model.Movie { + var ms = make([]*model.Movie, 0, len(movies)) + for i, m := range movies { + ms[i] = u.NewMovie(m) + } + return ms +} + func (u *User) IsRoot() bool { return u.Role == model.RoleRoot } diff --git a/server/handlers/init.go b/server/handlers/init.go index 2a81f72..f07d9b8 100644 --- a/server/handlers/init.go +++ b/server/handlers/init.go @@ -107,6 +107,8 @@ func Init(e *gin.Engine) { needAuthMovie.POST("/push", PushMovie) + needAuthMovie.POST("/pushs", PushMovies) + needAuthMovie.POST("/edit", EditMovie) needAuthMovie.POST("/swap", SwapMovie) diff --git a/server/handlers/movie.go b/server/handlers/movie.go index 6b8c0f6..ad48338 100644 --- a/server/handlers/movie.go +++ b/server/handlers/movie.go @@ -166,7 +166,7 @@ func PushMovie(ctx *gin.Context) { return } - mi := user.NewMovie(dbModel.BaseMovie(req)) + mi := user.NewMovie((*dbModel.BaseMovie)(&req)) err := room.AddMovie(mi) if err != nil { @@ -187,6 +187,49 @@ func PushMovie(ctx *gin.Context) { ctx.Status(http.StatusNoContent) } +func PushMovies(ctx *gin.Context) { + room := ctx.MustGet("room").(*op.Room) + user := ctx.MustGet("user").(*op.User) + + req := model.PushMoviesReq{} + if err := model.Decode(ctx, &req); err != nil { + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) + return + } + + var ms []*dbModel.Movie = make([]*dbModel.Movie, len(req)) + + for i, v := range req { + m := (*dbModel.BaseMovie)(v) + err := m.Validate() + if err != nil { + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) + return + } + ms[i] = user.NewMovie(m) + } + + for _, m := range ms { + err := room.AddMovie(m) + if err != nil { + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) + return + } + } + + if err := room.Broadcast(&op.ElementMessage{ + ElementMessage: &pb.ElementMessage{ + Type: pb.ElementMessageType_CHANGE_MOVIES, + Sender: user.Username, + }, + }); err != nil { + ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) + return + } + + ctx.Status(http.StatusNoContent) +} + func NewPublishKey(ctx *gin.Context) { room := ctx.MustGet("room").(*op.Room) user := ctx.MustGet("user").(*op.User) diff --git a/server/model/movie.go b/server/model/movie.go index 1cd32d9..e9503ec 100644 --- a/server/model/movie.go +++ b/server/model/movie.go @@ -44,6 +44,21 @@ func (p *PushMovieReq) Validate() error { return nil } +type PushMoviesReq []*PushMovieReq + +func (p *PushMoviesReq) Decode(ctx *gin.Context) error { + return json.NewDecoder(ctx.Request.Body).Decode(p) +} + +func (p *PushMoviesReq) Validate() error { + for _, v := range *p { + if err := v.Validate(); err != nil { + return err + } + } + return nil +} + type IdReq struct { Id string `json:"id"` }