732 lines
19 KiB
TypeScript
732 lines
19 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-explicit-any,no-console,no-case-declarations */
|
|
|
|
import { DoubanItem, DoubanResult } from './types';
|
|
|
|
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;
|
|
};
|
|
}>;
|
|
}
|
|
|
|
interface DoubanListApiResponse {
|
|
total: number;
|
|
subjects: Array<{
|
|
id: string;
|
|
title: string;
|
|
card_subtitle: string;
|
|
cover: string;
|
|
rate: string;
|
|
}>;
|
|
}
|
|
|
|
interface DoubanRecommendApiResponse {
|
|
total: number;
|
|
items: Array<{
|
|
id: string;
|
|
title: string;
|
|
year: string;
|
|
type: string;
|
|
pic: {
|
|
large: string;
|
|
normal: string;
|
|
};
|
|
rating: {
|
|
value: number;
|
|
};
|
|
}>;
|
|
}
|
|
|
|
interface DoubanDetailApiResponse {
|
|
id: string;
|
|
title: string;
|
|
original_title?: string;
|
|
year: string;
|
|
type: 'movie' | 'tv';
|
|
subtype?: string;
|
|
is_tv?: boolean;
|
|
pic?: {
|
|
large: string;
|
|
normal: string;
|
|
};
|
|
rating?: {
|
|
value: number;
|
|
count: number;
|
|
star_count: number;
|
|
};
|
|
card_subtitle?: string;
|
|
intro?: string;
|
|
genres?: string[];
|
|
directors?: Array<{ name: string; id?: string }>;
|
|
actors?: Array<{ name: string; id?: string }>;
|
|
countries?: string[];
|
|
languages?: string[];
|
|
pubdate?: string[];
|
|
durations?: string[];
|
|
aka?: string[];
|
|
episodes_count?: number;
|
|
episodes_info?: string;
|
|
cover_url?: string;
|
|
url?: string;
|
|
[key: string]: any; // 允许其他字段
|
|
}
|
|
|
|
type DoubanProxyType =
|
|
| 'direct'
|
|
| 'cors-proxy-zwei'
|
|
| 'cmliussss-cdn-tencent'
|
|
| 'cmliussss-cdn-ali'
|
|
| 'cors-anywhere'
|
|
| 'custom';
|
|
|
|
function normalizeDoubanProxyConfig(
|
|
proxyType: DoubanProxyType,
|
|
proxyUrl: string
|
|
): {
|
|
proxyType: DoubanProxyType;
|
|
proxyUrl: string;
|
|
} {
|
|
const normalizedProxyUrl = proxyUrl.trim();
|
|
|
|
if (proxyType === 'custom' && !normalizedProxyUrl) {
|
|
return {
|
|
proxyType: 'direct',
|
|
proxyUrl: '',
|
|
};
|
|
}
|
|
|
|
return {
|
|
proxyType,
|
|
proxyUrl: normalizedProxyUrl,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 带超时的 fetch 请求
|
|
*/
|
|
async function fetchWithTimeout(
|
|
url: string,
|
|
proxyUrl: string
|
|
): Promise<Response> {
|
|
const controller = new AbortController();
|
|
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10秒超时
|
|
|
|
// 检查是否使用代理
|
|
const finalUrl =
|
|
proxyUrl === 'https://cors-anywhere.com/'
|
|
? `${proxyUrl}${url}`
|
|
: proxyUrl
|
|
? `${proxyUrl}${encodeURIComponent(url)}`
|
|
: url;
|
|
|
|
const fetchOptions: RequestInit = {
|
|
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, */*',
|
|
},
|
|
};
|
|
|
|
try {
|
|
const response = await fetch(finalUrl, fetchOptions);
|
|
clearTimeout(timeoutId);
|
|
return response;
|
|
} catch (error) {
|
|
clearTimeout(timeoutId);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
function getDoubanProxyConfig(): {
|
|
proxyType:
|
|
| 'direct'
|
|
| 'cors-proxy-zwei'
|
|
| 'cmliussss-cdn-tencent'
|
|
| 'cmliussss-cdn-ali'
|
|
| 'cors-anywhere'
|
|
| 'custom';
|
|
proxyUrl: string;
|
|
backupProxyType:
|
|
| 'direct'
|
|
| 'cors-proxy-zwei'
|
|
| 'cmliussss-cdn-tencent'
|
|
| 'cmliussss-cdn-ali'
|
|
| 'cors-anywhere'
|
|
| 'custom';
|
|
backupProxyUrl: string;
|
|
} {
|
|
const doubanProxyType =
|
|
localStorage.getItem('doubanDataSource') ||
|
|
(window as any).RUNTIME_CONFIG?.DOUBAN_PROXY_TYPE ||
|
|
'cmliussss-cdn-tencent';
|
|
const doubanProxy =
|
|
localStorage.getItem('doubanProxyUrl') ||
|
|
(window as any).RUNTIME_CONFIG?.DOUBAN_PROXY ||
|
|
'';
|
|
const doubanProxyBackupType =
|
|
(localStorage.getItem('doubanDataSourceBackup') as DoubanProxyType | null) ||
|
|
'direct';
|
|
const doubanProxyBackupUrl =
|
|
localStorage.getItem('doubanProxyUrlBackup') || '';
|
|
const primaryConfig = normalizeDoubanProxyConfig(doubanProxyType, doubanProxy);
|
|
const backupConfig = normalizeDoubanProxyConfig(
|
|
doubanProxyBackupType,
|
|
doubanProxyBackupUrl
|
|
);
|
|
return {
|
|
proxyType: primaryConfig.proxyType,
|
|
proxyUrl: primaryConfig.proxyUrl,
|
|
backupProxyType: backupConfig.proxyType,
|
|
backupProxyUrl: backupConfig.proxyUrl,
|
|
};
|
|
}
|
|
|
|
function buildDoubanRequester(
|
|
proxyType: DoubanProxyType,
|
|
proxyUrl: string
|
|
): {
|
|
useDirectApi: boolean;
|
|
requestProxyUrl: string;
|
|
useTencentCDN: boolean;
|
|
useAliCDN: boolean;
|
|
} {
|
|
switch (proxyType) {
|
|
case 'cors-proxy-zwei':
|
|
return {
|
|
useDirectApi: false,
|
|
requestProxyUrl: 'https://ciao-cors.is-an.org/',
|
|
useTencentCDN: false,
|
|
useAliCDN: false,
|
|
};
|
|
case 'cmliussss-cdn-tencent':
|
|
return {
|
|
useDirectApi: false,
|
|
requestProxyUrl: '',
|
|
useTencentCDN: true,
|
|
useAliCDN: false,
|
|
};
|
|
case 'cmliussss-cdn-ali':
|
|
return {
|
|
useDirectApi: false,
|
|
requestProxyUrl: '',
|
|
useTencentCDN: false,
|
|
useAliCDN: true,
|
|
};
|
|
case 'cors-anywhere':
|
|
return {
|
|
useDirectApi: false,
|
|
requestProxyUrl: 'https://cors-anywhere.com/',
|
|
useTencentCDN: false,
|
|
useAliCDN: false,
|
|
};
|
|
case 'custom':
|
|
return {
|
|
useDirectApi: false,
|
|
requestProxyUrl: proxyUrl,
|
|
useTencentCDN: false,
|
|
useAliCDN: false,
|
|
};
|
|
case 'direct':
|
|
default:
|
|
return {
|
|
useDirectApi: true,
|
|
requestProxyUrl: '',
|
|
useTencentCDN: false,
|
|
useAliCDN: false,
|
|
};
|
|
}
|
|
}
|
|
|
|
async function requestDoubanWithFallback<T>(
|
|
primary: { proxyType: DoubanProxyType; proxyUrl: string },
|
|
backup: { proxyType: DoubanProxyType; proxyUrl: string },
|
|
runner: (requester: ReturnType<typeof buildDoubanRequester>) => Promise<T>
|
|
): Promise<T> {
|
|
const primaryRequester = buildDoubanRequester(primary.proxyType, primary.proxyUrl);
|
|
const backupRequester = buildDoubanRequester(backup.proxyType, backup.proxyUrl);
|
|
|
|
try {
|
|
return await runner(primaryRequester);
|
|
} catch (primaryError) {
|
|
const sameStrategy =
|
|
primary.proxyType === backup.proxyType && primary.proxyUrl === backup.proxyUrl;
|
|
if (sameStrategy) {
|
|
throw primaryError;
|
|
}
|
|
|
|
console.warn(
|
|
`[Douban] 主渠道失败,切换备用渠道: ${primary.proxyType} -> ${backup.proxyType}`,
|
|
primaryError
|
|
);
|
|
return runner(backupRequester);
|
|
}
|
|
}
|
|
|
|
function dispatchDoubanGlobalError(message: string) {
|
|
if (typeof window !== 'undefined') {
|
|
window.dispatchEvent(
|
|
new CustomEvent('globalError', {
|
|
detail: { message },
|
|
})
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 浏览器端豆瓣分类数据获取函数
|
|
*/
|
|
export async function fetchDoubanCategories(
|
|
params: DoubanCategoriesParams,
|
|
proxyUrl: string,
|
|
useTencentCDN = false,
|
|
useAliCDN = false
|
|
): 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 = useTencentCDN
|
|
? `https://m.douban.cmliussss.net/rexxar/api/v2/subject/recent_hot/${kind}?start=${pageStart}&limit=${pageLimit}&category=${category}&type=${type}`
|
|
: useAliCDN
|
|
? `https://m.douban.cmliussss.com/rexxar/api/v2/subject/recent_hot/${kind}?start=${pageStart}&limit=${pageLimit}&category=${category}&type=${type}`
|
|
: `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,
|
|
useTencentCDN || useAliCDN ? '' : proxyUrl
|
|
);
|
|
|
|
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> {
|
|
const { kind, category, type, pageLimit = 20, pageStart = 0 } = params;
|
|
const { proxyType, proxyUrl, backupProxyType, backupProxyUrl } =
|
|
getDoubanProxyConfig();
|
|
try {
|
|
return await requestDoubanWithFallback(
|
|
{ proxyType, proxyUrl },
|
|
{ proxyType: backupProxyType, proxyUrl: backupProxyUrl },
|
|
async ({ useDirectApi, requestProxyUrl, useTencentCDN, useAliCDN }) => {
|
|
if (useDirectApi) {
|
|
const response = await fetch(
|
|
`/api/douban/categories?kind=${kind}&category=${category}&type=${type}&limit=${pageLimit}&start=${pageStart}`
|
|
);
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
return fetchDoubanCategories(
|
|
params,
|
|
requestProxyUrl,
|
|
useTencentCDN,
|
|
useAliCDN
|
|
);
|
|
}
|
|
);
|
|
} catch (error) {
|
|
dispatchDoubanGlobalError('获取豆瓣分类数据失败');
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
interface DoubanListParams {
|
|
tag: string;
|
|
type: string;
|
|
pageLimit?: number;
|
|
pageStart?: number;
|
|
}
|
|
|
|
export async function getDoubanList(
|
|
params: DoubanListParams
|
|
): Promise<DoubanResult> {
|
|
const { tag, type, pageLimit = 20, pageStart = 0 } = params;
|
|
const { proxyType, proxyUrl, backupProxyType, backupProxyUrl } =
|
|
getDoubanProxyConfig();
|
|
try {
|
|
return await requestDoubanWithFallback(
|
|
{ proxyType, proxyUrl },
|
|
{ proxyType: backupProxyType, proxyUrl: backupProxyUrl },
|
|
async ({ useDirectApi, requestProxyUrl, useTencentCDN, useAliCDN }) => {
|
|
if (useDirectApi) {
|
|
const response = await fetch(
|
|
`/api/douban?tag=${tag}&type=${type}&pageSize=${pageLimit}&pageStart=${pageStart}`
|
|
);
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
return fetchDoubanList(
|
|
params,
|
|
requestProxyUrl,
|
|
useTencentCDN,
|
|
useAliCDN
|
|
);
|
|
}
|
|
);
|
|
} catch (error) {
|
|
dispatchDoubanGlobalError('获取豆瓣列表数据失败');
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export async function fetchDoubanList(
|
|
params: DoubanListParams,
|
|
proxyUrl: string,
|
|
useTencentCDN = false,
|
|
useAliCDN = false
|
|
): Promise<DoubanResult> {
|
|
const { tag, type, pageLimit = 20, pageStart = 0 } = params;
|
|
|
|
// 验证参数
|
|
if (!tag || !type) {
|
|
throw new Error('tag 和 type 参数不能为空');
|
|
}
|
|
|
|
if (!['tv', 'movie'].includes(type)) {
|
|
throw new Error('type 参数必须是 tv 或 movie');
|
|
}
|
|
|
|
if (pageLimit < 1 || pageLimit > 100) {
|
|
throw new Error('pageLimit 必须在 1-100 之间');
|
|
}
|
|
|
|
if (pageStart < 0) {
|
|
throw new Error('pageStart 不能小于 0');
|
|
}
|
|
|
|
const target = useTencentCDN
|
|
? `https://movie.douban.cmliussss.net/j/search_subjects?type=${type}&tag=${tag}&sort=recommend&page_limit=${pageLimit}&page_start=${pageStart}`
|
|
: useAliCDN
|
|
? `https://movie.douban.cmliussss.com/j/search_subjects?type=${type}&tag=${tag}&sort=recommend&page_limit=${pageLimit}&page_start=${pageStart}`
|
|
: `https://movie.douban.com/j/search_subjects?type=${type}&tag=${tag}&sort=recommend&page_limit=${pageLimit}&page_start=${pageStart}`;
|
|
|
|
try {
|
|
const response = await fetchWithTimeout(
|
|
target,
|
|
useTencentCDN || useAliCDN ? '' : proxyUrl
|
|
);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
}
|
|
|
|
const doubanData: DoubanListApiResponse = await response.json();
|
|
|
|
// 转换数据格式
|
|
const list: DoubanItem[] = doubanData.subjects.map((item) => ({
|
|
id: item.id,
|
|
title: item.title,
|
|
poster: item.cover,
|
|
rate: item.rate,
|
|
year: item.card_subtitle?.match(/(\d{4})/)?.[1] || '',
|
|
}));
|
|
|
|
return {
|
|
code: 200,
|
|
message: '获取成功',
|
|
list: list,
|
|
};
|
|
} catch (error) {
|
|
throw new Error(`获取豆瓣分类数据失败: ${(error as Error).message}`);
|
|
}
|
|
}
|
|
|
|
interface DoubanRecommendsParams {
|
|
kind: 'tv' | 'movie';
|
|
pageLimit?: number;
|
|
pageStart?: number;
|
|
category?: string;
|
|
format?: string;
|
|
label?: string;
|
|
region?: string;
|
|
year?: string;
|
|
platform?: string;
|
|
sort?: string;
|
|
}
|
|
|
|
export async function getDoubanRecommends(
|
|
params: DoubanRecommendsParams
|
|
): Promise<DoubanResult> {
|
|
const {
|
|
kind,
|
|
pageLimit = 20,
|
|
pageStart = 0,
|
|
category,
|
|
format,
|
|
label,
|
|
region,
|
|
year,
|
|
platform,
|
|
sort,
|
|
} = params;
|
|
const { proxyType, proxyUrl, backupProxyType, backupProxyUrl } =
|
|
getDoubanProxyConfig();
|
|
try {
|
|
return await requestDoubanWithFallback(
|
|
{ proxyType, proxyUrl },
|
|
{ proxyType: backupProxyType, proxyUrl: backupProxyUrl },
|
|
async ({ useDirectApi, requestProxyUrl, useTencentCDN, useAliCDN }) => {
|
|
if (useDirectApi) {
|
|
const response = await fetch(
|
|
`/api/douban/recommends?kind=${kind}&limit=${pageLimit}&start=${pageStart}&category=${category}&format=${format}®ion=${region}&year=${year}&platform=${platform}&sort=${sort}&label=${label}`
|
|
);
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
return fetchDoubanRecommends(
|
|
params,
|
|
requestProxyUrl,
|
|
useTencentCDN,
|
|
useAliCDN
|
|
);
|
|
}
|
|
);
|
|
} catch (error) {
|
|
dispatchDoubanGlobalError('获取豆瓣推荐数据失败');
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async function fetchDoubanRecommends(
|
|
params: DoubanRecommendsParams,
|
|
proxyUrl: string,
|
|
useTencentCDN = false,
|
|
useAliCDN = false
|
|
): Promise<DoubanResult> {
|
|
const { kind, pageLimit = 20, pageStart = 0 } = params;
|
|
let { category, format, region, year, platform, sort, label } = params;
|
|
if (category === 'all') {
|
|
category = '';
|
|
}
|
|
if (format === 'all') {
|
|
format = '';
|
|
}
|
|
if (label === 'all') {
|
|
label = '';
|
|
}
|
|
if (region === 'all') {
|
|
region = '';
|
|
}
|
|
if (year === 'all') {
|
|
year = '';
|
|
}
|
|
if (platform === 'all') {
|
|
platform = '';
|
|
}
|
|
if (sort === 'T') {
|
|
sort = '';
|
|
}
|
|
|
|
const selectedCategories = { 类型: category } as any;
|
|
if (format) {
|
|
selectedCategories['形式'] = format;
|
|
}
|
|
if (region) {
|
|
selectedCategories['地区'] = region;
|
|
}
|
|
|
|
const tags = [] as Array<string>;
|
|
if (category) {
|
|
tags.push(category);
|
|
}
|
|
if (!category && format) {
|
|
tags.push(format);
|
|
}
|
|
if (label) {
|
|
tags.push(label);
|
|
}
|
|
if (region) {
|
|
tags.push(region);
|
|
}
|
|
if (year) {
|
|
tags.push(year);
|
|
}
|
|
if (platform) {
|
|
tags.push(platform);
|
|
}
|
|
|
|
const baseUrl = useTencentCDN
|
|
? `https://m.douban.cmliussss.net/rexxar/api/v2/${kind}/recommend`
|
|
: useAliCDN
|
|
? `https://m.douban.cmliussss.com/rexxar/api/v2/${kind}/recommend`
|
|
: `https://m.douban.com/rexxar/api/v2/${kind}/recommend`;
|
|
const reqParams = new URLSearchParams();
|
|
reqParams.append('refresh', '0');
|
|
reqParams.append('start', pageStart.toString());
|
|
reqParams.append('count', pageLimit.toString());
|
|
reqParams.append('selected_categories', JSON.stringify(selectedCategories));
|
|
reqParams.append('uncollect', 'false');
|
|
reqParams.append('score_range', '0,10');
|
|
reqParams.append('tags', tags.join(','));
|
|
if (sort) {
|
|
reqParams.append('sort', sort);
|
|
}
|
|
const target = `${baseUrl}?${reqParams.toString()}`;
|
|
console.log(target);
|
|
try {
|
|
const response = await fetchWithTimeout(
|
|
target,
|
|
useTencentCDN || useAliCDN ? '' : proxyUrl
|
|
);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
}
|
|
|
|
const doubanData: DoubanRecommendApiResponse = await response.json();
|
|
const list: DoubanItem[] = doubanData.items
|
|
.filter((item) => item.type == 'movie' || item.type == 'tv')
|
|
.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.year,
|
|
}));
|
|
|
|
return {
|
|
code: 200,
|
|
message: '获取成功',
|
|
list: list,
|
|
};
|
|
} catch (error) {
|
|
throw new Error(`获取豆瓣推荐数据失败: ${(error as Error).message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 浏览器端豆瓣详情数据获取函数
|
|
*/
|
|
export async function fetchDoubanDetail(
|
|
id: string,
|
|
proxyUrl: string,
|
|
useTencentCDN = false,
|
|
useAliCDN = false
|
|
): Promise<DoubanDetailApiResponse> {
|
|
if (!id) {
|
|
throw new Error('id 参数不能为空');
|
|
}
|
|
|
|
const target = useTencentCDN
|
|
? `https://m.douban.cmliussss.net/rexxar/api/v2/subject/${id}`
|
|
: useAliCDN
|
|
? `https://m.douban.cmliussss.com/rexxar/api/v2/subject/${id}`
|
|
: `https://m.douban.com/rexxar/api/v2/subject/${id}`;
|
|
|
|
try {
|
|
const response = await fetchWithTimeout(
|
|
target,
|
|
useTencentCDN || useAliCDN ? '' : proxyUrl
|
|
);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
}
|
|
|
|
const doubanData: DoubanDetailApiResponse = await response.json();
|
|
return doubanData;
|
|
} catch (error) {
|
|
throw new Error(`获取豆瓣详情数据失败: ${(error as Error).message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 统一的豆瓣详情数据获取函数,根据代理设置选择使用服务端 API 或客户端代理获取
|
|
*/
|
|
export async function getDoubanDetail(
|
|
id: string
|
|
): Promise<DoubanDetailApiResponse> {
|
|
const { proxyType, proxyUrl, backupProxyType, backupProxyUrl } =
|
|
getDoubanProxyConfig();
|
|
try {
|
|
return await requestDoubanWithFallback(
|
|
{ proxyType, proxyUrl },
|
|
{ proxyType: backupProxyType, proxyUrl: backupProxyUrl },
|
|
async ({ useDirectApi, requestProxyUrl, useTencentCDN, useAliCDN }) => {
|
|
if (useDirectApi) {
|
|
const response = await fetch(`/api/douban/detail?id=${id}`);
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
return fetchDoubanDetail(id, requestProxyUrl, useTencentCDN, useAliCDN);
|
|
}
|
|
);
|
|
} catch (error) {
|
|
dispatchDoubanGlobalError('获取豆瓣详情数据失败');
|
|
throw error;
|
|
}
|
|
}
|