diff --git a/src/lib/tmdb.client.ts b/src/lib/tmdb.client.ts index d000c4d..53d1448 100644 --- a/src/lib/tmdb.client.ts +++ b/src/lib/tmdb.client.ts @@ -9,6 +9,43 @@ const DEFAULT_TMDB_BASE_URL = 'https://api.themoviedb.org'; // TMDB API Key 轮询管理 let currentKeyIndex = 0; +/** + * 检测是否在 Cloudflare 环境中运行 + */ +function isCloudflareEnvironment(): boolean { + return process.env.CF_PAGES === '1' || process.env.BUILD_TARGET === 'cloudflare'; +} + +/** + * 统一的 fetch 函数,根据环境选择使用 node-fetch 或原生 fetch + */ +async function universalFetch(url: string, proxy?: string): Promise { + const isCloudflare = isCloudflareEnvironment(); + + if (isCloudflare) { + // Cloudflare 环境:使用原生 fetch,忽略 proxy 参数 + const response = await fetch(url, { + signal: AbortSignal.timeout(15000), + }); + return response as unknown as Response; + } else { + // Node.js 环境:使用 node-fetch,支持 proxy + const fetchOptions: any = proxy + ? { + agent: new HttpsProxyAgent(proxy, { + timeout: 30000, + keepAlive: false, + }), + signal: AbortSignal.timeout(30000), + } + : { + signal: AbortSignal.timeout(15000), + }; + + return nodeFetch(url, fetchOptions) as unknown as Response; + } +} + /** * 解析并获取下一个可用的 TMDB API Key * @param apiKeys - API Key 字符串(支持逗号分隔的多个key) @@ -98,20 +135,9 @@ export async function getTMDBUpcomingMovies( const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; const url = `${baseUrl}/3/movie/upcoming?api_key=${actualKey}&language=zh-CN&page=${page}®ion=${region}`; - const fetchOptions: any = proxy - ? { - agent: new HttpsProxyAgent(proxy, { - timeout: 30000, - keepAlive: false, - }), - signal: AbortSignal.timeout(30000), - } - : { - signal: AbortSignal.timeout(15000), - }; - // 使用 node-fetch 而不是原生 fetch - const response = await nodeFetch(url, fetchOptions); + // 使用统一的 fetch 函数 + const response = await universalFetch(url, proxy); if (!response.ok) { console.error('TMDB API 请求失败:', response.status, response.statusText); @@ -153,20 +179,9 @@ export async function getTMDBUpcomingTVShows( // 使用 on_the_air 接口获取正在播出的电视剧 const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; const url = `${baseUrl}/3/tv/on_the_air?api_key=${actualKey}&language=zh-CN&page=${page}`; - const fetchOptions: any = proxy - ? { - agent: new HttpsProxyAgent(proxy, { - timeout: 30000, - keepAlive: false, - }), - signal: AbortSignal.timeout(30000), - } - : { - signal: AbortSignal.timeout(15000), - }; - // 使用 node-fetch 而不是原生 fetch - const response = await nodeFetch(url, fetchOptions); + // 使用统一的 fetch 函数 + const response = await universalFetch(url, proxy); if (!response.ok) { console.error('TMDB TV API 请求失败:', response.status, response.statusText); @@ -291,19 +306,8 @@ export async function getTMDBVideos( const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; const url = `${baseUrl}/3/${mediaType}/${mediaId}/videos?api_key=${actualKey}`; - const fetchOptions: any = proxy - ? { - agent: new HttpsProxyAgent(proxy, { - timeout: 30000, - keepAlive: false, - }), - signal: AbortSignal.timeout(30000), - } - : { - signal: AbortSignal.timeout(15000), - }; - const response = await nodeFetch(url, fetchOptions); + const response = await universalFetch(url, proxy); if (!response.ok) { return null; @@ -345,19 +349,8 @@ export async function getTMDBTrendingContent( // 获取本周热门内容(电影+电视剧) const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; const url = `${baseUrl}/3/trending/all/week?api_key=${actualKey}&language=zh-CN`; - const fetchOptions: any = proxy - ? { - agent: new HttpsProxyAgent(proxy, { - timeout: 30000, - keepAlive: false, - }), - signal: AbortSignal.timeout(30000), - } - : { - signal: AbortSignal.timeout(15000), - }; - const response = await nodeFetch(url, fetchOptions); + const response = await universalFetch(url, proxy); if (!response.ok) { console.error('TMDB Trending API 请求失败:', response.status, response.statusText); @@ -479,19 +472,8 @@ export async function searchTMDBMulti( const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; 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, { - timeout: 30000, - keepAlive: false, - }), - signal: AbortSignal.timeout(30000), - } - : { - signal: AbortSignal.timeout(15000), - }; - const response = await nodeFetch(url, fetchOptions); + const response = await universalFetch(url, proxy); if (!response.ok) { console.error('TMDB Search API 请求失败:', response.status, response.statusText); @@ -532,19 +514,8 @@ export async function getTMDBMovieRecommendations( const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; const url = `${baseUrl}/3/movie/${movieId}/recommendations?api_key=${actualKey}&language=zh-CN&page=1`; - const fetchOptions: any = proxy - ? { - agent: new HttpsProxyAgent(proxy, { - timeout: 30000, - keepAlive: false, - }), - signal: AbortSignal.timeout(30000), - } - : { - signal: AbortSignal.timeout(15000), - }; - const response = await nodeFetch(url, fetchOptions); + const response = await universalFetch(url, proxy); if (!response.ok) { console.error('TMDB Movie Recommendations API 请求失败:', response.status, response.statusText); @@ -585,19 +556,8 @@ export async function getTMDBTVRecommendations( const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; const url = `${baseUrl}/3/tv/${tvId}/recommendations?api_key=${actualKey}&language=zh-CN&page=1`; - const fetchOptions: any = proxy - ? { - agent: new HttpsProxyAgent(proxy, { - timeout: 30000, - keepAlive: false, - }), - signal: AbortSignal.timeout(30000), - } - : { - signal: AbortSignal.timeout(15000), - }; - const response = await nodeFetch(url, fetchOptions); + const response = await universalFetch(url, proxy); if (!response.ok) { console.error('TMDB TV Recommendations API 请求失败:', response.status, response.statusText); @@ -638,19 +598,8 @@ export async function getTMDBMovieDetails( const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; const url = `${baseUrl}/3/movie/${movieId}?api_key=${actualKey}&language=zh-CN`; - const fetchOptions: any = proxy - ? { - agent: new HttpsProxyAgent(proxy, { - timeout: 30000, - keepAlive: false, - }), - signal: AbortSignal.timeout(30000), - } - : { - signal: AbortSignal.timeout(15000), - }; - const response = await nodeFetch(url, fetchOptions); + const response = await universalFetch(url, proxy); if (!response.ok) { console.error('TMDB API 请求失败:', response.status, response.statusText); @@ -691,19 +640,8 @@ export async function getTMDBTVDetails( const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; const url = `${baseUrl}/3/tv/${tvId}?api_key=${actualKey}&language=zh-CN`; - const fetchOptions: any = proxy - ? { - agent: new HttpsProxyAgent(proxy, { - timeout: 30000, - keepAlive: false, - }), - signal: AbortSignal.timeout(30000), - } - : { - signal: AbortSignal.timeout(15000), - }; - const response = await nodeFetch(url, fetchOptions); + const response = await universalFetch(url, proxy); if (!response.ok) { console.error('TMDB API 请求失败:', response.status, response.statusText); diff --git a/src/lib/tmdb.search.ts b/src/lib/tmdb.search.ts index 63e0b6f..466aa51 100644 --- a/src/lib/tmdb.search.ts +++ b/src/lib/tmdb.search.ts @@ -8,6 +8,43 @@ import { getNextApiKey } from './tmdb.client'; // TMDB API 默认 Base URL(不包含 /3/,由程序拼接) const DEFAULT_TMDB_BASE_URL = 'https://api.themoviedb.org'; +/** + * 检测是否在 Cloudflare 环境中运行 + */ +function isCloudflareEnvironment(): boolean { + return process.env.CF_PAGES === '1' || process.env.BUILD_TARGET === 'cloudflare'; +} + +/** + * 统一的 fetch 函数,根据环境选择使用 node-fetch 或原生 fetch + */ +async function universalFetch(url: string, proxy?: string): Promise { + const isCloudflare = isCloudflareEnvironment(); + + if (isCloudflare) { + // Cloudflare 环境:使用原生 fetch,忽略 proxy 参数 + const response = await fetch(url, { + signal: AbortSignal.timeout(15000), + }); + return response as unknown as Response; + } else { + // Node.js 环境:使用 node-fetch,支持 proxy + const fetchOptions: any = proxy + ? { + agent: new HttpsProxyAgent(proxy, { + timeout: 30000, + keepAlive: false, + }), + signal: AbortSignal.timeout(30000), + } + : { + signal: AbortSignal.timeout(15000), + }; + + return nodeFetch(url, fetchOptions) as unknown as Response; + } +} + export interface TMDBSearchResult { id: number; title?: string; // 电影 @@ -52,20 +89,8 @@ export async function searchTMDB( url += `&year=${year}`; } - const fetchOptions: any = proxy - ? { - agent: new HttpsProxyAgent(proxy, { - timeout: 30000, - keepAlive: false, - }), - signal: AbortSignal.timeout(30000), - } - : { - signal: AbortSignal.timeout(15000), - }; - - // 使用 node-fetch 而不是原生 fetch,因为原生 fetch 不支持 agent 选项 - const response = await nodeFetch(url, fetchOptions); + // 使用统一的 fetch 函数 + const response = await universalFetch(url, proxy); if (!response.ok) { console.error('TMDB 搜索失败:', response.status, response.statusText); @@ -138,19 +163,7 @@ export async function getTVSeasons( const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL; const url = `${baseUrl}/3/tv/${tvId}?api_key=${actualKey}&language=zh-CN`; - const fetchOptions: any = proxy - ? { - agent: new HttpsProxyAgent(proxy, { - timeout: 30000, - keepAlive: false, - }), - signal: AbortSignal.timeout(30000), - } - : { - signal: AbortSignal.timeout(15000), - }; - - const response = await nodeFetch(url, fetchOptions); + const response = await universalFetch(url, proxy); if (!response.ok) { console.error('TMDB 获取电视剧详情失败:', response.status, response.statusText); @@ -191,19 +204,7 @@ export async function getTVSeasonDetails( 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 - ? { - agent: new HttpsProxyAgent(proxy, { - timeout: 30000, - keepAlive: false, - }), - signal: AbortSignal.timeout(30000), - } - : { - signal: AbortSignal.timeout(15000), - }; - - const response = await nodeFetch(url, fetchOptions); + const response = await universalFetch(url, proxy); if (!response.ok) { console.error('TMDB 获取季度详情失败:', response.status, response.statusText);