港澳台直播 v8.3.0 源码快照(含全部历史功能)

This commit is contained in:
Hermes Agent committed 2026-10-08 12:56:16 +08:00
commit 1d2449ec44
72 files changed
+7365

No files matched your search

+93
View File
@@ -0,0 +1,93 @@
import https from 'https';
import http from 'http';
import { URL } from 'url';
const UA = 'Mozilla/5.0 (Linux; Android 12; SM-S901B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36';
function fetch(url) {
return new Promise((res, rej) => {
const h = url.startsWith('https') ? https : http;
const req = h.get(url, { headers: { 'User-Agent': UA } }, r => {
let d = '';
r.on('data', c => d += c);
r.on('end', () => res(d));
});
req.on('error', rej);
req.setTimeout(15000, () => { req.destroy(); rej(new Error('timeout')); });
});
}
// 动态eval加密变量,再按解密函数链跑(bbot: page uses randomly-named vars each request)
function decodePage(html) {
// 提取 var xx=""; var yy="...拼接..."; var qq=""; var ww="..."; ouyit/token块
const blocks = [];
const re = /<script>([\s\S]*?)<\/script>/g;
let m;
while ((m = re.exec(html))) {
const body = m[1];
if (body.includes('.split("")') && body.includes('var')) blocks.push(body);
if (body.includes('function ') && body.includes('keyStr') && body.includes('charCodeAt')) blocks.push(body);
if (body.includes('wngbe') || /function \w+\(dek\)/.test(body)) blocks.push(body);
}
const context = {};
const sandbox = new Function(blocks.join('\n') + `
// 返回解密函数名 + 猜出变量名
const fnNames = [];
// 找形如 function XXXX(dek){...} 就把名字报出来
const decls = [...arguments.callee ? '' : ''][0];
return { xefgv: typeof xefgv !== 'undefined' ? xefgv : null, ouyit: typeof ouyit !== 'undefined' ? ouyit : null, qrtkk: typeof qrtkk !== 'undefined' ? qrtkk : null };
`);
return null;
}
// 更简单:直接在Node vm里跑整段,把全部函数和变量都留在沙箱里
import vm from 'vm';
async function processPage(url) {
const html = await fetch(url);
const ctx = { window: {}, atob: null };
const sandbox = vm.createContext({ window: {}, document: { getElementById: () => null, querySelectorAll: () => [], querySelector: () => null, addEventListener(){}, createElement() { return { setAttribute(){}, style:{} }; }, body: { appendChild(){} } }, location: { href: url }, navigator: { userAgent: UA }, setInterval: () => 0, clearInterval(){}, setTimeout: () => 0, clearTimeout(){}, fetch: () => {}, console, addEventListener(){}, $: () => ({ hide(){}, show(){}, selectmenu(){}, length:0 }), Hls: { isSupported: () => false, Events: {} }, mpegts: { isSupported: () => false, Events: {} }, devicePixelRatio: 2, innerWidth: 1280, innerHeight: 720, atob: (s) => Buffer.from(s, 'base64').toString('binary') });
// 只跑不依赖DOM交互的脚本:解密脚本+变量, crib main script(避免动画循环定时器问题——用vm.runInContext就够了,主脚本里$()等没被调用)
const scripts = [...html.matchAll(/<script>([\s\S]*?)<\/script>/g)].map(m => m[1]);
for (const s of scripts) {
try { vm.runInContext(s, sandbox, { timeout: 3000 }); } catch (e) { /* DOM依赖的忽略 */ }
}
// 抓options
const opts = [...html.matchAll(/<option value="([^"]+)">([^<]+)<\/option>/g)];
// 找到call_in_main
const main = scripts.find(s => s.includes('startPlayer') && s.includes('(uri)'));
const decName = (main && (main.match(/const \w+ = (\w+)\(uri\)/) || [])[1]) || (main && (main.match(/const puri ?= ?(\w+)\(/) || [])[1]);
if (!decName) return { url, error: 'no dec fn' };
const results = opts.map(o => {
try { return { line: o[2], url: sandbox[decName](o[1]) }; }
catch (e) { return { line: o[2], error: String(e).slice(0,100) }; }
});
return { url, title: (html.match(/<title>([^<]*)</) || [])[1], results };
}
// 频道列表:从各分类页抓全部链接(data-ajax="false")
const tids = ['tv', 'ty', 'ys', 'ws', 'gt'];
const seen = new Map(); // link -> name(按tid顺序去重)
for (const tid of tids) {
try {
const page = await fetch('https://m.iptv807.com/?tid=' + tid);
for (const m of page.matchAll(/<a href="(\?act=play[^"]*)"[^>]*>([^<]+)<\/a>/g)) {
const link = 'https://m.iptv807.com/' + m[1];
if (!seen.has(link)) seen.set(link, m[2]);
}
} catch (e) { console.error('cat fail', tid, e.message); }
}
const channels = [...seen.entries()].map(([link, name]) => ({ link, name }));
// 去掉首页广告位那条假频道
const real = channels.filter(c => c.link.includes('&id='));
console.error('channels found:', channels.length);
const out = [];
for (const ch of real) {
try {
const r = await processPage(ch.link);
out.push({ name: ch.name, url: r.url, lines: r.results?.map(l => l.url) });
console.error('OK', ch.name, r.results?.[0]?.url || r.error);
} catch (e) {
out.push({ name: ch.name, error: e.message });
console.error('ERR', ch.name, e.message);
}
}
// 输出 JSON 到stdout
console.log(JSON.stringify(out, null, 2));