diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index cb59e7c..e27b628 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -385,6 +385,7 @@ interface SiteConfig { MagnetMikanReverseProxy?: string; MagnetDmhyReverseProxy?: string; MagnetAcgripReverseProxy?: string; + MagnetNyaaReverseProxy?: string; EnableComments: boolean; EnableRegistration?: boolean; RequireRegistrationInviteCode?: boolean; @@ -10151,6 +10152,7 @@ const SiteConfigComponent = ({ MagnetMikanReverseProxy: '', MagnetDmhyReverseProxy: '', MagnetAcgripReverseProxy: '', + MagnetNyaaReverseProxy: '', EnableComments: false, EnableRegistration: false, RegistrationRequireTurnstile: false, @@ -10273,6 +10275,7 @@ const SiteConfigComponent = ({ MagnetDmhyReverseProxy: config.SiteConfig.MagnetDmhyReverseProxy || '', MagnetAcgripReverseProxy: config.SiteConfig.MagnetAcgripReverseProxy || '', + MagnetNyaaReverseProxy: config.SiteConfig.MagnetNyaaReverseProxy || '', EnableComments: config.SiteConfig.EnableComments || false, }); } @@ -11278,6 +11281,27 @@ const SiteConfigComponent = ({ 配置后将使用该地址替代默认的 ACG.RIP 域名进行请求。

+ +
+ + + setSiteSettings((prev) => ({ + ...prev, + MagnetNyaaReverseProxy: 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' + /> +

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

+
diff --git a/src/app/api/acg/nyaa/route.ts b/src/app/api/acg/nyaa/route.ts new file mode 100644 index 0000000..fee6efc --- /dev/null +++ b/src/app/api/acg/nyaa/route.ts @@ -0,0 +1,166 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +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'; +import { hasFeaturePermission } from '@/lib/permissions'; + +export const runtime = 'nodejs'; + +const pickText = (value: any): string => { + if (value === undefined || value === null) return ''; + const first = Array.isArray(value) ? value[0] : value; + if (first === undefined || first === null) return ''; + if (typeof first === 'object') return String(first._ ?? first.$?.url ?? first.$?.href ?? ''); + return String(first); +}; + +/** + * POST /api/acg/nyaa + * 搜索 Nyaa RSS(仅管理员和站长可用,不支持分页) + * - https://nyaa.si/?page=rss&q=xxx&c=1_0&f=0 + */ +export async function POST(req: NextRequest) { + try { + const authInfo = getAuthInfoFromCookie(req); + if (!authInfo?.username || !(await hasFeaturePermission(authInfo.username, 'magnet_search'))) { + return NextResponse.json( + { error: '无权限访问' }, + { status: 403 } + ); + } + + const { keyword, page = 1 } = await req.json(); + + if (!keyword || typeof keyword !== 'string') { + return NextResponse.json( + { error: '搜索关键词不能为空' }, + { status: 400 } + ); + } + + const trimmedKeyword = keyword.trim(); + if (!trimmedKeyword) { + return NextResponse.json( + { error: '搜索关键词不能为空' }, + { status: 400 } + ); + } + + const pageNum = parseInt(String(page), 10); + if (isNaN(pageNum) || pageNum < 1) { + return NextResponse.json( + { error: '页码必须是大于0的整数' }, + { status: 400 } + ); + } + + if (pageNum > 1) { + return NextResponse.json({ + keyword: trimmedKeyword, + page: pageNum, + total: 0, + items: [], + }); + } + + const config = await getConfig(); + const searchBaseUrl = getMagnetBaseUrl( + 'https://nyaa.si', + config.SiteConfig.MagnetNyaaReverseProxy + ); + const params = new URLSearchParams({ + page: 'rss', + q: trimmedKeyword, + c: '1_0', + f: '0', + }); + const searchUrl = `${searchBaseUrl}/?${params.toString()}`; + + 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', + }, + }); + + if (!response.ok) { + throw new Error(`Nyaa API 请求失败: ${response.status}`); + } + + const xmlData = await response.text(); + const parsed = await parseStringPromise(xmlData); + + if (!parsed?.rss?.channel?.[0]?.item) { + return NextResponse.json({ + keyword: trimmedKeyword, + page: pageNum, + total: 0, + items: [], + }); + } + + const items = parsed.rss.channel[0].item; + + const results = items.map((item: any) => { + const title = pickText(item.title); + // Nyaa RSS 的 link 是 .torrent 下载地址,guid 才是详情页(且 guid 带 isPermaLink 属性) + const torrentUrl = pickText(item.link); + const detailUrl = pickText(item.guid) || torrentUrl; + const guid = detailUrl || torrentUrl || `${title}-${pickText(item.pubDate)}`; + const pubDate = pickText(item.pubDate); + const size = pickText(item['nyaa:size']); + const category = pickText(item['nyaa:category']); + const seeders = pickText(item['nyaa:seeders']); + const leechers = pickText(item['nyaa:leechers']); + const downloads = pickText(item['nyaa:downloads']); + const infoHash = pickText(item['nyaa:infoHash']); + const description = + pickText(item.description) || + [ + size && `大小:${size}`, + category && `分类:${category}`, + seeders && `Seeders:${seeders}`, + leechers && `Leechers:${leechers}`, + downloads && `下载:${downloads}`, + infoHash && `Hash:${infoHash}`, + ].filter(Boolean).join(' | '); + + let images: string[] = []; + if (description) { + const imgMatches = description.match(/src="([^"]+)"/g); + if (imgMatches) { + images = imgMatches.map((match: string) => { + const urlMatch = match.match(/src="([^"]+)"/); + return urlMatch ? urlMatch[1] : ''; + }).filter(Boolean); + } + } + + return { + title, + link: detailUrl, + guid, + pubDate, + torrentUrl, + description, + images, + }; + }); + + return NextResponse.json({ + keyword: trimmedKeyword, + page: pageNum, + total: results.length, + items: results, + }); + } catch (error: any) { + console.error('Nyaa 搜索失败:', error); + return NextResponse.json( + { error: error.message || '搜索失败' }, + { status: 500 } + ); + } +} diff --git a/src/app/api/admin/anime-subscription/[id]/route.ts b/src/app/api/admin/anime-subscription/[id]/route.ts index 0c3165f..0c2a500 100644 --- a/src/app/api/admin/anime-subscription/[id]/route.ts +++ b/src/app/api/admin/anime-subscription/[id]/route.ts @@ -41,7 +41,7 @@ export async function PUT( subscription.filterText = updates.filterText.trim(); } if (updates.source !== undefined) { - if (!['acgrip', 'mikan', 'dmhy'].includes(updates.source)) { + if (!['acgrip', 'mikan', 'dmhy', 'nyaa'].includes(updates.source)) { return NextResponse.json({ error: '无效的搜索源' }, { status: 400 }); } subscription.source = updates.source; diff --git a/src/app/api/admin/anime-subscription/route.ts b/src/app/api/admin/anime-subscription/route.ts index 2b4c9a0..2c7138f 100644 --- a/src/app/api/admin/anime-subscription/route.ts +++ b/src/app/api/admin/anime-subscription/route.ts @@ -61,7 +61,7 @@ export async function POST(req: NextRequest) { } // 验证 source - if (!['acgrip', 'mikan', 'dmhy'].includes(source)) { + if (!['acgrip', 'mikan', 'dmhy', 'nyaa'].includes(source)) { return NextResponse.json({ error: '无效的搜索源' }, { status: 400 }); } diff --git a/src/app/api/admin/site/route.ts b/src/app/api/admin/site/route.ts index 6ec50d3..77a3a9e 100644 --- a/src/app/api/admin/site/route.ts +++ b/src/app/api/admin/site/route.ts @@ -60,6 +60,7 @@ export async function POST(request: NextRequest) { MagnetMikanReverseProxy, MagnetDmhyReverseProxy, MagnetAcgripReverseProxy, + MagnetNyaaReverseProxy, EnableComments, CustomAdFilterCode, CustomAdFilterVersion, @@ -113,6 +114,7 @@ export async function POST(request: NextRequest) { MagnetMikanReverseProxy?: string; MagnetDmhyReverseProxy?: string; MagnetAcgripReverseProxy?: string; + MagnetNyaaReverseProxy?: string; EnableComments: boolean; CustomAdFilterCode?: string; CustomAdFilterVersion?: number; @@ -181,6 +183,8 @@ export async function POST(request: NextRequest) { typeof MagnetDmhyReverseProxy !== 'string') || (MagnetAcgripReverseProxy !== undefined && typeof MagnetAcgripReverseProxy !== 'string') || + (MagnetNyaaReverseProxy !== undefined && + typeof MagnetNyaaReverseProxy !== 'string') || typeof EnableComments !== 'boolean' || (CustomAdFilterCode !== undefined && typeof CustomAdFilterCode !== 'string') || @@ -263,6 +267,7 @@ export async function POST(request: NextRequest) { MagnetMikanReverseProxy, MagnetDmhyReverseProxy, MagnetAcgripReverseProxy, + MagnetNyaaReverseProxy, EnableComments, CustomAdFilterCode, CustomAdFilterVersion, diff --git a/src/app/search/page.tsx b/src/app/search/page.tsx index 27369b1..355feec 100644 --- a/src/app/search/page.tsx +++ b/src/app/search/page.tsx @@ -1775,6 +1775,12 @@ function SearchPageClient() { {activeTab === 'pansou' && netdiskSearchEnabled && renderPansouCloudTypeFilter()} + + {activeTab === 'acg' && magnetSearchEnabled && ( +
+ +
+ )} {pansouCloudFilterOpen && @@ -2162,6 +2168,7 @@ function SearchPageClient() { )} diff --git a/src/components/AcgSearch.tsx b/src/components/AcgSearch.tsx index 855bd4b..e841aa4 100644 --- a/src/components/AcgSearch.tsx +++ b/src/components/AcgSearch.tsx @@ -2,7 +2,7 @@ 'use client'; import { AlertCircle, Download, ExternalLink, Loader2 } from 'lucide-react'; -import { useCallback,useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import CapsuleSwitch from '@/components/CapsuleSwitch'; import Toast, { ToastProps } from '@/components/Toast'; @@ -28,9 +28,11 @@ interface AcgSearchProps { keyword: string; triggerSearch?: boolean; onError?: (error: string) => void; + controlsOnly?: boolean; + showSourceSwitch?: boolean; } -type AcgSearchSource = 'acgrip' | 'mikan' | 'dmhy'; +type AcgSearchSource = 'acgrip' | 'mikan' | 'dmhy' | 'nyaa'; type DownloadTool = 'aria2' | 'Transmission' | 'qBittorrent'; const downloadToolOptions: Array<{ value: DownloadTool; label: string }> = [ @@ -39,12 +41,32 @@ const downloadToolOptions: Array<{ value: DownloadTool; label: string }> = [ { value: 'Transmission', label: 'Transmission' }, ]; +const ACG_SOURCE_STORAGE_KEY = 'acgSearchSource'; +const acgSourceOptions: Array<{ label: string; value: AcgSearchSource }> = [ + { label: 'ACG.RIP', value: 'acgrip' }, + { label: '蜜柑', value: 'mikan' }, + { label: '动漫花园', value: 'dmhy' }, + { label: 'Nyaa', value: 'nyaa' }, +]; + +function getStoredAcgSource(): AcgSearchSource { + if (typeof window === 'undefined') return 'acgrip'; + const saved = window.localStorage.getItem(ACG_SOURCE_STORAGE_KEY); + return acgSourceOptions.some((option) => option.value === saved) + ? saved as AcgSearchSource + : 'acgrip'; +} + export default function AcgSearch({ keyword, triggerSearch, onError, + controlsOnly = false, + showSourceSwitch = true, }: AcgSearchProps) { - const [source, setSource] = useState('acgrip'); + const [source, setSource] = useState(() => + getStoredAcgSource() + ); const [loading, setLoading] = useState(false); const [allItems, setAllItems] = useState([]); // 所有加载的项目 const [error, setError] = useState(null); @@ -60,11 +82,34 @@ export default function AcgSearch({ const isLoadingMoreRef = useRef(false); const didInitSourceRef = useRef(false); + useEffect(() => { + const handleSourceChange = (event: Event) => { + const nextSource = (event as CustomEvent).detail; + if (acgSourceOptions.some((option) => option.value === nextSource)) { + setSource(nextSource); + } + }; + + window.addEventListener('acg-search-source-change', handleSourceChange); + return () => { + window.removeEventListener('acg-search-source-change', handleSourceChange); + }; + }, []); + + const handleSourceChange = (value: AcgSearchSource) => { + setSource(value); + window.localStorage.setItem(ACG_SOURCE_STORAGE_KEY, value); + window.dispatchEvent( + new CustomEvent('acg-search-source-change', { detail: value }) + ); + }; + // 执行搜索 const performSearch = async (page: number, isLoadMore = false) => { if (isLoadingMoreRef.current) return; if (source === 'mikan' && page > 1) return; if (source === 'dmhy' && page > 1) return; + if (source === 'nyaa' && page > 1) return; isLoadingMoreRef.current = true; setLoading(true); @@ -76,7 +121,9 @@ export default function AcgSearch({ ? '/api/acg/mikan' : source === 'dmhy' ? '/api/acg/dmhy' - : '/api/acg/acgrip'; + : source === 'nyaa' + ? '/api/acg/nyaa' + : '/api/acg/acgrip'; const response = await fetch(apiUrl, { method: 'POST', headers: { @@ -97,14 +144,24 @@ export default function AcgSearch({ if (isLoadMore) { // 追加新数据 - setAllItems(prev => [...prev, ...data.items]); + setAllItems((prev) => [...prev, ...data.items]); // 如果当前页没有结果,说明没有更多了 - setHasMore(source !== 'mikan' && source !== 'dmhy' && data.items.length > 0); + setHasMore( + source !== 'mikan' && + source !== 'dmhy' && + source !== 'nyaa' && + data.items.length > 0 + ); } else { // 新搜索,重置数据 setAllItems(data.items); // 如果第一页有结果,假设可能还有更多 - setHasMore(source !== 'mikan' && source !== 'dmhy' && data.items.length > 0); + setHasMore( + source !== 'mikan' && + source !== 'dmhy' && + source !== 'nyaa' && + data.items.length > 0 + ); } setCurrentPage(page); @@ -120,7 +177,7 @@ export default function AcgSearch({ useEffect(() => { // triggerSearch 变化时触发搜索(无论是 true 还是 false) - if (triggerSearch === undefined) { + if (controlsOnly || triggerSearch === undefined) { return; } @@ -134,10 +191,12 @@ export default function AcgSearch({ setCurrentPage(1); setHasMore(true); performSearch(1, false); - }, [triggerSearch]); + }, [triggerSearch, controlsOnly]); // 切换搜索源时,自动重新搜索(避免组件初次挂载时重复触发) useEffect(() => { + if (controlsOnly) return; + if (!didInitSourceRef.current) { didInitSourceRef.current = true; return; @@ -150,12 +209,13 @@ export default function AcgSearch({ setCurrentPage(1); setHasMore(true); performSearch(1, false); - }, [source]); + }, [source, controlsOnly]); // 加载更多数据 const loadMore = useCallback(() => { if (source === 'mikan') return; if (source === 'dmhy') return; + if (source === 'nyaa') return; if (!loading && hasMore && !isLoadingMoreRef.current) { performSearch(currentPage + 1, true); } @@ -260,7 +320,9 @@ export default function AcgSearch({
-

{error}

+

+ {error} +

); @@ -349,7 +411,10 @@ export default function AcgSearch({ {/* 加载更多指示器 */} - {source !== 'mikan' && source !== 'dmhy' && hasMore && ( + {source !== 'mikan' && + source !== 'dmhy' && + source !== 'nyaa' && + hasMore && (
@@ -416,20 +481,24 @@ export default function AcgSearch({ ); }; + const sourceSwitch = ( +
+ handleSourceChange(value as AcgSearchSource)} + /> +
+ ); + + if (controlsOnly) { + return sourceSwitch; + } + return (
{/* 搜索源切换 */} -
- setSource(value as AcgSearchSource)} - /> -
+ {showSourceSwitch && sourceSwitch} {renderBody()} {/* Toast 提示 */} diff --git a/src/components/AnimeSubscriptionComponent.tsx b/src/components/AnimeSubscriptionComponent.tsx index 84a3dd5..910b41e 100644 --- a/src/components/AnimeSubscriptionComponent.tsx +++ b/src/components/AnimeSubscriptionComponent.tsx @@ -179,7 +179,7 @@ export default function AnimeSubscriptionComponent({ const [formData, setFormData] = useState({ title: '', filterText: '', - source: 'mikan' as 'acgrip' | 'mikan' | 'dmhy', + source: 'mikan' as 'acgrip' | 'mikan' | 'dmhy' | 'nyaa', lastEpisode: 0, enabled: true, }); @@ -551,6 +551,7 @@ export default function AnimeSubscriptionComponent({ +
@@ -619,7 +620,7 @@ export default function AnimeSubscriptionComponent({ {sub.title} - {sub.source === 'acgrip' ? 'ACG.RIP' : sub.source === 'mikan' ? '蜜柑' : '动漫花园'} + {sub.source === 'acgrip' ? 'ACG.RIP' : sub.source === 'mikan' ? '蜜柑' : sub.source === 'nyaa' ? 'Nyaa' : '动漫花园'}
@@ -675,7 +676,7 @@ export default function AnimeSubscriptionComponent({ {sub.title} - {sub.source === 'acgrip' ? 'ACG.RIP' : sub.source === 'mikan' ? '蜜柑' : '动漫花园'} + {sub.source === 'acgrip' ? 'ACG.RIP' : sub.source === 'mikan' ? '蜜柑' : sub.source === 'nyaa' ? 'Nyaa' : '动漫花园'}
{ + if (value === undefined || value === null) return ''; + const first = Array.isArray(value) ? value[0] : value; + if (first === undefined || first === null) return ''; + if (typeof first === 'object') return String(first._ ?? first.$?.url ?? first.$?.href ?? ''); + return String(first); +}; + function getAnimeSubscriptionDownloadTool(tool: unknown): AnimeSubscriptionDownloadTool { return typeof tool === 'string' && downloadTools.includes(tool as AnimeSubscriptionDownloadTool) ? tool as AnimeSubscriptionDownloadTool @@ -65,26 +74,50 @@ export function matchesFilter(title: string, filterText: string): boolean { */ export async function searchACG( keyword: string, - source: 'acgrip' | 'mikan' | 'dmhy' + source: 'acgrip' | 'mikan' | 'dmhy' | 'nyaa' ) { const trimmedKeyword = keyword.trim(); + const config = await getConfig(); let searchUrl: string; switch (source) { - case 'mikan': - searchUrl = `https://mikanani.me/RSS/Search?searchstr=${encodeURIComponent(trimmedKeyword)}`; + case 'mikan': { + const baseUrl = getMagnetBaseUrl( + 'https://mikanani.me', + config.SiteConfig.MagnetMikanReverseProxy + ); + searchUrl = `${baseUrl}/RSS/Search?searchstr=${encodeURIComponent(trimmedKeyword)}`; break; - case 'dmhy': - searchUrl = `http://share.dmhy.org/topics/rss/rss.xml?keyword=${encodeURIComponent(trimmedKeyword)}`; + } + case 'dmhy': { + const baseUrl = getMagnetBaseUrl( + 'http://share.dmhy.org', + config.SiteConfig.MagnetDmhyReverseProxy + ); + searchUrl = `${baseUrl}/topics/rss/rss.xml?keyword=${encodeURIComponent(trimmedKeyword)}`; break; + } + case 'nyaa': { + const baseUrl = getMagnetBaseUrl( + 'https://nyaa.si', + config.SiteConfig.MagnetNyaaReverseProxy + ); + searchUrl = `${baseUrl}/?page=rss&q=${encodeURIComponent(trimmedKeyword)}&c=1_0&f=0`; + break; + } case 'acgrip': - default: - searchUrl = `https://acg.rip/page/1.xml?term=${encodeURIComponent(trimmedKeyword)}`; + default: { + const baseUrl = getMagnetBaseUrl( + 'https://acg.rip', + config.SiteConfig.MagnetAcgripReverseProxy + ); + searchUrl = `${baseUrl}/page/1.xml?term=${encodeURIComponent(trimmedKeyword)}`; break; + } } - 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', @@ -104,14 +137,21 @@ export async function searchACG( const items = parsed.rss.channel[0].item; - // 统一格式 + // 统一格式。注意:Nyaa RSS 的 link 是 .torrent 下载地址,guid 才是详情页。 return items.map((item: any) => { - const title = item.title?.[0] || ''; - const link = item.link?.[0] || ''; - const guid = item.guid?.[0] || link || `${title}-${item.pubDate?.[0] || ''}`; - const pubDate = item.pubDate?.[0] || ''; - const torrentUrl = item.enclosure?.[0]?.$?.url || ''; - const description = item.description?.[0] || ''; + const title = pickRssText(item.title); + const rawLink = pickRssText(item.link); + const rawGuid = pickRssText(item.guid); + const pubDate = pickRssText(item.pubDate); + const description = pickRssText(item.description) || pickRssText(item['content:encoded']); + const enclosureUrl = + pickRssText(item.enclosure?.[0]?.$?.url) || + pickRssText(item.enclosure?.[0]?.$?.href); + + const isNyaa = source === 'nyaa'; + const link = isNyaa ? (rawGuid || rawLink) : rawLink; + const torrentUrl = isNyaa ? rawLink : enclosureUrl; + const guid = rawGuid || link || torrentUrl || `${title}-${pubDate}`; return { title, @@ -237,7 +277,7 @@ async function sendAnimeUpdateNotifications(

${subscription.title}

新增集数:第 ${episodeList} 集

-

搜索源:${subscription.source === 'acgrip' ? 'ACG.RIP' : subscription.source === 'mikan' ? '蜜柑' : '动漫花园'}

+

搜索源:${subscription.source === 'acgrip' ? 'ACG.RIP' : subscription.source === 'mikan' ? '蜜柑' : subscription.source === 'nyaa' ? 'Nyaa' : '动漫花园'}

这些集数已自动添加到 OpenList 离线下载队列。


diff --git a/src/lib/config.ts b/src/lib/config.ts index 7dbe128..0433ae2 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -313,6 +313,7 @@ async function getInitConfig( MagnetMikanReverseProxy: '', MagnetDmhyReverseProxy: '', MagnetAcgripReverseProxy: '', + MagnetNyaaReverseProxy: '', // 评论功能开关 EnableComments: false, EnableRegistration: false, @@ -509,6 +510,7 @@ export function configSelfCheck(adminConfig: AdminConfig): AdminConfig { MagnetMikanReverseProxy: '', MagnetDmhyReverseProxy: '', MagnetAcgripReverseProxy: '', + MagnetNyaaReverseProxy: '', EnableComments: false, EnableRegistration: false, RequireRegistrationInviteCode: false, @@ -579,6 +581,9 @@ export function configSelfCheck(adminConfig: AdminConfig): AdminConfig { if (adminConfig.SiteConfig.MagnetAcgripReverseProxy === undefined) { adminConfig.SiteConfig.MagnetAcgripReverseProxy = ''; } + if (adminConfig.SiteConfig.MagnetNyaaReverseProxy === undefined) { + adminConfig.SiteConfig.MagnetNyaaReverseProxy = ''; + } if (!adminConfig.UserConfig) { adminConfig.UserConfig = { Users: [] }; } diff --git a/src/types/anime-subscription.ts b/src/types/anime-subscription.ts index 8195ef2..b124ab0 100644 --- a/src/types/anime-subscription.ts +++ b/src/types/anime-subscription.ts @@ -2,7 +2,7 @@ export interface AnimeSubscription { id: string; title: string; filterText: string; - source: 'acgrip' | 'mikan' | 'dmhy'; + source: 'acgrip' | 'mikan' | 'dmhy' | 'nyaa'; enabled: boolean; lastCheckTime: number; lastEpisode: number;