diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 4420c61..7e9ca80 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -352,6 +352,10 @@ interface SiteConfig { PansouUsername?: string; PansouPassword?: string; PansouKeywordBlocklist?: string; + MagnetProxy?: string; + MagnetMikanReverseProxy?: string; + MagnetDmhyReverseProxy?: string; + MagnetAcgripReverseProxy?: string; EnableComments: boolean; EnableRegistration?: boolean; RegistrationRequireTurnstile?: boolean; @@ -7921,12 +7925,17 @@ const SiteConfigComponent = ({ DanmakuApiToken: '87654321', TMDBApiKey: '', TMDBProxy: '', + TMDBReverseProxy: '', BannerDataSource: 'Douban', RecommendationDataSource: 'Mixed', PansouApiUrl: '', PansouUsername: '', PansouPassword: '', PansouKeywordBlocklist: '', + MagnetProxy: '', + MagnetMikanReverseProxy: '', + MagnetDmhyReverseProxy: '', + MagnetAcgripReverseProxy: '', EnableComments: false, EnableRegistration: false, RegistrationRequireTurnstile: false, @@ -8012,12 +8021,17 @@ const SiteConfigComponent = ({ DanmakuApiToken: config.SiteConfig.DanmakuApiToken || '87654321', TMDBApiKey: config.SiteConfig.TMDBApiKey || '', TMDBProxy: config.SiteConfig.TMDBProxy || '', + TMDBReverseProxy: config.SiteConfig.TMDBReverseProxy || '', BannerDataSource: config.SiteConfig.BannerDataSource || 'Douban', RecommendationDataSource: config.SiteConfig.RecommendationDataSource || 'Mixed', PansouApiUrl: config.SiteConfig.PansouApiUrl || '', PansouUsername: config.SiteConfig.PansouUsername || '', PansouPassword: config.SiteConfig.PansouPassword || '', PansouKeywordBlocklist: config.SiteConfig.PansouKeywordBlocklist || '', + MagnetProxy: config.SiteConfig.MagnetProxy || '', + MagnetMikanReverseProxy: config.SiteConfig.MagnetMikanReverseProxy || '', + MagnetDmhyReverseProxy: config.SiteConfig.MagnetDmhyReverseProxy || '', + MagnetAcgripReverseProxy: config.SiteConfig.MagnetAcgripReverseProxy || '', EnableComments: config.SiteConfig.EnableComments || false, }); } @@ -8687,6 +8701,97 @@ const SiteConfigComponent = ({ +
+ + 磁链配置 + +
+
+ + + setSiteSettings((prev) => ({ + ...prev, + MagnetProxy: e.target.value, + })) + } + className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent' + /> +

+ 用于访问磁链搜索站点的系统代理。Cloudflare 部署环境下不会使用该代理。 +

+
+ +
+ + + setSiteSettings((prev) => ({ + ...prev, + MagnetMikanReverseProxy: e.target.value, + })) + } + className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent' + /> +

+ 配置后将使用该地址替代默认的 Mikan 域名进行请求。 +

+
+ +
+ + + setSiteSettings((prev) => ({ + ...prev, + MagnetDmhyReverseProxy: e.target.value, + })) + } + className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent' + /> +

+ 配置后将使用该地址替代默认的动漫花园域名进行请求。 +

+
+ +
+ + + setSiteSettings((prev) => ({ + ...prev, + MagnetAcgripReverseProxy: e.target.value, + })) + } + className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent' + /> +

+ 配置后将使用该地址替代默认的 ACG.RIP 域名进行请求。 +

+
+
+
+ {/* Pansou 配置 */}
diff --git a/src/app/api/acg/acgrip/route.ts b/src/app/api/acg/acgrip/route.ts index 21be31e..c212884 100644 --- a/src/app/api/acg/acgrip/route.ts +++ b/src/app/api/acg/acgrip/route.ts @@ -3,6 +3,8 @@ import { NextRequest, NextResponse } from 'next/server'; import { parseStringPromise } from 'xml2js'; import { getAuthInfoFromCookie } from '@/lib/auth'; +import { getConfig } from '@/lib/config'; +import { getMagnetBaseUrl, universalMagnetFetch } from '@/lib/magnet.client'; export const runtime = 'nodejs'; @@ -48,11 +50,17 @@ export async function POST(req: NextRequest) { } // 请求 acg.rip RSS - const searchUrl = `https://acg.rip/page/${pageNum}.xml?term=${encodeURIComponent(trimmedKeyword)}`; + const config = await getConfig(); + const searchBaseUrl = getMagnetBaseUrl( + 'https://acg.rip', + config.SiteConfig.MagnetAcgripReverseProxy + ); + const searchUrl = `${searchBaseUrl}/page/${pageNum}.xml?term=${encodeURIComponent(trimmedKeyword)}`; - const response = await fetch(searchUrl, { + const response = await universalMagnetFetch(searchUrl, config.SiteConfig.MagnetProxy, { headers: { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36', + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36', }, }); @@ -123,4 +131,3 @@ export async function POST(req: NextRequest) { ); } } - diff --git a/src/app/api/acg/dmhy/route.ts b/src/app/api/acg/dmhy/route.ts index d8cc158..4fc3b93 100644 --- a/src/app/api/acg/dmhy/route.ts +++ b/src/app/api/acg/dmhy/route.ts @@ -3,6 +3,8 @@ import { NextRequest, NextResponse } from 'next/server'; import { parseStringPromise } from 'xml2js'; import { getAuthInfoFromCookie } from '@/lib/auth'; +import { getConfig } from '@/lib/config'; +import { getMagnetBaseUrl, universalMagnetFetch } from '@/lib/magnet.client'; export const runtime = 'nodejs'; @@ -56,11 +58,15 @@ export async function POST(req: NextRequest) { }); } - const baseUrl = 'http://share.dmhy.org/topics/rss/rss.xml'; + const config = await getConfig(); + const baseUrl = `${getMagnetBaseUrl( + 'http://share.dmhy.org', + config.SiteConfig.MagnetDmhyReverseProxy + )}/topics/rss/rss.xml`; const params = new URLSearchParams({ keyword: trimmedKeyword }); const searchUrl = `${baseUrl}?${params.toString()}`; - const response = await fetch(searchUrl, { + const response = await universalMagnetFetch(searchUrl, config.SiteConfig.MagnetProxy, { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36', diff --git a/src/app/api/acg/mikan/route.ts b/src/app/api/acg/mikan/route.ts index 75c41e6..828f490 100644 --- a/src/app/api/acg/mikan/route.ts +++ b/src/app/api/acg/mikan/route.ts @@ -3,6 +3,8 @@ import { NextRequest, NextResponse } from 'next/server'; import { parseStringPromise } from 'xml2js'; import { getAuthInfoFromCookie } from '@/lib/auth'; +import { getConfig } from '@/lib/config'; +import { getMagnetBaseUrl, universalMagnetFetch } from '@/lib/magnet.client'; export const runtime = 'nodejs'; @@ -62,11 +64,17 @@ export async function POST(req: NextRequest) { }); } - const searchUrl = `https://mikanani.me/RSS/Search?searchstr=${encodeURIComponent(trimmedKeyword)}`; + const config = await getConfig(); + const searchBaseUrl = getMagnetBaseUrl( + 'https://mikanani.me', + config.SiteConfig.MagnetMikanReverseProxy + ); + const searchUrl = `${searchBaseUrl}/RSS/Search?searchstr=${encodeURIComponent(trimmedKeyword)}`; - const response = await fetch(searchUrl, { + const response = await universalMagnetFetch(searchUrl, config.SiteConfig.MagnetProxy, { headers: { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36', + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36', }, }); @@ -144,4 +152,3 @@ export async function POST(req: NextRequest) { ); } } - diff --git a/src/app/api/admin/site/route.ts b/src/app/api/admin/site/route.ts index 2a35297..6619bff 100644 --- a/src/app/api/admin/site/route.ts +++ b/src/app/api/admin/site/route.ts @@ -50,6 +50,10 @@ export async function POST(request: NextRequest) { PansouUsername, PansouPassword, PansouKeywordBlocklist, + MagnetProxy, + MagnetMikanReverseProxy, + MagnetDmhyReverseProxy, + MagnetAcgripReverseProxy, EnableComments, CustomAdFilterCode, CustomAdFilterVersion, @@ -91,6 +95,10 @@ export async function POST(request: NextRequest) { PansouUsername?: string; PansouPassword?: string; PansouKeywordBlocklist?: string; + MagnetProxy?: string; + MagnetMikanReverseProxy?: string; + MagnetDmhyReverseProxy?: string; + MagnetAcgripReverseProxy?: string; EnableComments: boolean; CustomAdFilterCode?: string; CustomAdFilterVersion?: number; @@ -132,6 +140,10 @@ export async function POST(request: NextRequest) { (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') || typeof EnableComments !== 'boolean' || (CustomAdFilterCode !== undefined && typeof CustomAdFilterCode !== 'string') || (CustomAdFilterVersion !== undefined && typeof CustomAdFilterVersion !== 'number') || @@ -188,6 +200,10 @@ export async function POST(request: NextRequest) { PansouUsername, PansouPassword, PansouKeywordBlocklist, + MagnetProxy, + MagnetMikanReverseProxy, + MagnetDmhyReverseProxy, + MagnetAcgripReverseProxy, EnableComments, CustomAdFilterCode, CustomAdFilterVersion, diff --git a/src/lib/admin.types.ts b/src/lib/admin.types.ts index 8737083..eb74fc8 100644 --- a/src/lib/admin.types.ts +++ b/src/lib/admin.types.ts @@ -30,6 +30,11 @@ export interface AdminConfig { PansouUsername?: string; PansouPassword?: string; PansouKeywordBlocklist?: string; + // 磁链配置 + MagnetProxy?: string; + MagnetMikanReverseProxy?: string; + MagnetDmhyReverseProxy?: string; + MagnetAcgripReverseProxy?: string; // 评论功能开关 EnableComments: boolean; // 自定义去广告代码 diff --git a/src/lib/config.ts b/src/lib/config.ts index be14316..6216130 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -256,6 +256,11 @@ async function getInitConfig(configFile: string, subConfig: { PansouUsername: '', PansouPassword: '', PansouKeywordBlocklist: '', + // 磁链配置 + MagnetProxy: '', + MagnetMikanReverseProxy: '', + MagnetDmhyReverseProxy: '', + MagnetAcgripReverseProxy: '', // 评论功能开关 EnableComments: false, }, @@ -432,6 +437,10 @@ export function configSelfCheck(adminConfig: AdminConfig): AdminConfig { PansouUsername: '', PansouPassword: '', PansouKeywordBlocklist: '', + MagnetProxy: '', + MagnetMikanReverseProxy: '', + MagnetDmhyReverseProxy: '', + MagnetAcgripReverseProxy: '', EnableComments: false, }; } @@ -449,6 +458,18 @@ export function configSelfCheck(adminConfig: AdminConfig): AdminConfig { if (adminConfig.SiteConfig.PansouKeywordBlocklist === undefined) { adminConfig.SiteConfig.PansouKeywordBlocklist = ''; } + if (adminConfig.SiteConfig.MagnetProxy === undefined) { + adminConfig.SiteConfig.MagnetProxy = ''; + } + if (adminConfig.SiteConfig.MagnetMikanReverseProxy === undefined) { + adminConfig.SiteConfig.MagnetMikanReverseProxy = ''; + } + if (adminConfig.SiteConfig.MagnetDmhyReverseProxy === undefined) { + adminConfig.SiteConfig.MagnetDmhyReverseProxy = ''; + } + if (adminConfig.SiteConfig.MagnetAcgripReverseProxy === undefined) { + adminConfig.SiteConfig.MagnetAcgripReverseProxy = ''; + } if (!adminConfig.UserConfig) { adminConfig.UserConfig = { Users: [] }; } diff --git a/src/lib/magnet.client.ts b/src/lib/magnet.client.ts new file mode 100644 index 0000000..50e8a9e --- /dev/null +++ b/src/lib/magnet.client.ts @@ -0,0 +1,42 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { HttpsProxyAgent } from 'https-proxy-agent'; +import nodeFetch from 'node-fetch'; + +function isCloudflareEnvironment(): boolean { + return process.env.CF_PAGES === '1' || process.env.BUILD_TARGET === 'cloudflare'; +} + +export function getMagnetBaseUrl(defaultBaseUrl: string, reverseProxyBaseUrl?: string): string { + return (reverseProxyBaseUrl || defaultBaseUrl).replace(/\/+$/, ''); +} + +export async function universalMagnetFetch( + url: string, + proxy?: string, + init?: RequestInit +): Promise { + if (isCloudflareEnvironment()) { + const response = await fetch(url, { + ...init, + signal: AbortSignal.timeout(15000), + }); + return response as unknown as Response; + } + + const fetchOptions: any = proxy + ? { + ...init, + agent: new HttpsProxyAgent(proxy, { + timeout: 30000, + keepAlive: false, + }), + signal: AbortSignal.timeout(30000), + } + : { + ...init, + signal: AbortSignal.timeout(15000), + }; + + return nodeFetch(url, fetchOptions) as unknown as Response; +}