mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-11 15:56:37 +08:00
处理无法播放的问题
处理无法播放的问题
This commit is contained in:
@@ -410,7 +410,11 @@ func cancelPendingDownloadsHandler(svc *service.Container) gin.HandlerFunc {
|
|||||||
func strmPlayHandler(svc *service.Container) gin.HandlerFunc {
|
func strmPlayHandler(svc *service.Container) gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
provider := strings.TrimSpace(c.Param("provider"))
|
provider := strings.TrimSpace(c.Param("provider"))
|
||||||
result, err := svc.Strm.ResolvePlay(c.Request.Context(), provider, url.Values(c.Request.URL.Query()))
|
q := url.Values(c.Request.URL.Query())
|
||||||
|
if userAgent := c.GetHeader("User-Agent"); userAgent != "" {
|
||||||
|
q.Set("__ua", userAgent)
|
||||||
|
}
|
||||||
|
result, err := svc.Strm.ResolvePlay(c.Request.Context(), provider, q)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, service.ErrStrmPlayNotFound) {
|
if errors.Is(err, service.ErrStrmPlayNotFound) {
|
||||||
c.Status(http.StatusNotFound)
|
c.Status(http.StatusNotFound)
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ func schemaHandler(_ *service.Container) gin.HandlerFunc {
|
|||||||
{"key": "qbittorrent.password", "type": "text"},
|
{"key": "qbittorrent.password", "type": "text"},
|
||||||
{"key": "qbittorrent.savepath", "type": "text"},
|
{"key": "qbittorrent.savepath", "type": "text"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "system-update",
|
"key": "system-update",
|
||||||
"label": "系统更新",
|
"label": "系统更新",
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func EmbyAuthRequired(secret string) gin.HandlerFunc {
|
|||||||
return []byte(secret), nil
|
return []byte(secret), nil
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil || !parsed.Valid || claims.UserID == "" {
|
if err != nil || !parsed.Valid || claims.UserID == "" {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{
|
c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
"Code": 40101,
|
"Code": 40101,
|
||||||
"Message": "Invalid token",
|
"Message": "Invalid token",
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ func (r *StorageConfigRepository) Upsert(ctx context.Context, c *model.StorageCo
|
|||||||
}).Error
|
}).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Delete removes a storage config by ID.
|
// Delete removes a storage config by ID.
|
||||||
func (r *StorageConfigRepository) Delete(ctx context.Context, id string) error {
|
func (r *StorageConfigRepository) Delete(ctx context.Context, id string) error {
|
||||||
return r.db.WithContext(ctx).Where("id = ?", id).Delete(&model.StorageConfig{}).Error
|
return r.db.WithContext(ctx).Where("id = ?", id).Delete(&model.StorageConfig{}).Error
|
||||||
|
|||||||
@@ -77,7 +77,11 @@ func (p *openAPI115Provider) List(ctx context.Context, dirID string) ([]FileEntr
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *openAPI115Provider) Resolve(ctx context.Context, fileRef string) (*DirectLink, error) {
|
func (p *openAPI115Provider) Resolve(ctx context.Context, fileRef string) (*DirectLink, error) {
|
||||||
url, err := p.c.GetDownloadURL(ctx, fileRef)
|
return p.ResolveWithUA(ctx, fileRef, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *openAPI115Provider) ResolveWithUA(ctx context.Context, fileRef, ua string) (*DirectLink, error) {
|
||||||
|
url, err := p.c.GetDownloadURLWithUA(ctx, fileRef, ua)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,11 +90,15 @@ type RespBase struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// doJSON 执行 HTTP 请求并解析为统一响应;带 AccessToken(access=true 时)。
|
// doJSON 执行 HTTP 请求并解析为统一响应;带 AccessToken(access=true 时)。
|
||||||
func (c *OpenClient) doJSON(ctx context.Context, method, rawURL string, form map[string]string, access bool, retries int) (*RespBase, error) {
|
func (c *OpenClient) doJSON(ctx context.Context, method, rawURL string, form map[string]string, access bool, retries int, uas ...string) (*RespBase, error) {
|
||||||
executor := c.executor
|
executor := c.executor
|
||||||
if executor == nil {
|
if executor == nil {
|
||||||
executor = GetGlobalExecutor()
|
executor = GetGlobalExecutor()
|
||||||
}
|
}
|
||||||
|
ua := ""
|
||||||
|
if len(uas) > 0 {
|
||||||
|
ua = uas[0]
|
||||||
|
}
|
||||||
|
|
||||||
var lastErr error
|
var lastErr error
|
||||||
for attempt := 0; attempt <= retries; attempt++ {
|
for attempt := 0; attempt <= retries; attempt++ {
|
||||||
@@ -103,7 +107,7 @@ func (c *OpenClient) doJSON(ctx context.Context, method, rawURL string, form map
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := c.buildRequest(ctx, method, rawURL, form, access)
|
req, err := c.buildRequestWithUA(ctx, method, rawURL, form, access, ua)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -187,6 +191,10 @@ func (c *OpenClient) doJSON(ctx context.Context, method, rawURL string, form map
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *OpenClient) buildRequest(ctx context.Context, method, rawURL string, form map[string]string, access bool) (*http.Request, error) {
|
func (c *OpenClient) buildRequest(ctx context.Context, method, rawURL string, form map[string]string, access bool) (*http.Request, error) {
|
||||||
|
return c.buildRequestWithUA(ctx, method, rawURL, form, access, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *OpenClient) buildRequestWithUA(ctx context.Context, method, rawURL string, form map[string]string, access bool, ua string) (*http.Request, error) {
|
||||||
method = strings.ToUpper(method)
|
method = strings.ToUpper(method)
|
||||||
var body io.Reader
|
var body io.Reader
|
||||||
if method == http.MethodPost && len(form) > 0 {
|
if method == http.MethodPost && len(form) > 0 {
|
||||||
@@ -211,7 +219,11 @@ func (c *OpenClient) buildRequest(ctx context.Context, method, rawURL string, fo
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
req.Header.Set("User-Agent", DefaultUA)
|
targetUA := DefaultUA
|
||||||
|
if strings.TrimSpace(ua) != "" {
|
||||||
|
targetUA = strings.TrimSpace(ua)
|
||||||
|
}
|
||||||
|
req.Header.Set("User-Agent", targetUA)
|
||||||
if method == http.MethodPost && len(form) > 0 {
|
if method == http.MethodPost && len(form) > 0 {
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
}
|
}
|
||||||
@@ -226,6 +238,11 @@ func (c *OpenClient) doAuthJSON(ctx context.Context, method, rawURL string, form
|
|||||||
return c.doJSON(ctx, method, rawURL, form, true, retries)
|
return c.doJSON(ctx, method, rawURL, form, true, retries)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// doAuthJSONWithUA 带自定义 User-Agent 的业务请求(换取直链等防盗链接口使用)。
|
||||||
|
func (c *OpenClient) doAuthJSONWithUA(ctx context.Context, method, rawURL string, form map[string]string, retries int, ua string) (*RespBase, error) {
|
||||||
|
return c.doJSON(ctx, method, rawURL, form, true, retries, ua)
|
||||||
|
}
|
||||||
|
|
||||||
// IsThrottleCode 判断是否为限流错误码。
|
// IsThrottleCode 判断是否为限流错误码。
|
||||||
func IsThrottleCode(code int) bool {
|
func IsThrottleCode(code int) bool {
|
||||||
return code == RequestMaxLimitCode || code == RequestRateLimitCode
|
return code == RequestMaxLimitCode || code == RequestRateLimitCode
|
||||||
|
|||||||
@@ -127,11 +127,17 @@ type downloadURLData struct {
|
|||||||
// GetDownloadURL 获取下载直链(pickcode)。命中缓存直接返回,
|
// GetDownloadURL 获取下载直链(pickcode)。命中缓存直接返回,
|
||||||
// 避免对同一文件反复换取直链触发 115 风控。
|
// 避免对同一文件反复换取直链触发 115 风控。
|
||||||
func (c *OpenClient) GetDownloadURL(ctx context.Context, pickCode string) (string, error) {
|
func (c *OpenClient) GetDownloadURL(ctx context.Context, pickCode string) (string, error) {
|
||||||
if cached := GetDownloadURLCache(pickCode); cached != "" {
|
return c.GetDownloadURLWithUA(ctx, pickCode, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDownloadURLWithUA 支持按调用方/播放器 User-Agent 换取对应的 115 CDN 直链(用于 115 防盗链白名单校验)。
|
||||||
|
func (c *OpenClient) GetDownloadURLWithUA(ctx context.Context, pickCode, ua string) (string, error) {
|
||||||
|
ua = strings.TrimSpace(ua)
|
||||||
|
if cached := GetDownloadURLCache(pickCode, ua); cached != "" {
|
||||||
return cached, nil
|
return cached, nil
|
||||||
}
|
}
|
||||||
params := map[string]string{"pick_code": pickCode}
|
params := map[string]string{"pick_code": pickCode}
|
||||||
resp, err := c.doAuthJSON(ctx, "POST", ProAPIBase+"/open/ufile/downurl", params, 1)
|
resp, err := c.doAuthJSONWithUA(ctx, "POST", ProAPIBase+"/open/ufile/downurl", params, 1, ua)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -143,7 +149,7 @@ func (c *OpenClient) GetDownloadURL(ctx context.Context, pickCode string) (strin
|
|||||||
if first.URL.URL == "" {
|
if first.URL.URL == "" {
|
||||||
return "", fmt.Errorf("115: 下载地址为空(文件可能未上传完成或已被删除)")
|
return "", fmt.Errorf("115: 下载地址为空(文件可能未上传完成或已被删除)")
|
||||||
}
|
}
|
||||||
SetDownloadURLCache(pickCode, first.URL.URL)
|
SetDownloadURLCache(pickCode, first.URL.URL, ua)
|
||||||
return first.URL.URL, nil
|
return first.URL.URL, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
package cloud115
|
package cloud115
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -28,17 +29,30 @@ var (
|
|||||||
urlCache = map[string]urlCacheEntry{}
|
urlCache = map[string]urlCacheEntry{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// urlCacheKey 构造缓存键名(pickCode + UA,实现直链防盗链按客户端 UA 独立缓存)。
|
||||||
|
func urlCacheKey(pickCode, ua string) string {
|
||||||
|
if ua == "" {
|
||||||
|
return pickCode
|
||||||
|
}
|
||||||
|
return pickCode + "@" + ua
|
||||||
|
}
|
||||||
|
|
||||||
// GetDownloadURLCache 返回未过期的缓存直链;不存在或已过期返回空串。
|
// GetDownloadURLCache 返回未过期的缓存直链;不存在或已过期返回空串。
|
||||||
func GetDownloadURLCache(pickCode string) string {
|
func GetDownloadURLCache(pickCode string, uas ...string) string {
|
||||||
if pickCode == "" {
|
if pickCode == "" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
ua := ""
|
||||||
|
if len(uas) > 0 {
|
||||||
|
ua = uas[0]
|
||||||
|
}
|
||||||
|
key := urlCacheKey(pickCode, ua)
|
||||||
urlCacheMu.Lock()
|
urlCacheMu.Lock()
|
||||||
defer urlCacheMu.Unlock()
|
defer urlCacheMu.Unlock()
|
||||||
entry, ok := urlCache[pickCode]
|
entry, ok := urlCache[key]
|
||||||
if !ok || time.Now().After(entry.expiresAt) {
|
if !ok || time.Now().After(entry.expiresAt) {
|
||||||
if ok {
|
if ok {
|
||||||
delete(urlCache, pickCode)
|
delete(urlCache, key)
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -46,10 +60,14 @@ func GetDownloadURLCache(pickCode string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetDownloadURLCache 写入直链缓存。
|
// SetDownloadURLCache 写入直链缓存。
|
||||||
func SetDownloadURLCache(pickCode, url string) {
|
func SetDownloadURLCache(pickCode string, url string, uas ...string) {
|
||||||
if pickCode == "" || url == "" {
|
if pickCode == "" || url == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ua := ""
|
||||||
|
if len(uas) > 0 {
|
||||||
|
ua = uas[0]
|
||||||
|
}
|
||||||
urlCacheMu.Lock()
|
urlCacheMu.Lock()
|
||||||
defer urlCacheMu.Unlock()
|
defer urlCacheMu.Unlock()
|
||||||
if len(urlCache) >= maxCachedURLs {
|
if len(urlCache) >= maxCachedURLs {
|
||||||
@@ -60,15 +78,20 @@ func SetDownloadURLCache(pickCode, url string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
urlCache[pickCode] = urlCacheEntry{url: url, expiresAt: time.Now().Add(downloadURLCacheTTL)}
|
key := urlCacheKey(pickCode, ua)
|
||||||
|
urlCache[key] = urlCacheEntry{url: url, expiresAt: time.Now().Add(downloadURLCacheTTL)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClearDownloadURLCache 删除指定 pickcode 的缓存(下载得到非 2xx 时调用)。
|
// ClearDownloadURLCache 删除指定 pickcode 的所有缓存(下载得到非 2xx 时调用)。
|
||||||
func ClearDownloadURLCache(pickCode string) {
|
func ClearDownloadURLCache(pickCode string) {
|
||||||
if pickCode == "" {
|
if pickCode == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
urlCacheMu.Lock()
|
urlCacheMu.Lock()
|
||||||
defer urlCacheMu.Unlock()
|
defer urlCacheMu.Unlock()
|
||||||
delete(urlCache, pickCode)
|
for k := range urlCache {
|
||||||
|
if k == pickCode || strings.HasPrefix(k, pickCode+"@") {
|
||||||
|
delete(urlCache, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,15 @@ func (s *StrmService) resolveCloudPlay(ctx context.Context, provider string, q u
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
link, err := p.Resolve(ctx, ref)
|
var link *cloud.DirectLink
|
||||||
|
ua := q.Get("__ua")
|
||||||
|
if uaProvider, ok := p.(interface {
|
||||||
|
ResolveWithUA(ctx context.Context, fileRef, ua string) (*cloud.DirectLink, error)
|
||||||
|
}); ok && ua != "" {
|
||||||
|
link, err = uaProvider.ResolveWithUA(ctx, ref, ua)
|
||||||
|
} else {
|
||||||
|
link, err = p.Resolve(ctx, ref)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user