import { initDatabase, PlatformAccount } from '../models/index.js'; import { logger } from '../utils/logger.js'; import { BrowserManager } from '../automation/browser.js'; import fs from 'node:fs/promises'; import path from 'node:path'; type PlaywrightCookie = { name: string; value: string; domain?: string; path?: string; url?: string; expires?: number; httpOnly?: boolean; secure?: boolean; sameSite?: 'Lax' | 'None' | 'Strict'; }; function parseCookiesFromAccount(cookieData: string | null): PlaywrightCookie[] { if (!cookieData) return []; const raw = cookieData.trim(); if (!raw) return []; // 1) JSON array / 对象 if (raw.startsWith('[') || raw.startsWith('{')) { try { const parsed = JSON.parse(raw); const arr = Array.isArray(parsed) ? parsed : (parsed?.cookies ? parsed.cookies : []); if (!Array.isArray(arr)) return []; return arr .map((c: any) => { const name = String(c?.name ?? '').trim(); const value = String(c?.value ?? '').trim(); if (!name) return null; const domain = c?.domain ? String(c.domain) : undefined; const pathVal = c?.path ? String(c.path) : '/'; const url = !domain ? 'https://channels.weixin.qq.com' : undefined; const sameSiteRaw = c?.sameSite; const sameSite = sameSiteRaw === 'Lax' || sameSiteRaw === 'None' || sameSiteRaw === 'Strict' ? sameSiteRaw : undefined; return { name, value, domain, path: pathVal, url, expires: typeof c?.expires === 'number' ? c.expires : undefined, httpOnly: typeof c?.httpOnly === 'boolean' ? c.httpOnly : undefined, secure: typeof c?.secure === 'boolean' ? c.secure : undefined, sameSite, } satisfies PlaywrightCookie; }) .filter(Boolean) as PlaywrightCookie[]; } catch { // fallthrough } } // 2) "a=b; c=d" 拼接 const pairs = raw.split(';').map((p) => p.trim()).filter(Boolean); const cookies: PlaywrightCookie[] = []; for (const p of pairs) { const idx = p.indexOf('='); if (idx <= 0) continue; const name = p.slice(0, idx).trim(); const value = p.slice(idx + 1).trim(); if (!name) continue; cookies.push({ name, value, url: 'https://channels.weixin.qq.com' }); } return cookies; } async function main() { try { await initDatabase(); const accountRepository = (await import('../models/index.js')).AppDataSource.getRepository( PlatformAccount ); const accountName = '嗯麦威欧洲古董'; const account = await accountRepository.findOne({ where: { platform: 'weixin_video' as any, accountName, }, }); if (!account) { logger.error(`[WX Video] Account not found for name=${accountName}`); process.exit(1); return; } const cookies = parseCookiesFromAccount(account.cookieData); if (!cookies.length) { logger.error('[WX Video] cookieData 为空或无法解析,无法带登录态打开页面'); process.exit(1); return; } logger.info( `[WX Video] Opening with account. id=${account.id} name=${account.accountName ?? ''}` ); const browser = await BrowserManager.getBrowser({ headless: false }); const context = await browser.newContext({ viewport: { width: 1920, height: 1080 }, locale: 'zh-CN', timezoneId: 'Asia/Shanghai', }); await context.addCookies(cookies as any); const page = await context.newPage(); const url = 'https://channels.weixin.qq.com/platform'; logger.info(`[WX Video] Opening page with cookies: ${url}`); await page.goto(url, { waitUntil: 'domcontentloaded' }); logger.info('[WX Video] Page opened with account cookies. 你可以在浏览器里操作该账号。'); // 等页面真正进入平台(避免太早保存到“登录页”的 storageState) await page .waitForFunction(() => { const t = document.body?.innerText || ''; return t.includes('数据中心') || t.includes('视频数据') || t.includes('关注者数据'); }, { timeout: 60_000 }) .catch(() => undefined); // 保存 storageState,供后台 headless 同步复用(避免 cookie-only 在 headless 下跳登录) const stateDir = path.resolve(process.cwd(), 'tmp', 'weixin-video-storage-state'); await fs.mkdir(stateDir, { recursive: true }); const statePath = path.join(stateDir, `${account.id}.json`); await context.storageState({ path: statePath }); logger.info(`[WX Video] storageState saved: ${statePath}`); // 不主动关闭浏览器,方便你手动操作 } catch (e) { logger.error('[WX Video] Failed to open page with account:', e); process.exit(1); } } void main();