149 lines
3.9 KiB
TypeScript
149 lines
3.9 KiB
TypeScript
import { DoubanItem, DoubanResult } from './types';
|
|
import { getDoubanProxyUrl } from './utils';
|
|
|
|
interface DoubanCategoriesParams {
|
|
kind: 'tv' | 'movie';
|
|
category: string;
|
|
type: string;
|
|
pageLimit?: number;
|
|
pageStart?: number;
|
|
}
|
|
|
|
interface DoubanCategoryApiResponse {
|
|
total: number;
|
|
items: Array<{
|
|
id: string;
|
|
title: string;
|
|
card_subtitle: string;
|
|
pic: {
|
|
large: string;
|
|
normal: string;
|
|
};
|
|
rating: {
|
|
value: number;
|
|
};
|
|
}>;
|
|
}
|
|
|
|
/**
|
|
* 带超时的 fetch 请求
|
|
*/
|
|
async function fetchWithTimeout(
|
|
url: string,
|
|
options: RequestInit = {}
|
|
): Promise<Response> {
|
|
const controller = new AbortController();
|
|
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10秒超时
|
|
|
|
// 检查是否使用代理
|
|
const proxyUrl = getDoubanProxyUrl();
|
|
const finalUrl = proxyUrl ? `${proxyUrl}${encodeURIComponent(url)}` : url;
|
|
|
|
const fetchOptions: RequestInit = {
|
|
...options,
|
|
signal: controller.signal,
|
|
headers: {
|
|
'User-Agent':
|
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
|
|
Referer: 'https://movie.douban.com/',
|
|
Accept: 'application/json, text/plain, */*',
|
|
...options.headers,
|
|
},
|
|
};
|
|
|
|
try {
|
|
const response = await fetch(finalUrl, fetchOptions);
|
|
clearTimeout(timeoutId);
|
|
return response;
|
|
} catch (error) {
|
|
clearTimeout(timeoutId);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 检查是否应该使用客户端获取豆瓣数据
|
|
*/
|
|
export function shouldUseDoubanClient(): boolean {
|
|
return getDoubanProxyUrl() !== null;
|
|
}
|
|
|
|
/**
|
|
* 浏览器端豆瓣分类数据获取函数
|
|
*/
|
|
export async function fetchDoubanCategories(
|
|
params: DoubanCategoriesParams
|
|
): Promise<DoubanResult> {
|
|
const { kind, category, type, pageLimit = 20, pageStart = 0 } = params;
|
|
|
|
// 验证参数
|
|
if (!['tv', 'movie'].includes(kind)) {
|
|
throw new Error('kind 参数必须是 tv 或 movie');
|
|
}
|
|
|
|
if (!category || !type) {
|
|
throw new Error('category 和 type 参数不能为空');
|
|
}
|
|
|
|
if (pageLimit < 1 || pageLimit > 100) {
|
|
throw new Error('pageLimit 必须在 1-100 之间');
|
|
}
|
|
|
|
if (pageStart < 0) {
|
|
throw new Error('pageStart 不能小于 0');
|
|
}
|
|
|
|
const target = `https://m.douban.com/rexxar/api/v2/subject/recent_hot/${kind}?start=${pageStart}&limit=${pageLimit}&category=${category}&type=${type}`;
|
|
|
|
try {
|
|
const response = await fetchWithTimeout(target);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
}
|
|
|
|
const doubanData: DoubanCategoryApiResponse = await response.json();
|
|
|
|
// 转换数据格式
|
|
const list: DoubanItem[] = doubanData.items.map((item) => ({
|
|
id: item.id,
|
|
title: item.title,
|
|
poster: item.pic?.normal || item.pic?.large || '',
|
|
rate: item.rating?.value ? item.rating.value.toFixed(1) : '',
|
|
year: item.card_subtitle?.match(/(\d{4})/)?.[1] || '',
|
|
}));
|
|
|
|
return {
|
|
code: 200,
|
|
message: '获取成功',
|
|
list: list,
|
|
};
|
|
} catch (error) {
|
|
throw new Error(`获取豆瓣分类数据失败: ${(error as Error).message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 统一的豆瓣分类数据获取函数,根据代理设置选择使用服务端 API 或客户端代理获取
|
|
*/
|
|
export async function getDoubanCategories(
|
|
params: DoubanCategoriesParams
|
|
): Promise<DoubanResult> {
|
|
if (shouldUseDoubanClient()) {
|
|
// 使用客户端代理获取(当设置了代理 URL 时)
|
|
return fetchDoubanCategories(params);
|
|
} else {
|
|
// 使用服务端 API(当没有设置代理 URL 时)
|
|
const { kind, category, type, pageLimit = 20, pageStart = 0 } = params;
|
|
const response = await fetch(
|
|
`/api/douban/categories?kind=${kind}&category=${category}&type=${type}&limit=${pageLimit}&start=${pageStart}`
|
|
);
|
|
|
|
if (!response.ok) {
|
|
throw new Error('获取豆瓣分类数据失败');
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
}
|