feat: implement iptv

This commit is contained in:
shinya
2025-08-24 00:26:48 +08:00
parent 179e74bf45
commit b4e81d94eb
22 changed files with 2399 additions and 12 deletions

View File

@@ -0,0 +1,53 @@
/* eslint-disable no-console */
import { NextRequest, NextResponse } from 'next/server';
import { getAuthInfoFromCookie } from '@/lib/auth';
import { getConfig } from '@/lib/config';
import { db } from '@/lib/db';
import { refreshLiveChannels } from '@/lib/live';
export async function POST(request: NextRequest) {
try {
// 权限检查
const authInfo = getAuthInfoFromCookie(request);
const username = authInfo?.username;
const config = await getConfig();
if (username !== process.env.USERNAME) {
// 管理员
const user = config.UserConfig.Users.find(
(u) => u.username === username
);
if (!user || user.role !== 'admin' || user.banned) {
return NextResponse.json({ error: '权限不足' }, { status: 401 });
}
}
for (const liveInfo of config.LiveConfig || []) {
if (liveInfo.disabled) {
continue;
}
try {
const nums = await refreshLiveChannels(liveInfo);
liveInfo.channelNumber = nums;
} catch (error) {
console.error('刷新直播源失败:', error);
liveInfo.channelNumber = 0;
}
}
// 保存配置
await db.saveAdminConfig(config);
return NextResponse.json({
success: true,
message: '直播源刷新成功',
});
} catch (error) {
console.error('直播源刷新失败:', error);
return NextResponse.json(
{ error: error instanceof Error ? error.message : '刷新失败' },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,143 @@
/* eslint-disable no-console,no-case-declarations */
import { NextRequest, NextResponse } from 'next/server';
import { getAuthInfoFromCookie } from '@/lib/auth';
import { getConfig } from '@/lib/config';
import { db } from '@/lib/db';
import { deleteCachedLiveChannels, refreshLiveChannels } from '@/lib/live';
export async function POST(request: NextRequest) {
try {
// 权限检查
const authInfo = getAuthInfoFromCookie(request);
const username = authInfo?.username;
const config = await getConfig();
if (username !== process.env.USERNAME) {
// 管理员
const user = config.UserConfig.Users.find(
(u) => u.username === username
);
if (!user || user.role !== 'admin' || user.banned) {
return NextResponse.json({ error: '权限不足' }, { status: 401 });
}
}
const body = await request.json();
const { action, key, name, url, ua, epg } = body;
if (!config) {
return NextResponse.json({ error: '配置不存在' }, { status: 404 });
}
// 确保 LiveConfig 存在
if (!config.LiveConfig) {
config.LiveConfig = [];
}
switch (action) {
case 'add':
// 检查是否已存在相同的 key
if (config.LiveConfig.some((l) => l.key === key)) {
return NextResponse.json({ error: '直播源 key 已存在' }, { status: 400 });
}
const liveInfo = {
key: key as string,
name: name as string,
url: url as string,
ua: ua || '',
epg: epg || '',
from: 'custom' as 'custom' | 'config',
channelNumber: 0,
disabled: false,
}
try {
const nums = await refreshLiveChannels(liveInfo);
liveInfo.channelNumber = nums;
} catch (error) {
console.error('刷新直播源失败:', error);
liveInfo.channelNumber = 0;
}
// 添加新的直播源
config.LiveConfig.push(liveInfo);
break;
case 'delete':
// 删除直播源
const deleteIndex = config.LiveConfig.findIndex((l) => l.key === key);
if (deleteIndex === -1) {
return NextResponse.json({ error: '直播源不存在' }, { status: 404 });
}
const liveSource = config.LiveConfig[deleteIndex];
if (liveSource.from === 'config') {
return NextResponse.json({ error: '不能删除配置文件中的直播源' }, { status: 400 });
}
deleteCachedLiveChannels(key);
config.LiveConfig.splice(deleteIndex, 1);
break;
case 'enable':
// 启用直播源
const enableSource = config.LiveConfig.find((l) => l.key === key);
if (!enableSource) {
return NextResponse.json({ error: '直播源不存在' }, { status: 404 });
}
enableSource.disabled = false;
break;
case 'disable':
// 禁用直播源
const disableSource = config.LiveConfig.find((l) => l.key === key);
if (!disableSource) {
return NextResponse.json({ error: '直播源不存在' }, { status: 404 });
}
disableSource.disabled = true;
break;
case 'sort':
// 排序直播源
const { order } = body;
if (!Array.isArray(order)) {
return NextResponse.json({ error: '排序数据格式错误' }, { status: 400 });
}
// 创建新的排序后的数组
const sortedLiveConfig: typeof config.LiveConfig = [];
order.forEach((key) => {
const source = config.LiveConfig?.find((l) => l.key === key);
if (source) {
sortedLiveConfig.push(source);
}
});
// 添加未在排序列表中的直播源(保持原有顺序)
config.LiveConfig.forEach((source) => {
if (!order.includes(source.key)) {
sortedLiveConfig.push(source);
}
});
config.LiveConfig = sortedLiveConfig;
break;
default:
return NextResponse.json({ error: '未知操作' }, { status: 400 });
}
// 保存配置
await db.saveAdminConfig(config);
return NextResponse.json({ success: true });
} catch (error) {
return NextResponse.json(
{ error: error instanceof Error ? error.message : '操作失败' },
{ status: 500 }
);
}
}