You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
synctv/internal/model/movie.go

177 lines
4.8 KiB
Go

package model
import (
"errors"
"fmt"
"net/url"
"sync/atomic"
"time"
"github.com/synctv-org/synctv/internal/conf"
"github.com/synctv-org/synctv/utils"
refreshcache "github.com/synctv-org/synctv/utils/refreshCache"
"gorm.io/gorm"
)
type Movie struct {
ID string `gorm:"primaryKey;type:varchar(36)" json:"id"`
CreatedAt time.Time `json:"-"`
UpdatedAt time.Time `json:"-"`
Position uint `gorm:"not null" json:"-"`
RoomID string `gorm:"not null;index" json:"-"`
CreatorID string `gorm:"not null;index" json:"creatorId"`
Base BaseMovie `gorm:"embedded;embeddedPrefix:base_" json:"base"`
}
func (m *Movie) BeforeCreate(tx *gorm.DB) error {
if m.ID == "" {
m.ID = utils.SortUUID()
}
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"`
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 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 !conf.Conf.Proxy.AllowProxyToLocal && 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 !conf.Conf.Proxy.AllowProxyToLocal && 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"`
Bilibili *BilibiliVendorInfo `gorm:"embedded;embeddedPrefix:bilibili_" json:"bilibili,omitempty"`
1 year ago
}
type BilibiliVendorInfo struct {
Bvid string `json:"bvid,omitempty"`
Cid uint `json:"cid,omitempty"`
Epid uint `json:"epid,omitempty"`
Quality uint `json:"quality,omitempty"`
Cache atomic.Pointer[refreshcache.RefreshCache[*BilibiliVendorCache]] `gorm:"-:all" json:"-"`
}
type BilibiliVendorCache struct {
MPDFile string
URLs []string
}
func (b *BilibiliVendorInfo) InitOrLoadCache(initCache func() *refreshcache.RefreshCache[*BilibiliVendorCache]) *refreshcache.RefreshCache[*BilibiliVendorCache] {
if c := b.Cache.Load(); c != nil {
return c
}
c := initCache()
if b.Cache.CompareAndSwap(nil, c) {
return c
} else {
return b.Cache.Load()
}
}