1
This commit is contained in:
@@ -41,7 +41,7 @@ function getChromiumExecutablePath() {
|
|||||||
path.join(pf, "Microsoft", "Edge", "Application", "msedge.exe"),
|
path.join(pf, "Microsoft", "Edge", "Application", "msedge.exe"),
|
||||||
path.join(pfx86, "Microsoft", "Edge", "Application", "msedge.exe"),
|
path.join(pfx86, "Microsoft", "Edge", "Application", "msedge.exe"),
|
||||||
path.join(pf, "Google", "Chrome", "Application", "chrome.exe"),
|
path.join(pf, "Google", "Chrome", "Application", "chrome.exe"),
|
||||||
path.join(administrator_appdata, "Google", "Chrome", "bin","chrome.exe"),
|
//
|
||||||
);
|
);
|
||||||
} else if (process.platform === "darwin") {
|
} else if (process.platform === "darwin") {
|
||||||
candidates.push(
|
candidates.push(
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ const {
|
|||||||
} = require("./douyin_browser_constants.js");
|
} = require("./douyin_browser_constants.js");
|
||||||
|
|
||||||
const browsers = new Map();
|
const browsers = new Map();
|
||||||
|
/** 扫码登录 / 打开账号:需可见窗口,使用 puppeteer.launch(Node 等待登录期间保持存活) */
|
||||||
|
const loginBrowsers = new Map();
|
||||||
|
|
||||||
function getRuntimeDataPath(...parts) {
|
function getRuntimeDataPath(...parts) {
|
||||||
const base = process.env.AICLIENT_DATA_DIR || path.join(os.homedir(), ".aiclient");
|
const base = process.env.AICLIENT_DATA_DIR || path.join(os.homedir(), ".aiclient");
|
||||||
@@ -38,7 +40,7 @@ function getChromiumExecutablePath() {
|
|||||||
path.join(pf, "Google", "Chrome", "Application", "chrome.exe"),
|
path.join(pf, "Google", "Chrome", "Application", "chrome.exe"),
|
||||||
path.join(local, "Google", "Chrome", "Application", "chrome.exe"),
|
path.join(local, "Google", "Chrome", "Application", "chrome.exe"),
|
||||||
path.join(pf, "Microsoft", "Edge", "Application", "msedge.exe"),
|
path.join(pf, "Microsoft", "Edge", "Application", "msedge.exe"),
|
||||||
path.join(administrator_appdata, "Google", "Chrome", "bin","chrome.exe"),
|
// path.join(administrator_appdata, "Google", "Chrome", "bin","chrome.exe"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return candidates.find((p) => p && fs.existsSync(p));
|
return candidates.find((p) => p && fs.existsSync(p));
|
||||||
@@ -145,6 +147,61 @@ async function connectPublishBrowser(puppeteer, port) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cleanupProfileLocks(dir) {
|
||||||
|
for (const name of [
|
||||||
|
"SingletonLock",
|
||||||
|
"SingletonSocket",
|
||||||
|
"SingletonCookie",
|
||||||
|
"Lock",
|
||||||
|
]) {
|
||||||
|
try {
|
||||||
|
const p = path.join(dir, name);
|
||||||
|
if (fs.existsSync(p)) fs.unlinkSync(p);
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扫码登录专用:可见 Chrome(对齐 Electron launchPersistentContext headless:false)
|
||||||
|
*/
|
||||||
|
async function getLoginBrowser(platform, accountId) {
|
||||||
|
const key = `login_${contextKey(platform, accountId)}`;
|
||||||
|
const existing = loginBrowsers.get(key);
|
||||||
|
if (existing && existing.isConnected()) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const puppeteer = loadPuppeteer();
|
||||||
|
const dir = userDataDir(platform, accountId);
|
||||||
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||||||
|
cleanupProfileLocks(dir);
|
||||||
|
|
||||||
|
const executablePath = getChromiumExecutablePath();
|
||||||
|
if (!executablePath) {
|
||||||
|
throw new Error(
|
||||||
|
"未找到 Chrome/Edge,请安装浏览器或设置 AICLIENT_CHROMIUM_PATH",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const browser = await puppeteer.launch({
|
||||||
|
executablePath,
|
||||||
|
headless: false,
|
||||||
|
userDataDir: dir,
|
||||||
|
args: [...COMMON_BROWSER_ARGS, "--start-maximized"],
|
||||||
|
ignoreDefaultArgs: BROWSER_IGNORE_DEFAULT_ARGS,
|
||||||
|
defaultViewport: { width: 1280, height: 900 },
|
||||||
|
handleSIGINT: false,
|
||||||
|
handleSIGTERM: false,
|
||||||
|
handleSIGHUP: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.on("disconnected", () => loginBrowsers.delete(key));
|
||||||
|
loginBrowsers.set(key, browser);
|
||||||
|
return browser;
|
||||||
|
}
|
||||||
|
|
||||||
async function getPublishBrowser(platform, accountId) {
|
async function getPublishBrowser(platform, accountId) {
|
||||||
const key = contextKey(platform, accountId);
|
const key = contextKey(platform, accountId);
|
||||||
const existing = browsers.get(key);
|
const existing = browsers.get(key);
|
||||||
@@ -194,6 +251,57 @@ async function closeBlankPages(browser, keepPage) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function newLoginPage(platform, accountId, cookies = []) {
|
||||||
|
const browser = await getLoginBrowser(platform, accountId);
|
||||||
|
const page = await browser.newPage();
|
||||||
|
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);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("setCookie 失败:", e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await page.bringToFront();
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function closeLoginBrowser(platform, accountId) {
|
||||||
|
const key = `login_${contextKey(platform, accountId)}`;
|
||||||
|
const browser = loginBrowsers.get(key);
|
||||||
|
if (!browser) return;
|
||||||
|
loginBrowsers.delete(key);
|
||||||
|
try {
|
||||||
|
await browser.close();
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开账号页后保留窗口(disconnect,避免 Node 退出时关窗)
|
||||||
|
*/
|
||||||
|
async function detachLoginBrowser(platform, accountId) {
|
||||||
|
const key = `login_${contextKey(platform, accountId)}`;
|
||||||
|
const browser = loginBrowsers.get(key);
|
||||||
|
if (!browser) return;
|
||||||
|
loginBrowsers.delete(key);
|
||||||
|
try {
|
||||||
|
const proc = typeof browser.process === "function" ? browser.process() : null;
|
||||||
|
if (proc && typeof proc.unref === "function") proc.unref();
|
||||||
|
if (browser.isConnected()) await browser.disconnect();
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("detachLoginBrowser:", e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function newPublishPage(platform, accountId, cookies) {
|
async function newPublishPage(platform, accountId, cookies) {
|
||||||
const browser = await getPublishBrowser(platform, accountId);
|
const browser = await getPublishBrowser(platform, accountId);
|
||||||
const pages = await browser.pages();
|
const pages = await browser.pages();
|
||||||
@@ -267,11 +375,15 @@ const LOGIN_URLS = {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
delay,
|
delay,
|
||||||
getPublishBrowser,
|
getPublishBrowser,
|
||||||
|
getLoginBrowser,
|
||||||
newPublishPage,
|
newPublishPage,
|
||||||
|
newLoginPage,
|
||||||
userDataDir,
|
userDataDir,
|
||||||
LOGIN_URLS,
|
LOGIN_URLS,
|
||||||
getChromiumExecutablePath,
|
getChromiumExecutablePath,
|
||||||
closePublishBrowser,
|
closePublishBrowser,
|
||||||
|
closeLoginBrowser,
|
||||||
|
detachLoginBrowser,
|
||||||
detachPublishBrowser,
|
detachPublishBrowser,
|
||||||
debugPortForKey,
|
debugPortForKey,
|
||||||
contextKey,
|
contextKey,
|
||||||
|
|||||||
@@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
delay,
|
delay,
|
||||||
newPublishPage,
|
newLoginPage,
|
||||||
LOGIN_URLS,
|
LOGIN_URLS,
|
||||||
closePublishBrowser,
|
closeLoginBrowser,
|
||||||
} = require("./publish_browser.js");
|
} = require("./publish_browser.js");
|
||||||
|
|
||||||
const DOUYIN_HOME_URL = "https://creator.douyin.com/creator-micro/home";
|
const DOUYIN_HOME_URL = "https://creator.douyin.com/creator-micro/home";
|
||||||
|
|
||||||
const LOGIN_SELECTORS = {
|
const LOGIN_SELECTORS = {
|
||||||
@@ -104,21 +105,48 @@ async function resolveDouyinLabel(page) {
|
|||||||
globalThis.__nodejsMain = async function main(params) {
|
globalThis.__nodejsMain = async function main(params) {
|
||||||
const p = params || {};
|
const p = params || {};
|
||||||
const platform = String(p.platform || "").trim();
|
const platform = String(p.platform || "").trim();
|
||||||
const accountId = p.accountId || null;
|
const accountId = p.accountId ?? null;
|
||||||
const loginUrl = LOGIN_URLS[platform];
|
const loginUrl = LOGIN_URLS[platform];
|
||||||
if (!loginUrl) {
|
if (!loginUrl) {
|
||||||
return { success: false, error: `不支持的平台: ${platform}` };
|
return { success: false, error: `不支持的平台: ${platform}` };
|
||||||
}
|
}
|
||||||
|
|
||||||
globalThis.__native?.emitProgress?.("正在打开登录页,请在浏览器中完成登录…");
|
let page = null;
|
||||||
const page = await newPublishPage(platform, accountId, []);
|
globalThis.__native?.emitProgress?.("正在启动浏览器,请扫码登录…");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await page.goto(loginUrl, { waitUntil: "domcontentloaded", timeout: 60000 });
|
page = await newLoginPage(platform, accountId, []);
|
||||||
|
|
||||||
|
globalThis.__native?.emitProgress?.("正在打开登录页…");
|
||||||
|
try {
|
||||||
|
await page.goto(loginUrl, { waitUntil: "domcontentloaded", timeout: 60000 });
|
||||||
|
} catch (e) {
|
||||||
|
globalThis.__native?.log?.(
|
||||||
|
"warn",
|
||||||
|
`登录页加载超时,继续等待扫码: ${e.message}`,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
}
|
||||||
await delay(3000);
|
await delay(3000);
|
||||||
|
try {
|
||||||
|
await page.bringToFront();
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
|
||||||
|
globalThis.__native?.emitProgress?.(
|
||||||
|
"请在弹出的浏览器窗口中扫码登录(最长等待 5 分钟)",
|
||||||
|
);
|
||||||
|
|
||||||
const maxMs = 300000;
|
const maxMs = 300000;
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
|
let lastLog = 0;
|
||||||
|
|
||||||
while (Date.now() - start < maxMs) {
|
while (Date.now() - start < maxMs) {
|
||||||
|
if (page.isClosed()) {
|
||||||
|
return { success: false, error: "浏览器窗口已关闭,登录已取消" };
|
||||||
|
}
|
||||||
|
|
||||||
if (await checkLoggedIn(page, platform)) {
|
if (await checkLoggedIn(page, platform)) {
|
||||||
const cookies = await page.cookies();
|
const cookies = await page.cookies();
|
||||||
const resolvedUid = await resolveUid(page);
|
const resolvedUid = await resolveUid(page);
|
||||||
@@ -155,23 +183,32 @@ globalThis.__nodejsMain = async function main(params) {
|
|||||||
cookies,
|
cookies,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
await delay(2000);
|
|
||||||
globalThis.__native?.emitProgress?.("等待登录完成…");
|
const elapsed = Date.now() - start;
|
||||||
|
if (elapsed - lastLog >= 15000) {
|
||||||
|
globalThis.__native?.emitProgress?.(
|
||||||
|
`等待扫码登录… ${Math.round(elapsed / 1000)} 秒`,
|
||||||
|
);
|
||||||
|
lastLog = elapsed;
|
||||||
|
}
|
||||||
|
await delay(3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: "登录超时(5 分钟),请重试",
|
error: "登录超时(5 分钟),请重试",
|
||||||
};
|
};
|
||||||
|
} catch (e) {
|
||||||
|
const msg = e instanceof Error ? e.message : String(e);
|
||||||
|
return { success: false, error: msg || "启动登录浏览器失败" };
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
if (page) {
|
||||||
await page.close();
|
try {
|
||||||
} catch {
|
await page.close();
|
||||||
/* ignore */
|
} catch {
|
||||||
}
|
/* ignore */
|
||||||
try {
|
}
|
||||||
await closePublishBrowser(platform, accountId);
|
|
||||||
} catch {
|
|
||||||
/* ignore */
|
|
||||||
}
|
}
|
||||||
|
await closeLoginBrowser(platform, accountId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const { newPublishPage, LOGIN_URLS } = require("./publish_browser.js");
|
const {
|
||||||
|
newLoginPage,
|
||||||
|
detachLoginBrowser,
|
||||||
|
LOGIN_URLS,
|
||||||
|
} = require("./publish_browser.js");
|
||||||
|
|
||||||
const OPEN_URLS = {
|
const OPEN_URLS = {
|
||||||
douyin: "https://creator.douyin.com/creator-micro/content/upload",
|
douyin: "https://creator.douyin.com/creator-micro/content/upload",
|
||||||
@@ -20,8 +24,14 @@ globalThis.__nodejsMain = async function main(params) {
|
|||||||
return { success: false, message: `不支持的平台: ${platform}` };
|
return { success: false, message: `不支持的平台: ${platform}` };
|
||||||
}
|
}
|
||||||
|
|
||||||
const page = await newPublishPage(platform, accountId, cookies);
|
const page = await newLoginPage(platform, accountId, cookies);
|
||||||
await page.goto(targetUrl, { waitUntil: "domcontentloaded", timeout: 60000 });
|
await page.goto(targetUrl, { waitUntil: "domcontentloaded", timeout: 60000 });
|
||||||
|
try {
|
||||||
|
await page.bringToFront();
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
await detachLoginBrowser(platform, accountId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user