OverviewFilesCommitsIssuesDocsReleasesPeers

stremio-vsembed / node_helper / rcp_diag.mjs

73 lines · javascript ref:
// rcp_diag.mjs — characterize what the rcp page does NOW after #pl_but.
// Usage: node rcp_diag.mjs <embedUrl>
import { chromium } from 'playwright';
const log = (...a) => console.error('[diag]', ...a);
const UA = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
const AD = /devilyquondam|dtscout|histats|\/cuid\/|popunder|propeller|adsco|doubleclick|onclick\.|popads|\/rum\?/i;
const EMBED = process.argv[2];
if (!EMBED) { console.error('usage: node rcp_diag.mjs <embedUrl>'); process.exit(1); }

const browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] });
const ctx = await browser.newContext({ userAgent: UA });
await ctx.route('**/*', r => AD.test(r.request().url()) ? r.abort() : r.continue());

// --- step 1: capture rcp from embed
const p1 = await ctx.newPage();
let rcp = null;
p1.on('framenavigated', f => { const u = f.url(); if (/\/rcp\//i.test(u) && !rcp) rcp = u; });
await p1.setExtraHTTPHeaders({ Referer: 'https://netoda.tech/' });
await p1.goto(EMBED, { waitUntil: 'domcontentloaded', timeout: 30000 }).catch(e => log('embed warn', e.message));
for (let i = 0; i < 16 && !rcp; i++) await p1.waitForTimeout(1000);
await p1.close().catch(() => {});
log('rcp:', rcp);
if (!rcp) { await browser.close(); console.log(JSON.stringify({ error: 'no rcp' })); process.exit(3); }

// --- step 2: drive the rcp page, log EVERYTHING
const p = await ctx.newPage();
const reqs = [];
const resps = [];
p.on('request', r => reqs.push({ u: r.url(), m: r.method(), rt: r.resourceType() }));
p.on('response', async r => { resps.push({ u: r.url(), s: r.status() }); });
p.on('console', m => log('console>', m.type(), m.text().slice(0, 200)));
p.on('pageerror', e => log('pageerror>', String(e).slice(0, 200)));
await p.setExtraHTTPHeaders({ Referer: EMBED });
await p.goto(rcp, { waitUntil: 'domcontentloaded', timeout: 30000 }).catch(e => log('rcp warn', e.message));
await p.waitForTimeout(2500);

const before = await p.evaluate(() => ({
  title: document.title,
  bodyLen: (document.body && document.body.innerHTML.length) || 0,
  text: ((document.body && document.body.innerText) || '').replace(/\s+/g, ' ').slice(0, 300),
  hasPlBut: !!document.querySelector('#pl_but'),
  iframes: [...document.querySelectorAll('iframe')].map(f => f.src).slice(0, 5),
  scriptSrcs: [...document.querySelectorAll('script[src]')].map(s => s.src).slice(0, 10),
})).catch(e => ({ evalErr: String(e) }));
log('BEFORE CLICK:', JSON.stringify(before, null, 1));

const clicked = await p.evaluate(() => {
  const el = document.querySelector('#pl_but') || document.querySelector('.play') || document.querySelector('[onclick]');
  if (el) { el.click(); return el.id || el.className || 'onclick'; }
  if (document.body) { document.body.click(); return 'body'; }
  return null;
}).catch(e => 'evalErr:' + e.message);
log('clicked:', clicked);

for (let i = 0; i < 8; i++) {
  await p.waitForTimeout(3000);
  const st = await p.evaluate(() => ({
    frames: [...document.querySelectorAll('iframe')].map(f => f.src).slice(0, 5),
    text: ((document.body && document.body.innerText) || '').replace(/\s+/g, ' ').slice(0, 160),
  })).catch(() => ({}));
  log(`t=${(i + 1) * 3}s frames=${JSON.stringify(st.frames)} text="${st.text}"`);
  log(`   subframes:`, p.frames().map(f => f.url()).filter(u => u && u !== 'about:blank').slice(0, 6).join(' | '));
}

await p.close().catch(() => {});
await browser.close().catch(() => {});
const interesting = resps.filter(r => /prorcp|m3u8|\.ts|playlist|master|\/pl\/|hls/i.test(r.u));
log('=== interesting responses ===');
for (const r of interesting) log(r.s, r.u.slice(0, 160));
log('=== all non-static responses ===');
for (const r of resps.filter(x => !/\.(css|png|jpg|jpeg|gif|svg|woff2?|ico)(\?|$)/i.test(x.u))) log(r.s, r.u.slice(0, 150));
console.log(JSON.stringify({ rcp, clicked, interesting }));