增加动漫数据源配置
This commit is contained in:
@@ -46,6 +46,10 @@ export async function POST(request: NextRequest) {
|
||||
TMDBApiKey,
|
||||
TMDBProxy,
|
||||
TMDBReverseProxy,
|
||||
BangumiDataSource,
|
||||
BangumiApiBaseUrl,
|
||||
BangumiImageBaseUrl,
|
||||
BangumiProxy,
|
||||
BannerDataSource,
|
||||
RecommendationDataSource,
|
||||
PansouApiUrl,
|
||||
@@ -95,6 +99,10 @@ export async function POST(request: NextRequest) {
|
||||
TMDBApiKey?: string;
|
||||
TMDBProxy?: string;
|
||||
TMDBReverseProxy?: string;
|
||||
BangumiDataSource?: 'direct' | 'server-proxy' | 'custom-baseurl';
|
||||
BangumiApiBaseUrl?: string;
|
||||
BangumiImageBaseUrl?: string;
|
||||
BangumiProxy?: string;
|
||||
BannerDataSource?: string;
|
||||
RecommendationDataSource?: string;
|
||||
PansouApiUrl?: string;
|
||||
@@ -149,33 +157,63 @@ export async function POST(request: NextRequest) {
|
||||
typeof DanmakuAutoLoadDefault !== 'boolean') ||
|
||||
(TMDBApiKey !== undefined && typeof TMDBApiKey !== 'string') ||
|
||||
(TMDBProxy !== undefined && typeof TMDBProxy !== 'string') ||
|
||||
(TMDBReverseProxy !== undefined && typeof TMDBReverseProxy !== 'string') ||
|
||||
(BannerDataSource !== undefined && typeof BannerDataSource !== 'string') ||
|
||||
(RecommendationDataSource !== undefined && typeof RecommendationDataSource !== 'string') ||
|
||||
(PansouKeywordBlocklist !== undefined && typeof PansouKeywordBlocklist !== 'string') ||
|
||||
(TMDBReverseProxy !== undefined &&
|
||||
typeof TMDBReverseProxy !== 'string') ||
|
||||
(BangumiDataSource !== undefined &&
|
||||
BangumiDataSource !== 'direct' &&
|
||||
BangumiDataSource !== 'server-proxy' &&
|
||||
BangumiDataSource !== 'custom-baseurl') ||
|
||||
(BangumiApiBaseUrl !== undefined &&
|
||||
typeof BangumiApiBaseUrl !== 'string') ||
|
||||
(BangumiImageBaseUrl !== undefined &&
|
||||
typeof BangumiImageBaseUrl !== 'string') ||
|
||||
(BangumiProxy !== undefined && typeof BangumiProxy !== 'string') ||
|
||||
(BannerDataSource !== undefined &&
|
||||
typeof BannerDataSource !== 'string') ||
|
||||
(RecommendationDataSource !== undefined &&
|
||||
typeof RecommendationDataSource !== 'string') ||
|
||||
(PansouKeywordBlocklist !== undefined &&
|
||||
typeof PansouKeywordBlocklist !== 'string') ||
|
||||
(MagnetProxy !== undefined && typeof MagnetProxy !== 'string') ||
|
||||
(MagnetMikanReverseProxy !== undefined && typeof MagnetMikanReverseProxy !== 'string') ||
|
||||
(MagnetDmhyReverseProxy !== undefined && typeof MagnetDmhyReverseProxy !== 'string') ||
|
||||
(MagnetAcgripReverseProxy !== undefined && typeof MagnetAcgripReverseProxy !== 'string') ||
|
||||
(MagnetMikanReverseProxy !== undefined &&
|
||||
typeof MagnetMikanReverseProxy !== 'string') ||
|
||||
(MagnetDmhyReverseProxy !== undefined &&
|
||||
typeof MagnetDmhyReverseProxy !== 'string') ||
|
||||
(MagnetAcgripReverseProxy !== undefined &&
|
||||
typeof MagnetAcgripReverseProxy !== 'string') ||
|
||||
typeof EnableComments !== 'boolean' ||
|
||||
(CustomAdFilterCode !== undefined && typeof CustomAdFilterCode !== 'string') ||
|
||||
(CustomAdFilterVersion !== undefined && typeof CustomAdFilterVersion !== 'number') ||
|
||||
(EnableRegistration !== undefined && typeof EnableRegistration !== 'boolean') ||
|
||||
(RequireRegistrationInviteCode !== undefined && typeof RequireRegistrationInviteCode !== 'boolean') ||
|
||||
(RegistrationInviteCode !== undefined && typeof RegistrationInviteCode !== 'string') ||
|
||||
(RegistrationRequireTurnstile !== undefined && typeof RegistrationRequireTurnstile !== 'boolean') ||
|
||||
(LoginRequireTurnstile !== undefined && typeof LoginRequireTurnstile !== 'boolean') ||
|
||||
(TurnstileSiteKey !== undefined && typeof TurnstileSiteKey !== 'string') ||
|
||||
(TurnstileSecretKey !== undefined && typeof TurnstileSecretKey !== 'string') ||
|
||||
(CustomAdFilterCode !== undefined &&
|
||||
typeof CustomAdFilterCode !== 'string') ||
|
||||
(CustomAdFilterVersion !== undefined &&
|
||||
typeof CustomAdFilterVersion !== 'number') ||
|
||||
(EnableRegistration !== undefined &&
|
||||
typeof EnableRegistration !== 'boolean') ||
|
||||
(RequireRegistrationInviteCode !== undefined &&
|
||||
typeof RequireRegistrationInviteCode !== 'boolean') ||
|
||||
(RegistrationInviteCode !== undefined &&
|
||||
typeof RegistrationInviteCode !== 'string') ||
|
||||
(RegistrationRequireTurnstile !== undefined &&
|
||||
typeof RegistrationRequireTurnstile !== 'boolean') ||
|
||||
(LoginRequireTurnstile !== undefined &&
|
||||
typeof LoginRequireTurnstile !== 'boolean') ||
|
||||
(TurnstileSiteKey !== undefined &&
|
||||
typeof TurnstileSiteKey !== 'string') ||
|
||||
(TurnstileSecretKey !== undefined &&
|
||||
typeof TurnstileSecretKey !== 'string') ||
|
||||
(DefaultUserTags !== undefined && !Array.isArray(DefaultUserTags)) ||
|
||||
(EnableOIDCLogin !== undefined && typeof EnableOIDCLogin !== 'boolean') ||
|
||||
(EnableOIDCRegistration !== undefined && typeof EnableOIDCRegistration !== 'boolean') ||
|
||||
(EnableOIDCRegistration !== undefined &&
|
||||
typeof EnableOIDCRegistration !== 'boolean') ||
|
||||
(OIDCIssuer !== undefined && typeof OIDCIssuer !== 'string') ||
|
||||
(OIDCAuthorizationEndpoint !== undefined && typeof OIDCAuthorizationEndpoint !== 'string') ||
|
||||
(OIDCTokenEndpoint !== undefined && typeof OIDCTokenEndpoint !== 'string') ||
|
||||
(OIDCUserInfoEndpoint !== undefined && typeof OIDCUserInfoEndpoint !== 'string') ||
|
||||
(OIDCAuthorizationEndpoint !== undefined &&
|
||||
typeof OIDCAuthorizationEndpoint !== 'string') ||
|
||||
(OIDCTokenEndpoint !== undefined &&
|
||||
typeof OIDCTokenEndpoint !== 'string') ||
|
||||
(OIDCUserInfoEndpoint !== undefined &&
|
||||
typeof OIDCUserInfoEndpoint !== 'string') ||
|
||||
(OIDCClientId !== undefined && typeof OIDCClientId !== 'string') ||
|
||||
(OIDCClientSecret !== undefined && typeof OIDCClientSecret !== 'string') ||
|
||||
(OIDCClientSecret !== undefined &&
|
||||
typeof OIDCClientSecret !== 'string') ||
|
||||
(OIDCButtonText !== undefined && typeof OIDCButtonText !== 'string') ||
|
||||
(OIDCMinTrustLevel !== undefined && typeof OIDCMinTrustLevel !== 'number')
|
||||
) {
|
||||
@@ -211,6 +249,10 @@ export async function POST(request: NextRequest) {
|
||||
TMDBApiKey,
|
||||
TMDBProxy,
|
||||
TMDBReverseProxy,
|
||||
BangumiDataSource,
|
||||
BangumiApiBaseUrl,
|
||||
BangumiImageBaseUrl,
|
||||
BangumiProxy,
|
||||
BannerDataSource,
|
||||
RecommendationDataSource,
|
||||
PansouApiUrl,
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
import { fetchBangumiFromServer } from '@/lib/bangumi.server';
|
||||
import { getConfig } from '@/lib/config';
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const config = await getConfig();
|
||||
const response = await fetchBangumiFromServer('/calendar', {
|
||||
baseUrl: config.SiteConfig.BangumiApiBaseUrl,
|
||||
proxy: config.SiteConfig.BangumiProxy,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: `Bangumi calendar 请求失败: ${response.status}` },
|
||||
{ status: response.status || 502 }
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return NextResponse.json(data, {
|
||||
headers: {
|
||||
'Cache-Control': 'public, max-age=3600, stale-while-revalidate=21600',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取 Bangumi calendar 失败:', error);
|
||||
return NextResponse.json(
|
||||
{ error: '获取 Bangumi calendar 失败' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { fetchBangumiFromServer } from '@/lib/bangumi.server';
|
||||
import { getConfig } from '@/lib/config';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const id = request.nextUrl.searchParams.get('id') || '';
|
||||
if (!/^\d+$/.test(id)) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Bangumi ID 格式错误' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const config = await getConfig();
|
||||
const response = await fetchBangumiFromServer(`/v0/subjects/${id}`, {
|
||||
baseUrl: config.SiteConfig.BangumiApiBaseUrl,
|
||||
proxy: config.SiteConfig.BangumiProxy,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: `Bangumi subject 请求失败: ${response.status}` },
|
||||
{ status: response.status || 502 }
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return NextResponse.json(data, {
|
||||
headers: {
|
||||
'Cache-Control': 'public, max-age=86400, stale-while-revalidate=604800',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取 Bangumi subject 失败:', error);
|
||||
return NextResponse.json(
|
||||
{ error: '获取 Bangumi subject 失败' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { fetchBangumiFromServer } from '@/lib/bangumi.server';
|
||||
import { getConfig } from '@/lib/config';
|
||||
|
||||
export async function GET(
|
||||
_request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const id = params.id;
|
||||
if (!/^\d+$/.test(id)) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Bangumi ID 格式错误' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const config = await getConfig();
|
||||
const response = await fetchBangumiFromServer(`/v0/subjects/${id}`, {
|
||||
baseUrl: config.SiteConfig.BangumiApiBaseUrl,
|
||||
proxy: config.SiteConfig.BangumiProxy,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: `Bangumi subject 请求失败: ${response.status}` },
|
||||
{ status: response.status || 502 }
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return NextResponse.json(data, {
|
||||
headers: {
|
||||
'Cache-Control': 'public, max-age=86400, stale-while-revalidate=604800',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取 Bangumi subject 失败:', error);
|
||||
return NextResponse.json(
|
||||
{ error: '获取 Bangumi subject 失败' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,103 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||
import nodeFetch from 'node-fetch';
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
import { getConfig } from '@/lib/config';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
function isCloudflareEnvironment(): boolean {
|
||||
return (
|
||||
process.env.CF_PAGES === '1' || process.env.BUILD_TARGET === 'cloudflare'
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeBaseUrl(baseUrl: string): string {
|
||||
return baseUrl.trim().replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
function applyBangumiImageBaseUrl(
|
||||
imageUrl: string,
|
||||
imageBaseUrl?: string
|
||||
): string {
|
||||
const normalizedBaseUrl = normalizeBaseUrl(imageBaseUrl || '');
|
||||
if (!normalizedBaseUrl) {
|
||||
return imageUrl;
|
||||
}
|
||||
|
||||
if (imageUrl.startsWith(`${normalizedBaseUrl}/`)) {
|
||||
return imageUrl;
|
||||
}
|
||||
|
||||
return `${normalizedBaseUrl}/${imageUrl}`;
|
||||
}
|
||||
|
||||
function isBangumiImageUrl(url: string): boolean {
|
||||
try {
|
||||
const hostname = new URL(url).hostname.toLowerCase();
|
||||
return (
|
||||
hostname === 'lain.bgm.tv' ||
|
||||
hostname === 'r.bgm.tv' ||
|
||||
hostname.endsWith('.bgm.tv') ||
|
||||
hostname.endsWith('.bangumi.tv')
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchImage(
|
||||
imageUrl: string,
|
||||
options?: { source?: string }
|
||||
): Promise<Response> {
|
||||
const isBangumiImage =
|
||||
options?.source === 'bangumi' || isBangumiImageUrl(imageUrl);
|
||||
const 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',
|
||||
Accept: 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
|
||||
Referer: isBangumiImage ? 'https://bgm.tv/' : 'https://movie.douban.com/',
|
||||
};
|
||||
|
||||
const config = isBangumiImage ? await getConfig() : null;
|
||||
const targetUrl = isBangumiImage
|
||||
? applyBangumiImageBaseUrl(imageUrl, config?.SiteConfig.BangumiImageBaseUrl)
|
||||
: imageUrl;
|
||||
|
||||
if (!isBangumiImage || isCloudflareEnvironment()) {
|
||||
return fetch(targetUrl, { headers, signal: AbortSignal.timeout(15000) });
|
||||
}
|
||||
|
||||
const proxy = config?.SiteConfig.BangumiProxy?.trim();
|
||||
const fetchOptions: any = {
|
||||
headers,
|
||||
signal: AbortSignal.timeout(proxy ? 30000 : 15000),
|
||||
};
|
||||
|
||||
if (proxy) {
|
||||
fetchOptions.agent = new HttpsProxyAgent(proxy, {
|
||||
timeout: 30000,
|
||||
keepAlive: false,
|
||||
});
|
||||
}
|
||||
|
||||
return nodeFetch(targetUrl, fetchOptions) as unknown as Promise<Response>;
|
||||
}
|
||||
|
||||
// OrionTV 兼容接口
|
||||
export async function GET(request: Request) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const imageUrl = searchParams.get('url');
|
||||
const source = searchParams.get('source') || undefined;
|
||||
|
||||
if (!imageUrl) {
|
||||
return NextResponse.json({ error: 'Missing image URL' }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const imageResponse = await fetch(imageUrl, {
|
||||
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',
|
||||
Accept: 'image/jpeg,image/png,image/gif,*/*;q=0.8',
|
||||
Referer: 'https://movie.douban.com/',
|
||||
},
|
||||
});
|
||||
const imageResponse = await fetchImage(imageUrl, { source });
|
||||
|
||||
if (!imageResponse.ok) {
|
||||
return NextResponse.json(
|
||||
@@ -55,6 +133,7 @@ export async function GET(request: Request) {
|
||||
headers,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('图片代理请求失败:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Error fetching image' },
|
||||
{ status: 500 }
|
||||
|
||||
Reference in New Issue
Block a user