Files
MoonTVPlus/src/app/api/tmdb-details/route.ts
T
2025-12-28 00:51:02 +08:00

181 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { NextRequest, NextResponse } from 'next/server';
import {
searchTMDBMulti,
getTMDBMovieDetails,
getTMDBTVDetails,
getTMDBImageUrl,
} from '@/lib/tmdb.client';
import { getConfig } from '@/lib/config';
// 服务器端缓存(内存)
const searchCache = new Map<
string,
{ data: { tmdbId: number; mediaType: 'movie' | 'tv' }; timestamp: number }
>();
const CACHE_TTL = 24 * 60 * 60 * 1000; // 1天
// 移除季度信息的辅助函数
function removeSeasonInfo(title: string): string {
return title
.replace(/第[一二三四五六七八九十\d]+[(]\d+[)][季部]/g, '')
.replace(/第[一二三四五六七八九十\d]+[季部]/g, '')
.replace(/[(]\d+[)]/g, '')
.replace(/\s+season\s+\d+/gi, '')
.replace(/\s+S\d+/gi, '')
.trim();
}
// 精确匹配标题
function findExactMatch(results: any[], originalTitle: string): any | null {
if (!results || results.length === 0) return null;
if (results.length === 1) return results[0];
const cleanTitle = originalTitle.toLowerCase().trim();
// 尝试精确匹配
for (const result of results) {
const resultTitle = (result.title || result.name || '').toLowerCase().trim();
if (resultTitle === cleanTitle) {
return result;
}
}
// 如果没有精确匹配,返回第一个
return results[0];
}
export async function GET(request: NextRequest) {
try {
const searchParams = request.nextUrl.searchParams;
const title = searchParams.get('title');
const cachedId = searchParams.get('cachedId');
if (!title && !cachedId) {
return NextResponse.json(
{ error: '缺少 title 或 cachedId 参数' },
{ status: 400 }
);
}
// 获取配置
const config = await getConfig();
const tmdbApiKey = config.SiteConfig.TMDBApiKey;
const tmdbProxy = config.SiteConfig.TMDBProxy;
if (!tmdbApiKey) {
return NextResponse.json(
{ error: '未配置 TMDB API Key' },
{ status: 500 }
);
}
let tmdbId: number;
let mediaType: 'movie' | 'tv';
// 如果提供了cachedId,直接使用
if (cachedId) {
const [type, id] = cachedId.split(':');
mediaType = type as 'movie' | 'tv';
tmdbId = parseInt(id, 10);
} else {
// 否则需要搜索获取ID
const cleanedTitle = removeSeasonInfo(title!);
const cacheKey = `search_${cleanedTitle}`;
// 检查服务器缓存
const cached = searchCache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
console.log('使用服务器缓存的搜索结果');
tmdbId = cached.data.tmdbId;
mediaType = cached.data.mediaType;
} else {
// 搜索TMDB
console.log('搜索TMDB:', cleanedTitle);
const searchResult = await searchTMDBMulti(
tmdbApiKey,
cleanedTitle,
tmdbProxy
);
if (searchResult.code !== 200 || !searchResult.results.length) {
return NextResponse.json(
{ error: '未找到匹配的内容' },
{ status: 404 }
);
}
// 精确匹配
const match = findExactMatch(searchResult.results, cleanedTitle);
if (!match) {
return NextResponse.json(
{ error: '未找到匹配的内容' },
{ status: 404 }
);
}
tmdbId = match.id;
mediaType = match.media_type;
// 保存到服务器缓存
searchCache.set(cacheKey, {
data: { tmdbId, mediaType },
timestamp: Date.now(),
});
// 清理过期缓存
Array.from(searchCache.entries()).forEach(([key, value]) => {
if (Date.now() - value.timestamp > CACHE_TTL) {
searchCache.delete(key);
}
});
}
}
// 获取详情
let detailsResult;
if (mediaType === 'movie') {
detailsResult = await getTMDBMovieDetails(tmdbApiKey, tmdbId, tmdbProxy);
} else {
detailsResult = await getTMDBTVDetails(tmdbApiKey, tmdbId, tmdbProxy);
}
if (detailsResult.code !== 200 || !detailsResult.details) {
return NextResponse.json(
{ error: '获取详情失败' },
{ status: detailsResult.code }
);
}
const details = detailsResult.details;
// 构建返回数据
const responseData = {
tmdbId: `${mediaType}:${tmdbId}`, // 用于缓存
mediaType,
title: details.title || details.name,
backdrop: details.backdrop_path
? getTMDBImageUrl(details.backdrop_path, 'w1280')
: null,
poster: details.poster_path
? getTMDBImageUrl(details.poster_path, 'w500')
: null,
overview: details.overview || '',
rating: details.vote_average ? details.vote_average.toFixed(1) : '',
releaseDate: details.release_date || details.first_air_date || '',
};
return NextResponse.json(responseData, {
status: 200,
headers: {
'Cache-Control': 'public, max-age=86400', // 浏览器缓存1天
},
});
} catch (error) {
console.error('获取 TMDB 详情失败:', error);
return NextResponse.json(
{ error: '获取详情失败' },
{ status: 500 }
);
}
}