This commit is contained in:
949036910@qq.com
2026-06-03 21:43:46 +08:00
parent bbdee469fc
commit 7d65e3e602
4 changed files with 178 additions and 38 deletions

View File

@@ -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(

View File

@@ -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] 登录页 URLspawn 时直接打开
*/
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 */
}

View File

@@ -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) {

View File

@@ -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);