/** * ===================================================================== * 🔞 SinParty 大秀直播 - Cloudflare Worker (T4 API + M3U多重分类完美聚合版) * ===================================================================== * * 👉 1. TVBox (T4) 配置: * { "key": "sinparty", "name": "🔞 SinParty", "type": 4, "api": "https://你的Worker域名.workers.dev/", "filterable": 1 } * * * 👉 2. IPTV / M3U 订阅配置: * https://你的Worker域名.workers.dev/live.m3u * ===================================================================== */ // ⚙️ 核心模式切换器:"filter" (使用原生隐藏筛选菜单) | "flat" (将所有筛选平铺为顶部大栏目) const CONFIG_MODE = "filter"; const HOST = "https://sinparty.com"; const API_HOST = "https://api.sinparty.com"; const UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"; const HEADERS = { "User-Agent": UA, "Accept": "application/json, text/plain, */*", "Referer": HOST + "/", "Origin": HOST }; // --- [模式A:原生标准分类矩阵] --- const NATIVE_CATEGORIES = [ {"type_id": "all", "type_name": "✨ 精选"}, {"type_id": "girls", "type_name": "👩 女生"}, {"type_id": "guys", "type_name": "👨 男生"}, {"type_id": "couples", "type_name": "👩‍❤️‍👨 情侣"}, {"type_id": "trans", "type_name": "🏳️‍⚧️ 变性人"} ]; const NATIVE_FILTERS = { "all": [{"key": "cat", "name": "排序", "value": [ {"n": "全部", "v": ""}, {"n": "热门推荐", "v": "trending"}, {"n": "近期新人", "v": "new"}, {"n": "私人节目", "v": "status_private"} ]}], "girls": [{"key": "cat", "name": "标签", "value": [ {"n": "全部", "v": ""}, {"n": "亚洲", "v": "asian"}, {"n": "成熟", "v": "mature"}, {"n": "大胸", "v": "big_boobs"}, {"n": "视角", "v": "pov"} ]}], "guys": [{"key": "cat", "name": "标签", "value": [ {"n": "全部", "v": ""}, {"n": "肌肉", "v": "muscular"}, {"n": "亚洲男", "v": "asian"}, {"n": "熊系", "v": "bear"}, {"n": "少年", "v": "twink"} ]}], "trans": [{"key": "cat", "name": "标签", "value": [ {"n": "全部", "v": ""}, {"n": "成熟", "v": "mature"}, {"n": "青少年", "v": "teen"} ]}] }; // --- [模式B:扁平化平铺分类栏目] --- const FLATTENED_CATEGORIES = [ {"type_id": "all", "type_name": "✨ 精选推荐"}, {"type_id": "all_trending", "type_name": "🔥 精选-热门"}, {"type_id": "all_new", "type_name": "🌱 精选-新人"}, {"type_id": "all_status_private", "type_name": "🔒 精选-私播"}, {"type_id": "girls", "type_name": "👩 女生全部"}, {"type_id": "girls_asian", "type_name": "🌏 女生-亚洲"}, {"type_id": "girls_mature", "type_name": "💃 女生-成熟"}, {"type_id": "girls_big_boobs", "type_name": "🍒 女生-大胸"}, {"type_id": "girls_pov", "type_name": "👁️ 女生-视角"}, {"type_id": "guys", "type_name": "👨 男生全部"}, {"type_id": "guys_muscular", "type_name": "💪 男生-肌肉"}, {"type_id": "guys_asian", "type_name": "👲 男生-亚洲"}, {"type_id": "guys_bear", "type_name": "🐻 男生-熊系"}, {"type_id": "guys_twink", "type_name": "🧑 男生-少年"}, {"type_id": "couples", "type_name": "👩‍❤️‍👨 情侣连播"}, {"type_id": "trans", "type_name": "🏳️‍⚧️ 变性全部"}, {"type_id": "trans_mature", "type_name": "👵 变性-成熟"}, {"type_id": "trans_teen", "type_name": "👧 变性-青少"} ]; export default { async fetch(request, env, ctx) { const url = new URL(request.url); const path = url.pathname; const params = url.searchParams; const m3uHeaders = { "Content-Type": "text/plain; charset=utf-8", "Access-Control-Allow-Origin": "*" }; const corsHeaders = { "Content-Type": "application/json; charset=utf-8", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, OPTIONS", "Access-Control-Allow-Headers": "*" }; if (request.method === "OPTIONS") return new Response(null, { headers: corsHeaders }); try { let action = params.get("ac") || path.replace("/", ""); let tid = params.get("tid") || params.get("t") || ""; let pg = params.get("pg") || params.get("page") || "1"; let wd = params.get("wd") || params.get("key") || ""; // 路由 1:M3U 订阅列表静态聚合生成 (已完美重构分类引擎) if (action === "live.m3u" || action === "m3u" || path.endsWith(".m3u")) { return new Response(await handleMultiCateM3U(url.origin), { headers: m3uHeaders }); } if (action === "proxy_play") return await handleProxyPlay(params); // 路由 2:T4 视频分类页数据拉取 if (tid) { return new Response(JSON.stringify(await handleCategory(tid, pg, params)), { headers: corsHeaders }); } // 路由 3:搜索过滤拦截 if (action === "search" || wd) { return new Response(JSON.stringify({ "list": [] }), { headers: corsHeaders }); } // 路由 4:详情页懒加载解析 if (action === "detail" || params.get("ids")) { let ids = params.get("ids") || ""; return new Response(JSON.stringify(await handleDetail(ids)), { headers: corsHeaders }); } // 路由 5:直接播放劫持 if (action === "play") { let id = params.get("id") || ""; return new Response(JSON.stringify(handlePlay(id)), { headers: corsHeaders }); } // 首页根路由:下发全量 Class 架构与 Filters 数据矩阵 return new Response(JSON.stringify(await handleHome()), { headers: corsHeaders }); } catch (err) { return new Response(JSON.stringify({ "list": [], "msg": err.message }), { status: 500, headers: corsHeaders }); } } }; // ========================================================= // 核心逻辑层:完美对齐 T4 规范与多栏目输出 // ========================================================= async function handleHome() { let defaultVideos = []; try { let resData = await fetchApiItems("all", "1", ""); defaultVideos = resData.list || []; } catch (e) {} if (CONFIG_MODE === "flat") { let classes = FLATTENED_CATEGORIES.map(cat => ({ "type_id": cat.type_id, "type_name": cat.type_name })); return { "class": classes, "list": defaultVideos }; } else { let classes = NATIVE_CATEGORIES.map(cat => ({ "type_id": cat.type_id, "type_name": cat.type_name })); return { "class": classes, "list": defaultVideos, "filters": NATIVE_FILTERS }; } } async function handleCategory(tid, pg, params) { let result = { "list": [], "page": parseInt(pg), "pagecount": 1, "limit": 40, "total": 0 }; try { let filterCat = getFilterCat(params); let resData = await fetchApiItems(tid, pg, filterCat); result.list = resData.list || []; result.total = resData.total || resData.list.length; result.pagecount = result.total > 0 ? Math.ceil(result.total / 40) : 1; } catch (e) {} return result; } function getFilterCat(params) { let f = params.get("f") || params.get("ext") || params.get("filter") || ""; if (!f) return ""; try { if (f.includes("%")) f = decodeURIComponent(f); } catch(e){} if (!f.startsWith("{") && /^[a-zA-Z0-9+/=]+$/.test(f)) { try { f = atob(f); } catch(e) {} } try { let obj = JSON.parse(f); if (obj && obj.cat) return obj.cat; } catch(e) { let match = f.match(/"cat"\s*:\s*"([^"]+)"/); if (match) return match[1]; } return ""; } // ========================================================= // 🔥 核心重构:多分类自适应 M3U 生成引擎 // ========================================================= async function handleMultiCateM3U(workerOrigin) { let m3uResult = "#EXTM3U x-tvg-url=\"\"\n"; // 对应标准分类以及在播放器中显示的文件夹分组名称 const m3uGroups = [ { id: "all", groupName: "🔞 SinParty - 精选推荐" }, { id: "girls", groupName: "🔞 SinParty - 女生直播" }, { id: "guys", groupName: "🔞 SinParty - 男生帅哥" }, { id: "couples", groupName: "🔞 SinParty - 情侣连播" }, { id: "trans", groupName: "🔞 SinParty - 变性视角" } ]; try { // 全量分类并发请求(Cloudflare 完美支持并发) const promises = m3uGroups.map(g => fetchApiItems(g.id, "1", "")); const results = await Promise.all(promises); for (let i = 0; i < m3uGroups.length; i++) { const groupInfo = m3uGroups[i]; const resData = results[i]; if (!resData || !resData.list || resData.list.length === 0) continue; let seenVidsInGroup = new Set(); // 单个分组内部去重 for (let room of resData.list) { if (!room.vod_id || seenVidsInGroup.has(room.vod_id)) continue; seenVidsInGroup.add(room.vod_id); // 注入规范的 group-title m3uResult += `#EXTINF:-1 tvg-logo="${room.vod_pic}" group-title="${groupInfo.groupName}",${room.vod_name} (${room.vod_remarks})\n${workerOrigin}/?ac=proxy_play&vid=${encodeURIComponent(room.vod_id)}\n`; } } } catch (e) { m3uResult += `#EXTINF:-1 group-title="错误提示",聚合数据解析异常: ${e.message}\nhttp://0.0.0.0\n`; } if (m3uResult === "#EXTM3U x-tvg-url=\"\"\n") { m3uResult += `#EXTINF:-1 group-title="错误提示",未捕获到任何在线直播源\nhttp://0.0.0.0\n`; } return m3uResult; } // ========================================================= // 网络抓取与流媒体底层交互 // ========================================================= async function fetchApiItems(tid, pg, extCat) { let url = `${API_HOST}/v2/web/live-cams/web-rtc`; let qParams = new URLSearchParams({ "page": String(pg), "per_page": "40", "od": "desc" }); let main_tid = tid; let sub_val = extCat || ""; if (tid.includes("_")) { let parts = tid.split("_"); main_tid = parts[0]; sub_val = parts.slice(1).join("_"); } if (main_tid === "couples") { qParams.append("category[]", "couples"); } else if (main_tid === "trans") { qParams.append("category[]", "trans"); } else if (main_tid === "guys") { qParams.append("gender[]", "m"); } else { qParams.append("gender[]", "f"); qParams.append("so", "has_straight"); } if (sub_val) { if (sub_val.startsWith("status_")) { qParams.append("status[]", sub_val.replace("status_", "")); } else { qParams.append("trending[]", sub_val); } } let res = await fetch(`${url}?${qParams.toString()}`, { method: "GET", headers: HEADERS }); let root = await res.json(); let data = root.data || {}; let items = data.items || []; let vlist = []; for (let item of items) { let vid = ""; let pic = ""; let name = ""; if (item.creator_user_hash) { vid = `native|${item.creator_user_hash}`; pic = item.thumbnail_url || (item.poster_set_template || "").replace("{size}", "360"); name = item.title; } else if (item.Nickname) { vid = `external|${item.Nickname}`; pic = item.Thumbnail || item.Snapshot; name = item.Nickname; } else { continue; } vlist.push({ "vod_id": vid, "vod_name": name || "Live Cam", "vod_pic": pic || "", "vod_remarks": `🔥 HOT: ${item.viewers || 0}` }); } return { "list": vlist, "total": data.total || 0 }; } async function requestStreamUrl(vid) { let parts = vid.split('|'); if (parts.length < 2) return null; let mode = parts[0]; let key = parts[1]; try { if (mode === "external") { let extApi = `https://manifest-server.naiadsystems.com/live/s:${key}.json?vdc=true`; let smHeaders = { "User-Agent": UA, "Referer": "https://sinpartylive.com/", "sitedomain": "sinpartylive.com", "tenantid": "SM" }; let res = await fetch(extApi, { method: "GET", headers: smHeaders }); if (res.status === 200) { let json = await res.json(); let hls = json.formats?.["mp4-hls"] || {}; let pUrl = hls.manifest || (hls.encodings?.length ? hls.encodings[hls.encodings.length - 1].location : null); if (pUrl) return pUrl.replace(/&/g, "&"); } } else { let apiUrl = `${API_HOST}/v2/web/live-cams/web-rtc/${key}`; let res = await fetch(apiUrl, { method: "GET", headers: HEADERS }); if (res.status === 200) { let json = await res.json(); return json.data?.playback_url || null; } } } catch (e) {} return null; } async function handleDetail(ids) { if (!ids) return { "list": [] }; let vid = Array.isArray(ids) ? ids[0] : String(ids); let vod = { "vod_id": vid, "vod_name": "大秀极速高清直播间", "vod_pic": "", "vod_remarks": "📡 LIVE", "vod_content": "源由TG频道@stymei全分类深度解密无阻塞重构版。原生秒开流硬解码支持。", "vod_play_from": "✈️ 頻道@stymei", "vod_play_url": "" }; try { let streamUrl = await requestStreamUrl(vid); vod.vod_play_url = streamUrl ? `超级原画极速流$${streamUrl}` : "主播下播或换切流$http://0.0.0.0/off.m3u8"; } catch (e) { vod.vod_play_url = "网络流解析异常$http://0.0.0.0/error.m3u8"; } return { "list": [vod] }; } function handlePlay(id) { return { "parse": 0, "url": id, "header": HEADERS }; } async function handleProxyPlay(params) { let vid = params.get("vid"); if (!vid) return new Response("Missing VID", { status: 400 }); let streamUrl = await requestStreamUrl(vid); return streamUrl ? Response.redirect(streamUrl, 302) : new Response("Offline", { status: 404 }); }