mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-08 22:36:37 +08:00
feat: Emby 兼容层完整实现 + 多模块功能增强
## Emby/Jellyfin 兼容层 (emby_compat.go / emby.go) - 新增 SystemInfoPublic、FindUser、Items、Item、LatestItems、ResumeItems - 新增 SetFavorite、MarkPlayed、RecordProgress 用户播放状态同步 - 新增 itemPayload、mediaSource、mediaStreams 媒体信息组装 - 双前缀路由 /emby/* 和 / 根路径,兼容 Infuse/Yamby/Senplayer/Kodi - 新增 Ping、SystemEndpoint、AuthByName 端点 - emby.go 扩展对应 handler 函数 ## 站点适配器 (site_adapter.go / site.go) - SiteConfig 扩展 UserAgent/Timeout/Extra/FlareSolverrURL 字段 - doRequest() GET 请求支持 FlareSolverr 代理绕过 Cloudflare/WAF - MTeam api_key 认证改为 Authorization: Bearer 格式 - Search() 重构为 sync.WaitGroup 并发执行,提升多站搜索性能 - siteModelToConfig() 改为 SiteService 方法,按 BrowserEmulation 填充 FlareSolverrURL ## 图片代理 (image_proxy.go) - 重构图片代理服务,支持更多来源和缓存策略 ## 下载管理 (downloads.go / download_clients.go / qbittorrent.go) - 下载任务增强:状态管理、进度追踪优化 - qBittorrent 客户端连接稳定性改进 ## 刮削与数据库 (scraper.go / tmdb.go / repository.go) - 刮削器增强 TMDB 集成,补全元数据字段 - repository 扩展查询方法 ## 前端 (web/src/) - HomePage: 首页布局重构,按媒体库分组展示,系列聚合优化 - DiscoverPage: 发现页增强,错误处理改进(API key 缺失/网络错误分离) - PosterWallPage: 海报墙优化,系列聚合展示 - MediaCard: 媒体卡片优化 - PlayerPage: 播放器改进 - 新增 utils/groupSeries.ts: 系列聚合工具函数 - .gitignore: 添加 .tmp_* 临时文件排除规则
This commit is contained in:
@@ -58,7 +58,20 @@ func NewTMDbProvider(cfg *config.Config, log *zap.Logger, apiConfig *APIConfigSe
|
||||
apiConfig: apiConfig,
|
||||
base: base,
|
||||
imgCDN: img,
|
||||
client: &http.Client{Timeout: 15 * time.Second},
|
||||
// 默认 8s 超时:首页同时调 trending + popular,15s 太久会让用户感觉
|
||||
// 卡死。如果 TMDb 真有问题,handler 层会快速降级返回空列表。
|
||||
// 同时让 client 显式读 HTTP(S)_PROXY 环境变量——这是 GFW 内部署最低
|
||||
// 成本能拉到 TMDb 的方式。
|
||||
client: &http.Client{
|
||||
Timeout: 8 * time.Second,
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
MaxIdleConns: 16,
|
||||
IdleConnTimeout: 60 * time.Second,
|
||||
TLSHandshakeTimeout: 8 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,6 +210,72 @@ func (t *TMDbProvider) SearchMovie(ctx context.Context, query string, year int)
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// SearchTV issues `/search/tv` and returns the best match. Used by anime /
|
||||
// tv libraries before falling back to SearchMovie.
|
||||
func (t *TMDbProvider) SearchTV(ctx context.Context, query string, year int) (*Match, error) {
|
||||
if query == "" {
|
||||
return nil, errors.New("empty query")
|
||||
}
|
||||
|
||||
apiKey := t.resolveAPIKey(ctx)
|
||||
if apiKey == "" {
|
||||
return nil, nil
|
||||
}
|
||||
base := t.resolveBaseURL(ctx)
|
||||
|
||||
q := url.Values{}
|
||||
q.Set("api_key", apiKey)
|
||||
q.Set("query", query)
|
||||
q.Set("language", "zh-CN")
|
||||
q.Set("include_adult", "false")
|
||||
if year > 0 {
|
||||
q.Set("first_air_date_year", fmt.Sprintf("%d", year))
|
||||
}
|
||||
u := base + "/search/tv?" + q.Encode()
|
||||
|
||||
type result struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
OriginalName string `json:"original_name"`
|
||||
Overview string `json:"overview"`
|
||||
PosterPath string `json:"poster_path"`
|
||||
BackdropPath string `json:"backdrop_path"`
|
||||
FirstAirDate string `json:"first_air_date"`
|
||||
VoteAverage float32 `json:"vote_average"`
|
||||
}
|
||||
type page struct {
|
||||
Results []result `json:"results"`
|
||||
}
|
||||
|
||||
var p page
|
||||
if err := t.getJSON(ctx, u, &p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(p.Results) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
r := p.Results[0]
|
||||
m := &Match{
|
||||
TMDbID: r.ID,
|
||||
Title: r.Name,
|
||||
Overview: r.Overview,
|
||||
Rating: r.VoteAverage,
|
||||
}
|
||||
if m.Title == "" {
|
||||
m.Title = r.OriginalName
|
||||
}
|
||||
if r.PosterPath != "" {
|
||||
m.PosterURL = t.imgCDN + "/w500" + r.PosterPath
|
||||
}
|
||||
if r.BackdropPath != "" {
|
||||
m.BackdropURL = t.imgCDN + "/w1280" + r.BackdropPath
|
||||
}
|
||||
if len(r.FirstAirDate) >= 4 {
|
||||
fmt.Sscanf(r.FirstAirDate[:4], "%d", &m.Year)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (t *TMDbProvider) getJSON(ctx context.Context, url string, out any) error {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user