Opt: vendor

pull/24/head
zijiren233 2 years ago
parent b80e98b695
commit 69f1425d2c

@ -25,18 +25,25 @@ func (m *Movie) BeforeCreate(tx *gorm.DB) error {
} }
type BaseMovie struct { type BaseMovie struct {
Url string `json:"url"` Url string `json:"url,omitempty"`
Name string `gorm:"not null" json:"name"` Name string `gorm:"not null" json:"name"`
Live bool `json:"live"` Live bool `json:"live,omitempty"`
Proxy bool `json:"proxy"` Proxy bool `json:"proxy,omitempty"`
RtmpSource bool `json:"rtmpSource"` RtmpSource bool `json:"rtmpSource,omitempty"`
Type string `json:"type"` Type string `json:"type,omitempty"`
Headers map[string]string `gorm:"serializer:fastjson" json:"headers"` Headers map[string]string `gorm:"serializer:fastjson" json:"headers,omitempty"`
VendorInfo `gorm:"embedded;embeddedPrefix:vendor_info_" json:"vendorInfo"` VendorInfo `gorm:"embedded;embeddedPrefix:vendor_info_" json:"vendorInfo,omitempty"`
} }
type VendorInfo struct { type VendorInfo struct {
Vendor StreamingVendor `json:"vendor"` Vendor StreamingVendor `json:"vendor"`
Shared bool `gorm:"not null;default:false" json:"shared"` Shared bool `gorm:"not null;default:false" json:"shared"`
Info map[string]any `gorm:"serializer:fastjson" json:"info"` BilibiliVendorInfo BilibiliVendorInfo `gorm:"embedded;embeddedPrefix:bilibili_" json:"bilibiliVendorInfo,omitempty"`
}
type BilibiliVendorInfo struct {
Bvid string `json:"bvid,omitempty"`
Cid uint `json:"cid,omitempty"`
Epid uint `json:"epid,omitempty"`
Quality uint `json:"quality,omitempty"`
} }

@ -147,71 +147,40 @@ func (m *movie) init() (err error) {
return nil return nil
} }
func (m *movie) validateVendorMovie() (err error) { func (m *movie) validateVendorMovie() error {
if m.Base.VendorInfo.Vendor == "" { if m.Base.VendorInfo.Vendor == "" {
return return nil
} }
switch m.Base.VendorInfo.Vendor { switch m.Base.VendorInfo.Vendor {
case model.StreamingVendorBilibili: case model.StreamingVendorBilibili:
info := map[string]any{} info := m.Base.VendorInfo.BilibiliVendorInfo
bvidI := m.Base.VendorInfo.Info["bvid"] if info.Bvid == "" && info.Epid == 0 {
epIdI := m.Base.VendorInfo.Info["epId"] return fmt.Errorf("bvid and epid are empty")
if bvidI != nil && epIdI != nil {
return fmt.Errorf("bvid(%v) and epId(%v) can't be used at the same time", bvidI, epIdI)
} }
if bvidI != nil { if info.Bvid != "" && info.Epid != 0 {
bvid, ok := bvidI.(string) return fmt.Errorf("bvid and epid can't be set at the same time")
if !ok {
return fmt.Errorf("bvid is not string")
}
info["bvid"] = bvid
} else if epIdI != nil {
epId, ok := epIdI.(float64)
if !ok {
return fmt.Errorf("epId is not number")
}
info["epId"] = epId
} else {
return fmt.Errorf("bvid and epId is empty")
} }
qnI, ok := m.Base.VendorInfo.Info["qn"] if info.Bvid != "" && info.Cid == 0 {
if ok { return fmt.Errorf("cid is empty")
qn, ok := qnI.(float64)
if !ok {
return fmt.Errorf("qn is not number")
}
info["qn"] = qn
} }
if bvidI != nil { if m.Base.Headers == nil {
cidI := m.Base.VendorInfo.Info["cid"] m.Base.Headers = map[string]string{
if cidI == nil { "Referer": "https://www.bilibili.com",
return fmt.Errorf("cid is empty") "User-Agent": utils.UA,
}
cid, ok := cidI.(float64)
if !ok {
return fmt.Errorf("cid is not number")
} }
info["cid"] = cid
m.Base.Url = ""
m.Base.Proxy = false
m.Base.RtmpSource = false
m.Base.VendorInfo.Info = info
return nil
} else { } else {
m.Base.Url = "" m.Base.Headers["Referer"] = "https://www.bilibili.com"
m.Base.RtmpSource = false m.Base.Headers["User-Agent"] = utils.UA
m.Base.Proxy = true
m.Base.VendorInfo.Info = info
return nil
} }
default: default:
return fmt.Errorf("vendor not support") return fmt.Errorf("vendor not support")
} }
return nil
} }
func (m *movie) Terminate() { func (m *movie) Terminate() {

@ -5,13 +5,11 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"path" "path"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/go-resty/resty/v2"
"github.com/synctv-org/synctv/internal/conf" "github.com/synctv-org/synctv/internal/conf"
"github.com/synctv-org/synctv/internal/db" "github.com/synctv-org/synctv/internal/db"
dbModel "github.com/synctv-org/synctv/internal/model" dbModel "github.com/synctv-org/synctv/internal/model"
@ -68,7 +66,7 @@ func MovieList(ctx *gin.Context) {
} }
} }
current, err := genCurrent(room.Current(), user) current, err := genCurrent(room.Current(), user.ID)
if err != nil { if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return return
@ -83,9 +81,9 @@ func MovieList(ctx *gin.Context) {
})) }))
} }
func genCurrent(current *op.Current, user *op.User) (*op.Current, error) { func genCurrent(current *op.Current, userID string) (*op.Current, error) {
if current.Movie.Base.Vendor != "" { if current.Movie.Base.Vendor != "" {
return current, parse2VendorMovie(user, &current.Movie) return current, parse2VendorMovie(userID, &current.Movie)
} }
return current, nil return current, nil
} }
@ -105,7 +103,7 @@ func CurrentMovie(ctx *gin.Context) {
room := ctx.MustGet("room").(*op.Room) room := ctx.MustGet("room").(*op.Room)
user := ctx.MustGet("user").(*op.User) user := ctx.MustGet("user").(*op.User)
current, err := genCurrent(room.Current(), user) current, err := genCurrent(room.Current(), user.ID)
if err != nil { if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return return
@ -343,7 +341,7 @@ func ChangeCurrentMovie(ctx *gin.Context) {
return return
} }
current, err := genCurrent(room.Current(), user) current, err := genCurrent(room.Current(), user.ID)
if err != nil { if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return return
@ -380,7 +378,7 @@ func ChangeCurrentMovie(ctx *gin.Context) {
if err := room.Broadcast(&op.ElementMessage{ if err := room.Broadcast(&op.ElementMessage{
ElementMessage: m, ElementMessage: m,
BeforeSendFunc: func(sendTo *op.User) error { BeforeSendFunc: func(sendTo *op.User) error {
current, err := genCurrent(room.Current(), sendTo) current, err := genCurrent(room.Current(), sendTo.ID)
if err != nil { if err != nil {
return err return err
} }
@ -397,12 +395,6 @@ func ChangeCurrentMovie(ctx *gin.Context) {
ctx.Status(http.StatusNoContent) ctx.Status(http.StatusNoContent)
} }
var allowedProxyMovieContentType = map[string]struct{}{
"video/avi": {},
"video/mp4": {},
"video/webm": {},
}
func ProxyMovie(ctx *gin.Context) { func ProxyMovie(ctx *gin.Context) {
roomId := ctx.Param("roomId") roomId := ctx.Param("roomId")
if roomId == "" { if roomId == "" {
@ -428,8 +420,11 @@ func ProxyMovie(ctx *gin.Context) {
} }
if m.Base.VendorInfo.Vendor != "" { if m.Base.VendorInfo.Vendor != "" {
ProxyVendorMovie(ctx, m.Movie) err = parse2VendorMovie(m.Movie.CreatorID, m.Movie)
return if err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
} }
if l, err := utils.ParseURLIsLocalIP(m.Base.Url); err != nil || l { if l, err := utils.ParseURLIsLocalIP(m.Base.Url); err != nil || l {
@ -437,46 +432,11 @@ func ProxyMovie(ctx *gin.Context) {
return return
} }
r := resty.New().R() hrs := proxy.NewBufferedHttpReadSeeker(256*1024, m.Base.Url,
for k, v := range m.Base.Headers {
r.SetHeader(k, v)
}
resp, err := r.Head(m.Base.Url)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
defer resp.RawBody().Close()
if _, ok := allowedProxyMovieContentType[resp.Header().Get("Content-Type")]; !ok {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(fmt.Errorf("this movie type support proxy: %s", resp.Header().Get("Content-Type"))))
return
}
ctx.Status(resp.StatusCode())
ctx.Header("Content-Type", resp.Header().Get("Content-Type"))
l := resp.Header().Get("Content-Length")
ctx.Header("Content-Length", l)
ctx.Header("Content-Encoding", resp.Header().Get("Content-Encoding"))
length, err := strconv.ParseInt(l, 10, 64)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
hrs := proxy.NewBufferedHttpReadSeeker(128*1024, m.Base.Url,
proxy.WithContext(ctx), proxy.WithContext(ctx),
proxy.WithHeaders(m.Base.Headers), proxy.WithHeaders(m.Base.Headers),
proxy.WithContentLength(length),
) )
name := resp.Header().Get("Content-Disposition") http.ServeContent(ctx.Writer, ctx.Request, m.Base.Url, time.Now(), hrs)
if name == "" {
name = filepath.Base(resp.Request.RawRequest.URL.Path)
} else {
ctx.Header("Content-Disposition", name)
}
http.ServeContent(ctx.Writer, ctx.Request, name, time.Now(), hrs)
} }
type FormatErrNotSupportFileType string type FormatErrNotSupportFileType string
@ -532,152 +492,34 @@ func JoinLive(ctx *gin.Context) {
} }
} }
func ProxyVendorMovie(ctx *gin.Context, m *dbModel.Movie) { func parse2VendorMovie(userID string, movie *dbModel.Movie) (err error) {
if m.Base.VendorInfo.Vendor == "" { if movie.Base.VendorInfo.Shared {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("vendor is empty")) userID = movie.CreatorID
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
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
}
vendor, err := db.AssignFirstOrCreateVendorByUserIDAndVendor(m.CreatorID, dbModel.StreamingVendorBilibili)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
cli := bilibili.NewClient(vendor.Cookies)
var mu *bilibili.VideoURL
if bvid != "" {
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
}
mu, err = cli.GetVideoURL(0, bvid, uint(cid))
} else {
mu, err = cli.GetPGCURL(uint(epId), 0)
}
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)
default:
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("vendor not support"))
return
}
}
func parse2VendorMovie(user *op.User, movie *dbModel.Movie) (err error) {
if movie.Base.Proxy {
return nil
} }
switch movie.Base.VendorInfo.Vendor { switch movie.Base.VendorInfo.Vendor {
case dbModel.StreamingVendorBilibili: case dbModel.StreamingVendorBilibili:
bvidI := movie.Base.VendorInfo.Info["bvid"] info := movie.Base.VendorInfo.BilibiliVendorInfo
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 ( vendor, err := db.AssignFirstOrCreateVendorByUserIDAndVendor(userID, dbModel.StreamingVendorBilibili)
bvid string
ok bool
)
if bvidI != nil {
bvid, ok = bvidI.(string)
if !ok {
return fmt.Errorf("bvid is not string")
}
} else if epIdI != nil {
_, ok = epIdI.(float64)
if !ok {
return fmt.Errorf("epId is not number")
}
return
} else {
return fmt.Errorf("bvid and epId is empty")
}
id := user.ID
if movie.Base.VendorInfo.Shared {
id = movie.CreatorID
}
vendor, err := db.AssignFirstOrCreateVendorByUserIDAndVendor(id, dbModel.StreamingVendorBilibili)
if err != nil { if err != nil {
return err return err
} }
cli := bilibili.NewClient(vendor.Cookies) cli := bilibili.NewClient(vendor.Cookies)
var qn float64 = float64(bilibili.Q1080PP)
qnI, ok := movie.Base.VendorInfo.Info["qn"]
if ok {
qn, ok = qnI.(float64)
if !ok {
return fmt.Errorf("qn is not number")
}
}
cidI := movie.Base.VendorInfo.Info["cid"] var mu *bilibili.VideoURL
if cidI == nil { if info.Bvid != "" {
return fmt.Errorf("cid is empty") mu, err = cli.GetVideoURL(0, info.Bvid, info.Cid, bilibili.WithQuality(info.Quality))
} } else if info.Epid != 0 {
cid, ok := cidI.(float64) mu, err = cli.GetPGCURL(info.Epid, 0, bilibili.WithQuality(info.Quality))
if !ok { } else {
return fmt.Errorf("cid is not number") err = errors.New("bvid and epid are empty")
} }
mu, err := cli.GetVideoURL(0, bvid, uint(cid), bilibili.WithQuality(uint(qn)))
if err != nil { if err != nil {
return err return err
} }
movie.Base.Url = mu.URL movie.Base.Url = mu.URL
return nil return nil
default: default:

@ -41,10 +41,6 @@ func (p *PushMovieReq) Validate() error {
return ErrTypeTooLong return ErrTypeTooLong
} }
if p.VendorInfo.Vendor != "" && p.VendorInfo.Info == nil {
return errors.New("vendor info is empty")
}
return nil return nil
} }

@ -8,17 +8,18 @@ import (
) )
type VideoPageInfo struct { type VideoPageInfo struct {
Bvid string `json:"bvid"`
Title string `json:"title"` Title string `json:"title"`
CoverImage string `json:"coverImage"` CoverImage string `json:"coverImage"`
Actors string `json:"actors"`
VideoInfos []*VideoInfo `json:"videoInfos"` VideoInfos []*VideoInfo `json:"videoInfos"`
} }
type VideoInfo struct { type VideoInfo struct {
Cid int `json:"cid"` Bvid string `json:"bvid,omitempty"`
// 分P Cid int `json:"cid,omitempty"`
Epid uint `json:"epid,omitempty"`
Name string `json:"name"` Name string `json:"name"`
FirstFrame string `json:"firstFrame"` CoverImage string `json:"coverImage"`
} }
func (c *Client) ParseVideoPage(aid uint, bvid string) (*VideoPageInfo, error) { func (c *Client) ParseVideoPage(aid uint, bvid string) (*VideoPageInfo, error) {
@ -46,16 +47,17 @@ func (c *Client) ParseVideoPage(aid uint, bvid string) (*VideoPageInfo, error) {
} }
// TODO: error message // TODO: error message
r := &VideoPageInfo{ r := &VideoPageInfo{
Bvid: info.Data.Bvid,
Title: info.Data.Title, Title: info.Data.Title,
CoverImage: info.Data.Pic, CoverImage: info.Data.Pic,
Actors: info.Data.Owner.Name,
VideoInfos: make([]*VideoInfo, 0, len(info.Data.Pages)),
} }
r.VideoInfos = make([]*VideoInfo, 0, len(info.Data.Pages))
for _, page := range info.Data.Pages { for _, page := range info.Data.Pages {
r.VideoInfos = append(r.VideoInfos, &VideoInfo{ r.VideoInfos = append(r.VideoInfos, &VideoInfo{
Bvid: info.Data.Bvid,
Cid: page.Cid, Cid: page.Cid,
Name: page.Part, Name: page.Part,
FirstFrame: page.FirstFrame, CoverImage: page.FirstFrame,
}) })
} }
return r, nil return r, nil
@ -170,20 +172,7 @@ func (c *Client) GetSubtitles(aid uint, bvid string, cid uint) ([]*Subtitle, err
return r, nil return r, nil
} }
type PGCPageInfo struct { func (c *Client) ParsePGCPage(epId, season_id uint) (*VideoPageInfo, error) {
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 var url string
if epId != 0 { if epId != 0 {
url = fmt.Sprintf("https://api.bilibili.com/pgc/view/web/season?ep_id=%d", epId) url = fmt.Sprintf("https://api.bilibili.com/pgc/view/web/season?ep_id=%d", epId)
@ -208,16 +197,16 @@ func (c *Client) ParsePGCPage(epId, season_id uint) (*PGCPageInfo, error) {
return nil, err return nil, err
} }
r := &PGCPageInfo{ r := &VideoPageInfo{
Actors: info.Result.Actors, Title: info.Result.Title,
CoverImage: info.Result.Cover, CoverImage: info.Result.Cover,
PGCInfos: make([]*PGCInfo, len(info.Result.Episodes)), Actors: info.Result.Actors,
VideoInfos: make([]*VideoInfo, len(info.Result.Episodes)),
} }
for i, v := range info.Result.Episodes { for i, v := range info.Result.Episodes {
r.PGCInfos[i] = &PGCInfo{ r.VideoInfos[i] = &VideoInfo{
EpId: v.EpID, Epid: v.EpID,
Cid: v.Cid,
Name: v.ShareCopy, Name: v.ShareCopy,
CoverImage: v.Cover, CoverImage: v.Cover,
} }

Loading…
Cancel
Save