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 {
Url string `json:"url"`
Url string `json:"url,omitempty"`
Name string `gorm:"not null" json:"name"`
Live bool `json:"live"`
Proxy bool `json:"proxy"`
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"`
Live bool `json:"live,omitempty"`
Proxy bool `json:"proxy,omitempty"`
RtmpSource bool `json:"rtmpSource,omitempty"`
Type string `json:"type,omitempty"`
Headers map[string]string `gorm:"serializer:fastjson" json:"headers,omitempty"`
VendorInfo `gorm:"embedded;embeddedPrefix:vendor_info_" json:"vendorInfo,omitempty"`
}
type VendorInfo struct {
Vendor StreamingVendor `json:"vendor"`
Shared bool `gorm:"not null;default:false" json:"shared"`
Info map[string]any `gorm:"serializer:fastjson" json:"info"`
Vendor StreamingVendor `json:"vendor"`
Shared bool `gorm:"not null;default:false" json:"shared"`
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
}
func (m *movie) validateVendorMovie() (err error) {
func (m *movie) validateVendorMovie() error {
if m.Base.VendorInfo.Vendor == "" {
return
return nil
}
switch m.Base.VendorInfo.Vendor {
case model.StreamingVendorBilibili:
info := map[string]any{}
bvidI := m.Base.VendorInfo.Info["bvid"]
epIdI := m.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)
info := m.Base.VendorInfo.BilibiliVendorInfo
if info.Bvid == "" && info.Epid == 0 {
return fmt.Errorf("bvid and epid are empty")
}
if bvidI != nil {
bvid, ok := bvidI.(string)
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")
if info.Bvid != "" && info.Epid != 0 {
return fmt.Errorf("bvid and epid can't be set at the same time")
}
qnI, ok := m.Base.VendorInfo.Info["qn"]
if ok {
qn, ok := qnI.(float64)
if !ok {
return fmt.Errorf("qn is not number")
}
info["qn"] = qn
if info.Bvid != "" && info.Cid == 0 {
return fmt.Errorf("cid is empty")
}
if bvidI != nil {
cidI := m.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")
if m.Base.Headers == nil {
m.Base.Headers = map[string]string{
"Referer": "https://www.bilibili.com",
"User-Agent": utils.UA,
}
info["cid"] = cid
m.Base.Url = ""
m.Base.Proxy = false
m.Base.RtmpSource = false
m.Base.VendorInfo.Info = info
return nil
} else {
m.Base.Url = ""
m.Base.RtmpSource = false
m.Base.Proxy = true
m.Base.VendorInfo.Info = info
return nil
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() {

@ -5,13 +5,11 @@ import (
"fmt"
"net/http"
"path"
"path/filepath"
"strconv"
"strings"
"time"
"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"
@ -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 {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
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 != "" {
return current, parse2VendorMovie(user, &current.Movie)
return current, parse2VendorMovie(userID, &current.Movie)
}
return current, nil
}
@ -105,7 +103,7 @@ func CurrentMovie(ctx *gin.Context) {
room := ctx.MustGet("room").(*op.Room)
user := ctx.MustGet("user").(*op.User)
current, err := genCurrent(room.Current(), user)
current, err := genCurrent(room.Current(), user.ID)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
@ -343,7 +341,7 @@ func ChangeCurrentMovie(ctx *gin.Context) {
return
}
current, err := genCurrent(room.Current(), user)
current, err := genCurrent(room.Current(), user.ID)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
@ -380,7 +378,7 @@ func ChangeCurrentMovie(ctx *gin.Context) {
if err := room.Broadcast(&op.ElementMessage{
ElementMessage: m,
BeforeSendFunc: func(sendTo *op.User) error {
current, err := genCurrent(room.Current(), sendTo)
current, err := genCurrent(room.Current(), sendTo.ID)
if err != nil {
return err
}
@ -397,12 +395,6 @@ func ChangeCurrentMovie(ctx *gin.Context) {
ctx.Status(http.StatusNoContent)
}
var allowedProxyMovieContentType = map[string]struct{}{
"video/avi": {},
"video/mp4": {},
"video/webm": {},
}
func ProxyMovie(ctx *gin.Context) {
roomId := ctx.Param("roomId")
if roomId == "" {
@ -428,8 +420,11 @@ func ProxyMovie(ctx *gin.Context) {
}
if m.Base.VendorInfo.Vendor != "" {
ProxyVendorMovie(ctx, m.Movie)
return
err = parse2VendorMovie(m.Movie.CreatorID, m.Movie)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
}
if l, err := utils.ParseURLIsLocalIP(m.Base.Url); err != nil || l {
@ -437,46 +432,11 @@ func ProxyMovie(ctx *gin.Context) {
return
}
r := resty.New().R()
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,
hrs := proxy.NewBufferedHttpReadSeeker(256*1024, m.Base.Url,
proxy.WithContext(ctx),
proxy.WithHeaders(m.Base.Headers),
proxy.WithContentLength(length),
)
name := resp.Header().Get("Content-Disposition")
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)
http.ServeContent(ctx.Writer, ctx.Request, m.Base.Url, time.Now(), hrs)
}
type FormatErrNotSupportFileType string
@ -532,152 +492,34 @@ func JoinLive(ctx *gin.Context) {
}
}
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
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
func parse2VendorMovie(userID string, movie *dbModel.Movie) (err error) {
if movie.Base.VendorInfo.Shared {
userID = movie.CreatorID
}
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)
}
info := movie.Base.VendorInfo.BilibiliVendorInfo
var (
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)
vendor, err := db.AssignFirstOrCreateVendorByUserIDAndVendor(userID, dbModel.StreamingVendorBilibili)
if err != nil {
return err
}
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"]
if cidI == nil {
return fmt.Errorf("cid is empty")
}
cid, ok := cidI.(float64)
if !ok {
return fmt.Errorf("cid is not number")
var mu *bilibili.VideoURL
if info.Bvid != "" {
mu, err = cli.GetVideoURL(0, info.Bvid, info.Cid, bilibili.WithQuality(info.Quality))
} else if info.Epid != 0 {
mu, err = cli.GetPGCURL(info.Epid, 0, bilibili.WithQuality(info.Quality))
} else {
err = errors.New("bvid and epid are empty")
}
mu, err := cli.GetVideoURL(0, bvid, uint(cid), bilibili.WithQuality(uint(qn)))
if err != nil {
return err
}
movie.Base.Url = mu.URL
return nil
default:

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

@ -8,17 +8,18 @@ import (
)
type VideoPageInfo struct {
Bvid string `json:"bvid"`
Title string `json:"title"`
CoverImage string `json:"coverImage"`
Actors string `json:"actors"`
VideoInfos []*VideoInfo `json:"videoInfos"`
}
type VideoInfo struct {
Cid int `json:"cid"`
// 分P
Bvid string `json:"bvid,omitempty"`
Cid int `json:"cid,omitempty"`
Epid uint `json:"epid,omitempty"`
Name string `json:"name"`
FirstFrame string `json:"firstFrame"`
CoverImage string `json:"coverImage"`
}
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
r := &VideoPageInfo{
Bvid: info.Data.Bvid,
Title: info.Data.Title,
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 {
r.VideoInfos = append(r.VideoInfos, &VideoInfo{
Bvid: info.Data.Bvid,
Cid: page.Cid,
Name: page.Part,
FirstFrame: page.FirstFrame,
CoverImage: page.FirstFrame,
})
}
return r, nil
@ -170,20 +172,7 @@ func (c *Client) GetSubtitles(aid uint, bvid string, cid uint) ([]*Subtitle, err
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) {
func (c *Client) ParsePGCPage(epId, season_id uint) (*VideoPageInfo, error) {
var url string
if epId != 0 {
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
}
r := &PGCPageInfo{
Actors: info.Result.Actors,
r := &VideoPageInfo{
Title: info.Result.Title,
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 {
r.PGCInfos[i] = &PGCInfo{
EpId: v.EpID,
Cid: v.Cid,
r.VideoInfos[i] = &VideoInfo{
Epid: v.EpID,
Name: v.ShareCopy,
CoverImage: v.Cover,
}

Loading…
Cancel
Save