Files
MoonTVPlus/src/lib/tmdb.client.ts

604 lines
16 KiB
TypeScript
Raw Normal View History

2025-12-14 22:15:38 +08:00
/* eslint-disable @typescript-eslint/no-explicit-any,no-console */
2025-12-20 22:11:34 +08:00
import { HttpsProxyAgent } from 'https-proxy-agent';
import nodeFetch from 'node-fetch';
2025-12-20 22:11:34 +08:00
2025-12-14 22:15:38 +08:00
export interface TMDBMovie {
id: number;
title: string;
poster_path: string | null;
release_date: string;
overview: string;
vote_average: number;
}
export interface TMDBTVShow {
id: number;
name: string;
poster_path: string | null;
first_air_date: string;
overview: string;
vote_average: number;
}
// 统一的类型,用于显示
export interface TMDBItem {
id: number;
title: string;
poster_path: string | null;
2025-12-27 11:37:43 +08:00
backdrop_path?: string | null; // 背景图,用于轮播图
2025-12-14 22:15:38 +08:00
release_date: string;
overview: string;
vote_average: number;
media_type: 'movie' | 'tv';
2025-12-27 11:37:43 +08:00
genre_ids?: number[]; // 类型ID列表
2025-12-14 22:15:38 +08:00
}
interface TMDBUpcomingResponse {
results: TMDBMovie[];
page: number;
total_pages: number;
total_results: number;
}
interface TMDBTVAiringTodayResponse {
results: TMDBTVShow[];
page: number;
total_pages: number;
total_results: number;
}
/**
*
* @param apiKey - TMDB API Key
* @param page -
* @param region - CN ()
2025-12-20 22:11:34 +08:00
* @param proxy -
2025-12-14 22:15:38 +08:00
* @returns
*/
export async function getTMDBUpcomingMovies(
apiKey: string,
page: number = 1,
2025-12-20 22:11:34 +08:00
region: string = 'CN',
proxy?: string
2025-12-14 22:15:38 +08:00
): Promise<{ code: number; list: TMDBMovie[] }> {
try {
if (!apiKey) {
return { code: 400, list: [] };
}
2025-12-20 22:11:34 +08:00
const url = `https://api.themoviedb.org/3/movie/upcoming?api_key=${apiKey}&language=zh-CN&page=${page}&region=${region}`;
2025-12-22 00:42:27 +08:00
const fetchOptions: any = proxy
? {
agent: new HttpsProxyAgent(proxy, {
timeout: 30000,
keepAlive: false,
}),
2025-12-22 00:42:27 +08:00
signal: AbortSignal.timeout(30000),
}
: {
signal: AbortSignal.timeout(15000),
};
2025-12-20 22:11:34 +08:00
// 使用 node-fetch 而不是原生 fetch
const response = await nodeFetch(url, fetchOptions);
2025-12-14 22:15:38 +08:00
if (!response.ok) {
console.error('TMDB API 请求失败:', response.status, response.statusText);
return { code: response.status, list: [] };
}
const data: TMDBUpcomingResponse = await response.json() as TMDBUpcomingResponse;
2025-12-14 22:15:38 +08:00
return {
code: 200,
list: data.results,
};
} catch (error) {
console.error('获取 TMDB 即将上映电影失败:', error);
return { code: 500, list: [] };
}
}
/**
*
* @param apiKey - TMDB API Key
* @param page -
2025-12-20 22:11:34 +08:00
* @param proxy -
2025-12-14 22:15:38 +08:00
* @returns
*/
export async function getTMDBUpcomingTVShows(
apiKey: string,
2025-12-20 22:11:34 +08:00
page: number = 1,
proxy?: string
2025-12-14 22:15:38 +08:00
): Promise<{ code: number; list: TMDBTVShow[] }> {
try {
if (!apiKey) {
return { code: 400, list: [] };
}
// 使用 on_the_air 接口获取正在播出的电视剧
2025-12-20 22:11:34 +08:00
const url = `https://api.themoviedb.org/3/tv/on_the_air?api_key=${apiKey}&language=zh-CN&page=${page}`;
2025-12-22 00:42:27 +08:00
const fetchOptions: any = proxy
? {
agent: new HttpsProxyAgent(proxy, {
timeout: 30000,
keepAlive: false,
}),
2025-12-22 00:42:27 +08:00
signal: AbortSignal.timeout(30000),
}
: {
signal: AbortSignal.timeout(15000),
};
2025-12-20 22:11:34 +08:00
// 使用 node-fetch 而不是原生 fetch
const response = await nodeFetch(url, fetchOptions);
2025-12-14 22:15:38 +08:00
if (!response.ok) {
console.error('TMDB TV API 请求失败:', response.status, response.statusText);
return { code: response.status, list: [] };
}
const data: TMDBTVAiringTodayResponse = await response.json() as TMDBTVAiringTodayResponse;
2025-12-14 22:15:38 +08:00
return {
code: 200,
list: data.results,
};
} catch (error) {
console.error('获取 TMDB 正在播出电视剧失败:', error);
return { code: 500, list: [] };
}
}
/**
* /+
* @param apiKey - TMDB API Key
2025-12-20 22:11:34 +08:00
* @param proxy -
2025-12-14 22:15:38 +08:00
* @returns /
*/
export async function getTMDBUpcomingContent(
2025-12-20 22:11:34 +08:00
apiKey: string,
proxy?: string
2025-12-14 22:15:38 +08:00
): Promise<{ code: number; list: TMDBItem[] }> {
try {
if (!apiKey) {
return { code: 400, list: [] };
}
// 并行获取电影和电视剧数据
const [moviesResult, tvShowsResult] = await Promise.all([
2025-12-20 22:11:34 +08:00
getTMDBUpcomingMovies(apiKey, 1, 'CN', proxy),
getTMDBUpcomingTVShows(apiKey, 1, proxy),
2025-12-14 22:15:38 +08:00
]);
// 检查是否有错误
if (moviesResult.code !== 200 && tvShowsResult.code !== 200) {
// 两个请求都失败,返回错误
return { code: moviesResult.code, list: [] };
}
2025-12-14 22:15:38 +08:00
// 获取今天的日期(本地时区)
const today = new Date();
const todayStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
// 转换电影数据为统一格式,并过滤掉已上映的
const movies: TMDBItem[] = moviesResult.code === 200
? moviesResult.list
.filter((movie) => {
// 只保留未来上映的电影
return movie.release_date && movie.release_date >= todayStr;
})
.map((movie) => ({
id: movie.id,
title: movie.title,
poster_path: movie.poster_path,
release_date: movie.release_date,
overview: movie.overview,
vote_average: movie.vote_average,
media_type: 'movie' as const,
}))
: [];
2025-12-14 22:15:38 +08:00
// 转换电视剧数据为统一格式,并过滤掉已播出的
const tvShows: TMDBItem[] = tvShowsResult.code === 200
? tvShowsResult.list
.filter((tv) => {
// 只保留未来播出的电视剧
return tv.first_air_date && tv.first_air_date >= todayStr;
})
.map((tv) => ({
id: tv.id,
title: tv.name,
poster_path: tv.poster_path,
release_date: tv.first_air_date,
overview: tv.overview,
vote_average: tv.vote_average,
media_type: 'tv' as const,
}))
: [];
2025-12-14 22:15:38 +08:00
// 合并并返回
const allContent = [...movies, ...tvShows];
return {
code: 200,
list: allContent,
};
} catch (error) {
console.error('获取 TMDB 即将上映内容失败:', error);
return { code: 500, list: [] };
}
}
2025-12-27 11:37:43 +08:00
/**
* +
* @param apiKey - TMDB API Key
* @param proxy -
* @returns
*/
export async function getTMDBTrendingContent(
apiKey: string,
proxy?: string
): Promise<{ code: number; list: TMDBItem[] }> {
try {
if (!apiKey) {
return { code: 400, list: [] };
}
// 获取本周热门内容(电影+电视剧)
const url = `https://api.themoviedb.org/3/trending/all/week?api_key=${apiKey}&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);
if (!response.ok) {
console.error('TMDB Trending API 请求失败:', response.status, response.statusText);
return { code: response.status, list: [] };
}
const data: any = await response.json();
// 转换为统一格式只保留有backdrop_path的项目用于轮播图
const items: TMDBItem[] = data.results
.filter((item: any) => item.backdrop_path) // 只保留有背景图的
.slice(0, 10) // 只取前10个
.map((item: any) => ({
id: item.id,
title: item.title || item.name,
poster_path: item.poster_path,
backdrop_path: item.backdrop_path, // 添加背景图
release_date: item.release_date || item.first_air_date || '',
overview: item.overview,
vote_average: item.vote_average,
media_type: item.media_type as 'movie' | 'tv',
genre_ids: item.genre_ids || [], // 保存类型ID
}));
return {
code: 200,
list: items,
};
} catch (error) {
console.error('获取 TMDB 热门内容失败:', error);
return { code: 500, list: [] };
}
}
2025-12-14 22:15:38 +08:00
/**
* TMDB URL
* @param path -
* @param size - w500
* @returns URL
*/
export function getTMDBImageUrl(
path: string | null,
size: string = 'w500'
): string {
if (!path) return '';
return `https://image.tmdb.org/t/p/${size}${path}`;
}
2025-12-27 11:37:43 +08:00
/**
* TMDB
*/
export const TMDB_GENRES: Record<number, string> = {
// 电影类型
28: '动作',
12: '冒险',
16: '动画',
35: '喜剧',
80: '犯罪',
99: '纪录',
18: '剧情',
10751: '家庭',
14: '奇幻',
36: '历史',
27: '恐怖',
10402: '音乐',
9648: '悬疑',
10749: '爱情',
878: '科幻',
10770: '电视电影',
53: '惊悚',
10752: '战争',
37: '西部',
// 电视剧类型
10759: '动作冒险',
10762: '儿童',
10763: '新闻',
10764: '真人秀',
10765: '科幻奇幻',
10766: '肥皂剧',
10767: '脱口秀',
10768: '战争政治',
};
/**
* ID获取类型名称列表
* @param genreIds - ID数组
* @param limit - 2
* @returns
*/
export function getGenreNames(genreIds: number[] = [], limit: number = 2): string[] {
return genreIds
.map(id => TMDB_GENRES[id])
.filter(Boolean)
.slice(0, limit);
}
2025-12-27 23:45:34 +08:00
/**
*
* @param apiKey - TMDB API Key
* @param query -
* @param proxy -
* @returns
*/
export async function searchTMDBMulti(
apiKey: string,
query: string,
proxy?: string
): Promise<{ code: number; results: any[] }> {
try {
if (!apiKey || !query) {
return { code: 400, results: [] };
}
const url = `https://api.themoviedb.org/3/search/multi?api_key=${apiKey}&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);
if (!response.ok) {
console.error('TMDB Search API 请求失败:', response.status, response.statusText);
return { code: response.status, results: [] };
}
const data: any = await response.json();
return {
code: 200,
results: data.results || [],
};
} catch (error) {
console.error('搜索 TMDB 内容失败:', error);
return { code: 500, results: [] };
}
}
/**
*
* @param apiKey - TMDB API Key
* @param movieId - ID
* @param proxy -
* @returns
*/
export async function getTMDBMovieRecommendations(
apiKey: string,
movieId: number,
proxy?: string
): Promise<{ code: number; results: TMDBMovie[] }> {
try {
if (!apiKey || !movieId) {
return { code: 400, results: [] };
}
const url = `https://api.themoviedb.org/3/movie/${movieId}/recommendations?api_key=${apiKey}&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);
if (!response.ok) {
console.error('TMDB Movie Recommendations API 请求失败:', response.status, response.statusText);
return { code: response.status, results: [] };
}
const data: any = await response.json();
return {
code: 200,
results: data.results || [],
};
} catch (error) {
console.error('获取 TMDB 电影推荐失败:', error);
return { code: 500, results: [] };
}
}
/**
*
* @param apiKey - TMDB API Key
* @param tvId - ID
* @param proxy -
* @returns
*/
export async function getTMDBTVRecommendations(
apiKey: string,
tvId: number,
proxy?: string
): Promise<{ code: number; results: TMDBTVShow[] }> {
try {
if (!apiKey || !tvId) {
return { code: 400, results: [] };
}
const url = `https://api.themoviedb.org/3/tv/${tvId}/recommendations?api_key=${apiKey}&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);
if (!response.ok) {
console.error('TMDB TV Recommendations API 请求失败:', response.status, response.statusText);
return { code: response.status, results: [] };
}
const data: any = await response.json();
return {
code: 200,
results: data.results || [],
};
} catch (error) {
console.error('获取 TMDB 电视剧推荐失败:', error);
return { code: 500, results: [] };
}
}
2025-12-28 00:51:02 +08:00
/**
* TMDB
* @param apiKey - TMDB API Key
* @param movieId - ID
* @param proxy -
* @returns
*/
export async function getTMDBMovieDetails(
apiKey: string,
movieId: number,
proxy?: string
): Promise<{ code: number; details: any }> {
try {
if (!apiKey) {
return { code: 400, details: null };
}
const url = `https://api.themoviedb.org/3/movie/${movieId}?api_key=${apiKey}&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);
if (!response.ok) {
console.error('TMDB API 请求失败:', response.status, response.statusText);
return { code: response.status, details: null };
}
const data: any = await response.json();
return {
code: 200,
details: data,
};
} catch (error) {
console.error('获取 TMDB 电影详情失败:', error);
return { code: 500, details: null };
}
}
/**
* TMDB
* @param apiKey - TMDB API Key
* @param tvId - ID
* @param proxy -
* @returns
*/
export async function getTMDBTVDetails(
apiKey: string,
tvId: number,
proxy?: string
): Promise<{ code: number; details: any }> {
try {
if (!apiKey) {
return { code: 400, details: null };
}
const url = `https://api.themoviedb.org/3/tv/${tvId}?api_key=${apiKey}&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);
if (!response.ok) {
console.error('TMDB API 请求失败:', response.status, response.statusText);
return { code: response.status, details: null };
}
const data: any = await response.json();
return {
code: 200,
details: data,
};
} catch (error) {
console.error('获取 TMDB 电视剧详情失败:', error);
return { code: 500, details: null };
}
}