diff --git a/src/app/api/acg/download/route.ts b/src/app/api/acg/download/route.ts index 7304fda..25d1d18 100644 --- a/src/app/api/acg/download/route.ts +++ b/src/app/api/acg/download/route.ts @@ -8,6 +8,13 @@ import { hasFeaturePermission } from '@/lib/permissions'; export const runtime = 'nodejs'; +const downloadTools = ['aria2', 'Transmission', 'qBittorrent'] as const; +type DownloadTool = typeof downloadTools[number]; + +function isDownloadTool(tool: unknown): tool is DownloadTool { + return typeof tool === 'string' && downloadTools.includes(tool as DownloadTool); +} + /** * POST /api/acg/download * 添加 ACG 资源到 OpenList 离线下载(仅管理员和站长可用) @@ -23,7 +30,7 @@ export async function POST(req: NextRequest) { ); } - const { url, name } = await req.json(); + const { url, name, tool = 'aria2' } = await req.json(); if (!url || typeof url !== 'string') { return NextResponse.json( @@ -39,6 +46,13 @@ export async function POST(req: NextRequest) { ); } + if (!isDownloadTool(tool)) { + return NextResponse.json( + { error: '下载方式不支持' }, + { status: 400 } + ); + } + // 获取 OpenList 配置 const config = await getConfig(); const openlistConfig = config.OpenListConfig; @@ -81,7 +95,7 @@ export async function POST(req: NextRequest) { body: JSON.stringify({ path: downloadPath, urls: [url], - tool: 'aria2', + tool, }), }); diff --git a/src/app/api/admin/anime-subscription/route.ts b/src/app/api/admin/anime-subscription/route.ts index 501cfe1..2b4c9a0 100644 --- a/src/app/api/admin/anime-subscription/route.ts +++ b/src/app/api/admin/anime-subscription/route.ts @@ -23,10 +23,14 @@ export async function GET(req: NextRequest) { const config = await getConfig(); const animeConfig = config.AnimeSubscriptionConfig || { Enabled: false, + DownloadTool: 'aria2', Subscriptions: [], }; - return NextResponse.json(animeConfig); + return NextResponse.json({ + ...animeConfig, + DownloadTool: animeConfig.DownloadTool || 'aria2', + }); } catch (error: any) { console.error('获取追番订阅配置失败:', error); return NextResponse.json( @@ -63,7 +67,13 @@ export async function POST(req: NextRequest) { const config = await getConfig(); if (!config.AnimeSubscriptionConfig) { - config.AnimeSubscriptionConfig = { Enabled: false, Subscriptions: [] }; + config.AnimeSubscriptionConfig = { + Enabled: false, + DownloadTool: 'aria2', + Subscriptions: [], + }; + } else if (!config.AnimeSubscriptionConfig.DownloadTool) { + config.AnimeSubscriptionConfig.DownloadTool = 'aria2'; } // 验证集数 diff --git a/src/app/api/admin/anime-subscription/toggle/route.ts b/src/app/api/admin/anime-subscription/toggle/route.ts index 6dc2fee..3190746 100644 --- a/src/app/api/admin/anime-subscription/toggle/route.ts +++ b/src/app/api/admin/anime-subscription/toggle/route.ts @@ -7,6 +7,13 @@ import { db } from '@/lib/db'; export const runtime = 'nodejs'; +const downloadTools = ['aria2', 'qBittorrent', 'Transmission'] as const; +type DownloadTool = typeof downloadTools[number]; + +function isDownloadTool(tool: unknown): tool is DownloadTool { + return typeof tool === 'string' && downloadTools.includes(tool as DownloadTool); +} + /** * PUT /api/admin/anime-subscription/toggle * 切换追番功能启用状态 @@ -19,7 +26,7 @@ export async function PUT(req: NextRequest) { return NextResponse.json({ error: '无权限访问' }, { status: 403 }); } - const { enabled } = await req.json(); + const { enabled, downloadTool } = await req.json(); if (typeof enabled !== 'boolean') { return NextResponse.json( @@ -28,15 +35,31 @@ export async function PUT(req: NextRequest) { ); } + if (downloadTool !== undefined && !isDownloadTool(downloadTool)) { + return NextResponse.json( + { error: '下载方式不支持' }, + { status: 400 } + ); + } + const config = await getConfig(); if (!config.AnimeSubscriptionConfig) { config.AnimeSubscriptionConfig = { Enabled: false, Subscriptions: [] }; } config.AnimeSubscriptionConfig.Enabled = enabled; + if (downloadTool !== undefined) { + config.AnimeSubscriptionConfig.DownloadTool = downloadTool; + } else if (!config.AnimeSubscriptionConfig.DownloadTool) { + config.AnimeSubscriptionConfig.DownloadTool = 'aria2'; + } await db.saveAdminConfig(config); - return NextResponse.json({ success: true, enabled }); + return NextResponse.json({ + success: true, + enabled, + downloadTool: config.AnimeSubscriptionConfig.DownloadTool, + }); } catch (error: any) { console.error('切换追番功能状态失败:', error); return NextResponse.json( diff --git a/src/components/AcgSearch.tsx b/src/components/AcgSearch.tsx index 3d3c4ed..855bd4b 100644 --- a/src/components/AcgSearch.tsx +++ b/src/components/AcgSearch.tsx @@ -31,6 +31,13 @@ interface AcgSearchProps { } type AcgSearchSource = 'acgrip' | 'mikan' | 'dmhy'; +type DownloadTool = 'aria2' | 'Transmission' | 'qBittorrent'; + +const downloadToolOptions: Array<{ value: DownloadTool; label: string }> = [ + { value: 'aria2', label: 'aria2' }, + { value: 'qBittorrent', label: 'qBittorrent' }, + { value: 'Transmission', label: 'Transmission' }, +]; export default function AcgSearch({ keyword, @@ -47,6 +54,7 @@ export default function AcgSearch({ const [showNameDialog, setShowNameDialog] = useState(false); const [selectedItem, setSelectedItem] = useState(null); const [customName, setCustomName] = useState(''); + const [downloadTool, setDownloadTool] = useState('aria2'); const [toast, setToast] = useState(null); const loadMoreRef = useRef(null); const isLoadingMoreRef = useRef(false); @@ -204,6 +212,7 @@ export default function AcgSearch({ body: JSON.stringify({ url: selectedItem.torrentUrl, name: customName.trim(), + tool: downloadTool, }), }); @@ -228,6 +237,7 @@ export default function AcgSearch({ setDownloadingId(null); setSelectedItem(null); setCustomName(''); + setDownloadTool('aria2'); } }; @@ -365,12 +375,27 @@ export default function AcgSearch({ className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-green-500' autoFocus /> + +