完善tmdb反代
This commit is contained in:
@@ -240,6 +240,7 @@ export async function POST(request: NextRequest) {
|
|||||||
// TMDB 配置
|
// TMDB 配置
|
||||||
tmdbApiKey: adminConfig.SiteConfig.TMDBApiKey,
|
tmdbApiKey: adminConfig.SiteConfig.TMDBApiKey,
|
||||||
tmdbProxy: adminConfig.SiteConfig.TMDBProxy,
|
tmdbProxy: adminConfig.SiteConfig.TMDBProxy,
|
||||||
|
tmdbReverseProxy: adminConfig.SiteConfig.TMDBReverseProxy,
|
||||||
// 决策模型配置(固定使用自定义provider,复用主模型的API配置)
|
// 决策模型配置(固定使用自定义provider,复用主模型的API配置)
|
||||||
enableDecisionModel: aiConfig.EnableDecisionModel,
|
enableDecisionModel: aiConfig.EnableDecisionModel,
|
||||||
decisionProvider: 'custom',
|
decisionProvider: 'custom',
|
||||||
|
|||||||
@@ -179,7 +179,8 @@ export async function GET(request: NextRequest) {
|
|||||||
client,
|
client,
|
||||||
metadataPath,
|
metadataPath,
|
||||||
config.SiteConfig.TMDBApiKey,
|
config.SiteConfig.TMDBApiKey,
|
||||||
config.SiteConfig.TMDBProxy
|
config.SiteConfig.TMDBProxy,
|
||||||
|
config.SiteConfig.TMDBReverseProxy
|
||||||
);
|
);
|
||||||
|
|
||||||
// 获取集数列表(使用目录路径或点击的文件路径)
|
// 获取集数列表(使用目录路径或点击的文件路径)
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export async function GET(request: NextRequest) {
|
|||||||
const config = await getConfig();
|
const config = await getConfig();
|
||||||
const tmdbApiKey = config.SiteConfig.TMDBApiKey;
|
const tmdbApiKey = config.SiteConfig.TMDBApiKey;
|
||||||
const tmdbProxy = config.SiteConfig.TMDBProxy;
|
const tmdbProxy = config.SiteConfig.TMDBProxy;
|
||||||
|
const tmdbReverseProxy = config.SiteConfig.TMDBReverseProxy;
|
||||||
|
|
||||||
const actualKey = getNextApiKey(tmdbApiKey || '');
|
const actualKey = getNextApiKey(tmdbApiKey || '');
|
||||||
if (!actualKey) {
|
if (!actualKey) {
|
||||||
@@ -41,9 +42,11 @@ export async function GET(request: NextRequest) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 使用反代代理或默认 Base URL
|
||||||
|
const baseUrl = tmdbReverseProxy || 'https://api.themoviedb.org';
|
||||||
// 根据类型选择API端点
|
// 根据类型选择API端点
|
||||||
const endpoint = type === 'movie' ? 'movie' : 'tv';
|
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
|
const fetchOptions: any = tmdbProxy
|
||||||
? {
|
? {
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export async function GET(request: NextRequest) {
|
|||||||
const config = await getConfig();
|
const config = await getConfig();
|
||||||
const tmdbApiKey = config.SiteConfig.TMDBApiKey;
|
const tmdbApiKey = config.SiteConfig.TMDBApiKey;
|
||||||
const tmdbProxy = config.SiteConfig.TMDBProxy;
|
const tmdbProxy = config.SiteConfig.TMDBProxy;
|
||||||
|
const tmdbReverseProxy = config.SiteConfig.TMDBReverseProxy;
|
||||||
|
|
||||||
if (!tmdbApiKey) {
|
if (!tmdbApiKey) {
|
||||||
return NextResponse.json({ error: 'TMDB API Key 未配置' }, { status: 400 });
|
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 });
|
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
|
const fetchOptions: any = tmdbProxy
|
||||||
? {
|
? {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export async function GET(request: NextRequest) {
|
|||||||
const config = await getConfig();
|
const config = await getConfig();
|
||||||
const tmdbApiKey = config.SiteConfig.TMDBApiKey;
|
const tmdbApiKey = config.SiteConfig.TMDBApiKey;
|
||||||
const tmdbProxy = config.SiteConfig.TMDBProxy;
|
const tmdbProxy = config.SiteConfig.TMDBProxy;
|
||||||
|
const tmdbReverseProxy = config.SiteConfig.TMDBReverseProxy;
|
||||||
|
|
||||||
const actualKey = getNextApiKey(tmdbApiKey || '');
|
const actualKey = getNextApiKey(tmdbApiKey || '');
|
||||||
if (!actualKey) {
|
if (!actualKey) {
|
||||||
@@ -40,8 +41,10 @@ export async function GET(request: NextRequest) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 使用反代代理或默认 Base URL
|
||||||
|
const baseUrl = tmdbReverseProxy || 'https://api.themoviedb.org';
|
||||||
// 使用 multi search 同时搜索电影和电视剧
|
// 使用 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
|
const fetchOptions: any = tmdbProxy
|
||||||
? {
|
? {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export async function GET(request: NextRequest) {
|
|||||||
const config = await getConfig();
|
const config = await getConfig();
|
||||||
const tmdbApiKey = config.SiteConfig.TMDBApiKey;
|
const tmdbApiKey = config.SiteConfig.TMDBApiKey;
|
||||||
const tmdbProxy = config.SiteConfig.TMDBProxy;
|
const tmdbProxy = config.SiteConfig.TMDBProxy;
|
||||||
|
const tmdbReverseProxy = config.SiteConfig.TMDBReverseProxy;
|
||||||
|
|
||||||
if (!tmdbApiKey) {
|
if (!tmdbApiKey) {
|
||||||
return NextResponse.json(
|
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) {
|
if (result.code === 200 && result.seasons) {
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { CheckCircle, AlertCircle, Plus } from 'lucide-react';
|
|||||||
|
|
||||||
import PageLayout from '@/components/PageLayout';
|
import PageLayout from '@/components/PageLayout';
|
||||||
import { getTMDBImageUrl } from '@/lib/tmdb.client';
|
import { getTMDBImageUrl } from '@/lib/tmdb.client';
|
||||||
|
import { processImageUrl } from '@/lib/utils';
|
||||||
|
|
||||||
interface TMDBResult {
|
interface TMDBResult {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -117,7 +118,7 @@ export default function MovieRequestPage() {
|
|||||||
const submitRequest = async (item: TMDBResult, season?: number) => {
|
const submitRequest = async (item: TMDBResult, season?: number) => {
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
try {
|
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 || '';
|
let title = item.title || item.name || '';
|
||||||
|
|
||||||
if (season && seasons.length > 0) {
|
if (season && seasons.length > 0) {
|
||||||
@@ -125,7 +126,7 @@ export default function MovieRequestPage() {
|
|||||||
if (seasonData) {
|
if (seasonData) {
|
||||||
title = `${title} ${seasonData.name}`;
|
title = `${title} ${seasonData.name}`;
|
||||||
if (seasonData.poster_path) {
|
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.poster_path ? (
|
||||||
<img
|
<img
|
||||||
src={getTMDBImageUrl(item.poster_path, 'w500')}
|
src={processImageUrl(getTMDBImageUrl(item.poster_path, 'w500'))}
|
||||||
alt={item.title || item.name}
|
alt={item.title || item.name}
|
||||||
className='w-full aspect-[2/3] object-cover'
|
className='w-full aspect-[2/3] object-cover'
|
||||||
/>
|
/>
|
||||||
|
|||||||
+2
-1
@@ -13,6 +13,7 @@ import {
|
|||||||
import { getDoubanCategories } from '@/lib/douban.client';
|
import { getDoubanCategories } from '@/lib/douban.client';
|
||||||
import { getTMDBImageUrl, TMDBItem } from '@/lib/tmdb.client';
|
import { getTMDBImageUrl, TMDBItem } from '@/lib/tmdb.client';
|
||||||
import { DoubanItem } from '@/lib/types';
|
import { DoubanItem } from '@/lib/types';
|
||||||
|
import { processImageUrl } from '@/lib/utils';
|
||||||
|
|
||||||
import ContinueWatching from '@/components/ContinueWatching';
|
import ContinueWatching from '@/components/ContinueWatching';
|
||||||
import PageLayout from '@/components/PageLayout';
|
import PageLayout from '@/components/PageLayout';
|
||||||
@@ -516,7 +517,7 @@ function HomeClient() {
|
|||||||
>
|
>
|
||||||
<VideoCard
|
<VideoCard
|
||||||
title={item.title}
|
title={item.title}
|
||||||
poster={getTMDBImageUrl(item.poster_path)}
|
poster={processImageUrl(getTMDBImageUrl(item.poster_path))}
|
||||||
year={item.release_date?.split('-')?.[0] || ''}
|
year={item.release_date?.split('-')?.[0] || ''}
|
||||||
rate={
|
rate={
|
||||||
item.vote_average && item.vote_average > 0
|
item.vote_average && item.vote_average > 0
|
||||||
|
|||||||
@@ -1020,7 +1020,7 @@ function PlayPageClient() {
|
|||||||
|
|
||||||
if (detCacheAge < detCacheMaxAge && data && data.backdrop) {
|
if (detCacheAge < detCacheMaxAge && data && data.backdrop) {
|
||||||
console.log('使用缓存的TMDB详情数据');
|
console.log('使用缓存的TMDB详情数据');
|
||||||
setTmdbBackdrop(data.backdrop);
|
setTmdbBackdrop(processImageUrl(data.backdrop));
|
||||||
|
|
||||||
// 如果没有豆瓣ID,使用TMDb数据补充
|
// 如果没有豆瓣ID,使用TMDb数据补充
|
||||||
if (!videoDoubanId || videoDoubanId === 0) {
|
if (!videoDoubanId || videoDoubanId === 0) {
|
||||||
@@ -1054,7 +1054,7 @@ function PlayPageClient() {
|
|||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
if (result.backdrop) {
|
if (result.backdrop) {
|
||||||
setTmdbBackdrop(result.backdrop);
|
setTmdbBackdrop(processImageUrl(result.backdrop));
|
||||||
|
|
||||||
// 如果没有豆瓣ID,使用TMDb数据补充
|
// 如果没有豆瓣ID,使用TMDb数据补充
|
||||||
if (!videoDoubanId || videoDoubanId === 0) {
|
if (!videoDoubanId || videoDoubanId === 0) {
|
||||||
@@ -3279,7 +3279,7 @@ function PlayPageClient() {
|
|||||||
finalTitle = correction.title;
|
finalTitle = correction.title;
|
||||||
}
|
}
|
||||||
if (correction.posterPath) {
|
if (correction.posterPath) {
|
||||||
finalCover = getTMDBImageUrl(correction.posterPath);
|
finalCover = processImageUrl(getTMDBImageUrl(correction.posterPath));
|
||||||
}
|
}
|
||||||
if (correction.overview) {
|
if (correction.overview) {
|
||||||
finalDesc = correction.overview;
|
finalDesc = correction.overview;
|
||||||
@@ -4424,7 +4424,7 @@ function PlayPageClient() {
|
|||||||
setVideoTitle(correction.title);
|
setVideoTitle(correction.title);
|
||||||
}
|
}
|
||||||
if (correction.posterPath) {
|
if (correction.posterPath) {
|
||||||
const fullPosterUrl = getTMDBImageUrl(correction.posterPath);
|
const fullPosterUrl = processImageUrl(getTMDBImageUrl(correction.posterPath));
|
||||||
setVideoCover(fullPosterUrl);
|
setVideoCover(fullPosterUrl);
|
||||||
}
|
}
|
||||||
if (correction.overview) {
|
if (correction.overview) {
|
||||||
@@ -7856,7 +7856,7 @@ const applyCorrection = (detail: SearchResult, correction: any): SearchResult =>
|
|||||||
return {
|
return {
|
||||||
...detail,
|
...detail,
|
||||||
title: correction.title || detail.title,
|
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,
|
year: correction.releaseDate || detail.year,
|
||||||
desc: correction.overview || detail.desc,
|
desc: correction.overview || detail.desc,
|
||||||
rating: correction.voteAverage || detail.rating,
|
rating: correction.voteAverage || detail.rating,
|
||||||
|
|||||||
@@ -53,8 +53,8 @@ export default function BannerCarousel({ autoPlayInterval = 5000 }: BannerCarous
|
|||||||
if (path.startsWith('http://') || path.startsWith('https://')) {
|
if (path.startsWith('http://') || path.startsWith('https://')) {
|
||||||
return processImageUrl(path);
|
return processImageUrl(path);
|
||||||
}
|
}
|
||||||
// 否则使用TMDB的URL拼接
|
// 否则使用TMDB的URL拼接,并通过processImageUrl处理
|
||||||
return getTMDBImageUrl(path, 'original');
|
return processImageUrl(getTMDBImageUrl(path, 'original'));
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取视频URL(处理豆瓣视频代理)
|
// 获取视频URL(处理豆瓣视频代理)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import Image from 'next/image';
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { getTMDBImageUrl } from '@/lib/tmdb.client';
|
import { getTMDBImageUrl } from '@/lib/tmdb.client';
|
||||||
|
import { processImageUrl } from '@/lib/utils';
|
||||||
|
|
||||||
interface DetailPanelProps {
|
interface DetailPanelProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -380,7 +381,7 @@ const DetailPanel: React.FC<DetailPanelProps> = ({
|
|||||||
? detailResult.release_date?.substring(0, 4)
|
? detailResult.release_date?.substring(0, 4)
|
||||||
: detailResult.first_air_date?.substring(0, 4),
|
: detailResult.first_air_date?.substring(0, 4),
|
||||||
poster: detailResult.poster_path
|
poster: detailResult.poster_path
|
||||||
? getTMDBImageUrl(detailResult.poster_path, 'w500')
|
? processImageUrl(getTMDBImageUrl(detailResult.poster_path, 'w500'))
|
||||||
: poster,
|
: poster,
|
||||||
rating: detailResult.vote_average
|
rating: detailResult.vote_average
|
||||||
? {
|
? {
|
||||||
@@ -488,7 +489,7 @@ const DetailPanel: React.FC<DetailPanelProps> = ({
|
|||||||
...prev,
|
...prev,
|
||||||
title: episodesData.name || season?.name || prev.title,
|
title: episodesData.name || season?.name || prev.title,
|
||||||
intro: episodesData.overview || season?.overview || prev.overview,
|
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,
|
releaseDate: episodesData.air_date || season?.air_date || prev.releaseDate,
|
||||||
year: episodesData.air_date?.substring(0, 4) || season?.air_date?.substring(0, 4) || prev.year,
|
year: episodesData.air_date?.substring(0, 4) || season?.air_date?.substring(0, 4) || prev.year,
|
||||||
episodesCount: episodesData.episodes?.length || season?.episode_count || prev.episodesCount,
|
episodesCount: episodesData.episodes?.length || season?.episode_count || prev.episodesCount,
|
||||||
@@ -788,7 +789,7 @@ const DetailPanel: React.FC<DetailPanelProps> = ({
|
|||||||
{season.poster_path && (
|
{season.poster_path && (
|
||||||
<div className="relative w-12 h-16 rounded overflow-hidden bg-gray-200 dark:bg-gray-700 flex-shrink-0">
|
<div className="relative w-12 h-16 rounded overflow-hidden bg-gray-200 dark:bg-gray-700 flex-shrink-0">
|
||||||
<Image
|
<Image
|
||||||
src={getTMDBImageUrl(season.poster_path, 'w92')}
|
src={processImageUrl(getTMDBImageUrl(season.poster_path, 'w92'))}
|
||||||
alt={season.name}
|
alt={season.name}
|
||||||
fill
|
fill
|
||||||
className="object-cover"
|
className="object-cover"
|
||||||
@@ -840,7 +841,7 @@ const DetailPanel: React.FC<DetailPanelProps> = ({
|
|||||||
{episode.still_path && (
|
{episode.still_path && (
|
||||||
<div className="relative w-full h-36 rounded overflow-hidden bg-gray-200 dark:bg-gray-700 mb-2">
|
<div className="relative w-full h-36 rounded overflow-hidden bg-gray-200 dark:bg-gray-700 mb-2">
|
||||||
<Image
|
<Image
|
||||||
src={getTMDBImageUrl(episode.still_path, 'w300')}
|
src={processImageUrl(getTMDBImageUrl(episode.still_path, 'w300'))}
|
||||||
alt={episode.name}
|
alt={episode.name}
|
||||||
fill
|
fill
|
||||||
className="object-cover"
|
className="object-cover"
|
||||||
|
|||||||
@@ -249,7 +249,8 @@ async function fetchTMDBData(
|
|||||||
type?: 'movie' | 'tv';
|
type?: 'movie' | 'tv';
|
||||||
},
|
},
|
||||||
tmdbApiKey?: string,
|
tmdbApiKey?: string,
|
||||||
tmdbProxy?: string
|
tmdbProxy?: string,
|
||||||
|
tmdbReverseProxy?: string
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const actualKey = getNextApiKey(tmdbApiKey || '');
|
const actualKey = getNextApiKey(tmdbApiKey || '');
|
||||||
@@ -263,9 +264,11 @@ async function fetchTMDBData(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 使用反代代理或默认 Base URL
|
||||||
|
const baseUrl = tmdbReverseProxy || 'https://api.themoviedb.org';
|
||||||
// 使用 TMDB API 获取详情
|
// 使用 TMDB API 获取详情
|
||||||
// TMDB API: https://api.themoviedb.org/3/{type}/{id}
|
// 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);
|
console.log('📡 获取TMDB详情:', params.type, params.id);
|
||||||
|
|
||||||
@@ -517,6 +520,7 @@ export async function orchestrateDataSources(
|
|||||||
// TMDB 配置
|
// TMDB 配置
|
||||||
tmdbApiKey?: string;
|
tmdbApiKey?: string;
|
||||||
tmdbProxy?: string;
|
tmdbProxy?: string;
|
||||||
|
tmdbReverseProxy?: string;
|
||||||
// 决策模型配置
|
// 决策模型配置
|
||||||
enableDecisionModel?: boolean;
|
enableDecisionModel?: boolean;
|
||||||
decisionProvider?: 'openai' | 'claude' | 'custom';
|
decisionProvider?: 'openai' | 'claude' | 'custom';
|
||||||
@@ -653,7 +657,8 @@ export async function orchestrateDataSources(
|
|||||||
type: context.type,
|
type: context.type,
|
||||||
},
|
},
|
||||||
config?.tmdbApiKey,
|
config?.tmdbApiKey,
|
||||||
config?.tmdbProxy
|
config?.tmdbProxy,
|
||||||
|
config?.tmdbReverseProxy
|
||||||
);
|
);
|
||||||
dataPromises.push(tmdbPromise);
|
dataPromises.push(tmdbPromise);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ export async function startOpenListRefresh(clearMetaInfo: boolean = false): Prom
|
|||||||
|
|
||||||
const tmdbApiKey = config.SiteConfig.TMDBApiKey;
|
const tmdbApiKey = config.SiteConfig.TMDBApiKey;
|
||||||
const tmdbProxy = config.SiteConfig.TMDBProxy;
|
const tmdbProxy = config.SiteConfig.TMDBProxy;
|
||||||
|
const tmdbReverseProxy = config.SiteConfig.TMDBReverseProxy;
|
||||||
|
|
||||||
if (!tmdbApiKey) {
|
if (!tmdbApiKey) {
|
||||||
throw new Error('TMDB API Key 未配置');
|
throw new Error('TMDB API Key 未配置');
|
||||||
@@ -124,6 +125,7 @@ export async function startOpenListRefresh(clearMetaInfo: boolean = false): Prom
|
|||||||
rootPaths,
|
rootPaths,
|
||||||
tmdbApiKey,
|
tmdbApiKey,
|
||||||
tmdbProxy,
|
tmdbProxy,
|
||||||
|
tmdbReverseProxy,
|
||||||
openListConfig.Username,
|
openListConfig.Username,
|
||||||
openListConfig.Password,
|
openListConfig.Password,
|
||||||
clearMetaInfo,
|
clearMetaInfo,
|
||||||
@@ -145,6 +147,7 @@ async function performMultiRootScan(
|
|||||||
rootPaths: string[],
|
rootPaths: string[],
|
||||||
tmdbApiKey: string,
|
tmdbApiKey: string,
|
||||||
tmdbProxy: string | undefined,
|
tmdbProxy: string | undefined,
|
||||||
|
tmdbReverseProxy: string | undefined,
|
||||||
username: string,
|
username: string,
|
||||||
password: string,
|
password: string,
|
||||||
clearMetaInfo: boolean,
|
clearMetaInfo: boolean,
|
||||||
@@ -161,6 +164,7 @@ async function performMultiRootScan(
|
|||||||
rootPath,
|
rootPath,
|
||||||
tmdbApiKey,
|
tmdbApiKey,
|
||||||
tmdbProxy,
|
tmdbProxy,
|
||||||
|
tmdbReverseProxy,
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
clearMetaInfo && i === 0, // 只在第一个根目录时清除
|
clearMetaInfo && i === 0, // 只在第一个根目录时清除
|
||||||
@@ -182,6 +186,7 @@ async function performScan(
|
|||||||
rootPath: string,
|
rootPath: string,
|
||||||
tmdbApiKey: string,
|
tmdbApiKey: string,
|
||||||
tmdbProxy?: string,
|
tmdbProxy?: string,
|
||||||
|
tmdbReverseProxy?: string,
|
||||||
username?: string,
|
username?: string,
|
||||||
password?: string,
|
password?: string,
|
||||||
clearMetaInfo?: boolean,
|
clearMetaInfo?: boolean,
|
||||||
@@ -288,7 +293,7 @@ async function performScan(
|
|||||||
console.log(`[OpenList Refresh] 种子库模式 - 文件夹: ${folder.name}`);
|
console.log(`[OpenList Refresh] 种子库模式 - 文件夹: ${folder.name}`);
|
||||||
console.log(`[OpenList Refresh] 解析结果 - 标题: ${searchQuery}, 季度: ${seasonNumber}, 年份: ${year}`);
|
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))) {
|
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] 名字匹配模式 - 文件夹: ${folder.name}`);
|
||||||
console.log(`[OpenList Refresh] 清理后标题: ${searchQuery}, 季度: ${seasonNumber}, 年份: ${year}`);
|
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) {
|
if (searchResult.code === 200 && searchResult.result) {
|
||||||
@@ -325,7 +330,8 @@ async function performScan(
|
|||||||
tmdbApiKey,
|
tmdbApiKey,
|
||||||
result.id,
|
result.id,
|
||||||
seasonNumber,
|
seasonNumber,
|
||||||
tmdbProxy
|
tmdbProxy,
|
||||||
|
tmdbReverseProxy
|
||||||
);
|
);
|
||||||
|
|
||||||
if (seasonDetails.code === 200 && seasonDetails.season) {
|
if (seasonDetails.code === 200 && seasonDetails.season) {
|
||||||
|
|||||||
@@ -354,7 +354,8 @@ export async function getXiaoyaDetail(id: string): Promise<SearchResult> {
|
|||||||
client,
|
client,
|
||||||
decodedDirPath,
|
decodedDirPath,
|
||||||
config.SiteConfig.TMDBApiKey,
|
config.SiteConfig.TMDBApiKey,
|
||||||
config.SiteConfig.TMDBProxy
|
config.SiteConfig.TMDBProxy,
|
||||||
|
config.SiteConfig.TMDBReverseProxy
|
||||||
);
|
);
|
||||||
|
|
||||||
// 获取集数列表
|
// 获取集数列表
|
||||||
|
|||||||
+11
-11
@@ -3,8 +3,8 @@
|
|||||||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||||
import nodeFetch from 'node-fetch';
|
import nodeFetch from 'node-fetch';
|
||||||
|
|
||||||
// TMDB API 默认 Base URL
|
// TMDB API 默认 Base URL(不包含 /3/,由程序拼接)
|
||||||
const DEFAULT_TMDB_BASE_URL = 'https://api.themoviedb.org/3';
|
const DEFAULT_TMDB_BASE_URL = 'https://api.themoviedb.org';
|
||||||
|
|
||||||
// TMDB API Key 轮询管理
|
// TMDB API Key 轮询管理
|
||||||
let currentKeyIndex = 0;
|
let currentKeyIndex = 0;
|
||||||
@@ -97,7 +97,7 @@ export async function getTMDBUpcomingMovies(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL;
|
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
|
const fetchOptions: any = proxy
|
||||||
? {
|
? {
|
||||||
agent: new HttpsProxyAgent(proxy, {
|
agent: new HttpsProxyAgent(proxy, {
|
||||||
@@ -152,7 +152,7 @@ export async function getTMDBUpcomingTVShows(
|
|||||||
|
|
||||||
// 使用 on_the_air 接口获取正在播出的电视剧
|
// 使用 on_the_air 接口获取正在播出的电视剧
|
||||||
const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL;
|
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
|
const fetchOptions: any = proxy
|
||||||
? {
|
? {
|
||||||
agent: new HttpsProxyAgent(proxy, {
|
agent: new HttpsProxyAgent(proxy, {
|
||||||
@@ -290,7 +290,7 @@ export async function getTMDBVideos(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL;
|
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
|
const fetchOptions: any = proxy
|
||||||
? {
|
? {
|
||||||
agent: new HttpsProxyAgent(proxy, {
|
agent: new HttpsProxyAgent(proxy, {
|
||||||
@@ -344,7 +344,7 @@ export async function getTMDBTrendingContent(
|
|||||||
|
|
||||||
// 获取本周热门内容(电影+电视剧)
|
// 获取本周热门内容(电影+电视剧)
|
||||||
const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL;
|
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
|
const fetchOptions: any = proxy
|
||||||
? {
|
? {
|
||||||
agent: new HttpsProxyAgent(proxy, {
|
agent: new HttpsProxyAgent(proxy, {
|
||||||
@@ -478,7 +478,7 @@ export async function searchTMDBMulti(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL;
|
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
|
const fetchOptions: any = proxy
|
||||||
? {
|
? {
|
||||||
agent: new HttpsProxyAgent(proxy, {
|
agent: new HttpsProxyAgent(proxy, {
|
||||||
@@ -531,7 +531,7 @@ export async function getTMDBMovieRecommendations(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL;
|
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
|
const fetchOptions: any = proxy
|
||||||
? {
|
? {
|
||||||
agent: new HttpsProxyAgent(proxy, {
|
agent: new HttpsProxyAgent(proxy, {
|
||||||
@@ -584,7 +584,7 @@ export async function getTMDBTVRecommendations(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL;
|
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
|
const fetchOptions: any = proxy
|
||||||
? {
|
? {
|
||||||
agent: new HttpsProxyAgent(proxy, {
|
agent: new HttpsProxyAgent(proxy, {
|
||||||
@@ -637,7 +637,7 @@ export async function getTMDBMovieDetails(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL;
|
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
|
const fetchOptions: any = proxy
|
||||||
? {
|
? {
|
||||||
agent: new HttpsProxyAgent(proxy, {
|
agent: new HttpsProxyAgent(proxy, {
|
||||||
@@ -690,7 +690,7 @@ export async function getTMDBTVDetails(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL;
|
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
|
const fetchOptions: any = proxy
|
||||||
? {
|
? {
|
||||||
agent: new HttpsProxyAgent(proxy, {
|
agent: new HttpsProxyAgent(proxy, {
|
||||||
|
|||||||
+15
-6
@@ -4,6 +4,9 @@ import { HttpsProxyAgent } from 'https-proxy-agent';
|
|||||||
import nodeFetch from 'node-fetch';
|
import nodeFetch from 'node-fetch';
|
||||||
import { getNextApiKey } from './tmdb.client';
|
import { getNextApiKey } from './tmdb.client';
|
||||||
|
|
||||||
|
// TMDB API 默认 Base URL(不包含 /3/,由程序拼接)
|
||||||
|
const DEFAULT_TMDB_BASE_URL = 'https://api.themoviedb.org';
|
||||||
|
|
||||||
export interface TMDBSearchResult {
|
export interface TMDBSearchResult {
|
||||||
id: number;
|
id: number;
|
||||||
title?: string; // 电影
|
title?: string; // 电影
|
||||||
@@ -30,7 +33,8 @@ export async function searchTMDB(
|
|||||||
apiKey: string,
|
apiKey: string,
|
||||||
query: string,
|
query: string,
|
||||||
proxy?: string,
|
proxy?: string,
|
||||||
year?: number
|
year?: number,
|
||||||
|
reverseProxyBaseUrl?: string
|
||||||
): Promise<{ code: number; result: TMDBSearchResult | null }> {
|
): Promise<{ code: number; result: TMDBSearchResult | null }> {
|
||||||
try {
|
try {
|
||||||
const actualKey = getNextApiKey(apiKey);
|
const actualKey = getNextApiKey(apiKey);
|
||||||
@@ -38,8 +42,9 @@ export async function searchTMDB(
|
|||||||
return { code: 400, result: null };
|
return { code: 400, result: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const baseUrl = reverseProxyBaseUrl || DEFAULT_TMDB_BASE_URL;
|
||||||
// 使用 multi search 同时搜索电影和电视剧
|
// 使用 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) {
|
if (year) {
|
||||||
@@ -120,7 +125,8 @@ interface TMDBTVDetails {
|
|||||||
export async function getTVSeasons(
|
export async function getTVSeasons(
|
||||||
apiKey: string,
|
apiKey: string,
|
||||||
tvId: number,
|
tvId: number,
|
||||||
proxy?: string
|
proxy?: string,
|
||||||
|
reverseProxyBaseUrl?: string
|
||||||
): Promise<{ code: number; seasons: TMDBSeasonInfo[] | null }> {
|
): Promise<{ code: number; seasons: TMDBSeasonInfo[] | null }> {
|
||||||
try {
|
try {
|
||||||
const actualKey = getNextApiKey(apiKey);
|
const actualKey = getNextApiKey(apiKey);
|
||||||
@@ -128,7 +134,8 @@ export async function getTVSeasons(
|
|||||||
return { code: 400, seasons: null };
|
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
|
const fetchOptions: any = proxy
|
||||||
? {
|
? {
|
||||||
@@ -171,7 +178,8 @@ export async function getTVSeasonDetails(
|
|||||||
apiKey: string,
|
apiKey: string,
|
||||||
tvId: number,
|
tvId: number,
|
||||||
seasonNumber: number,
|
seasonNumber: number,
|
||||||
proxy?: string
|
proxy?: string,
|
||||||
|
reverseProxyBaseUrl?: string
|
||||||
): Promise<{ code: number; season: TMDBSeasonInfo | null }> {
|
): Promise<{ code: number; season: TMDBSeasonInfo | null }> {
|
||||||
try {
|
try {
|
||||||
const actualKey = getNextApiKey(apiKey);
|
const actualKey = getNextApiKey(apiKey);
|
||||||
@@ -179,7 +187,8 @@ export async function getTVSeasonDetails(
|
|||||||
return { code: 400, season: null };
|
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
|
const fetchOptions: any = proxy
|
||||||
? {
|
? {
|
||||||
|
|||||||
+13
-1
@@ -46,7 +46,19 @@ export function processImageUrl(originalUrl: string): string {
|
|||||||
return originalUrl;
|
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')) {
|
if (!originalUrl.includes('doubanio.com')) {
|
||||||
return originalUrl;
|
return originalUrl;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,7 +99,8 @@ export async function getXiaoyaMetadata(
|
|||||||
xiaoyaClient: XiaoyaClient,
|
xiaoyaClient: XiaoyaClient,
|
||||||
videoPath: string,
|
videoPath: string,
|
||||||
tmdbApiKey?: string,
|
tmdbApiKey?: string,
|
||||||
tmdbProxy?: string
|
tmdbProxy?: string,
|
||||||
|
tmdbReverseProxy?: string
|
||||||
): Promise<XiaoyaMetadata> {
|
): Promise<XiaoyaMetadata> {
|
||||||
const pathParts = videoPath.split('/').filter(Boolean);
|
const pathParts = videoPath.split('/').filter(Boolean);
|
||||||
|
|
||||||
@@ -216,7 +217,7 @@ export async function getXiaoyaMetadata(
|
|||||||
|
|
||||||
if (!isPureNumber && !isSeasonEpisode) {
|
if (!isPureNumber && !isSeasonEpisode) {
|
||||||
const { searchTMDB, getTMDBImageUrl } = await import('./tmdb.search');
|
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) {
|
if (tmdbResult.code === 200 && tmdbResult.result) {
|
||||||
return {
|
return {
|
||||||
@@ -244,7 +245,7 @@ export async function getXiaoyaMetadata(
|
|||||||
.trim();
|
.trim();
|
||||||
|
|
||||||
const { searchTMDB, getTMDBImageUrl } = await import('./tmdb.search');
|
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) {
|
if (tmdbResult.code === 200 && tmdbResult.result) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user