mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-09 06:46:37 +08:00
fix: 使 TMDbProvider 支持从数据库读取 API Key
- 修改 TMDbProvider 添加 apiConfig *APIConfigService 字段 - NewTMDbProvider 接受 apiConfig 参数(可为 nil) - 添加 resolveAPIKey(ctx) 方法:优先从 config 读取,回退到数据库 - 添加 resolveBaseURL(ctx) 方法:解析基础 URL - 修改 SearchMovie 使用 resolveAPIKey / resolveBaseURL - 修改 DiscoverService.Fetch 使用 resolveAPIKey / resolveBaseURL - 调整 service.go 初始化顺序,确保 tmdb 在 scraper 之前初始化 - 构建验证通过
This commit is contained in:
@@ -53,14 +53,22 @@ func (d *DiscoverService) fetch(ctx context.Context, path string) ([]Match, erro
|
|||||||
// It paginates page=1 only — that's all the home page needs and it
|
// It paginates page=1 only — that's all the home page needs and it
|
||||||
// keeps us under TMDb's 50 rps limit.
|
// keeps us under TMDb's 50 rps limit.
|
||||||
func (d *DiscoverService) Fetch(ctx context.Context, path string) ([]Match, error) {
|
func (d *DiscoverService) Fetch(ctx context.Context, path string) ([]Match, error) {
|
||||||
if d.tmdb == nil || !d.tmdb.Enabled() {
|
if d.tmdb == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve API key from config or database
|
||||||
|
apiKey := d.tmdb.resolveAPIKey(ctx)
|
||||||
|
if apiKey == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
base := d.tmdb.resolveBaseURL(ctx)
|
||||||
|
|
||||||
q := url.Values{}
|
q := url.Values{}
|
||||||
q.Set("api_key", d.tmdb.cfg.Secrets.TMDbAPIKey)
|
q.Set("api_key", apiKey)
|
||||||
q.Set("language", "zh-CN")
|
q.Set("language", "zh-CN")
|
||||||
q.Set("page", "1")
|
q.Set("page", "1")
|
||||||
u := d.tmdb.base + path + "?" + q.Encode()
|
u := base + path + "?" + q.Encode()
|
||||||
|
|
||||||
type result struct {
|
type result struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
|
|||||||
@@ -81,7 +81,9 @@ func New(cfg *config.Config, log *zap.Logger, repos *repository.Container) *Cont
|
|||||||
go sseHub.Run()
|
go sseHub.Run()
|
||||||
|
|
||||||
probe := NewFFprobeService(cfg, log)
|
probe := NewFFprobeService(cfg, log)
|
||||||
tmdb := NewTMDbProvider(cfg, log)
|
crypto := NewCryptoService(cfg.Secrets.JWTSecret, log)
|
||||||
|
apiConfig := NewAPIConfigService(log, repos, crypto)
|
||||||
|
tmdb := NewTMDbProvider(cfg, log, apiConfig)
|
||||||
bangumi := NewBangumiProvider(cfg, log)
|
bangumi := NewBangumiProvider(cfg, log)
|
||||||
thetvdb := NewTheTVDBProvider(cfg, log)
|
thetvdb := NewTheTVDBProvider(cfg, log)
|
||||||
fanart := NewFanartProvider(cfg, log)
|
fanart := NewFanartProvider(cfg, log)
|
||||||
@@ -94,8 +96,6 @@ func New(cfg *config.Config, log *zap.Logger, repos *repository.Container) *Cont
|
|||||||
watcher := NewWatcherService(log, repos, scanner)
|
watcher := NewWatcherService(log, repos, scanner)
|
||||||
nfo := NewNFOService(log, repos)
|
nfo := NewNFOService(log, repos)
|
||||||
ai := NewAIService(cfg, log)
|
ai := NewAIService(cfg, log)
|
||||||
crypto := NewCryptoService(cfg.Secrets.JWTSecret, log)
|
|
||||||
apiConfig := NewAPIConfigService(log, repos, crypto)
|
|
||||||
duplicate := NewDuplicateService(log, repos, hub)
|
duplicate := NewDuplicateService(log, repos, hub)
|
||||||
filemanager := NewFileManagerService(cfg, log, repos)
|
filemanager := NewFileManagerService(cfg, log, repos)
|
||||||
dlna := NewDLNAService(log)
|
dlna := NewDLNAService(log)
|
||||||
|
|||||||
+69
-17
@@ -31,16 +31,19 @@ import (
|
|||||||
|
|
||||||
// TMDbProvider talks to https://api.themoviedb.org/3.
|
// TMDbProvider talks to https://api.themoviedb.org/3.
|
||||||
type TMDbProvider struct {
|
type TMDbProvider struct {
|
||||||
cfg *config.Config
|
cfg *config.Config
|
||||||
log *zap.Logger
|
log *zap.Logger
|
||||||
client *http.Client
|
client *http.Client
|
||||||
base string
|
base string
|
||||||
imgCDN string
|
imgCDN string
|
||||||
|
apiConfig *APIConfigService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTMDbProvider is the constructor. APIBase / image CDN can be overridden
|
// NewTMDbProvider is the constructor. APIBase / image CDN can be overridden
|
||||||
// via secrets.tmdb_api_proxy + tmdb_image_proxy for users behind GFW.
|
// via secrets.tmdb_api_proxy + tmdb_image_proxy for users behind GFW.
|
||||||
func NewTMDbProvider(cfg *config.Config, log *zap.Logger) *TMDbProvider {
|
// apiConfig is optional; when non-nil, the provider will also check the
|
||||||
|
// api_configs table for TMDB API key.
|
||||||
|
func NewTMDbProvider(cfg *config.Config, log *zap.Logger, apiConfig *APIConfigService) *TMDbProvider {
|
||||||
base := cfg.Secrets.TMDbAPIProxy
|
base := cfg.Secrets.TMDbAPIProxy
|
||||||
if base == "" {
|
if base == "" {
|
||||||
base = "https://api.themoviedb.org/3"
|
base = "https://api.themoviedb.org/3"
|
||||||
@@ -50,16 +53,61 @@ func NewTMDbProvider(cfg *config.Config, log *zap.Logger) *TMDbProvider {
|
|||||||
img = "https://image.tmdb.org/t/p"
|
img = "https://image.tmdb.org/t/p"
|
||||||
}
|
}
|
||||||
return &TMDbProvider{
|
return &TMDbProvider{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
log: log,
|
log: log,
|
||||||
base: base,
|
apiConfig: apiConfig,
|
||||||
imgCDN: img,
|
base: base,
|
||||||
client: &http.Client{Timeout: 15 * time.Second},
|
imgCDN: img,
|
||||||
|
client: &http.Client{Timeout: 15 * time.Second},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enabled reports whether the operator has supplied an API key.
|
// Enabled reports whether the operator has supplied an API key.
|
||||||
func (t *TMDbProvider) Enabled() bool { return t.cfg.Secrets.TMDbAPIKey != "" }
|
// It checks both the config file and the database (via apiConfig).
|
||||||
|
func (t *TMDbProvider) Enabled() bool {
|
||||||
|
// Fast path: check config
|
||||||
|
if t.cfg.Secrets.TMDbAPIKey != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Secondary check: if we have apiConfig, the key might be in the database
|
||||||
|
// We can't query the database here (no ctx), so we rely on the caller
|
||||||
|
// to check properly before making API calls.
|
||||||
|
// The actual key resolution happens in resolveAPIKey(ctx).
|
||||||
|
return t.apiConfig != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveAPIKey returns the TMDb API key, checking config first, then database.
|
||||||
|
func (t *TMDbProvider) resolveAPIKey(ctx context.Context) string {
|
||||||
|
// Check config first (fast path)
|
||||||
|
if t.cfg.Secrets.TMDbAPIKey != "" {
|
||||||
|
return t.cfg.Secrets.TMDbAPIKey
|
||||||
|
}
|
||||||
|
// Fall back to database
|
||||||
|
if t.apiConfig != nil {
|
||||||
|
resolved, err := t.apiConfig.Resolve(ctx, "tmdb")
|
||||||
|
if err == nil && resolved.APIKey != "" {
|
||||||
|
return resolved.APIKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveBaseURL returns the TMDb base URL, checking config first, then database.
|
||||||
|
func (t *TMDbProvider) resolveBaseURL(ctx context.Context) string {
|
||||||
|
// Check config first
|
||||||
|
base := t.cfg.Secrets.TMDbAPIProxy
|
||||||
|
if base == "" {
|
||||||
|
base = "https://api.themoviedb.org/3"
|
||||||
|
}
|
||||||
|
// Override from database if available
|
||||||
|
if t.apiConfig != nil {
|
||||||
|
resolved, err := t.apiConfig.Resolve(ctx, "tmdb")
|
||||||
|
if err == nil && resolved.BaseURL != "" {
|
||||||
|
base = resolved.BaseURL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
// Match describes a successful metadata match. The same struct is reused
|
// Match describes a successful metadata match. The same struct is reused
|
||||||
// across providers; provider-specific IDs sit side-by-side so the scraper
|
// across providers; provider-specific IDs sit side-by-side so the scraper
|
||||||
@@ -78,22 +126,26 @@ type Match struct {
|
|||||||
// SearchMovie issues `/search/movie` and returns the best match, or nil
|
// SearchMovie issues `/search/movie` and returns the best match, or nil
|
||||||
// when no result is found. The `year` argument is optional (0 = any).
|
// when no result is found. The `year` argument is optional (0 = any).
|
||||||
func (t *TMDbProvider) SearchMovie(ctx context.Context, query string, year int) (*Match, error) {
|
func (t *TMDbProvider) SearchMovie(ctx context.Context, query string, year int) (*Match, error) {
|
||||||
if !t.Enabled() {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
if query == "" {
|
if query == "" {
|
||||||
return nil, errors.New("empty query")
|
return nil, errors.New("empty query")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve API key from config or database
|
||||||
|
apiKey := t.resolveAPIKey(ctx)
|
||||||
|
if apiKey == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
base := t.resolveBaseURL(ctx)
|
||||||
|
|
||||||
q := url.Values{}
|
q := url.Values{}
|
||||||
q.Set("api_key", t.cfg.Secrets.TMDbAPIKey)
|
q.Set("api_key", apiKey)
|
||||||
q.Set("query", query)
|
q.Set("query", query)
|
||||||
q.Set("language", "zh-CN")
|
q.Set("language", "zh-CN")
|
||||||
q.Set("include_adult", "false")
|
q.Set("include_adult", "false")
|
||||||
if year > 0 {
|
if year > 0 {
|
||||||
q.Set("year", fmt.Sprintf("%d", year))
|
q.Set("year", fmt.Sprintf("%d", year))
|
||||||
}
|
}
|
||||||
u := t.base + "/search/movie?" + q.Encode()
|
u := base + "/search/movie?" + q.Encode()
|
||||||
|
|
||||||
type result struct {
|
type result struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
|
|||||||
Reference in New Issue
Block a user