// Cloudflare Worker: IPTV Admin Panel & Xtream Proxy
const COOKIE_NAME = "iptv_session";
export default {
async fetch(request, env) {
const url = new URL(request.url);
const path = url.pathname;
// 1. PUBLIC API ENDPOINTS (Xtream Codes / Player Compatibility)
if (path.startsWith("/player_api.php")) return handleXtreamAPI(request, env);
if (path.startsWith("/get.php")) return handleM3UDownload(request, env);
if (path.startsWith("/xmltv.php")) return handleEPG(request, env);
if (path.startsWith("/proxy/")) return handleStreamProxy(request, env);
// 2. PUBLIC ADMIN AUTHS
if (path === "/login" && request.method === "POST") return handleLoginSubmit(request, env);
if (path === "/logout") return handleLogout();
// Session Verification for Protected Admin Spaces
const authorized = await verifySession(request, env);
if (!authorized) return loginPageResponse();
// Admin Actions (POST)
if (request.method === "POST") {
if (path === "/admin/update") return updateAdminCreds(request, env);
if (path === "/user/add") return addUser(request, env);
if (path === "/user/edit") return editUser(request, env);
if (path === "/user/delete") return deleteUser(request, env);
if (path === "/stream/add") return addStream(request, env);
if (path === "/stream/edit") return editStream(request, env);
if (path === "/stream/delete") return deleteStream(request, env);
if (path === "/stream/import") return massImportStreams(request, env);
}
// Admin UI Routing (GET)
if (path === "/admin/settings") return renderAdminSettingsPage(request, env);
return renderDashboard(request, env);
}
};
// --- AUTHENTICATION FUNCTIONS ---
async function verifySession(request, env) {
const cookie = request.headers.get("Cookie") || "";
if (!cookie.includes(COOKIE_NAME)) return false;
const match = cookie.match(new RegExp('(^| )' + COOKIE_NAME + '=([^;]+)'));
if (!match) return false;
try {
const [user, pass] = atob(match[2]).split(":");
const admin = await env.DB.prepare("SELECT * FROM admins WHERE username = ? AND password = ?").bind(user, pass).first();
return !!admin;
} catch (e) {
return false;
}
}
function loginPageResponse(error = "") {
return new Response(`
IPTV LoginIPTV Panel Access
TFMS IPTV Panel V3
${error ? `
${error}
` : ""}
`, { headers: { "Content-Type": "text/html" } });
}
async function handleLoginSubmit(request, env) {
const formData = await request.formData();
const user = formData.get("username");
const pass = formData.get("password");
const admin = await env.DB.prepare("SELECT * FROM admins WHERE username = ? AND password = ?").bind(user, pass).first();
if (admin) {
const token = btoa(`${user}:${pass}`);
return new Response("Redirecting...", {
status: 302,
headers: { "Location": "/", "Set-Cookie": `${COOKIE_NAME}=${token}; Path=/; HttpOnly; Max-Age=86400` }
});
}
return loginPageResponse("Invalid credentials.");
}
function handleLogout() {
return new Response("Redirecting...", {
status: 302,
headers: { "Location": "/", "Set-Cookie": `${COOKIE_NAME}=; Path=/; Max-Age=0` }
});
}
// --- MASTER PANEL MANAGEMENT SETTINGS ---
async function updateAdminCreds(request, env) {
const cookie = request.headers.get("Cookie") || "";
const match = cookie.match(new RegExp('(^| )' + COOKIE_NAME + '=([^;]+)'));
if (!match) return new Response("Session missing", { status: 401 });
const [oldUser] = atob(match[2]).split(":");
const f = await request.formData();
const newUsername = f.get("new_username");
const newPassword = f.get("new_password");
await env.DB.prepare("UPDATE admins SET username = ?, password = ? WHERE username = ?")
.bind(newUsername, newPassword, oldUser).run();
return new Response("Password changed. Re-authenticating...", {
status: 302,
headers: { "Location": "/", "Set-Cookie": `${COOKIE_NAME}=; Path=/; Max-Age=0` }
});
}
// --- ADMIN CONTROL ACTIONS ---
async function addUser(request, env) {
const f = await request.formData();
await env.DB.prepare("INSERT INTO users (username, password, max_connections, exp_date) VALUES (?, ?, ?, ?)")
.bind(f.get("username"), f.get("password"), parseInt(f.get("max_connections")), f.get("exp_date")).run();
return new Response(null, { status: 302, headers: { "Location": "/" } });
}
async function editUser(request, env) {
const f = await request.formData();
await env.DB.prepare("UPDATE users SET username = ?, password = ?, max_connections = ?, exp_date = ?, status = ? WHERE id = ?")
.bind(f.get("username"), f.get("password"), parseInt(f.get("max_connections")), f.get("exp_date"), f.get("status"), f.get("id")).run();
return new Response(null, { status: 302, headers: { "Location": "/" } });
}
async function deleteUser(request, env) {
const f = await request.formData();
await env.DB.prepare("DELETE FROM users WHERE id = ?").bind(f.get("id")).run();
return new Response(null, { status: 302, headers: { "Location": "/" } });
}
async function addStream(request, env) {
const f = await request.formData();
await env.DB.prepare("INSERT INTO streams (name, url, category, type) VALUES (?, ?, ?, ?)")
.bind(f.get("name"), f.get("url"), f.get("category"), f.get("type")).run();
return new Response(null, { status: 302, headers: { "Location": "/" } });
}
async function editStream(request, env) {
const f = await request.formData();
await env.DB.prepare("UPDATE streams SET name = ?, url = ?, category = ?, type = ? WHERE id = ?")
.bind(f.get("name"), f.get("url"), f.get("category"), f.get("type"), f.get("id")).run();
return new Response(null, { status: 302, headers: { "Location": "/" } });
}
async function deleteStream(request, env) {
const f = await request.formData();
await env.DB.prepare("DELETE FROM streams WHERE id = ?").bind(f.get("id")).run();
return new Response(null, { status: 302, headers: { "Location": "/" } });
}
async function massImportStreams(request, env) {
const f = await request.formData();
const text = f.get("m3u_data") || "";
const lines = text.split("\n");
let currentName = "";
let currentCat = "Imported";
for (let line of lines) {
line = line.trim();
if (line.startsWith("#EXTINF:")) {
const nameMatch = line.match(/,(.*)$/);
if (nameMatch) currentName = nameMatch[1].trim();
const catMatch = line.match(/group-title="([^"]+)"/);
if (catMatch) currentCat = catMatch[1].trim();
} else if (line.startsWith("http")) {
if (!currentName) currentName = "Untitled Stream";
await env.DB.prepare("INSERT INTO streams (name, url, category, type) VALUES (?, ?, ?, 'live')")
.bind(currentName, line, currentCat).run();
currentName = "";
}
}
return new Response(null, { status: 302, headers: { "Location": "/" } });
}
// --- SHARED NAVIGATION HEADER FRAGMENT ---
function getNavHeaderHTML(activePath) {
return `
`;
}
// --- MAIN DATA DASHBOARD VIEW ---
async function renderDashboard(request, env) {
const users = (await env.DB.prepare("SELECT * FROM users").all()).results;
const streams = (await env.DB.prepare("SELECT * FROM streams").all()).results;
return new Response(`
IPTV Core Engine Dashboard
${getNavHeaderHTML("/")}
Total Registered Lines
${users.length}
Active Managed Streams
${streams.length}
User Lines Management
| Line Configuration Details (Editable Fields) | Status | Playlist | Actions |
${users.map(u => `
|
|
|
Get M3U |
|
`).join("")}
`, { headers: { "Content-Type": "text/html" } });
}
// --- DEDICATED SEPARATE ADMIN SETTINGS PAGE VIEW ---
async function renderAdminSettingsPage(request, env) {
return new Response(`
Admin Panel Settings
${getNavHeaderHTML("/admin/settings")}
`, { headers: { "Content-Type": "text/html" } });
}
// --- PLAYLIST DOWNLOAD GENERATOR (.M3U) ---
async function handleM3UDownload(request, env) {
const url = new URL(request.url);
let auth = url.searchParams.get("auth");
if (!auth) return new Response("Missing authorization token", { status: 401 });
const [username, password] = atob(auth).split(":");
const user = await env.DB.prepare("SELECT * FROM users WHERE username = ? AND password = ? AND status = 'active'").bind(username, password).first();
if (!user) return new Response("Unauthorized Line", { status: 403 });
const streams = (await env.DB.prepare("SELECT * FROM streams").all()).results;
let playlist = "#EXTM3U\n";
const origin = url.origin;
for (const s of streams) {
playlist += `#EXTINF:-1 tvg-id="${s.id}" group-title="${s.category}",${s.name}\n`;
playlist += `${origin}/proxy/${s.id}?u=${username}&p=${password}\n`;
}
return new Response(playlist, { headers: { "Content-Type": "application/x-mpegurl", "Content-Disposition": "attachment; filename=playlist.m3u" } });
}
// --- STREAM PROXY ENGINE (Strict 20-Second Connection Lifespans) ---
async function handleStreamProxy(request, env) {
const url = new URL(request.url);
const pathParts = url.pathname.split("/proxy/");
const streamId = pathParts[1];
const username = url.searchParams.get("u");
const password = url.searchParams.get("p");
const clientIp = request.headers.get("CF-Connecting-IP") || "0.0.0.0";
const now = Math.floor(Date.now() / 1000);
const user = await env.DB.prepare("SELECT * FROM users WHERE username = ? AND password = ? AND status = 'active'").bind(username, password).first();
if (!user) return new Response("Access Forbidden: Invalid or Inactive Account", { status: 403 });
if (user.exp_date && new Date(user.exp_date) < new Date()) {
return new Response("Access Forbidden: Line Playlist Expired", { status: 403 });
}
// Strictly delete any session data link heartbeat records older than 20 seconds
await env.DB.prepare("DELETE FROM connections WHERE last_active < ?").bind(now - 20).run();
await env.DB.prepare("INSERT OR REPLACE INTO connections (username, device_ip, last_active) VALUES (?, ?, ?)")
.bind(username, clientIp, now).run();
const activeCount = await env.DB.prepare("SELECT COUNT(DISTINCT device_ip) as count FROM connections WHERE username = ?")
.bind(username).first("count");
if (activeCount > user.max_connections) {
return new Response(`Max Concurrent Multiroom Connections (${user.max_connections}) Reached`, { status: 429 });
}
const stream = await env.DB.prepare("SELECT url FROM streams WHERE id = ?").bind(streamId).first();
if (!stream) return new Response("Target Stream Route ID Not Found", { status: 404 });
try {
const response = await fetch(stream.url);
const newHeaders = new Headers(response.headers);
newHeaders.set("Access-Control-Allow-Origin", "*");
return new Response(response.body, { status: response.status, headers: newHeaders });
} catch (err) {
return new Response("Stream Data Route Offline/Unreachable", { status: 502 });
}
}
// --- AUTOMATED EPG RENDERER (XMLTV) ---
async function handleEPG(request, env) {
const streams = (await env.DB.prepare("SELECT id, name FROM streams").all()).results;
let xml = ``;
for (const s of streams) {
xml += `${s.name}`;
xml += `Live BroadcastStreamed content from IPTV Matrix.`;
}
xml += ``;
return new Response(xml, { headers: { "Content-Type": "application/xml" } });
}
// --- XTREAM CODES API COMPATIBILITY ENGINE ---
async function handleXtreamAPI(request, env) {
const { searchParams } = new URL(request.url);
const user = searchParams.get("user");
const pass = searchParams.get("password");
const action = searchParams.get("action");
const validUser = await env.DB.prepare("SELECT * FROM users WHERE username = ? AND password = ? AND status = 'active'").bind(user, pass).first();
if (!validUser) return new Response(JSON.stringify({ user_info: { auth: 0 } }), { headers: { "Content-Type": "application/json" } });
const baseResponse = {
user_info: {
auth: 1,
status: "Active",
exp_date: validUser.exp_date ? Math.floor(new Date(validUser.exp_date).getTime() / 1000).toString() : "null",
max_connections: validUser.max_connections.toString(),
active_cons: "0"
},
server_info: { url: new URL(request.url).hostname, port: "80", https_port: "443" }
};
if (!action) return new Response(JSON.stringify(baseResponse), { headers: { "Content-Type": "application/json" } });
if (action === "get_live_categories") {
const categories = (await env.DB.prepare("SELECT DISTINCT category FROM streams").all()).results;
return new Response(JSON.stringify(categories.map((c, i) => ({ category_id: (i + 1).toString(), category_name: c.category, parent_id: 0 }))), { headers: { "Content-Type": "application/json" } });
}
if (action === "get_live_streams") {
const streams = (await env.DB.prepare("SELECT * FROM streams WHERE type='live'").all()).results;
const origin = new URL(request.url).origin;
return new Response(JSON.stringify(streams.map(s => ({
num: s.id, name: s.name, stream_id: s.id, stream_icon: "", epg_channel_id: s.id.toString(), category_id: "1",
direct_source: `${origin}/proxy/${s.id}?u=${user}&p=${pass}`
}))), { headers: { "Content-Type": "application/json" } });
}
return new Response(JSON.stringify([]), { headers: { "Content-Type": "application/json" } });
}