import { mkdir, writeFile } from "fs/promises"; import path from "path"; import type { Page } from "playwright"; import { getActiveAccountConnectionSettings } from "@/lib/account-connection-settings"; const DEBUG_DIR = path.join(process.cwd(), "data", "debug-runs"); export interface DebugRun { id: string; label: string; dir: string; enabled: boolean; } export async function isPlaywrightDebugEnabled(): Promise { if (process.env.THREADS_DEBUG === "true") return true; try { const connection = await getActiveAccountConnectionSettings(); return connection.playwrightDebug === true; } catch { return false; } } export async function shouldRunHeaded(): Promise { if (process.env.PLAYWRIGHT_HEADLESS === "false") return true; if (process.env.PLAYWRIGHT_HEADLESS === "true") return false; return isPlaywrightDebugEnabled(); } /** 發布預設用可見瀏覽器,較不容易被擋、也方便手動完成 Instagram 授權 */ export async function shouldRunHeadedForPublish(): Promise { if (process.env.PLAYWRIGHT_HEADLESS === "false") return true; if (process.env.PLAYWRIGHT_HEADLESS === "true") return false; try { const connection = await getActiveAccountConnectionSettings(); if (connection.playwrightDebug) return true; return connection.publishHeaded ?? true; } catch { return true; } } export function getPlaywrightSlowMo(): number { const raw = process.env.PLAYWRIGHT_SLOW_MO; if (raw) { const parsed = Number(raw); if (!Number.isNaN(parsed) && parsed >= 0) return parsed; } return 0; } export async function createDebugRun( label: string, options?: { force?: boolean } ): Promise { const enabled = options?.force === true || await isPlaywrightDebugEnabled(); if (!enabled) return null; const id = `${Date.now()}-${label.replace(/[^a-zA-Z0-9_-]+/g, "-").slice(0, 40)}`; const dir = path.join(DEBUG_DIR, id); await mkdir(dir, { recursive: true }); await writeFile( path.join(dir, "manifest.json"), JSON.stringify({ id, label, startedAt: new Date().toISOString(), steps: [] }, null, 2) ); console.log(`[threads-debug] run=${id} label=${label}`); return { id, label, dir, enabled }; } export async function captureDebugStep( page: Page, run: DebugRun | null | undefined, step: string, meta?: Record ) { if (!run?.enabled) return; const safeStep = step.replace(/[^a-zA-Z0-9_-]+/g, "-").slice(0, 80); const timestamp = new Date().toISOString(); const shotPath = path.join(run.dir, `${safeStep}.png`); try { await page.screenshot({ path: shotPath, fullPage: true }); } catch (error) { console.warn(`[threads-debug] screenshot failed (${step})`, error); } try { const manifestPath = path.join(run.dir, "manifest.json"); const { readFile } = await import("fs/promises"); const manifest = JSON.parse(await readFile(manifestPath, "utf8")) as { steps: Array>; }; manifest.steps.push({ step, at: timestamp, url: page.url(), title: await page.title().catch(() => ""), screenshot: `${safeStep}.png`, ...meta, }); await writeFile(manifestPath, JSON.stringify(manifest, null, 2)); } catch (error) { console.warn(`[threads-debug] manifest update failed (${step})`, error); } console.log(`[threads-debug] ${run.id} · ${step} · ${page.url()}`); } export function debugDirPath(): string { return DEBUG_DIR; }