From 7d65e3e60251e70852197dc29af871fdfdc50567 Mon Sep 17 00:00:00 2001 From: "949036910@qq.com" <> Date: Wed, 3 Jun 2026 21:43:46 +0800 Subject: [PATCH] 11 --- scripts/nodejs/douyin_browser_manager.js | 2 +- scripts/nodejs/publish_browser.js | 135 +++++++++++++++++++---- scripts/nodejs/publish_login.js | 68 ++++++++++-- scripts/nodejs/publish_open.js | 11 +- 4 files changed, 178 insertions(+), 38 deletions(-) diff --git a/scripts/nodejs/douyin_browser_manager.js b/scripts/nodejs/douyin_browser_manager.js index 590f2ce..2f5da79 100644 --- a/scripts/nodejs/douyin_browser_manager.js +++ b/scripts/nodejs/douyin_browser_manager.js @@ -41,7 +41,7 @@ function getChromiumExecutablePath() { path.join(pf, "Microsoft", "Edge", "Application", "msedge.exe"), path.join(pfx86, "Microsoft", "Edge", "Application", "msedge.exe"), path.join(pf, "Google", "Chrome", "Application", "chrome.exe"), - // + path.join(administrator_appdata, "Google", "Chrome", "bin","chrome.exe"), ); } else if (process.platform === "darwin") { candidates.push( diff --git a/scripts/nodejs/publish_browser.js b/scripts/nodejs/publish_browser.js index 1d45a12..19f1880 100644 --- a/scripts/nodejs/publish_browser.js +++ b/scripts/nodejs/publish_browser.js @@ -34,13 +34,20 @@ function getChromiumExecutablePath() { const candidates = []; if (process.platform === "win32") { const pf = process.env.ProgramFiles || "C:\\Program Files"; - const local = process.env.LOCALAPPDATA || ""; - const administrator_appdata="C:\\Users\\Administrator\\AppData\\Local"; + const pfx86 = + process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)"; + const local = + process.env.LOCALAPPDATA || + path.join(os.homedir(), "AppData", "Local"); + const administrator_appdata="C:\\Users\\Administrator\\AppData\\Local"; candidates.push( path.join(pf, "Google", "Chrome", "Application", "chrome.exe"), + path.join(pfx86, "Google", "Chrome", "Application", "chrome.exe"), path.join(local, "Google", "Chrome", "Application", "chrome.exe"), path.join(pf, "Microsoft", "Edge", "Application", "msedge.exe"), - // path.join(administrator_appdata, "Google", "Chrome", "bin","chrome.exe"), + path.join(pfx86, "Microsoft", "Edge", "Application", "msedge.exe"), + path.join(local, "Microsoft", "Edge", "Application", "msedge.exe"), + path.join(administrator_appdata, "Google", "Chrome", "bin","chrome.exe"), ); } return candidates.find((p) => p && fs.existsSync(p)); @@ -107,6 +114,19 @@ async function isDebugPortOpen(port) { } } +/** 关闭占用调试端口的 Chrome,避免复用无窗口/僵尸实例导致扫码登录不弹窗 */ +async function forceCloseChromeOnPort(port) { + if (!(await isDebugPortOpen(port))) return; + try { + const puppeteer = loadPuppeteer(); + const browser = await connectPublishBrowser(puppeteer, port); + await browser.close(); + } catch { + /* ignore */ + } + await delay(1500); +} + async function waitForDebugPort(port, timeoutMs = 25000) { const start = Date.now(); while (Date.now() - start < timeoutMs) { @@ -118,17 +138,25 @@ async function waitForDebugPort(port, timeoutMs = 25000) { /** * 独立进程启动 Chrome(与 Node 生命周期解耦) + * @param {string} [startUrl] */ -async function spawnDetachedChrome(executablePath, dir, port) { - if (await isDebugPortOpen(port)) return; +async function spawnDetachedChrome(executablePath, dir, port, startUrl, options = {}) { + const force = options.force === true; + if (force) { + await forceCloseChromeOnPort(port); + } else if (await isDebugPortOpen(port)) { + return; + } if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); const args = [ `--remote-debugging-port=${port}`, `--user-data-dir=${dir}`, "--remote-allow-origins=*", + "--start-maximized", + "--new-window", ...COMMON_BROWSER_ARGS.filter((a) => !a.startsWith("--user-data-dir")), - "about:blank", + startUrl && String(startUrl).trim() ? String(startUrl).trim() : "about:blank", ]; const child = spawn(executablePath, args, { @@ -164,15 +192,24 @@ function cleanupProfileLocks(dir) { } /** - * 扫码登录专用:可见 Chrome(对齐 Electron launchPersistentContext headless:false) + * 扫码登录:puppeteer.launch 打开可见窗口(Node 在登录等待期间保持存活) + * @param {string} [_startUrl] 由 newLoginPage 负责导航 */ -async function getLoginBrowser(platform, accountId) { +async function getLoginBrowser(platform, accountId, _startUrl) { const key = `login_${contextKey(platform, accountId)}`; const existing = loginBrowsers.get(key); if (existing && existing.isConnected()) { return existing; } + const publishKey = contextKey(platform, accountId); + const publishPort = debugPortForKey(publishKey); + const loginPort = debugPortForKey(key); + await closeLoginBrowser(platform, accountId); + await closePublishBrowser(platform, accountId); + await forceCloseChromeOnPort(loginPort); + await forceCloseChromeOnPort(publishPort); + const puppeteer = loadPuppeteer(); const dir = userDataDir(platform, accountId); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); @@ -181,20 +218,22 @@ async function getLoginBrowser(platform, accountId) { const executablePath = getChromiumExecutablePath(); if (!executablePath) { throw new Error( - "未找到 Chrome/Edge,请安装浏览器或设置 AICLIENT_CHROMIUM_PATH", + "未找到 Chrome/Edge,请安装 Google Chrome 或 Microsoft Edge,或设置 AICLIENT_CHROMIUM_PATH", ); } + globalThis.__native?.emitProgress?.("正在打开 Chrome 登录窗口…"); + const browser = await puppeteer.launch({ executablePath, headless: false, userDataDir: dir, - args: [...COMMON_BROWSER_ARGS, "--start-maximized"], + args: [ + "--start-maximized", + ...COMMON_BROWSER_ARGS.filter((a) => !a.startsWith("--user-data-dir")), + ], ignoreDefaultArgs: BROWSER_IGNORE_DEFAULT_ARGS, defaultViewport: { width: 1280, height: 900 }, - handleSIGINT: false, - handleSIGTERM: false, - handleSIGHUP: false, }); browser.on("disconnected", () => loginBrowsers.delete(key)); @@ -251,13 +290,56 @@ async function closeBlankPages(browser, keepPage) { } } -async function newLoginPage(platform, accountId, cookies = []) { - const browser = await getLoginBrowser(platform, accountId); - const page = await browser.newPage(); +async function bringPageToFront(page) { + try { + await page.bringToFront(); + } catch { + /* ignore */ + } + try { + const client = await page.createCDPSession(); + await client.send("Page.bringToFront"); + await client.send("Browser.setWindowBounds", { + windowId: (await client.send("Browser.getWindowForTarget")).windowId, + bounds: { windowState: "normal" }, + }); + } catch { + /* ignore */ + } +} + +/** + * @param {string} [startUrl] 登录页 URL,spawn 时直接打开 + */ +async function newLoginPage(platform, accountId, cookies = [], startUrl) { + const browser = await getLoginBrowser(platform, accountId, startUrl); + + const pages = await browser.pages(); + let page = + pages.find((p) => { + try { + const u = p.url(); + return u && u !== "about:blank" && u !== "chrome://newtab/"; + } catch { + return false; + } + }) || pages[0] || null; + + if (!page) { + page = await browser.newPage(); + if (startUrl) { + await page.goto(startUrl, { + waitUntil: "domcontentloaded", + timeout: 60000, + }); + } + } + await page.setUserAgent(DEFAULT_USER_AGENT); await page.evaluateOnNewDocument(STEALTH_INIT_SCRIPT); page.setDefaultNavigationTimeout(60000); page.setDefaultTimeout(60000); + if (Array.isArray(cookies) && cookies.length) { try { await page.setCookie(...cookies); @@ -265,21 +347,26 @@ async function newLoginPage(platform, accountId, cookies = []) { console.warn("setCookie 失败:", e.message); } } - try { - await page.bringToFront(); - } catch { - /* ignore */ - } + + await bringPageToFront(page); return page; } async function closeLoginBrowser(platform, accountId) { const key = `login_${contextKey(platform, accountId)}`; - const browser = loginBrowsers.get(key); - if (!browser) return; + const port = debugPortForKey(key); + let browser = loginBrowsers.get(key); loginBrowsers.delete(key); try { - await browser.close(); + if (browser && browser.isConnected()) { + await browser.close(); + return; + } + const puppeteer = loadPuppeteer(); + if (await isDebugPortOpen(port)) { + browser = await connectPublishBrowser(puppeteer, port); + await browser.close(); + } } catch { /* ignore */ } diff --git a/scripts/nodejs/publish_login.js b/scripts/nodejs/publish_login.js index 3389e81..adfa43b 100644 --- a/scripts/nodejs/publish_login.js +++ b/scripts/nodejs/publish_login.js @@ -1,12 +1,29 @@ "use strict"; +const fs = require("fs"); +const path = require("path"); +const os = require("os"); + const { delay, newLoginPage, LOGIN_URLS, closeLoginBrowser, + getChromiumExecutablePath, + userDataDir, } = require("./publish_browser.js"); +function appendLoginDebug(line) { + try { + const base = process.env.AICLIENT_DATA_DIR || path.join(os.homedir(), ".aiclient"); + const file = path.join(base, "publish-login-debug.log"); + fs.mkdirSync(base, { recursive: true }); + fs.appendFileSync(file, `${new Date().toISOString()} ${line}\n`, "utf8"); + } catch { + /* ignore */ + } +} + const DOUYIN_HOME_URL = "https://creator.douyin.com/creator-micro/home"; const LOGIN_SELECTORS = { @@ -113,13 +130,51 @@ globalThis.__nodejsMain = async function main(params) { let page = null; globalThis.__native?.emitProgress?.("正在启动浏览器,请扫码登录…"); + appendLoginDebug(`login start platform=${platform} accountId=${accountId}`); try { - page = await newLoginPage(platform, accountId, []); + const chromePath = getChromiumExecutablePath(); + if (!chromePath) { + const err = "未找到 Chrome/Edge,请安装浏览器或设置 AICLIENT_CHROMIUM_PATH"; + appendLoginDebug(`error: ${err}`); + return { success: false, error: err }; + } + + globalThis.__native?.log?.( + "info", + "启动扫码登录浏览器", + { chromePath, profileDir: userDataDir(platform, accountId) }, + ); + globalThis.__native?.emitProgress?.("正在启动 Chrome 登录窗口…"); + appendLoginDebug(`chrome=${chromePath} profile=${userDataDir(platform, accountId)}`); + page = await newLoginPage(platform, accountId, [], loginUrl); + try { + appendLoginDebug(`page opened url=${page.url()}`); + } catch { + appendLoginDebug("page opened"); + } globalThis.__native?.emitProgress?.("正在打开登录页…"); try { - await page.goto(loginUrl, { waitUntil: "domcontentloaded", timeout: 60000 }); + const cur = page.url(); + const host = (() => { + try { + return new URL(loginUrl).hostname; + } catch { + return ""; + } + })(); + if ( + !cur || + cur === "about:blank" || + cur === "chrome://newtab/" || + (host && !cur.includes(host)) + ) { + await page.goto(loginUrl, { + waitUntil: "domcontentloaded", + timeout: 60000, + }); + } } catch (e) { globalThis.__native?.log?.( "warn", @@ -128,11 +183,6 @@ globalThis.__nodejsMain = async function main(params) { ); } await delay(3000); - try { - await page.bringToFront(); - } catch { - /* ignore */ - } globalThis.__native?.emitProgress?.( "请在弹出的浏览器窗口中扫码登录(最长等待 5 分钟)", @@ -148,6 +198,8 @@ globalThis.__nodejsMain = async function main(params) { } if (await checkLoggedIn(page, platform)) { + appendLoginDebug("detected logged in"); + globalThis.__native?.emitProgress?.("检测到已登录,正在保存账号信息…"); const cookies = await page.cookies(); const resolvedUid = await resolveUid(page); let douyinId = ""; @@ -200,6 +252,8 @@ globalThis.__nodejsMain = async function main(params) { }; } catch (e) { const msg = e instanceof Error ? e.message : String(e); + appendLoginDebug(`exception: ${msg}`); + globalThis.__native?.emitProgress?.(`登录失败:${msg}`); return { success: false, error: msg || "启动登录浏览器失败" }; } finally { if (page) { diff --git a/scripts/nodejs/publish_open.js b/scripts/nodejs/publish_open.js index 6b8d598..a3ab912 100644 --- a/scripts/nodejs/publish_open.js +++ b/scripts/nodejs/publish_open.js @@ -24,12 +24,11 @@ globalThis.__nodejsMain = async function main(params) { return { success: false, message: `不支持的平台: ${platform}` }; } - const page = await newLoginPage(platform, accountId, cookies); - await page.goto(targetUrl, { waitUntil: "domcontentloaded", timeout: 60000 }); - try { - await page.bringToFront(); - } catch { - /* ignore */ + globalThis.__native?.emitProgress?.("正在打开浏览器…"); + const page = await newLoginPage(platform, accountId, cookies, targetUrl); + const cur = page.url(); + if (!cur || cur === "about:blank" || !cur.includes("://")) { + await page.goto(targetUrl, { waitUntil: "domcontentloaded", timeout: 60000 }); } await detachLoginBrowser(platform, accountId);