feat: 재생 403 우회용 poToken 자동 생성·주입 (youtube-plugin 런타임 엔드포인트)

googlevideo 재생 403(스트림 거절)은 Lavalink youtube-plugin에 유효한
poToken/visitorData가 없어서 발생. 봇이 노드 ready 시점과 2시간 주기로
poToken을 생성해 Lavalink의 POST /youtube 엔드포인트에 주입한다.
- scripts/potoken-refresh.mjs: youtubei.js+bgutils-js+jsdom로 생성 후 주입 (독립 ESM 프로세스)
- LavalinkManager: ready 이벤트 + 2h 인터벌로 자식 프로세스 실행
- deps: bgutils-js 3.2.0, jsdom, youtubei.js 추가 / Docker base node:22-alpine

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-08-28 23:25:38 +09:00
parent 5003c044c5
commit 1263ca4308
5 changed files with 630 additions and 3 deletions

View File

@@ -1,5 +1,7 @@
import { Connectors, LoadType, Shoukaku, Track } from "shoukaku";
import { Client } from "discord.js";
import { spawn } from "node:child_process";
import { join } from "node:path";
import { GuildPlayer } from "./GuildPlayer";
import { Config } from "../utils/Config";
import { Logger } from "../utils/Logger";
@@ -25,10 +27,43 @@ export class LavalinkManager {
this.shoukaku.on("ready", (name) => {
Logger.ready(`[LavalinkManager] 노드 [${name}] 연결 완료!`);
// 노드가 (재)연결될 때마다 poToken 주입 — Lavalink 재시작 후에도 자동 복구
this.refreshPoToken();
});
this.shoukaku.on("error", (name, err) => {
Logger.error(`[LavalinkManager] 노드 [${name}] 에러: ${String(err)}`);
});
// poToken은 수 시간 뒤 만료되므로 주기적으로 재생성/재주입 (2시간)
setInterval(() => this.refreshPoToken(), 2 * 60 * 60 * 1000).unref();
}
/**
* googlevideo 재생 403 우회용 poToken/visitorData를 생성해 Lavalink youtube-plugin에 주입한다.
* BotGuard 생성의 메모리 스파이크를 봇 본체와 격리하기 위해 독립 ESM 스크립트를 자식 프로세스로 실행한다.
*/
private refreshPoToken(): void {
const script = join(__dirname, "..", "..", "scripts", "potoken-refresh.mjs");
const child = spawn(process.execPath, [script], {
env: {
...process.env,
LAVALINK_HOST: Config.lavalink.host,
LAVALINK_PORT: String(Config.lavalink.port),
LAVALINK_PW: Config.lavalink.pw,
},
stdio: ["ignore", "pipe", "pipe"],
});
let out = "";
child.stdout?.on("data", (d) => { out += String(d); });
child.stderr?.on("data", (d) => { out += String(d); });
child.on("error", (err) => {
Logger.error(`[LavalinkManager] poToken 갱신 실행 실패: ${String(err)}`);
});
child.on("close", (code) => {
const msg = out.trim().split("\n").pop() ?? "";
if (code === 0) Logger.ready(`[LavalinkManager] ${msg || "poToken 주입 완료"}`);
else Logger.error(`[LavalinkManager] poToken 갱신 실패(code ${code}): ${msg}`);
});
}
/** 연결 가능한(CONNECTED 상태) Lavalink 노드가 하나라도 있는지 확인 */