/** * Refresh Viaplay session via persistentLogin, then probe byguid. * Writes results to stdout as JSON lines + summary. */ const path = require("path"); const fs = require("fs"); const { chromium } = require( path.resolve(__dirname, "../../token-tray/node_modules/playwright") ); const UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.0.0 Safari/537.36 Edg/153.0.0.0"; const DEVICE_KEY = "pcdash-nl"; const PROFILES_ROOT = path.join( process.env.APPDATA || "", "@media-cluster/token-tray/browser-profiles/viaplay" ); const GUID = (process.argv.find((a) => a.startsWith("--guid=")) || "").slice(7) || "vp-sports-clip-d9a3b8a7-0804-44d"; async function refreshFromPersistentLogin(persistentLoginValue) { const url = "https://login.viaplay.com/api/persistentLogin/v1?deviceKey=pcdash-nl&returnurl=https%3A%2F%2Fcontent.viaplay.com%2Fpcdash-nl"; const res = await fetch(url, { headers: { Accept: "application/json", "User-Agent": UA, Origin: "https://viaplay.com", Referer: "https://viaplay.com/", Cookie: `persistentLogin=${persistentLoginValue}`, }, }); const jar = {}; for (const sc of res.headers.getSetCookie?.() || []) { const m = sc.match(/^([^=]+)=([^;]*)/); if (m) jar[m[1]] = m[2]; } const body = await res.json().catch(() => ({})); return { status: res.status, success: !!body.success, session: jar.session ? decodeURIComponent(jar.session) : null, accessToken: jar.accessToken || body.userData?.accessToken || null, username: body.userData?.username || null, raw: body, }; } function buildHarUrl(guid, deviceId, profileId) { const u = new URL("https://play.viaplay.com/api/stream/byguid"); u.searchParams.set("deviceId", deviceId); u.searchParams.set("deviceName", "web"); u.searchParams.set("deviceType", "pc"); u.searchParams.set("userAgent", UA); u.searchParams.set("deviceKey", DEVICE_KEY); u.searchParams.set("cse", "true"); u.searchParams.set("guid", guid); u.searchParams.set( "producturl", `https://content.viaplay.com/${DEVICE_KEY}/sport/${guid}?partial=true` ); u.searchParams.set( "returnurl", `https://content.viaplay.com/${DEVICE_KEY}/sport` ); u.searchParams.set("sectionPath", "/sport"); u.searchParams.set( "templatedproducturl", `https://content.viaplay.com/{deviceKey}/sport/${guid}?partial=true` ); u.searchParams.set( "contextualv2url", `https://content.viaplay.com/${DEVICE_KEY}/sport/${guid}?contextualV2=true&productType=clip` ); u.searchParams.set("defaultAvailabilityContext", "svod"); u.searchParams.set("win10edge", "true"); if (profileId) u.searchParams.set("profileId", profileId); return u.toString(); } async function play(session, accessToken, profileId, guid, label) { const headers = { Accept: "*/*", Origin: "https://viaplay.com", Referer: "https://viaplay.com/", "User-Agent": UA, Cookie: [ `session=${session}`, `accessToken=${accessToken}`, profileId ? `viaplay_profileId=${profileId}` : "", ] .filter(Boolean) .join("; "), Authorization: `VIAPLAY-AT ${accessToken}`, }; const url = buildHarUrl(guid, "probe-device-1", profileId); const res = await fetch(url, { headers }); const body = await res.json().catch(() => ({})); const title = body.product?.content?.title || null; const media = body._links?.["viaplay:media"]?.href || null; const respGuid = body.product?.system?.guid || null; const promo = /don'?t\s*go|viaplay\s*presents|documentary/i.test( String(title || "") ); const out = { label, status: res.status, title, respGuid, productType: body.product?.productType || null, durationMs: body.duration ?? null, media, promo, message: body.message || body.userMessage || null, ok: res.status === 200 && !!media && !promo && respGuid === guid, }; console.log(JSON.stringify(out)); return out; } async function main() { const dirs = fs .readdirSync(PROFILES_ROOT) .map((name) => ({ name, full: path.join(PROFILES_ROOT, name), mtime: fs.statSync(path.join(PROFILES_ROOT, name)).mtimeMs, })) .sort((a, b) => b.mtime - a.mtime); let best = null; for (const d of dirs.slice(0, 5)) { let ctx; try { ctx = await chromium.launchPersistentContext(d.full, { headless: true, channel: "msedge", viewport: { width: 800, height: 600 }, }); const cookies = await ctx.cookies(); const pl = cookies.find((c) => c.name === "persistentLogin"); const profileId = cookies.find((c) => c.name === "viaplay_profileId")?.value || null; if (!pl?.value) { console.log(JSON.stringify({ skip: d.name, reason: "no persistentLogin" })); continue; } const refreshed = await refreshFromPersistentLogin(pl.value); console.log( JSON.stringify({ profile: d.name, refreshStatus: refreshed.status, refreshOk: refreshed.success, username: refreshed.username, hasSession: !!refreshed.session, hasAT: !!refreshed.accessToken, }) ); if (!refreshed.success || !refreshed.session || !refreshed.accessToken) { continue; } const result = await play( refreshed.session, refreshed.accessToken, profileId, GUID, `profile:${d.name}` ); if (result.ok) { best = result; // MPD check const mpd = await fetch(result.media, { headers: { "User-Agent": UA, Referer: "https://viaplay.com/" }, }); const xml = await mpd.text(); console.log( JSON.stringify({ mpdStatus: mpd.status, mpdBytes: xml.length, isMpd: /]/i.test(xml), protections: (xml.match(/ContentProtection/g) || []).length, }) ); break; } } catch (err) { console.log( JSON.stringify({ profile: d.name, error: String(err.message || err) }) ); } finally { if (ctx) await ctx.close().catch(() => {}); } } if (!best) { console.log(JSON.stringify({ summary: "FAIL", guid: GUID })); process.exitCode = 2; } else { console.log( JSON.stringify({ summary: "OK", guid: GUID, title: best.title, media: best.media, }) ); } } main().catch((e) => { console.error(e); process.exit(1); });