run-weixin-video-open-with-account.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. import { initDatabase, PlatformAccount } from '../models/index.js';
  2. import { logger } from '../utils/logger.js';
  3. import { BrowserManager } from '../automation/browser.js';
  4. import fs from 'node:fs/promises';
  5. import path from 'node:path';
  6. type PlaywrightCookie = {
  7. name: string;
  8. value: string;
  9. domain?: string;
  10. path?: string;
  11. url?: string;
  12. expires?: number;
  13. httpOnly?: boolean;
  14. secure?: boolean;
  15. sameSite?: 'Lax' | 'None' | 'Strict';
  16. };
  17. function parseCookiesFromAccount(cookieData: string | null): PlaywrightCookie[] {
  18. if (!cookieData) return [];
  19. const raw = cookieData.trim();
  20. if (!raw) return [];
  21. // 1) JSON array / 对象
  22. if (raw.startsWith('[') || raw.startsWith('{')) {
  23. try {
  24. const parsed = JSON.parse(raw);
  25. const arr = Array.isArray(parsed) ? parsed : (parsed?.cookies ? parsed.cookies : []);
  26. if (!Array.isArray(arr)) return [];
  27. return arr
  28. .map((c: any) => {
  29. const name = String(c?.name ?? '').trim();
  30. const value = String(c?.value ?? '').trim();
  31. if (!name) return null;
  32. const domain = c?.domain ? String(c.domain) : undefined;
  33. const pathVal = c?.path ? String(c.path) : '/';
  34. const url = !domain ? 'https://channels.weixin.qq.com' : undefined;
  35. const sameSiteRaw = c?.sameSite;
  36. const sameSite =
  37. sameSiteRaw === 'Lax' || sameSiteRaw === 'None' || sameSiteRaw === 'Strict'
  38. ? sameSiteRaw
  39. : undefined;
  40. return {
  41. name,
  42. value,
  43. domain,
  44. path: pathVal,
  45. url,
  46. expires: typeof c?.expires === 'number' ? c.expires : undefined,
  47. httpOnly: typeof c?.httpOnly === 'boolean' ? c.httpOnly : undefined,
  48. secure: typeof c?.secure === 'boolean' ? c.secure : undefined,
  49. sameSite,
  50. } satisfies PlaywrightCookie;
  51. })
  52. .filter(Boolean) as PlaywrightCookie[];
  53. } catch {
  54. // fallthrough
  55. }
  56. }
  57. // 2) "a=b; c=d" 拼接
  58. const pairs = raw.split(';').map((p) => p.trim()).filter(Boolean);
  59. const cookies: PlaywrightCookie[] = [];
  60. for (const p of pairs) {
  61. const idx = p.indexOf('=');
  62. if (idx <= 0) continue;
  63. const name = p.slice(0, idx).trim();
  64. const value = p.slice(idx + 1).trim();
  65. if (!name) continue;
  66. cookies.push({ name, value, url: 'https://channels.weixin.qq.com' });
  67. }
  68. return cookies;
  69. }
  70. async function main() {
  71. try {
  72. await initDatabase();
  73. const accountRepository = (await import('../models/index.js')).AppDataSource.getRepository(
  74. PlatformAccount
  75. );
  76. const accountName = '嗯麦威欧洲古董';
  77. const account = await accountRepository.findOne({
  78. where: {
  79. platform: 'weixin_video' as any,
  80. accountName,
  81. },
  82. });
  83. if (!account) {
  84. logger.error(`[WX Video] Account not found for name=${accountName}`);
  85. process.exit(1);
  86. return;
  87. }
  88. const cookies = parseCookiesFromAccount(account.cookieData);
  89. if (!cookies.length) {
  90. logger.error('[WX Video] cookieData 为空或无法解析,无法带登录态打开页面');
  91. process.exit(1);
  92. return;
  93. }
  94. logger.info(
  95. `[WX Video] Opening with account. id=${account.id} name=${account.accountName ?? ''}`
  96. );
  97. const browser = await BrowserManager.getBrowser({ headless: false });
  98. const context = await browser.newContext({
  99. viewport: { width: 1920, height: 1080 },
  100. locale: 'zh-CN',
  101. timezoneId: 'Asia/Shanghai',
  102. });
  103. await context.addCookies(cookies as any);
  104. const page = await context.newPage();
  105. const url = 'https://channels.weixin.qq.com/platform';
  106. logger.info(`[WX Video] Opening page with cookies: ${url}`);
  107. await page.goto(url, { waitUntil: 'domcontentloaded' });
  108. logger.info('[WX Video] Page opened with account cookies. 你可以在浏览器里操作该账号。');
  109. // 等页面真正进入平台(避免太早保存到“登录页”的 storageState)
  110. await page
  111. .waitForFunction(() => {
  112. const t = document.body?.innerText || '';
  113. return t.includes('数据中心') || t.includes('视频数据') || t.includes('关注者数据');
  114. }, { timeout: 60_000 })
  115. .catch(() => undefined);
  116. // 保存 storageState,供后台 headless 同步复用(避免 cookie-only 在 headless 下跳登录)
  117. const stateDir = path.resolve(process.cwd(), 'tmp', 'weixin-video-storage-state');
  118. await fs.mkdir(stateDir, { recursive: true });
  119. const statePath = path.join(stateDir, `${account.id}.json`);
  120. await context.storageState({ path: statePath });
  121. logger.info(`[WX Video] storageState saved: ${statePath}`);
  122. // 不主动关闭浏览器,方便你手动操作
  123. } catch (e) {
  124. logger.error('[WX Video] Failed to open page with account:', e);
  125. process.exit(1);
  126. }
  127. }
  128. void main();