feat: Implement IPTV functionality with channel management and player, and add admin account configuration generation.

This commit is contained in:
kuekhaoyang
2026-02-17 16:37:53 +08:00
parent 91280f250f
commit 5d139114b5
30 changed files with 1362 additions and 50 deletions
+58
View File
@@ -0,0 +1,58 @@
/**
* Accounts API Route
* Returns account list (names + roles, no passwords) for admin visibility
*/
import { NextResponse } from 'next/server';
export const runtime = 'edge';
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || '';
const ACCESS_PASSWORD = process.env.ACCESS_PASSWORD || '';
const ACCOUNTS = process.env.ACCOUNTS || '';
const effectiveAdminPassword = ADMIN_PASSWORD || ACCESS_PASSWORD;
interface AccountInfo {
name: string;
role: 'admin' | 'viewer';
}
function getAccountList(): AccountInfo[] {
const accounts: AccountInfo[] = [];
// Add admin from ADMIN_PASSWORD
if (effectiveAdminPassword) {
accounts.push({ name: '管理员', role: 'admin' });
}
// Add accounts from ACCOUNTS env var
if (ACCOUNTS) {
ACCOUNTS.split(',')
.map(entry => entry.trim())
.filter(entry => entry.length > 0)
.forEach(entry => {
const parts = entry.split(':');
if (parts.length >= 2) {
const name = parts[1].trim();
const role = parts[2]?.trim() === 'admin' ? 'admin' : 'viewer';
if (name) {
accounts.push({ name, role });
}
}
});
}
return accounts;
}
export async function GET() {
const accounts = getAccountList();
return NextResponse.json({
accounts,
hasAdminPassword: !!effectiveAdminPassword,
hasAccounts: !!ACCOUNTS,
totalCount: accounts.length,
});
}
+47
View File
@@ -0,0 +1,47 @@
/**
* IPTV Proxy API Route
* Fetches M3U playlist files to avoid CORS issues
*/
import { NextRequest, NextResponse } from 'next/server';
export const runtime = 'edge';
export async function GET(request: NextRequest) {
const url = request.nextUrl.searchParams.get('url');
if (!url) {
return NextResponse.json({ error: 'Missing url parameter' }, { status: 400 });
}
try {
const response = await fetch(url, {
headers: {
'User-Agent': 'Mozilla/5.0 (compatible; KVideo/1.0)',
},
});
if (!response.ok) {
return NextResponse.json(
{ error: `Failed to fetch: ${response.status}` },
{ status: response.status }
);
}
const text = await response.text();
return new NextResponse(text, {
status: 200,
headers: {
'Content-Type': 'text/plain; charset=utf-8',
'Access-Control-Allow-Origin': '*',
'Cache-Control': 'public, max-age=300', // Cache for 5 minutes
},
});
} catch (e) {
return NextResponse.json(
{ error: 'Failed to fetch M3U playlist' },
{ status: 500 }
);
}
}