From 69c45352cc2df2e0843bd541746c4b34f3bb65fa Mon Sep 17 00:00:00 2001 From: mtvpls Date: Wed, 31 Dec 2025 01:11:47 +0800 Subject: [PATCH] =?UTF-8?q?tmdb=E5=A2=9E=E5=8A=A0=E8=BD=AE=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/admin/page.tsx | 22 ++++++++++-- src/app/api/tmdb/search/route.ts | 6 ++-- src/lib/ai-orchestrator.ts | 6 ++-- src/lib/db.ts | 1 + src/lib/tmdb.client.ts | 62 +++++++++++++++++++++++--------- src/lib/tmdb.search.ts | 16 +++++---- 6 files changed, 85 insertions(+), 28 deletions(-) diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 5b76950..6902521 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -6382,7 +6382,7 @@ const SiteConfigComponent = ({ setSiteSettings((prev) => ({ @@ -6393,7 +6393,7 @@ const SiteConfigComponent = ({ className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent' />

- 配置后首页将显示 TMDB 即将上映电影。获取 API Key 请访问{' '} + 配置后首页将显示 TMDB 即将上映电影。支持配置多个 API Key(用英文逗号分隔)以实现轮询,避免单个 Key 请求限制。获取 API Key 请访问{' '} + {/* TMDB 未配置提示 */} + {config && !config.SiteConfig.TMDBApiKey && ( +

+
+
+ + + +
+
+

+ 未配置 TMDB API Key,配置后可获得更丰富的影视信息和推荐内容 +

+
+
+
+ )} + {/* 配置文件标签 - 仅站长可见 */} {role === 'owner' && ( { try { - if (!tmdbApiKey) { + const actualKey = getNextApiKey(tmdbApiKey || ''); + if (!actualKey) { console.log('⚠️ TMDB API Key 未配置,跳过TMDB数据获取'); return null; } @@ -263,7 +265,7 @@ async function fetchTMDBData( // 使用 TMDB API 获取详情 // TMDB API: https://api.themoviedb.org/3/{type}/{id} - const url = `https://api.themoviedb.org/3/${params.type}/${params.id}?api_key=${tmdbApiKey}&language=zh-CN&append_to_response=keywords,similar`; + const url = `https://api.themoviedb.org/3/${params.type}/${params.id}?api_key=${actualKey}&language=zh-CN&append_to_response=keywords,similar`; console.log('📡 获取TMDB详情:', params.type, params.id); diff --git a/src/lib/db.ts b/src/lib/db.ts index f4df91f..0e2b109 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -183,6 +183,7 @@ export class DbManager { enabledApis?: string[]; created_at: number; playrecord_migrated?: boolean; + favorite_migrated?: boolean; } | null> { if (typeof (this.storage as any).getUserInfoV2 === 'function') { return (this.storage as any).getUserInfoV2(userName); diff --git a/src/lib/tmdb.client.ts b/src/lib/tmdb.client.ts index 52f6f03..26174fe 100644 --- a/src/lib/tmdb.client.ts +++ b/src/lib/tmdb.client.ts @@ -3,6 +3,28 @@ import { HttpsProxyAgent } from 'https-proxy-agent'; import nodeFetch from 'node-fetch'; +// TMDB API Key 轮询管理 +let currentKeyIndex = 0; + +/** + * 解析并获取下一个可用的 TMDB API Key + * @param apiKeys - API Key 字符串(支持逗号分隔的多个key) + * @returns 当前应使用的 API Key + */ +export function getNextApiKey(apiKeys: string): string { + if (!apiKeys) return ''; + + const keys = apiKeys.split(',').map(k => k.trim()).filter(k => k); + if (keys.length === 0) return ''; + if (keys.length === 1) return keys[0]; + + // 轮询获取下一个key + const key = keys[currentKeyIndex % keys.length]; + currentKeyIndex = (currentKeyIndex + 1) % keys.length; + + return key; +} + export interface TMDBMovie { id: number; title: string; @@ -63,11 +85,12 @@ export async function getTMDBUpcomingMovies( proxy?: string ): Promise<{ code: number; list: TMDBMovie[] }> { try { - if (!apiKey) { + const actualKey = getNextApiKey(apiKey); + if (!actualKey) { return { code: 400, list: [] }; } - const url = `https://api.themoviedb.org/3/movie/upcoming?api_key=${apiKey}&language=zh-CN&page=${page}®ion=${region}`; + const url = `https://api.themoviedb.org/3/movie/upcoming?api_key=${actualKey}&language=zh-CN&page=${page}®ion=${region}`; const fetchOptions: any = proxy ? { agent: new HttpsProxyAgent(proxy, { @@ -113,12 +136,13 @@ export async function getTMDBUpcomingTVShows( proxy?: string ): Promise<{ code: number; list: TMDBTVShow[] }> { try { - if (!apiKey) { + const actualKey = getNextApiKey(apiKey); + if (!actualKey) { return { code: 400, list: [] }; } // 使用 on_the_air 接口获取正在播出的电视剧 - const url = `https://api.themoviedb.org/3/tv/on_the_air?api_key=${apiKey}&language=zh-CN&page=${page}`; + const url = `https://api.themoviedb.org/3/tv/on_the_air?api_key=${actualKey}&language=zh-CN&page=${page}`; const fetchOptions: any = proxy ? { agent: new HttpsProxyAgent(proxy, { @@ -242,12 +266,13 @@ export async function getTMDBTrendingContent( proxy?: string ): Promise<{ code: number; list: TMDBItem[] }> { try { - if (!apiKey) { + const actualKey = getNextApiKey(apiKey); + if (!actualKey) { return { code: 400, list: [] }; } // 获取本周热门内容(电影+电视剧) - const url = `https://api.themoviedb.org/3/trending/all/week?api_key=${apiKey}&language=zh-CN`; + const url = `https://api.themoviedb.org/3/trending/all/week?api_key=${actualKey}&language=zh-CN`; const fetchOptions: any = proxy ? { agent: new HttpsProxyAgent(proxy, { @@ -370,11 +395,12 @@ export async function searchTMDBMulti( proxy?: string ): Promise<{ code: number; results: any[] }> { try { - if (!apiKey || !query) { + const actualKey = getNextApiKey(apiKey); + if (!actualKey || !query) { return { code: 400, results: [] }; } - const url = `https://api.themoviedb.org/3/search/multi?api_key=${apiKey}&language=zh-CN&query=${encodeURIComponent(query)}&page=1`; + const url = `https://api.themoviedb.org/3/search/multi?api_key=${actualKey}&language=zh-CN&query=${encodeURIComponent(query)}&page=1`; const fetchOptions: any = proxy ? { agent: new HttpsProxyAgent(proxy, { @@ -419,11 +445,12 @@ export async function getTMDBMovieRecommendations( proxy?: string ): Promise<{ code: number; results: TMDBMovie[] }> { try { - if (!apiKey || !movieId) { + const actualKey = getNextApiKey(apiKey); + if (!actualKey || !movieId) { return { code: 400, results: [] }; } - const url = `https://api.themoviedb.org/3/movie/${movieId}/recommendations?api_key=${apiKey}&language=zh-CN&page=1`; + const url = `https://api.themoviedb.org/3/movie/${movieId}/recommendations?api_key=${actualKey}&language=zh-CN&page=1`; const fetchOptions: any = proxy ? { agent: new HttpsProxyAgent(proxy, { @@ -468,11 +495,12 @@ export async function getTMDBTVRecommendations( proxy?: string ): Promise<{ code: number; results: TMDBTVShow[] }> { try { - if (!apiKey || !tvId) { + const actualKey = getNextApiKey(apiKey); + if (!actualKey || !tvId) { return { code: 400, results: [] }; } - const url = `https://api.themoviedb.org/3/tv/${tvId}/recommendations?api_key=${apiKey}&language=zh-CN&page=1`; + const url = `https://api.themoviedb.org/3/tv/${tvId}/recommendations?api_key=${actualKey}&language=zh-CN&page=1`; const fetchOptions: any = proxy ? { agent: new HttpsProxyAgent(proxy, { @@ -517,11 +545,12 @@ export async function getTMDBMovieDetails( proxy?: string ): Promise<{ code: number; details: any }> { try { - if (!apiKey) { + const actualKey = getNextApiKey(apiKey); + if (!actualKey) { return { code: 400, details: null }; } - const url = `https://api.themoviedb.org/3/movie/${movieId}?api_key=${apiKey}&language=zh-CN`; + const url = `https://api.themoviedb.org/3/movie/${movieId}?api_key=${actualKey}&language=zh-CN`; const fetchOptions: any = proxy ? { agent: new HttpsProxyAgent(proxy, { @@ -566,11 +595,12 @@ export async function getTMDBTVDetails( proxy?: string ): Promise<{ code: number; details: any }> { try { - if (!apiKey) { + const actualKey = getNextApiKey(apiKey); + if (!actualKey) { return { code: 400, details: null }; } - const url = `https://api.themoviedb.org/3/tv/${tvId}?api_key=${apiKey}&language=zh-CN`; + const url = `https://api.themoviedb.org/3/tv/${tvId}?api_key=${actualKey}&language=zh-CN`; const fetchOptions: any = proxy ? { agent: new HttpsProxyAgent(proxy, { diff --git a/src/lib/tmdb.search.ts b/src/lib/tmdb.search.ts index 4d321a5..9c1058e 100644 --- a/src/lib/tmdb.search.ts +++ b/src/lib/tmdb.search.ts @@ -2,6 +2,7 @@ import { HttpsProxyAgent } from 'https-proxy-agent'; import nodeFetch from 'node-fetch'; +import { getNextApiKey } from './tmdb.client'; export interface TMDBSearchResult { id: number; @@ -32,12 +33,13 @@ export async function searchTMDB( year?: number ): Promise<{ code: number; result: TMDBSearchResult | null }> { try { - if (!apiKey) { + const actualKey = getNextApiKey(apiKey); + if (!actualKey) { return { code: 400, result: null }; } // 使用 multi search 同时搜索电影和电视剧 - let url = `https://api.themoviedb.org/3/search/multi?api_key=${apiKey}&language=zh-CN&query=${encodeURIComponent(query)}&page=1`; + let url = `https://api.themoviedb.org/3/search/multi?api_key=${actualKey}&language=zh-CN&query=${encodeURIComponent(query)}&page=1`; // 如果提供了年份,添加到搜索参数中 if (year) { @@ -121,11 +123,12 @@ export async function getTVSeasons( proxy?: string ): Promise<{ code: number; seasons: TMDBSeasonInfo[] | null }> { try { - if (!apiKey) { + const actualKey = getNextApiKey(apiKey); + if (!actualKey) { return { code: 400, seasons: null }; } - const url = `https://api.themoviedb.org/3/tv/${tvId}?api_key=${apiKey}&language=zh-CN`; + const url = `https://api.themoviedb.org/3/tv/${tvId}?api_key=${actualKey}&language=zh-CN`; const fetchOptions: any = proxy ? { @@ -171,11 +174,12 @@ export async function getTVSeasonDetails( proxy?: string ): Promise<{ code: number; season: TMDBSeasonInfo | null }> { try { - if (!apiKey) { + const actualKey = getNextApiKey(apiKey); + if (!actualKey) { return { code: 400, season: null }; } - const url = `https://api.themoviedb.org/3/tv/${tvId}/season/${seasonNumber}?api_key=${apiKey}&language=zh-CN`; + const url = `https://api.themoviedb.org/3/tv/${tvId}/season/${seasonNumber}?api_key=${actualKey}&language=zh-CN`; const fetchOptions: any = proxy ? {