From be8373de4080b5f1397fffe8f5bb1851cf01d9fd Mon Sep 17 00:00:00 2001 From: mtvpls Date: Sat, 17 Jan 2026 18:04:58 +0800 Subject: [PATCH] =?UTF-8?q?tmdb=E6=94=AF=E6=8C=81=E8=AE=BE=E7=BD=AEbaseurl?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/admin/page.tsx | 24 +++++++- src/app/api/admin/site/route.ts | 3 + src/app/api/tmdb-details/route.ts | 8 ++- src/app/api/tmdb-recommendations/route.ts | 7 ++- src/app/api/tmdb/trending/route.ts | 5 +- src/app/api/tmdb/upcoming/route.ts | 3 +- src/lib/admin.types.ts | 1 + src/lib/config.ts | 1 + src/lib/tmdb.client.ts | 74 ++++++++++++++++------- 9 files changed, 95 insertions(+), 31 deletions(-) diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 673b045..d76c5eb 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -7384,7 +7384,7 @@ const SiteConfigComponent = ({ {/* TMDB Proxy */}
+ + {/* TMDB Reverse Proxy */} +
+ + + setSiteSettings((prev) => ({ + ...prev, + TMDBReverseProxy: e.target.value, + })) + } + 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 反向代理 Base URL(可选) +

+
{/* Pansou 配置 */} diff --git a/src/app/api/admin/site/route.ts b/src/app/api/admin/site/route.ts index 6bcfc05..800795c 100644 --- a/src/app/api/admin/site/route.ts +++ b/src/app/api/admin/site/route.ts @@ -43,6 +43,7 @@ export async function POST(request: NextRequest) { DanmakuApiToken, TMDBApiKey, TMDBProxy, + TMDBReverseProxy, BannerDataSource, RecommendationDataSource, PansouApiUrl, @@ -124,6 +125,7 @@ export async function POST(request: NextRequest) { typeof DanmakuApiToken !== 'string' || (TMDBApiKey !== undefined && typeof TMDBApiKey !== 'string') || (TMDBProxy !== undefined && typeof TMDBProxy !== 'string') || + (TMDBReverseProxy !== undefined && typeof TMDBReverseProxy !== 'string') || (BannerDataSource !== undefined && typeof BannerDataSource !== 'string') || (RecommendationDataSource !== undefined && typeof RecommendationDataSource !== 'string') || typeof EnableComments !== 'boolean' || @@ -175,6 +177,7 @@ export async function POST(request: NextRequest) { DanmakuApiToken, TMDBApiKey, TMDBProxy, + TMDBReverseProxy, BannerDataSource, RecommendationDataSource, PansouApiUrl, diff --git a/src/app/api/tmdb-details/route.ts b/src/app/api/tmdb-details/route.ts index 531e4a8..7f48dcc 100644 --- a/src/app/api/tmdb-details/route.ts +++ b/src/app/api/tmdb-details/route.ts @@ -61,6 +61,7 @@ export async function GET(request: NextRequest) { const config = await getConfig(); const tmdbApiKey = config.SiteConfig.TMDBApiKey; const tmdbProxy = config.SiteConfig.TMDBProxy; + const tmdbReverseProxy = config.SiteConfig.TMDBReverseProxy; if (!tmdbApiKey) { return NextResponse.json( @@ -94,7 +95,8 @@ export async function GET(request: NextRequest) { const searchResult = await searchTMDBMulti( tmdbApiKey, cleanedTitle, - tmdbProxy + tmdbProxy, + tmdbReverseProxy ); if (searchResult.code !== 200 || !searchResult.results.length) { @@ -134,9 +136,9 @@ export async function GET(request: NextRequest) { // 获取详情 let detailsResult; if (mediaType === 'movie') { - detailsResult = await getTMDBMovieDetails(tmdbApiKey, tmdbId, tmdbProxy); + detailsResult = await getTMDBMovieDetails(tmdbApiKey, tmdbId, tmdbProxy, tmdbReverseProxy); } else { - detailsResult = await getTMDBTVDetails(tmdbApiKey, tmdbId, tmdbProxy); + detailsResult = await getTMDBTVDetails(tmdbApiKey, tmdbId, tmdbProxy, tmdbReverseProxy); } if (detailsResult.code !== 200 || !detailsResult.details) { diff --git a/src/app/api/tmdb-recommendations/route.ts b/src/app/api/tmdb-recommendations/route.ts index 3b1b43d..08e8690 100644 --- a/src/app/api/tmdb-recommendations/route.ts +++ b/src/app/api/tmdb-recommendations/route.ts @@ -62,6 +62,7 @@ export async function GET(request: NextRequest) { const config = await getConfig(); const tmdbApiKey = config.SiteConfig.TMDBApiKey; const tmdbProxy = config.SiteConfig.TMDBProxy; + const tmdbReverseProxy = config.SiteConfig.TMDBReverseProxy; if (!tmdbApiKey) { return NextResponse.json( @@ -90,7 +91,7 @@ export async function GET(request: NextRequest) { mediaType = cached.data.mediaType; } else { // 搜索TMDB - const searchResult = await searchTMDBMulti(tmdbApiKey, cleanedTitle, tmdbProxy); + const searchResult = await searchTMDBMulti(tmdbApiKey, cleanedTitle, tmdbProxy, tmdbReverseProxy); if (searchResult.code !== 200 || !searchResult.results.length) { return NextResponse.json( @@ -145,8 +146,8 @@ export async function GET(request: NextRequest) { // 获取推荐 const recommendationsResult = mediaType === 'movie' - ? await getTMDBMovieRecommendations(tmdbApiKey, tmdbId, tmdbProxy) - : await getTMDBTVRecommendations(tmdbApiKey, tmdbId, tmdbProxy); + ? await getTMDBMovieRecommendations(tmdbApiKey, tmdbId, tmdbProxy, tmdbReverseProxy) + : await getTMDBTVRecommendations(tmdbApiKey, tmdbId, tmdbProxy, tmdbReverseProxy); if (recommendationsResult.code !== 200) { return NextResponse.json( diff --git a/src/app/api/tmdb/trending/route.ts b/src/app/api/tmdb/trending/route.ts index cf54406..e23f49c 100644 --- a/src/app/api/tmdb/trending/route.ts +++ b/src/app/api/tmdb/trending/route.ts @@ -56,6 +56,7 @@ export async function GET() { // 使用TMDB数据源(默认) const apiKey = config.SiteConfig?.TMDBApiKey; const proxy = config.SiteConfig?.TMDBProxy; + const reverseProxy = config.SiteConfig?.TMDBReverseProxy; if (!apiKey) { return NextResponse.json( @@ -65,13 +66,13 @@ export async function GET() { } // 获取热门内容 - result = await getTMDBTrendingContent(apiKey, proxy); + result = await getTMDBTrendingContent(apiKey, proxy, reverseProxy); // 为每个项目获取视频数据 if (result.code === 200 && result.list) { const itemsWithVideos = await Promise.all( result.list.map(async (item: any) => { - const videoKey = await getTMDBVideos(apiKey, item.media_type, item.id, proxy); + const videoKey = await getTMDBVideos(apiKey, item.media_type, item.id, proxy, reverseProxy); return { ...item, video_key: videoKey }; }) ); diff --git a/src/app/api/tmdb/upcoming/route.ts b/src/app/api/tmdb/upcoming/route.ts index 5486d20..e78b6ea 100644 --- a/src/app/api/tmdb/upcoming/route.ts +++ b/src/app/api/tmdb/upcoming/route.ts @@ -28,6 +28,7 @@ export async function GET(request: NextRequest) { const config = await getConfig(); const tmdbApiKey = config.SiteConfig?.TMDBApiKey; const tmdbProxy = config.SiteConfig?.TMDBProxy; + const tmdbReverseProxy = config.SiteConfig?.TMDBReverseProxy; if (!tmdbApiKey) { return NextResponse.json( @@ -37,7 +38,7 @@ export async function GET(request: NextRequest) { } // 调用TMDB API获取数据 - const result = await getTMDBUpcomingContent(tmdbApiKey, tmdbProxy); + const result = await getTMDBUpcomingContent(tmdbApiKey, tmdbProxy, tmdbReverseProxy); if (result.code !== 200) { return NextResponse.json( diff --git a/src/lib/admin.types.ts b/src/lib/admin.types.ts index 1feb7bb..778f1e8 100644 --- a/src/lib/admin.types.ts +++ b/src/lib/admin.types.ts @@ -22,6 +22,7 @@ export interface AdminConfig { // TMDB配置 TMDBApiKey?: string; TMDBProxy?: string; + TMDBReverseProxy?: string; BannerDataSource?: string; // 轮播图数据源:TMDB、TX 或 Douban RecommendationDataSource?: string; // 更多推荐数据源:Douban、TMDB、Mixed、MixedSmart // Pansou配置 diff --git a/src/lib/config.ts b/src/lib/config.ts index a6a99d6..8d8d86a 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -226,6 +226,7 @@ async function getInitConfig(configFile: string, subConfig: { // TMDB配置 TMDBApiKey: '', TMDBProxy: '', + TMDBReverseProxy: '', // 评论功能开关 EnableComments: false, }, diff --git a/src/lib/tmdb.client.ts b/src/lib/tmdb.client.ts index 404940a..f546591 100644 --- a/src/lib/tmdb.client.ts +++ b/src/lib/tmdb.client.ts @@ -3,6 +3,9 @@ import { HttpsProxyAgent } from 'https-proxy-agent'; import nodeFetch from 'node-fetch'; +// TMDB API 默认 Base URL +const DEFAULT_TMDB_BASE_URL = 'https://api.themoviedb.org/3'; + // TMDB API Key 轮询管理 let currentKeyIndex = 0; @@ -77,13 +80,15 @@ interface TMDBTVAiringTodayResponse { * @param page - 页码 * @param region - 地区代码,默认 CN (中国) * @param proxy - 代理服务器地址 + * @param reverseProxyBaseUrl - 反代 Base URL * @returns 即将上映的电影列表 */ export async function getTMDBUpcomingMovies( apiKey: string, page: number = 1, region: string = 'CN', - proxy?: string + proxy?: string, + reverseProxyBaseUrl?: string ): Promise<{ code: number; list: TMDBMovie[] }> { try { const actualKey = getNextApiKey(apiKey); @@ -91,7 +96,8 @@ export async function getTMDBUpcomingMovies( return { code: 400, list: [] }; } - const url = `https://api.themoviedb.org/3/movie/upcoming?api_key=${actualKey}&language=zh-CN&page=${page}®ion=${region}`; + const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; + const url = `${baseUrl}/movie/upcoming?api_key=${actualKey}&language=zh-CN&page=${page}®ion=${region}`; const fetchOptions: any = proxy ? { agent: new HttpsProxyAgent(proxy, { @@ -129,12 +135,14 @@ export async function getTMDBUpcomingMovies( * @param apiKey - TMDB API Key * @param page - 页码 * @param proxy - 代理服务器地址 + * @param reverseProxyBaseUrl - 反代 Base URL * @returns 正在播出的电视剧列表 */ export async function getTMDBUpcomingTVShows( apiKey: string, page: number = 1, - proxy?: string + proxy?: string, + reverseProxyBaseUrl?: string ): Promise<{ code: number; list: TMDBTVShow[] }> { try { const actualKey = getNextApiKey(apiKey); @@ -143,7 +151,8 @@ export async function getTMDBUpcomingTVShows( } // 使用 on_the_air 接口获取正在播出的电视剧 - const url = `https://api.themoviedb.org/3/tv/on_the_air?api_key=${actualKey}&language=zh-CN&page=${page}`; + const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; + const url = `${baseUrl}/tv/on_the_air?api_key=${actualKey}&language=zh-CN&page=${page}`; const fetchOptions: any = proxy ? { agent: new HttpsProxyAgent(proxy, { @@ -180,11 +189,13 @@ export async function getTMDBUpcomingTVShows( * 获取即将上映/播出的内容(电影+电视剧) * @param apiKey - TMDB API Key * @param proxy - 代理服务器地址 + * @param reverseProxyBaseUrl - 反代 Base URL * @returns 统一格式的即将上映/播出列表 */ export async function getTMDBUpcomingContent( apiKey: string, - proxy?: string + proxy?: string, + reverseProxyBaseUrl?: string ): Promise<{ code: number; list: TMDBItem[] }> { try { if (!apiKey) { @@ -193,8 +204,8 @@ export async function getTMDBUpcomingContent( // 并行获取电影和电视剧数据 const [moviesResult, tvShowsResult] = await Promise.all([ - getTMDBUpcomingMovies(apiKey, 1, 'CN', proxy), - getTMDBUpcomingTVShows(apiKey, 1, proxy), + getTMDBUpcomingMovies(apiKey, 1, 'CN', proxy, reverseProxyBaseUrl), + getTMDBUpcomingTVShows(apiKey, 1, proxy, reverseProxyBaseUrl), ]); // 检查是否有错误 @@ -262,13 +273,15 @@ export async function getTMDBUpcomingContent( * @param mediaType - 媒体类型 (movie 或 tv) * @param mediaId - 媒体ID * @param proxy - 代理服务器地址 + * @param reverseProxyBaseUrl - 反代 Base URL * @returns YouTube视频key(只返回预告片) */ export async function getTMDBVideos( apiKey: string, mediaType: 'movie' | 'tv', mediaId: number, - proxy?: string + proxy?: string, + reverseProxyBaseUrl?: string ): Promise { try { const actualKey = getNextApiKey(apiKey); @@ -276,7 +289,8 @@ export async function getTMDBVideos( return null; } - const url = `https://api.themoviedb.org/3/${mediaType}/${mediaId}/videos?api_key=${actualKey}`; + const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; + const url = `${baseUrl}/${mediaType}/${mediaId}/videos?api_key=${actualKey}`; const fetchOptions: any = proxy ? { agent: new HttpsProxyAgent(proxy, { @@ -314,11 +328,13 @@ export async function getTMDBVideos( * 获取热门内容(电影+电视剧) * @param apiKey - TMDB API Key * @param proxy - 代理服务器地址 + * @param reverseProxyBaseUrl - 反代 Base URL * @returns 热门内容列表 */ export async function getTMDBTrendingContent( apiKey: string, - proxy?: string + proxy?: string, + reverseProxyBaseUrl?: string ): Promise<{ code: number; list: TMDBItem[] }> { try { const actualKey = getNextApiKey(apiKey); @@ -327,7 +343,8 @@ export async function getTMDBTrendingContent( } // 获取本周热门内容(电影+电视剧) - const url = `https://api.themoviedb.org/3/trending/all/week?api_key=${actualKey}&language=zh-CN`; + const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; + const url = `${baseUrl}/trending/all/week?api_key=${actualKey}&language=zh-CN`; const fetchOptions: any = proxy ? { agent: new HttpsProxyAgent(proxy, { @@ -442,12 +459,14 @@ export function getGenreNames(genreIds: number[] = [], limit: number = 2): strin * @param apiKey - TMDB API Key * @param query - 搜索关键词 * @param proxy - 代理服务器地址 + * @param reverseProxyBaseUrl - 反代 Base URL * @returns 搜索结果列表 */ export async function searchTMDBMulti( apiKey: string, query: string, - proxy?: string + proxy?: string, + reverseProxyBaseUrl?: string ): Promise<{ code: number; results: any[] }> { try { const actualKey = getNextApiKey(apiKey); @@ -455,7 +474,8 @@ export async function searchTMDBMulti( return { code: 400, results: [] }; } - const url = `https://api.themoviedb.org/3/search/multi?api_key=${actualKey}&language=zh-CN&query=${encodeURIComponent(query)}&page=1`; + const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; + const url = `${baseUrl}/search/multi?api_key=${actualKey}&language=zh-CN&query=${encodeURIComponent(query)}&page=1`; const fetchOptions: any = proxy ? { agent: new HttpsProxyAgent(proxy, { @@ -492,12 +512,14 @@ export async function searchTMDBMulti( * @param apiKey - TMDB API Key * @param movieId - 电影ID * @param proxy - 代理服务器地址 + * @param reverseProxyBaseUrl - 反代 Base URL * @returns 推荐列表 */ export async function getTMDBMovieRecommendations( apiKey: string, movieId: number, - proxy?: string + proxy?: string, + reverseProxyBaseUrl?: string ): Promise<{ code: number; results: TMDBMovie[] }> { try { const actualKey = getNextApiKey(apiKey); @@ -505,7 +527,8 @@ export async function getTMDBMovieRecommendations( return { code: 400, results: [] }; } - const url = `https://api.themoviedb.org/3/movie/${movieId}/recommendations?api_key=${actualKey}&language=zh-CN&page=1`; + const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; + const url = `${baseUrl}/movie/${movieId}/recommendations?api_key=${actualKey}&language=zh-CN&page=1`; const fetchOptions: any = proxy ? { agent: new HttpsProxyAgent(proxy, { @@ -542,12 +565,14 @@ export async function getTMDBMovieRecommendations( * @param apiKey - TMDB API Key * @param tvId - 电视剧ID * @param proxy - 代理服务器地址 + * @param reverseProxyBaseUrl - 反代 Base URL * @returns 推荐列表 */ export async function getTMDBTVRecommendations( apiKey: string, tvId: number, - proxy?: string + proxy?: string, + reverseProxyBaseUrl?: string ): Promise<{ code: number; results: TMDBTVShow[] }> { try { const actualKey = getNextApiKey(apiKey); @@ -555,7 +580,8 @@ export async function getTMDBTVRecommendations( return { code: 400, results: [] }; } - const url = `https://api.themoviedb.org/3/tv/${tvId}/recommendations?api_key=${actualKey}&language=zh-CN&page=1`; + const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; + const url = `${baseUrl}/tv/${tvId}/recommendations?api_key=${actualKey}&language=zh-CN&page=1`; const fetchOptions: any = proxy ? { agent: new HttpsProxyAgent(proxy, { @@ -592,12 +618,14 @@ export async function getTMDBTVRecommendations( * @param apiKey - TMDB API Key * @param movieId - 电影ID * @param proxy - 代理服务器地址 + * @param reverseProxyBaseUrl - 反代 Base URL * @returns 电影详情 */ export async function getTMDBMovieDetails( apiKey: string, movieId: number, - proxy?: string + proxy?: string, + reverseProxyBaseUrl?: string ): Promise<{ code: number; details: any }> { try { const actualKey = getNextApiKey(apiKey); @@ -605,7 +633,8 @@ export async function getTMDBMovieDetails( return { code: 400, details: null }; } - const url = `https://api.themoviedb.org/3/movie/${movieId}?api_key=${actualKey}&language=zh-CN`; + const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; + const url = `${baseUrl}/movie/${movieId}?api_key=${actualKey}&language=zh-CN`; const fetchOptions: any = proxy ? { agent: new HttpsProxyAgent(proxy, { @@ -642,12 +671,14 @@ export async function getTMDBMovieDetails( * @param apiKey - TMDB API Key * @param tvId - 电视剧ID * @param proxy - 代理服务器地址 + * @param reverseProxyBaseUrl - 反代 Base URL * @returns 电视剧详情 */ export async function getTMDBTVDetails( apiKey: string, tvId: number, - proxy?: string + proxy?: string, + reverseProxyBaseUrl?: string ): Promise<{ code: number; details: any }> { try { const actualKey = getNextApiKey(apiKey); @@ -655,7 +686,8 @@ export async function getTMDBTVDetails( return { code: 400, details: null }; } - const url = `https://api.themoviedb.org/3/tv/${tvId}?api_key=${actualKey}&language=zh-CN`; + const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; + const url = `${baseUrl}/tv/${tvId}?api_key=${actualKey}&language=zh-CN`; const fetchOptions: any = proxy ? { agent: new HttpsProxyAgent(proxy, {