私人影库增加追番订阅功能
This commit is contained in:
@@ -25,6 +25,7 @@ import {
|
||||
AlertCircle,
|
||||
AlertTriangle,
|
||||
Bot,
|
||||
Cat,
|
||||
Check,
|
||||
CheckCircle,
|
||||
ChevronDown,
|
||||
@@ -50,6 +51,7 @@ import { createPortal } from 'react-dom';
|
||||
import { AdminConfig, AdminConfigResult } from '@/lib/admin.types';
|
||||
import { getAuthInfoFromBrowserCookie } from '@/lib/auth';
|
||||
|
||||
import AnimeSubscriptionComponent from '@/components/AnimeSubscriptionComponent';
|
||||
import CorrectDialog from '@/components/CorrectDialog';
|
||||
import DataMigration from '@/components/DataMigration';
|
||||
import PageLayout from '@/components/PageLayout';
|
||||
@@ -11597,6 +11599,7 @@ function AdminPageClient() {
|
||||
openListConfig: false,
|
||||
embyConfig: false,
|
||||
xiaoyaConfig: false,
|
||||
animeSubscription: false,
|
||||
aiConfig: false,
|
||||
liveSource: false,
|
||||
webLive: false,
|
||||
@@ -12049,6 +12052,18 @@ function AdminPageClient() {
|
||||
>
|
||||
<MovieRequestsComponent config={config} refreshConfig={fetchConfig} />
|
||||
</CollapsibleTab>
|
||||
|
||||
{/* 追番订阅子标签 */}
|
||||
<CollapsibleTab
|
||||
title='追番订阅'
|
||||
icon={
|
||||
<Cat size={20} className='text-gray-600 dark:text-gray-400' />
|
||||
}
|
||||
isExpanded={expandedTabs.animeSubscription}
|
||||
onToggle={() => toggleTab('animeSubscription')}
|
||||
>
|
||||
<AnimeSubscriptionComponent config={config} refreshConfig={fetchConfig} />
|
||||
</CollapsibleTab>
|
||||
</div>
|
||||
</CollapsibleTab>
|
||||
|
||||
|
||||
51
src/app/api/admin/anime-subscription/[id]/check/route.ts
Normal file
51
src/app/api/admin/anime-subscription/[id]/check/route.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { checkSubscription } from '@/lib/anime-subscription';
|
||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import { getConfig } from '@/lib/config';
|
||||
import { db } from '@/lib/db';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
/**
|
||||
* POST /api/admin/anime-subscription/[id]/check
|
||||
* 手动触发检查单个订阅
|
||||
*/
|
||||
export async function POST(
|
||||
req: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
// 权限检查
|
||||
const authInfo = getAuthInfoFromCookie(req);
|
||||
if (!authInfo || (authInfo.role !== 'admin' && authInfo.role !== 'owner')) {
|
||||
return NextResponse.json({ error: '无权限访问' }, { status: 403 });
|
||||
}
|
||||
|
||||
const config = await getConfig();
|
||||
const subscriptions = config.AnimeSubscriptionConfig?.Subscriptions || [];
|
||||
|
||||
const subscription = subscriptions.find((sub) => sub.id === params.id);
|
||||
if (!subscription) {
|
||||
return NextResponse.json({ error: '订阅不存在' }, { status: 404 });
|
||||
}
|
||||
|
||||
// 执行检查逻辑(忽略时间间隔限制)
|
||||
const result = await checkSubscription(subscription);
|
||||
|
||||
// 保存配置
|
||||
await db.saveAdminConfig(config);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
...result,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error('检查追番订阅失败:', error);
|
||||
return NextResponse.json(
|
||||
{ error: error.message || '检查失败' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
112
src/app/api/admin/anime-subscription/[id]/route.ts
Normal file
112
src/app/api/admin/anime-subscription/[id]/route.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import { getConfig } from '@/lib/config';
|
||||
import { db } from '@/lib/db';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
/**
|
||||
* PUT /api/admin/anime-subscription/[id]
|
||||
* 更新订阅
|
||||
*/
|
||||
export async function PUT(
|
||||
req: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
// 权限检查
|
||||
const authInfo = getAuthInfoFromCookie(req);
|
||||
if (!authInfo || (authInfo.role !== 'admin' && authInfo.role !== 'owner')) {
|
||||
return NextResponse.json({ error: '无权限访问' }, { status: 403 });
|
||||
}
|
||||
|
||||
const config = await getConfig();
|
||||
const subscriptions = config.AnimeSubscriptionConfig?.Subscriptions || [];
|
||||
|
||||
const index = subscriptions.findIndex((sub) => sub.id === params.id);
|
||||
if (index === -1) {
|
||||
return NextResponse.json({ error: '订阅不存在' }, { status: 404 });
|
||||
}
|
||||
|
||||
const updates = await req.json();
|
||||
const subscription = subscriptions[index];
|
||||
|
||||
// 更新字段
|
||||
if (updates.title !== undefined) {
|
||||
subscription.title = updates.title.trim();
|
||||
}
|
||||
if (updates.filterText !== undefined) {
|
||||
subscription.filterText = updates.filterText.trim();
|
||||
}
|
||||
if (updates.source !== undefined) {
|
||||
if (!['acgrip', 'mikan', 'dmhy'].includes(updates.source)) {
|
||||
return NextResponse.json({ error: '无效的搜索源' }, { status: 400 });
|
||||
}
|
||||
subscription.source = updates.source;
|
||||
}
|
||||
if (updates.enabled !== undefined) {
|
||||
subscription.enabled = updates.enabled;
|
||||
}
|
||||
if (updates.lastEpisode !== undefined) {
|
||||
// 验证集数为非负整数
|
||||
const episode = parseInt(String(updates.lastEpisode), 10);
|
||||
if (isNaN(episode) || episode < 0) {
|
||||
return NextResponse.json(
|
||||
{ error: '集数必须是非负整数' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
subscription.lastEpisode = episode;
|
||||
}
|
||||
|
||||
subscription.updatedAt = Date.now();
|
||||
|
||||
await db.saveAdminConfig(config);
|
||||
|
||||
return NextResponse.json(subscription);
|
||||
} catch (error: any) {
|
||||
console.error('更新追番订阅失败:', error);
|
||||
return NextResponse.json(
|
||||
{ error: error.message || '更新订阅失败' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /api/admin/anime-subscription/[id]
|
||||
* 删除订阅
|
||||
*/
|
||||
export async function DELETE(
|
||||
req: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
// 权限检查
|
||||
const authInfo = getAuthInfoFromCookie(req);
|
||||
if (!authInfo || (authInfo.role !== 'admin' && authInfo.role !== 'owner')) {
|
||||
return NextResponse.json({ error: '无权限访问' }, { status: 403 });
|
||||
}
|
||||
|
||||
const config = await getConfig();
|
||||
const subscriptions = config.AnimeSubscriptionConfig?.Subscriptions || [];
|
||||
|
||||
const index = subscriptions.findIndex((sub) => sub.id === params.id);
|
||||
if (index === -1) {
|
||||
return NextResponse.json({ error: '订阅不存在' }, { status: 404 });
|
||||
}
|
||||
|
||||
subscriptions.splice(index, 1);
|
||||
await db.saveAdminConfig(config);
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error: any) {
|
||||
console.error('删除追番订阅失败:', error);
|
||||
return NextResponse.json(
|
||||
{ error: error.message || '删除订阅失败' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
106
src/app/api/admin/anime-subscription/route.ts
Normal file
106
src/app/api/admin/anime-subscription/route.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import { getConfig } from '@/lib/config';
|
||||
import { db } from '@/lib/db';
|
||||
import { AnimeSubscription } from '@/types/anime-subscription';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
/**
|
||||
* GET /api/admin/anime-subscription
|
||||
* 获取订阅列表和配置
|
||||
*/
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
// 权限检查
|
||||
const authInfo = getAuthInfoFromCookie(req);
|
||||
if (!authInfo || (authInfo.role !== 'admin' && authInfo.role !== 'owner')) {
|
||||
return NextResponse.json({ error: '无权限访问' }, { status: 403 });
|
||||
}
|
||||
|
||||
const config = await getConfig();
|
||||
const animeConfig = config.AnimeSubscriptionConfig || {
|
||||
Enabled: false,
|
||||
Subscriptions: [],
|
||||
};
|
||||
|
||||
return NextResponse.json(animeConfig);
|
||||
} catch (error: any) {
|
||||
console.error('获取追番订阅配置失败:', error);
|
||||
return NextResponse.json(
|
||||
{ error: error.message || '获取配置失败' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/admin/anime-subscription
|
||||
* 创建新订阅
|
||||
*/
|
||||
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 { title, filterText, source, enabled, lastEpisode } =
|
||||
await req.json();
|
||||
|
||||
// 验证必填字段
|
||||
if (!title || !filterText || !source) {
|
||||
return NextResponse.json({ error: '缺少必填字段' }, { status: 400 });
|
||||
}
|
||||
|
||||
// 验证 source
|
||||
if (!['acgrip', 'mikan', 'dmhy'].includes(source)) {
|
||||
return NextResponse.json({ error: '无效的搜索源' }, { status: 400 });
|
||||
}
|
||||
|
||||
const config = await getConfig();
|
||||
if (!config.AnimeSubscriptionConfig) {
|
||||
config.AnimeSubscriptionConfig = { Enabled: false, Subscriptions: [] };
|
||||
}
|
||||
|
||||
// 验证集数
|
||||
let episodeNum = 0;
|
||||
if (lastEpisode !== undefined) {
|
||||
episodeNum = parseInt(String(lastEpisode), 10);
|
||||
if (isNaN(episodeNum) || episodeNum < 0) {
|
||||
return NextResponse.json(
|
||||
{ error: '集数必须是非负整数' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 创建新订阅
|
||||
const newSubscription: AnimeSubscription = {
|
||||
id: crypto.randomUUID(),
|
||||
title: title.trim(),
|
||||
filterText: filterText.trim(),
|
||||
source,
|
||||
enabled: enabled ?? true,
|
||||
lastCheckTime: 0,
|
||||
lastEpisode: episodeNum,
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
createdBy: authInfo.username || 'unknown',
|
||||
};
|
||||
|
||||
config.AnimeSubscriptionConfig.Subscriptions.push(newSubscription);
|
||||
await db.saveAdminConfig(config);
|
||||
|
||||
return NextResponse.json(newSubscription);
|
||||
} catch (error: any) {
|
||||
console.error('创建追番订阅失败:', error);
|
||||
return NextResponse.json(
|
||||
{ error: error.message || '创建订阅失败' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
47
src/app/api/admin/anime-subscription/toggle/route.ts
Normal file
47
src/app/api/admin/anime-subscription/toggle/route.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import { getConfig } from '@/lib/config';
|
||||
import { db } from '@/lib/db';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
/**
|
||||
* PUT /api/admin/anime-subscription/toggle
|
||||
* 切换追番功能启用状态
|
||||
*/
|
||||
export async function PUT(req: NextRequest) {
|
||||
try {
|
||||
// 权限检查
|
||||
const authInfo = getAuthInfoFromCookie(req);
|
||||
if (!authInfo || (authInfo.role !== 'admin' && authInfo.role !== 'owner')) {
|
||||
return NextResponse.json({ error: '无权限访问' }, { status: 403 });
|
||||
}
|
||||
|
||||
const { enabled } = await req.json();
|
||||
|
||||
if (typeof enabled !== 'boolean') {
|
||||
return NextResponse.json(
|
||||
{ error: 'enabled 必须是布尔值' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const config = await getConfig();
|
||||
if (!config.AnimeSubscriptionConfig) {
|
||||
config.AnimeSubscriptionConfig = { Enabled: false, Subscriptions: [] };
|
||||
}
|
||||
|
||||
config.AnimeSubscriptionConfig.Enabled = enabled;
|
||||
await db.saveAdminConfig(config);
|
||||
|
||||
return NextResponse.json({ success: true, enabled });
|
||||
} catch (error: any) {
|
||||
console.error('切换追番功能状态失败:', error);
|
||||
return NextResponse.json(
|
||||
{ error: error.message || '切换状态失败' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { checkAnimeSubscriptions } from '@/lib/anime-subscription';
|
||||
import { getConfig, refineConfig } from '@/lib/config';
|
||||
import { db, getStorage } from '@/lib/db';
|
||||
import { EmailService } from '@/lib/email.service';
|
||||
@@ -57,6 +58,7 @@ async function cronJob() {
|
||||
await refreshAllLiveChannels();
|
||||
await refreshOpenList();
|
||||
await refreshRecordAndFavorites();
|
||||
await checkAnimeSubscriptions();
|
||||
}
|
||||
|
||||
async function refreshAllLiveChannels() {
|
||||
|
||||
Reference in New Issue
Block a user