diff --git a/package.json b/package.json index 4527951..7040508 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "framer-motion": "^12.18.1", "he": "^1.2.0", "hls.js": "^1.6.10", + "https-proxy-agent": "^7.0.6", "lucide-react": "^0.438.0", "media-icons": "^1.1.5", "mux.js": "^6.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7de938d..14e4b6f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,6 +65,9 @@ importers: hls.js: specifier: ^1.6.10 version: 1.6.10 + https-proxy-agent: + specifier: ^7.0.6 + version: 7.0.6 lucide-react: specifier: ^0.438.0 version: 0.438.0(react@18.3.1) @@ -1889,6 +1892,10 @@ packages: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} @@ -3211,6 +3218,10 @@ packages: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -7539,6 +7550,8 @@ snapshots: transitivePeerDependencies: - supports-color + agent-base@7.1.4: {} + aggregate-error@3.1.0: dependencies: clean-stack: 2.2.0 @@ -9088,6 +9101,13 @@ snapshots: transitivePeerDependencies: - supports-color + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.1(supports-color@9.4.0) + transitivePeerDependencies: + - supports-color + human-signals@2.1.0: {} husky@7.0.4: {} diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 39f6112..6744fa2 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -298,6 +298,7 @@ interface SiteConfig { DanmakuApiBase: string; DanmakuApiToken: string; TMDBApiKey?: string; + TMDBProxy?: string; EnableComments: boolean; EnableRegistration?: boolean; RegistrationRequireTurnstile?: boolean; @@ -4601,6 +4602,7 @@ const SiteConfigComponent = ({ DanmakuApiBase: 'http://localhost:9321', DanmakuApiToken: '87654321', TMDBApiKey: '', + TMDBProxy: '', EnableComments: false, EnableRegistration: false, RegistrationRequireTurnstile: false, @@ -4684,6 +4686,7 @@ const SiteConfigComponent = ({ config.SiteConfig.DanmakuApiBase || 'http://localhost:9321', DanmakuApiToken: config.SiteConfig.DanmakuApiToken || '87654321', TMDBApiKey: config.SiteConfig.TMDBApiKey || '', + TMDBProxy: config.SiteConfig.TMDBProxy || '', EnableComments: config.SiteConfig.EnableComments || false, }); } @@ -5242,6 +5245,28 @@ const SiteConfigComponent = ({

+ + {/* TMDB Proxy */} +
+ + + setSiteSettings((prev) => ({ + ...prev, + TMDBProxy: 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 API(可选) +

+
{/* 评论功能配置 */} diff --git a/src/app/api/admin/site/route.ts b/src/app/api/admin/site/route.ts index 51cc10c..00d78e9 100644 --- a/src/app/api/admin/site/route.ts +++ b/src/app/api/admin/site/route.ts @@ -42,6 +42,7 @@ export async function POST(request: NextRequest) { DanmakuApiBase, DanmakuApiToken, TMDBApiKey, + TMDBProxy, EnableComments, CustomAdFilterCode, CustomAdFilterVersion, @@ -74,6 +75,7 @@ export async function POST(request: NextRequest) { DanmakuApiBase: string; DanmakuApiToken: string; TMDBApiKey?: string; + TMDBProxy?: string; EnableComments: boolean; CustomAdFilterCode?: string; CustomAdFilterVersion?: number; @@ -109,6 +111,7 @@ export async function POST(request: NextRequest) { typeof DanmakuApiBase !== 'string' || typeof DanmakuApiToken !== 'string' || (TMDBApiKey !== undefined && typeof TMDBApiKey !== 'string') || + (TMDBProxy !== undefined && typeof TMDBProxy !== 'string') || typeof EnableComments !== 'boolean' || (CustomAdFilterCode !== undefined && typeof CustomAdFilterCode !== 'string') || (CustomAdFilterVersion !== undefined && typeof CustomAdFilterVersion !== 'number') || @@ -159,6 +162,7 @@ export async function POST(request: NextRequest) { DanmakuApiBase, DanmakuApiToken, TMDBApiKey, + TMDBProxy, EnableComments, CustomAdFilterCode, CustomAdFilterVersion, diff --git a/src/app/api/tmdb/upcoming/route.ts b/src/app/api/tmdb/upcoming/route.ts index aa9c743..5486d20 100644 --- a/src/app/api/tmdb/upcoming/route.ts +++ b/src/app/api/tmdb/upcoming/route.ts @@ -27,6 +27,7 @@ export async function GET(request: NextRequest) { // 缓存不存在或已过期,获取新数据 const config = await getConfig(); const tmdbApiKey = config.SiteConfig?.TMDBApiKey; + const tmdbProxy = config.SiteConfig?.TMDBProxy; if (!tmdbApiKey) { return NextResponse.json( @@ -36,7 +37,7 @@ export async function GET(request: NextRequest) { } // 调用TMDB API获取数据 - const result = await getTMDBUpcomingContent(tmdbApiKey); + const result = await getTMDBUpcomingContent(tmdbApiKey, tmdbProxy); if (result.code !== 200) { return NextResponse.json( diff --git a/src/lib/admin.types.ts b/src/lib/admin.types.ts index f5b1aea..609939f 100644 --- a/src/lib/admin.types.ts +++ b/src/lib/admin.types.ts @@ -21,6 +21,7 @@ export interface AdminConfig { DanmakuApiToken: string; // TMDB配置 TMDBApiKey?: string; + TMDBProxy?: string; // 评论功能开关 EnableComments: boolean; // 自定义去广告代码 diff --git a/src/lib/config.ts b/src/lib/config.ts index ba7b782..91016cf 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -223,6 +223,7 @@ async function getInitConfig(configFile: string, subConfig: { DanmakuApiToken: process.env.DANMAKU_API_TOKEN || '87654321', // TMDB配置 TMDBApiKey: '', + TMDBProxy: '', // 评论功能开关 EnableComments: false, }, diff --git a/src/lib/tmdb.client.ts b/src/lib/tmdb.client.ts index a72cc9e..062ac81 100644 --- a/src/lib/tmdb.client.ts +++ b/src/lib/tmdb.client.ts @@ -1,5 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any,no-console */ +import { HttpsProxyAgent } from 'https-proxy-agent'; + export interface TMDBMovie { id: number; title: string; @@ -48,21 +50,24 @@ interface TMDBTVAiringTodayResponse { * @param apiKey - TMDB API Key * @param page - 页码 * @param region - 地区代码,默认 CN (中国) + * @param proxy - 代理服务器地址 * @returns 即将上映的电影列表 */ export async function getTMDBUpcomingMovies( apiKey: string, page: number = 1, - region: string = 'CN' + region: string = 'CN', + proxy?: string ): Promise<{ code: number; list: TMDBMovie[] }> { try { if (!apiKey) { return { code: 400, list: [] }; } - const response = await fetch( - `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=${apiKey}&language=zh-CN&page=${page}®ion=${region}`; + const fetchOptions: RequestInit = proxy ? { agent: new HttpsProxyAgent(proxy) as any } : {}; + + const response = await fetch(url, fetchOptions); if (!response.ok) { console.error('TMDB API 请求失败:', response.status, response.statusText); @@ -85,11 +90,13 @@ export async function getTMDBUpcomingMovies( * 获取正在播出的电视剧 * @param apiKey - TMDB API Key * @param page - 页码 + * @param proxy - 代理服务器地址 * @returns 正在播出的电视剧列表 */ export async function getTMDBUpcomingTVShows( apiKey: string, - page: number = 1 + page: number = 1, + proxy?: string ): Promise<{ code: number; list: TMDBTVShow[] }> { try { if (!apiKey) { @@ -97,9 +104,10 @@ export async function getTMDBUpcomingTVShows( } // 使用 on_the_air 接口获取正在播出的电视剧 - const response = await fetch( - `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=${apiKey}&language=zh-CN&page=${page}`; + const fetchOptions: RequestInit = proxy ? { agent: new HttpsProxyAgent(proxy) as any } : {}; + + const response = await fetch(url, fetchOptions); if (!response.ok) { console.error('TMDB TV API 请求失败:', response.status, response.statusText); @@ -121,10 +129,12 @@ export async function getTMDBUpcomingTVShows( /** * 获取即将上映/播出的内容(电影+电视剧) * @param apiKey - TMDB API Key + * @param proxy - 代理服务器地址 * @returns 统一格式的即将上映/播出列表 */ export async function getTMDBUpcomingContent( - apiKey: string + apiKey: string, + proxy?: string ): Promise<{ code: number; list: TMDBItem[] }> { try { if (!apiKey) { @@ -133,8 +143,8 @@ export async function getTMDBUpcomingContent( // 并行获取电影和电视剧数据 const [moviesResult, tvShowsResult] = await Promise.all([ - getTMDBUpcomingMovies(apiKey), - getTMDBUpcomingTVShows(apiKey), + getTMDBUpcomingMovies(apiKey, 1, 'CN', proxy), + getTMDBUpcomingTVShows(apiKey, 1, proxy), ]); // 检查是否有错误