动漫磁力搜索增加蜜柑

This commit is contained in:
mtvpls
2026-01-22 11:24:11 +08:00
parent a41f2fd1d6
commit 06efd8121f
3 changed files with 365 additions and 167 deletions

View File

@@ -7,8 +7,8 @@ import { getAuthInfoFromCookie } from '@/lib/auth';
export const runtime = 'nodejs';
/**
* POST /api/acg/search
* ACG
* POST /api/acg/acgrip
* ACG.RIP
*/
export async function POST(req: NextRequest) {
try {
@@ -47,10 +47,10 @@ export async function POST(req: NextRequest) {
);
}
// 请求 acg.rip API
const acgUrl = `https://acg.rip/page/${pageNum}.xml?term=${encodeURIComponent(trimmedKeyword)}`;
// 请求 acg.rip RSS
const searchUrl = `https://acg.rip/page/${pageNum}.xml?term=${encodeURIComponent(trimmedKeyword)}`;
const response = await fetch(acgUrl, {
const response = await fetch(searchUrl, {
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',
},
@@ -78,10 +78,12 @@ export async function POST(req: NextRequest) {
// 转换为标准格式
const results = items.map((item: any) => {
const description = item.description?.[0] || '';
// 提取描述中的图片(如果有)
let images: string[] = [];
if (item.description?.[0]) {
const imgMatches = item.description[0].match(/src="([^"]+)"/g);
if (description) {
const imgMatches = description.match(/src="([^"]+)"/g);
if (imgMatches) {
images = imgMatches.map((match: string) => {
const urlMatch = match.match(/src="([^"]+)"/);
@@ -90,13 +92,19 @@ export async function POST(req: NextRequest) {
}
}
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 || '';
return {
title: item.title?.[0] || '',
link: item.link?.[0] || '',
guid: item.guid?.[0] || '',
pubDate: item.pubDate?.[0] || '',
torrentUrl: item.enclosure?.[0]?.$?.url || '',
description: item.description?.[0] || '',
title,
link,
guid,
pubDate,
torrentUrl,
description,
images,
};
});
@@ -107,12 +115,12 @@ export async function POST(req: NextRequest) {
total: results.length,
items: results,
});
} catch (error: any) {
console.error('ACG 搜索失败:', error);
console.error('ACG.RIP 搜索失败:', error);
return NextResponse.json(
{ error: error.message || '搜索失败' },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,147 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { NextRequest, NextResponse } from 'next/server';
import { parseStringPromise } from 'xml2js';
import { getAuthInfoFromCookie } from '@/lib/auth';
export const runtime = 'nodejs';
const pickText = (value: any): string => {
if (value === undefined || value === null) return '';
if (Array.isArray(value)) return String(value[0] ?? '');
return String(value);
};
/**
* POST /api/acg/mikan
* 搜索 Mikan RSS仅管理员和站长可用不支持分页
*/
export async function POST(req: NextRequest) {
try {
// 检查权限
const authInfo = getAuthInfoFromCookie(req);
if (!authInfo || (authInfo.role !== 'admin' && authInfo.role !== 'owner')) {
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 }
);
}
// 验证页码Mikan RSS 不支持分页,这里仍接收 page 以保持接口一致)
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 searchUrl = `https://mikanani.me/RSS/Search?searchstr=${encodeURIComponent(trimmedKeyword)}`;
const response = await fetch(searchUrl, {
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(`Mikan 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);
const link = pickText(item.link);
const guid = pickText(item.guid) || link || `${title}-${pickText(item.torrent?.[0]?.pubDate)}`;
const pubDate =
pickText(item.pubDate) ||
pickText(item.torrent?.[0]?.pubDate) ||
pickText(item['dc:date']);
const description =
pickText(item.description) ||
pickText(item['content:encoded']) ||
'';
const torrentUrl =
pickText(item.enclosure?.[0]?.$?.url) ||
pickText(item.enclosure?.[0]?.$?.href) ||
'';
// 提取描述中的图片(如果有)
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,
guid,
pubDate,
torrentUrl,
description,
images,
};
});
return NextResponse.json({
keyword: trimmedKeyword,
page: pageNum,
total: results.length,
items: results,
});
} catch (error: any) {
console.error('Mikan 搜索失败:', error);
return NextResponse.json(
{ error: error.message || '搜索失败' },
{ status: 500 }
);
}
}