增加动漫磁力搜索
This commit is contained in:
107
src/app/api/acg/download/route.ts
Normal file
107
src/app/api/acg/download/route.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import { getConfig } from '@/lib/config';
|
||||
import { OpenListClient } from '@/lib/openlist.client';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
/**
|
||||
* POST /api/acg/download
|
||||
* 添加 ACG 资源到 OpenList 离线下载(仅管理员和站长可用)
|
||||
*/
|
||||
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 { url, name } = await req.json();
|
||||
|
||||
if (!url || typeof url !== 'string') {
|
||||
return NextResponse.json(
|
||||
{ error: '下载链接不能为空' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
if (!name || typeof name !== 'string') {
|
||||
return NextResponse.json(
|
||||
{ error: '资源名称不能为空' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// 获取 OpenList 配置
|
||||
const config = await getConfig();
|
||||
const openlistConfig = config.OpenListConfig;
|
||||
|
||||
if (!openlistConfig?.Enabled) {
|
||||
return NextResponse.json(
|
||||
{ error: '私人影库功能未启用' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
if (!openlistConfig.URL || !openlistConfig.Username || !openlistConfig.Password) {
|
||||
return NextResponse.json(
|
||||
{ error: 'OpenList 配置不完整' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// 构建下载路径(使用离线下载目录)
|
||||
const offlineDownloadPath = openlistConfig.OfflineDownloadPath || '/';
|
||||
const downloadPath = `${offlineDownloadPath.replace(/\/$/, '')}/${name}`;
|
||||
|
||||
// 使用 OpenListClient 添加离线下载任务
|
||||
const client = new OpenListClient(
|
||||
openlistConfig.URL,
|
||||
openlistConfig.Username,
|
||||
openlistConfig.Password
|
||||
);
|
||||
|
||||
// 获取 Token 并调用 API
|
||||
const token = await (client as any).getToken();
|
||||
const openlistUrl = `${openlistConfig.URL.replace(/\/$/, '')}/api/fs/add_offline_download`;
|
||||
|
||||
const response = await fetch(openlistUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': token,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
path: downloadPath,
|
||||
urls: [url],
|
||||
tool: 'aria2',
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// 检查响应状态
|
||||
if (!response.ok || data.code !== 200) {
|
||||
throw new Error(data.message || '添加离线下载任务失败');
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '已添加到离线下载队列',
|
||||
path: downloadPath,
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('添加离线下载任务失败:', error);
|
||||
return NextResponse.json(
|
||||
{ error: error.message || '添加离线下载任务失败' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
118
src/app/api/acg/search/route.ts
Normal file
118
src/app/api/acg/search/route.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
/* 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';
|
||||
|
||||
/**
|
||||
* POST /api/acg/search
|
||||
* 搜索 ACG 磁力资源(仅管理员和站长可用)
|
||||
*/
|
||||
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 }
|
||||
);
|
||||
}
|
||||
|
||||
// 验证页码
|
||||
const pageNum = parseInt(String(page), 10);
|
||||
if (isNaN(pageNum) || pageNum < 1) {
|
||||
return NextResponse.json(
|
||||
{ error: '页码必须是大于0的整数' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// 请求 acg.rip API
|
||||
const acgUrl = `https://acg.rip/page/${pageNum}.xml?term=${encodeURIComponent(trimmedKeyword)}`;
|
||||
|
||||
const response = await fetch(acgUrl, {
|
||||
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(`ACG.RIP API 请求失败: ${response.status}`);
|
||||
}
|
||||
|
||||
const xmlData = await response.text();
|
||||
|
||||
// 解析 XML
|
||||
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) => {
|
||||
// 提取描述中的图片(如果有)
|
||||
let images: string[] = [];
|
||||
if (item.description?.[0]) {
|
||||
const imgMatches = item.description[0].match(/src="([^"]+)"/g);
|
||||
if (imgMatches) {
|
||||
images = imgMatches.map((match: string) => {
|
||||
const urlMatch = match.match(/src="([^"]+)"/);
|
||||
return urlMatch ? urlMatch[1] : '';
|
||||
}).filter(Boolean);
|
||||
}
|
||||
}
|
||||
|
||||
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] || '',
|
||||
images,
|
||||
};
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
keyword: trimmedKeyword,
|
||||
page: pageNum,
|
||||
total: results.length,
|
||||
items: results,
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('ACG 搜索失败:', error);
|
||||
return NextResponse.json(
|
||||
{ error: error.message || '搜索失败' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { action, Enabled, URL, Username, Password, RootPath, ScanInterval } = body;
|
||||
const { action, Enabled, URL, Username, Password, RootPath, OfflineDownloadPath, ScanInterval } = body;
|
||||
|
||||
const authInfo = getAuthInfoFromCookie(request);
|
||||
if (!authInfo || !authInfo.username) {
|
||||
@@ -56,6 +56,7 @@ export async function POST(request: NextRequest) {
|
||||
Username: Username || '',
|
||||
Password: Password || '',
|
||||
RootPath: RootPath || '/',
|
||||
OfflineDownloadPath: OfflineDownloadPath || '/',
|
||||
LastRefreshTime: adminConfig.OpenListConfig?.LastRefreshTime,
|
||||
ResourceCount: adminConfig.OpenListConfig?.ResourceCount,
|
||||
ScanInterval: 0,
|
||||
@@ -105,6 +106,7 @@ export async function POST(request: NextRequest) {
|
||||
Username,
|
||||
Password,
|
||||
RootPath: RootPath || '/',
|
||||
OfflineDownloadPath: OfflineDownloadPath || '/',
|
||||
LastRefreshTime: adminConfig.OpenListConfig?.LastRefreshTime,
|
||||
ResourceCount: adminConfig.OpenListConfig?.ResourceCount,
|
||||
ScanInterval: scanInterval,
|
||||
|
||||
Reference in New Issue
Block a user