diff --git a/src/app/api/ai/chat/route.ts b/src/app/api/ai/chat/route.ts index 736d02d..9430bde 100644 --- a/src/app/api/ai/chat/route.ts +++ b/src/app/api/ai/chat/route.ts @@ -240,6 +240,7 @@ export async function POST(request: NextRequest) { // TMDB 配置 tmdbApiKey: adminConfig.SiteConfig.TMDBApiKey, tmdbProxy: adminConfig.SiteConfig.TMDBProxy, + tmdbReverseProxy: adminConfig.SiteConfig.TMDBReverseProxy, // 决策模型配置(固定使用自定义provider,复用主模型的API配置) enableDecisionModel: aiConfig.EnableDecisionModel, decisionProvider: 'custom', diff --git a/src/app/api/source-detail/route.ts b/src/app/api/source-detail/route.ts index 6f019ce..5dc2f8e 100644 --- a/src/app/api/source-detail/route.ts +++ b/src/app/api/source-detail/route.ts @@ -179,7 +179,8 @@ export async function GET(request: NextRequest) { client, metadataPath, config.SiteConfig.TMDBApiKey, - config.SiteConfig.TMDBProxy + config.SiteConfig.TMDBProxy, + config.SiteConfig.TMDBReverseProxy ); // 获取集数列表(使用目录路径或点击的文件路径) diff --git a/src/app/api/tmdb/detail/route.ts b/src/app/api/tmdb/detail/route.ts index d1cfb0c..8ff93eb 100644 --- a/src/app/api/tmdb/detail/route.ts +++ b/src/app/api/tmdb/detail/route.ts @@ -32,6 +32,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; const actualKey = getNextApiKey(tmdbApiKey || ''); if (!actualKey) { @@ -41,9 +42,11 @@ export async function GET(request: NextRequest) { ); } + // 使用反代代理或默认 Base URL + const baseUrl = tmdbReverseProxy || 'https://api.themoviedb.org'; // 根据类型选择API端点 const endpoint = type === 'movie' ? 'movie' : 'tv'; - const url = `https://api.themoviedb.org/3/${endpoint}/${id}?api_key=${actualKey}&language=zh-CN&append_to_response=credits`; + const url = `${baseUrl}/3/${endpoint}/${id}?api_key=${actualKey}&language=zh-CN&append_to_response=credits`; const fetchOptions: any = tmdbProxy ? { diff --git a/src/app/api/tmdb/episodes/route.ts b/src/app/api/tmdb/episodes/route.ts index e73ec6f..c00282f 100644 --- a/src/app/api/tmdb/episodes/route.ts +++ b/src/app/api/tmdb/episodes/route.ts @@ -32,6 +32,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({ error: 'TMDB API Key 未配置' }, { status: 400 }); @@ -42,7 +43,9 @@ export async function GET(request: NextRequest) { return NextResponse.json({ error: 'TMDB API Key 无效' }, { status: 400 }); } - const url = `https://api.themoviedb.org/3/tv/${id}/season/${season}?api_key=${actualKey}&language=zh-CN`; + // 使用反代代理或默认 Base URL + const baseUrl = tmdbReverseProxy || 'https://api.themoviedb.org'; + const url = `${baseUrl}/3/tv/${id}/season/${season}?api_key=${actualKey}&language=zh-CN`; const fetchOptions: any = tmdbProxy ? { diff --git a/src/app/api/tmdb/search/route.ts b/src/app/api/tmdb/search/route.ts index bb35b5f..50d12cc 100644 --- a/src/app/api/tmdb/search/route.ts +++ b/src/app/api/tmdb/search/route.ts @@ -31,6 +31,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; const actualKey = getNextApiKey(tmdbApiKey || ''); if (!actualKey) { @@ -40,8 +41,10 @@ export async function GET(request: NextRequest) { ); } + // 使用反代代理或默认 Base URL + const baseUrl = tmdbReverseProxy || 'https://api.themoviedb.org'; // 使用 multi search 同时搜索电影和电视剧 - const url = `https://api.themoviedb.org/3/search/multi?api_key=${actualKey}&language=zh-CN&query=${encodeURIComponent(query)}&page=1`; + const url = `${baseUrl}/3/search/multi?api_key=${actualKey}&language=zh-CN&query=${encodeURIComponent(query)}&page=1`; const fetchOptions: any = tmdbProxy ? { diff --git a/src/app/api/tmdb/seasons/route.ts b/src/app/api/tmdb/seasons/route.ts index c2026c7..c3cbd33 100644 --- a/src/app/api/tmdb/seasons/route.ts +++ b/src/app/api/tmdb/seasons/route.ts @@ -34,6 +34,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( @@ -42,7 +43,7 @@ export async function GET(request: NextRequest) { ); } - const result = await getTVSeasons(tmdbApiKey, tvId, tmdbProxy); + const result = await getTVSeasons(tmdbApiKey, tvId, tmdbProxy, tmdbReverseProxy); if (result.code === 200 && result.seasons) { return NextResponse.json({ diff --git a/src/app/movie-request/page.tsx b/src/app/movie-request/page.tsx index 7781eb5..2126290 100644 --- a/src/app/movie-request/page.tsx +++ b/src/app/movie-request/page.tsx @@ -7,6 +7,7 @@ import { CheckCircle, AlertCircle, Plus } from 'lucide-react'; import PageLayout from '@/components/PageLayout'; import { getTMDBImageUrl } from '@/lib/tmdb.client'; +import { processImageUrl } from '@/lib/utils'; interface TMDBResult { id: number; @@ -117,7 +118,7 @@ export default function MovieRequestPage() { const submitRequest = async (item: TMDBResult, season?: number) => { setSubmitting(true); try { - let poster = item.poster_path ? getTMDBImageUrl(item.poster_path, 'w500') : undefined; + let poster = item.poster_path ? processImageUrl(getTMDBImageUrl(item.poster_path, 'w500')) : undefined; let title = item.title || item.name || ''; if (season && seasons.length > 0) { @@ -125,7 +126,7 @@ export default function MovieRequestPage() { if (seasonData) { title = `${title} ${seasonData.name}`; if (seasonData.poster_path) { - poster = getTMDBImageUrl(seasonData.poster_path, 'w500'); + poster = processImageUrl(getTMDBImageUrl(seasonData.poster_path, 'w500')); } } } @@ -311,7 +312,7 @@ export default function MovieRequestPage() { > {item.poster_path ? ( {item.title diff --git a/src/app/page.tsx b/src/app/page.tsx index e02abc4..fbc7325 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -13,6 +13,7 @@ import { import { getDoubanCategories } from '@/lib/douban.client'; import { getTMDBImageUrl, TMDBItem } from '@/lib/tmdb.client'; import { DoubanItem } from '@/lib/types'; +import { processImageUrl } from '@/lib/utils'; import ContinueWatching from '@/components/ContinueWatching'; import PageLayout from '@/components/PageLayout'; @@ -516,7 +517,7 @@ function HomeClient() { > 0 diff --git a/src/app/play/page.tsx b/src/app/play/page.tsx index a09b118..91d3126 100644 --- a/src/app/play/page.tsx +++ b/src/app/play/page.tsx @@ -1020,7 +1020,7 @@ function PlayPageClient() { if (detCacheAge < detCacheMaxAge && data && data.backdrop) { console.log('使用缓存的TMDB详情数据'); - setTmdbBackdrop(data.backdrop); + setTmdbBackdrop(processImageUrl(data.backdrop)); // 如果没有豆瓣ID,使用TMDb数据补充 if (!videoDoubanId || videoDoubanId === 0) { @@ -1054,7 +1054,7 @@ function PlayPageClient() { const result = await response.json(); if (result.backdrop) { - setTmdbBackdrop(result.backdrop); + setTmdbBackdrop(processImageUrl(result.backdrop)); // 如果没有豆瓣ID,使用TMDb数据补充 if (!videoDoubanId || videoDoubanId === 0) { @@ -3279,7 +3279,7 @@ function PlayPageClient() { finalTitle = correction.title; } if (correction.posterPath) { - finalCover = getTMDBImageUrl(correction.posterPath); + finalCover = processImageUrl(getTMDBImageUrl(correction.posterPath)); } if (correction.overview) { finalDesc = correction.overview; @@ -4424,7 +4424,7 @@ function PlayPageClient() { setVideoTitle(correction.title); } if (correction.posterPath) { - const fullPosterUrl = getTMDBImageUrl(correction.posterPath); + const fullPosterUrl = processImageUrl(getTMDBImageUrl(correction.posterPath)); setVideoCover(fullPosterUrl); } if (correction.overview) { @@ -7856,7 +7856,7 @@ const applyCorrection = (detail: SearchResult, correction: any): SearchResult => return { ...detail, title: correction.title || detail.title, - poster: correction.posterPath ? getTMDBImageUrl(correction.posterPath) : detail.poster, + poster: correction.posterPath ? processImageUrl(getTMDBImageUrl(correction.posterPath)) : detail.poster, year: correction.releaseDate || detail.year, desc: correction.overview || detail.desc, rating: correction.voteAverage || detail.rating, diff --git a/src/components/BannerCarousel.tsx b/src/components/BannerCarousel.tsx index 3efcdef..c12bac1 100644 --- a/src/components/BannerCarousel.tsx +++ b/src/components/BannerCarousel.tsx @@ -53,8 +53,8 @@ export default function BannerCarousel({ autoPlayInterval = 5000 }: BannerCarous if (path.startsWith('http://') || path.startsWith('https://')) { return processImageUrl(path); } - // 否则使用TMDB的URL拼接 - return getTMDBImageUrl(path, 'original'); + // 否则使用TMDB的URL拼接,并通过processImageUrl处理 + return processImageUrl(getTMDBImageUrl(path, 'original')); }; // 获取视频URL(处理豆瓣视频代理) diff --git a/src/components/DetailPanel.tsx b/src/components/DetailPanel.tsx index 1a4cbfd..517e9bd 100644 --- a/src/components/DetailPanel.tsx +++ b/src/components/DetailPanel.tsx @@ -5,6 +5,7 @@ import Image from 'next/image'; import React, { useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import { getTMDBImageUrl } from '@/lib/tmdb.client'; +import { processImageUrl } from '@/lib/utils'; interface DetailPanelProps { isOpen: boolean; @@ -380,7 +381,7 @@ const DetailPanel: React.FC = ({ ? detailResult.release_date?.substring(0, 4) : detailResult.first_air_date?.substring(0, 4), poster: detailResult.poster_path - ? getTMDBImageUrl(detailResult.poster_path, 'w500') + ? processImageUrl(getTMDBImageUrl(detailResult.poster_path, 'w500')) : poster, rating: detailResult.vote_average ? { @@ -488,7 +489,7 @@ const DetailPanel: React.FC = ({ ...prev, title: episodesData.name || season?.name || prev.title, intro: episodesData.overview || season?.overview || prev.overview, - poster: season?.poster_path ? getTMDBImageUrl(season.poster_path, 'w500') : prev.poster, + poster: season?.poster_path ? processImageUrl(getTMDBImageUrl(season.poster_path, 'w500')) : prev.poster, releaseDate: episodesData.air_date || season?.air_date || prev.releaseDate, year: episodesData.air_date?.substring(0, 4) || season?.air_date?.substring(0, 4) || prev.year, episodesCount: episodesData.episodes?.length || season?.episode_count || prev.episodesCount, @@ -788,7 +789,7 @@ const DetailPanel: React.FC = ({ {season.poster_path && (
{season.name} = ({ {episode.still_path && (
{episode.name} { try { const actualKey = getNextApiKey(tmdbApiKey || ''); @@ -263,9 +264,11 @@ async function fetchTMDBData( return null; } + // 使用反代代理或默认 Base URL + const baseUrl = tmdbReverseProxy || 'https://api.themoviedb.org'; // 使用 TMDB API 获取详情 // TMDB API: https://api.themoviedb.org/3/{type}/{id} - const url = `https://api.themoviedb.org/3/${params.type}/${params.id}?api_key=${actualKey}&language=zh-CN&append_to_response=keywords,similar`; + const url = `${baseUrl}/3/${params.type}/${params.id}?api_key=${actualKey}&language=zh-CN&append_to_response=keywords,similar`; console.log('📡 获取TMDB详情:', params.type, params.id); @@ -517,6 +520,7 @@ export async function orchestrateDataSources( // TMDB 配置 tmdbApiKey?: string; tmdbProxy?: string; + tmdbReverseProxy?: string; // 决策模型配置 enableDecisionModel?: boolean; decisionProvider?: 'openai' | 'claude' | 'custom'; @@ -653,7 +657,8 @@ export async function orchestrateDataSources( type: context.type, }, config?.tmdbApiKey, - config?.tmdbProxy + config?.tmdbProxy, + config?.tmdbReverseProxy ); dataPromises.push(tmdbPromise); } diff --git a/src/lib/openlist-refresh.ts b/src/lib/openlist-refresh.ts index f59269a..065fde5 100644 --- a/src/lib/openlist-refresh.ts +++ b/src/lib/openlist-refresh.ts @@ -99,6 +99,7 @@ export async function startOpenListRefresh(clearMetaInfo: boolean = false): Prom const tmdbApiKey = config.SiteConfig.TMDBApiKey; const tmdbProxy = config.SiteConfig.TMDBProxy; + const tmdbReverseProxy = config.SiteConfig.TMDBReverseProxy; if (!tmdbApiKey) { throw new Error('TMDB API Key 未配置'); @@ -124,6 +125,7 @@ export async function startOpenListRefresh(clearMetaInfo: boolean = false): Prom rootPaths, tmdbApiKey, tmdbProxy, + tmdbReverseProxy, openListConfig.Username, openListConfig.Password, clearMetaInfo, @@ -145,6 +147,7 @@ async function performMultiRootScan( rootPaths: string[], tmdbApiKey: string, tmdbProxy: string | undefined, + tmdbReverseProxy: string | undefined, username: string, password: string, clearMetaInfo: boolean, @@ -161,6 +164,7 @@ async function performMultiRootScan( rootPath, tmdbApiKey, tmdbProxy, + tmdbReverseProxy, username, password, clearMetaInfo && i === 0, // 只在第一个根目录时清除 @@ -182,6 +186,7 @@ async function performScan( rootPath: string, tmdbApiKey: string, tmdbProxy?: string, + tmdbReverseProxy?: string, username?: string, password?: string, clearMetaInfo?: boolean, @@ -288,7 +293,7 @@ async function performScan( console.log(`[OpenList Refresh] 种子库模式 - 文件夹: ${folder.name}`); console.log(`[OpenList Refresh] 解析结果 - 标题: ${searchQuery}, 季度: ${seasonNumber}, 年份: ${year}`); - searchResult = await searchTMDB(tmdbApiKey, searchQuery, tmdbProxy, year || undefined); + searchResult = await searchTMDB(tmdbApiKey, searchQuery, tmdbProxy, year || undefined, tmdbReverseProxy); } if (scanMode === 'name' || (scanMode === 'hybrid' && (!searchResult || searchResult.code !== 200 || !searchResult.result))) { @@ -300,7 +305,7 @@ async function performScan( console.log(`[OpenList Refresh] 名字匹配模式 - 文件夹: ${folder.name}`); console.log(`[OpenList Refresh] 清理后标题: ${searchQuery}, 季度: ${seasonNumber}, 年份: ${year}`); - searchResult = await searchTMDB(tmdbApiKey, searchQuery, tmdbProxy, year || undefined); + searchResult = await searchTMDB(tmdbApiKey, searchQuery, tmdbProxy, year || undefined, tmdbReverseProxy); } if (searchResult.code === 200 && searchResult.result) { @@ -325,7 +330,8 @@ async function performScan( tmdbApiKey, result.id, seasonNumber, - tmdbProxy + tmdbProxy, + tmdbReverseProxy ); if (seasonDetails.code === 200 && seasonDetails.season) { diff --git a/src/lib/special-sources-detail.ts b/src/lib/special-sources-detail.ts index 53239b7..1cc4cfb 100644 --- a/src/lib/special-sources-detail.ts +++ b/src/lib/special-sources-detail.ts @@ -354,7 +354,8 @@ export async function getXiaoyaDetail(id: string): Promise { client, decodedDirPath, config.SiteConfig.TMDBApiKey, - config.SiteConfig.TMDBProxy + config.SiteConfig.TMDBProxy, + config.SiteConfig.TMDBReverseProxy ); // 获取集数列表 diff --git a/src/lib/tmdb.client.ts b/src/lib/tmdb.client.ts index 531c43a..e49cb10 100644 --- a/src/lib/tmdb.client.ts +++ b/src/lib/tmdb.client.ts @@ -3,8 +3,8 @@ 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 默认 Base URL(不包含 /3/,由程序拼接) +const DEFAULT_TMDB_BASE_URL = 'https://api.themoviedb.org'; // TMDB API Key 轮询管理 let currentKeyIndex = 0; @@ -97,7 +97,7 @@ export async function getTMDBUpcomingMovies( } const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; - const url = `${baseUrl}/movie/upcoming?api_key=${actualKey}&language=zh-CN&page=${page}®ion=${region}`; + const url = `${baseUrl}/3/movie/upcoming?api_key=${actualKey}&language=zh-CN&page=${page}®ion=${region}`; const fetchOptions: any = proxy ? { agent: new HttpsProxyAgent(proxy, { @@ -152,7 +152,7 @@ export async function getTMDBUpcomingTVShows( // 使用 on_the_air 接口获取正在播出的电视剧 const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; - const url = `${baseUrl}/tv/on_the_air?api_key=${actualKey}&language=zh-CN&page=${page}`; + const url = `${baseUrl}/3/tv/on_the_air?api_key=${actualKey}&language=zh-CN&page=${page}`; const fetchOptions: any = proxy ? { agent: new HttpsProxyAgent(proxy, { @@ -290,7 +290,7 @@ export async function getTMDBVideos( } const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; - const url = `${baseUrl}/${mediaType}/${mediaId}/videos?api_key=${actualKey}`; + const url = `${baseUrl}/3/${mediaType}/${mediaId}/videos?api_key=${actualKey}`; const fetchOptions: any = proxy ? { agent: new HttpsProxyAgent(proxy, { @@ -344,7 +344,7 @@ export async function getTMDBTrendingContent( // 获取本周热门内容(电影+电视剧) const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; - const url = `${baseUrl}/trending/all/week?api_key=${actualKey}&language=zh-CN`; + const url = `${baseUrl}/3/trending/all/week?api_key=${actualKey}&language=zh-CN`; const fetchOptions: any = proxy ? { agent: new HttpsProxyAgent(proxy, { @@ -478,7 +478,7 @@ export async function searchTMDBMulti( } const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; - const url = `${baseUrl}/search/multi?api_key=${actualKey}&language=zh-CN&query=${encodeURIComponent(query)}&page=1`; + const url = `${baseUrl}/3/search/multi?api_key=${actualKey}&language=zh-CN&query=${encodeURIComponent(query)}&page=1`; const fetchOptions: any = proxy ? { agent: new HttpsProxyAgent(proxy, { @@ -531,7 +531,7 @@ export async function getTMDBMovieRecommendations( } const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; - const url = `${baseUrl}/movie/${movieId}/recommendations?api_key=${actualKey}&language=zh-CN&page=1`; + const url = `${baseUrl}/3/movie/${movieId}/recommendations?api_key=${actualKey}&language=zh-CN&page=1`; const fetchOptions: any = proxy ? { agent: new HttpsProxyAgent(proxy, { @@ -584,7 +584,7 @@ export async function getTMDBTVRecommendations( } const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; - const url = `${baseUrl}/tv/${tvId}/recommendations?api_key=${actualKey}&language=zh-CN&page=1`; + const url = `${baseUrl}/3/tv/${tvId}/recommendations?api_key=${actualKey}&language=zh-CN&page=1`; const fetchOptions: any = proxy ? { agent: new HttpsProxyAgent(proxy, { @@ -637,7 +637,7 @@ export async function getTMDBMovieDetails( } const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; - const url = `${baseUrl}/movie/${movieId}?api_key=${actualKey}&language=zh-CN`; + const url = `${baseUrl}/3/movie/${movieId}?api_key=${actualKey}&language=zh-CN`; const fetchOptions: any = proxy ? { agent: new HttpsProxyAgent(proxy, { @@ -690,7 +690,7 @@ export async function getTMDBTVDetails( } const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; - const url = `${baseUrl}/tv/${tvId}?api_key=${actualKey}&language=zh-CN`; + const url = `${baseUrl}/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 a91a8ed..bcfd40f 100644 --- a/src/lib/tmdb.search.ts +++ b/src/lib/tmdb.search.ts @@ -4,6 +4,9 @@ import { HttpsProxyAgent } from 'https-proxy-agent'; import nodeFetch from 'node-fetch'; import { getNextApiKey } from './tmdb.client'; +// TMDB API 默认 Base URL(不包含 /3/,由程序拼接) +const DEFAULT_TMDB_BASE_URL = 'https://api.themoviedb.org'; + export interface TMDBSearchResult { id: number; title?: string; // 电影 @@ -30,7 +33,8 @@ export async function searchTMDB( apiKey: string, query: string, proxy?: string, - year?: number + year?: number, + reverseProxyBaseUrl?: string ): Promise<{ code: number; result: TMDBSearchResult | null }> { try { const actualKey = getNextApiKey(apiKey); @@ -38,8 +42,9 @@ export async function searchTMDB( return { code: 400, result: null }; } + const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; // 使用 multi search 同时搜索电影和电视剧 - let url = `https://api.themoviedb.org/3/search/multi?api_key=${actualKey}&language=zh-CN&query=${encodeURIComponent(query)}&page=1`; + let url = `${baseUrl}/3/search/multi?api_key=${actualKey}&language=zh-CN&query=${encodeURIComponent(query)}&page=1`; // 如果提供了年份,添加到搜索参数中 if (year) { @@ -120,7 +125,8 @@ interface TMDBTVDetails { export async function getTVSeasons( apiKey: string, tvId: number, - proxy?: string + proxy?: string, + reverseProxyBaseUrl?: string ): Promise<{ code: number; seasons: TMDBSeasonInfo[] | null }> { try { const actualKey = getNextApiKey(apiKey); @@ -128,7 +134,8 @@ export async function getTVSeasons( return { code: 400, seasons: 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}/3/tv/${tvId}?api_key=${actualKey}&language=zh-CN`; const fetchOptions: any = proxy ? { @@ -171,7 +178,8 @@ export async function getTVSeasonDetails( apiKey: string, tvId: number, seasonNumber: number, - proxy?: string + proxy?: string, + reverseProxyBaseUrl?: string ): Promise<{ code: number; season: TMDBSeasonInfo | null }> { try { const actualKey = getNextApiKey(apiKey); @@ -179,7 +187,8 @@ export async function getTVSeasonDetails( return { code: 400, season: null }; } - const url = `https://api.themoviedb.org/3/tv/${tvId}/season/${seasonNumber}?api_key=${actualKey}&language=zh-CN`; + const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; + const url = `${baseUrl}/3/tv/${tvId}/season/${seasonNumber}?api_key=${actualKey}&language=zh-CN`; const fetchOptions: any = proxy ? { diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 37dfa1b..aa024fe 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -46,7 +46,19 @@ export function processImageUrl(originalUrl: string): string { return originalUrl; } - // 仅处理豆瓣图片代理 + // 处理 TMDB 图片 URL 替换 + if (originalUrl.includes('image.tmdb.org')) { + if (typeof window !== 'undefined') { + const tmdbImageBaseUrl = localStorage.getItem('tmdbImageBaseUrl') || 'https://image.tmdb.org'; + // 只有当用户设置了不同的 baseUrl 时才进行替换 + if (tmdbImageBaseUrl !== 'https://image.tmdb.org') { + return originalUrl.replace('https://image.tmdb.org', tmdbImageBaseUrl); + } + } + return originalUrl; + } + + // 处理豆瓣图片代理 if (!originalUrl.includes('doubanio.com')) { return originalUrl; } diff --git a/src/lib/xiaoya-metadata.ts b/src/lib/xiaoya-metadata.ts index c58f56a..fada19d 100644 --- a/src/lib/xiaoya-metadata.ts +++ b/src/lib/xiaoya-metadata.ts @@ -99,7 +99,8 @@ export async function getXiaoyaMetadata( xiaoyaClient: XiaoyaClient, videoPath: string, tmdbApiKey?: string, - tmdbProxy?: string + tmdbProxy?: string, + tmdbReverseProxy?: string ): Promise { const pathParts = videoPath.split('/').filter(Boolean); @@ -216,7 +217,7 @@ export async function getXiaoyaMetadata( if (!isPureNumber && !isSeasonEpisode) { const { searchTMDB, getTMDBImageUrl } = await import('./tmdb.search'); - const tmdbResult = await searchTMDB(tmdbApiKey, searchQuery, tmdbProxy); + const tmdbResult = await searchTMDB(tmdbApiKey, searchQuery, tmdbProxy, undefined, tmdbReverseProxy); if (tmdbResult.code === 200 && tmdbResult.result) { return { @@ -244,7 +245,7 @@ export async function getXiaoyaMetadata( .trim(); const { searchTMDB, getTMDBImageUrl } = await import('./tmdb.search'); - const tmdbResult = await searchTMDB(tmdbApiKey, searchQuery, tmdbProxy); + const tmdbResult = await searchTMDB(tmdbApiKey, searchQuery, tmdbProxy, undefined, tmdbReverseProxy); if (tmdbResult.code === 200 && tmdbResult.result) { return {