/** * refresh-cookies.mjs — 성인인증 계정 쿠키(ytcookie.txt) 재발급 스크립트 * * 쿠키가 만료되면(19금 재생만 막히고 일반 재생은 계속 됨) 이 스크립트로 새 쿠키를 뽑는다. * Chromium을 띄워 구글 로그인 → youtube.com 쿠키를 Netscape 형식으로 저장한다. * * 사용 전: npm i -D playwright (그리고 최초 1회) npx playwright install chromium * * 실행 예: * GOOGLE_EMAIL=tmdgks040466@gmail.com GOOGLE_PASSWORD='...' \ * OUT=/root/bot/db/ytcookie.txt node scripts/refresh-cookies.mjs * * 환경변수: * GOOGLE_EMAIL / GOOGLE_PASSWORD : 자동 로그인 시도용(생략 가능 — 그러면 수동 로그인) * OUT : 저장 경로 (기본 ./ytcookie.txt). 운영은 /root/bot/db/ytcookie.txt * HEADLESS : "1"이면 창 없이(자동 로그인 성공 시에만 권장). 기본은 창 표시(수동 로그인 가능) * * 구글이 자동 로그인을 막으면(2차인증/캡차/"안전하지 않은 브라우저") 뜬 창에서 직접 로그인하면 된다. * 스크립트는 로그인 세션 쿠키(SID)가 잡힐 때까지 최대 5분 기다린 뒤 저장한다. */ import { chromium } from "playwright"; import { writeFileSync } from "node:fs"; const EMAIL = process.env.GOOGLE_EMAIL; const PASSWORD = process.env.GOOGLE_PASSWORD; const OUT = process.env.OUT || "./ytcookie.txt"; const HEADLESS = process.env.HEADLESS === "1"; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); // Playwright 쿠키 배열 -> Netscape cookies.txt (yt-dlp/youtubei.js 호환) function toNetscape(cookies) { const lines = ["# Netscape HTTP Cookie File", "# music_bot_v2 refresh-cookies.mjs"]; for (const c of cookies) { if (!/(^|\.)youtube\.com$|(^|\.)google\.com$/.test(c.domain)) continue; const domain = c.domain.startsWith(".") ? c.domain : "." + c.domain; const expires = c.expires && c.expires > 0 ? Math.floor(c.expires) : 2000000000; lines.push([ domain, "TRUE", c.path || "/", c.secure ? "TRUE" : "FALSE", expires, c.name, c.value, ].join("\t")); } return lines.join("\n") + "\n"; } async function tryAutoLogin(page) { if (!EMAIL || !PASSWORD) return; try { await page.goto("https://accounts.google.com/ServiceLogin?continue=https://www.youtube.com/", { waitUntil: "domcontentloaded" }); await page.fill('input[type="email"]', EMAIL, { timeout: 15000 }); await page.click('#identifierNext, button:has-text("다음"), button:has-text("Next")'); await page.waitForTimeout(2500); await page.fill('input[type="password"]', PASSWORD, { timeout: 15000 }); await page.click('#passwordNext, button:has-text("다음"), button:has-text("Next")'); await page.waitForTimeout(3000); } catch (e) { console.log("[auto-login] 자동 로그인 중단(수동 로그인으로 진행):", String(e).slice(0, 100)); } } async function waitForLogin(context, page, maxMs = 5 * 60 * 1000) { const start = Date.now(); while (Date.now() - start < maxMs) { const cookies = await context.cookies("https://www.youtube.com"); if (cookies.some((c) => c.name === "SID") && cookies.some((c) => c.name === "LOGIN_INFO")) { return cookies; } await sleep(3000); } return null; } (async () => { const browser = await chromium.launch({ headless: HEADLESS }); const context = await browser.newContext(); const page = await context.newPage(); await tryAutoLogin(page); // youtube를 한번 열어 로그인 세션 쿠키를 확실히 발급 try { await page.goto("https://www.youtube.com/", { waitUntil: "domcontentloaded" }); } catch {} console.log("로그인 완료를 기다리는 중... (자동 로그인이 막혔으면 뜬 창에서 직접 로그인하세요)"); const cookies = await waitForLogin(context, page); if (!cookies) { console.error("❌ 로그인 세션 쿠키(SID/LOGIN_INFO)를 못 잡았습니다. 다시 시도하세요."); await browser.close(); process.exit(1); } const all = await context.cookies(); writeFileSync(OUT, toNetscape(all)); console.log(`✅ 쿠키 저장 완료: ${OUT} (youtube/google 쿠키 ${all.length}개 중 관련 항목)`); await browser.close(); process.exit(0); })();