// YouTube poToken/visitorData 생성 후 Lavalink youtube-plugin 런타임 엔드포인트(POST /youtube)에 주입. // googlevideo 재생 403(스트림 거절) 우회용. 런타임 주입은 휘발성(Lavalink 재시작 시 소멸, 토큰도 수 시간 뒤 만료)이라 // 봇이 노드 ready 시점과 일정 주기로 이 스크립트를 자식 프로세스로 실행한다. // 독립 ESM 프로세스로 돌려 BotGuard 생성 시의 메모리 스파이크를 봇 본체와 격리한다. // // 필요 env: LAVALINK_HOST, LAVALINK_PORT, LAVALINK_PW import { Innertube } from "youtubei.js"; import { BG } from "bgutils-js"; import { JSDOM } from "jsdom"; const HOST = process.env.LAVALINK_HOST?.trim(); const PORT = process.env.LAVALINK_PORT?.trim(); const PW = process.env.LAVALINK_PW?.trim(); const REQUEST_KEY = "O43z0dpjhgX20SCx4KAo"; function log(msg) { process.stdout.write(`[potoken] ${msg}\n`); } if (!HOST || !PORT || !PW) { log("FAIL: LAVALINK_HOST/PORT/PW env 누락"); process.exit(1); } async function generate() { const innertube = await Innertube.create({ retrieve_player: false }); const visitorData = innertube.session.context.client.visitorData; if (!visitorData) throw new Error("visitorData 획득 실패"); const dom = new JSDOM("", { url: "https://www.youtube.com/" }); Object.assign(globalThis, { window: dom.window, document: dom.window.document, location: dom.window.location, origin: dom.window.origin, }); if (!Reflect.has(globalThis, "navigator")) Object.defineProperty(globalThis, "navigator", { value: dom.window.navigator }); const bgConfig = { fetch: (url, options) => fetch(url, options), globalObj: globalThis, identifier: visitorData, requestKey: REQUEST_KEY, }; const challenge = await BG.Challenge.create(bgConfig); if (!challenge) throw new Error("BotGuard challenge 생성 실패"); const js = challenge.interpreterJavascript.privateDoNotAccessOrElseSafeScriptWrappedValue; if (js) new Function(js)(); else throw new Error("BotGuard interpreter 로드 실패"); const pot = await BG.PoToken.generate({ program: challenge.program, globalName: challenge.globalName, bgConfig, }); if (!pot?.poToken) throw new Error("poToken 생성 실패"); return { visitorData, poToken: pot.poToken }; } async function inject({ visitorData, poToken }) { const res = await fetch(`http://${HOST}:${PORT}/youtube`, { method: "POST", headers: { Authorization: PW, "Content-Type": "application/json" }, body: JSON.stringify({ poToken, visitorData }), }); return res.status; } try { const pair = await generate(); const status = await inject(pair); if (status === 204) { log(`OK poToken 주입 완료 (visitorData ${pair.visitorData.length}자, poToken ${pair.poToken.length}자)`); process.exit(0); } log(`WARN 주입 응답 http ${status}`); process.exit(2); } catch (e) { log(`FAIL ${e && e.stack ? e.stack : e}`); process.exit(1); }