@@ -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;