mirror of
https://github.com/foxhui/WebAI2API.git
synced 2026-06-16 21:03:59 +08:00
feat: 修改监听跳转的逻辑为任何时候被触发都执行自动登录
This commit is contained in:
@@ -17,15 +17,21 @@ import { logger } from '../../utils/logger.js';
|
||||
// Gemini Biz 输入框选择器
|
||||
const INPUT_SELECTOR = 'ucs-prosemirror-editor .ProseMirror';
|
||||
|
||||
// 防止重复处理登录的锁
|
||||
let isHandlingAuth = false;
|
||||
|
||||
/**
|
||||
* 处理账户选择页面跳转
|
||||
* @param {import('puppeteer').Page} page
|
||||
* @param {string} targetUrl - 目标 URL,用于判断跳转完成
|
||||
* @returns {Promise<boolean>} 是否处理了跳转
|
||||
*/
|
||||
let isHandlingAuth = false;
|
||||
|
||||
/** 等待登录处理完成 */
|
||||
async function waitForAuthComplete() {
|
||||
while (isHandlingAuth) {
|
||||
await sleep(500, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAccountChooser(page) {
|
||||
// 防止重复处理
|
||||
if (isHandlingAuth) return false;
|
||||
@@ -68,8 +74,12 @@ async function handleAccountChooser(page) {
|
||||
isHandlingAuth = false;
|
||||
return true;
|
||||
} else {
|
||||
logger.warn('适配器', '[登录器] 未找到确认按钮 button[type="submit"]');
|
||||
isHandlingAuth = false;
|
||||
// 按钮还没加载出来,保持锁,等待下次检查
|
||||
// 不要释放 isHandlingAuth,让全局监听器下次再试
|
||||
logger.debug('适配器', '[登录器] 按钮尚未加载,等待中...');
|
||||
await sleep(500, 1000);
|
||||
isHandlingAuth = false; // 释放锁让下次尝试
|
||||
return true; // 返回 true 表示"仍在处理中"
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -91,59 +101,43 @@ async function handleAccountChooser(page) {
|
||||
async function waitForInputWithAccountChooser(page, options = {}) {
|
||||
const { timeout = 60000, click = true } = options;
|
||||
|
||||
// 设置导航监听器,自动处理账户选择页面跳转
|
||||
const navigationHandler = async () => {
|
||||
await handleAccountChooser(page);
|
||||
};
|
||||
page.on('framenavigated', navigationHandler);
|
||||
// 先检查一次当前页面 (全局监听器也会处理,但显式调用确保首次检查)
|
||||
await handleAccountChooser(page);
|
||||
|
||||
try {
|
||||
// 先检查一次当前页面
|
||||
await handleAccountChooser(page);
|
||||
|
||||
// 轮询等待输入框,同时处理账户选择
|
||||
const startTime = Date.now();
|
||||
while (Date.now() - startTime < timeout) {
|
||||
// 如果正在处理跳转,暂停检测输入框
|
||||
if (isHandlingAuth) {
|
||||
await sleep(500, 1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (await handleAccountChooser(page)) {
|
||||
// 处理了账户选择,重置等待
|
||||
continue;
|
||||
}
|
||||
|
||||
let inputHandle = null;
|
||||
try {
|
||||
inputHandle = await page.$(INPUT_SELECTOR);
|
||||
} catch (e) {
|
||||
// 忽略执行上下文销毁错误
|
||||
if (e.message.includes('Execution context was destroyed')) {
|
||||
inputHandle = null;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (inputHandle) break;
|
||||
|
||||
await sleep(1000, 1500);
|
||||
}
|
||||
|
||||
// 最终确认输入框存在
|
||||
await page.waitForSelector(INPUT_SELECTOR, { timeout: 5000 }).catch(() => {
|
||||
throw new Error('未找到输入框 (.ProseMirror)');
|
||||
});
|
||||
|
||||
if (click) {
|
||||
await safeClick(page, INPUT_SELECTOR, { bias: 'input' });
|
||||
// 轮询等待输入框
|
||||
const startTime = Date.now();
|
||||
while (Date.now() - startTime < timeout) {
|
||||
// 如果正在处理跳转,暂停检测输入框
|
||||
if (isHandlingAuth) {
|
||||
await sleep(500, 1000);
|
||||
continue;
|
||||
}
|
||||
} finally {
|
||||
// 清理监听器
|
||||
page.off('framenavigated', navigationHandler);
|
||||
|
||||
let inputHandle = null;
|
||||
try {
|
||||
inputHandle = await page.$(INPUT_SELECTOR);
|
||||
} catch (e) {
|
||||
// 忽略执行上下文销毁错误
|
||||
if (e.message.includes('Execution context was destroyed')) {
|
||||
inputHandle = null;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (inputHandle) break;
|
||||
|
||||
await sleep(1000, 1500);
|
||||
}
|
||||
|
||||
// 最终确认输入框存在
|
||||
await page.waitForSelector(INPUT_SELECTOR, { timeout: 5000 }).catch(() => {
|
||||
throw new Error('未找到输入框 (.ProseMirror)');
|
||||
});
|
||||
|
||||
if (click) {
|
||||
await safeClick(page, INPUT_SELECTOR, { bias: 'input' });
|
||||
await sleep(500, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +173,8 @@ async function initBrowser(config) {
|
||||
userDataDir: config.paths.userDataDir,
|
||||
targetUrl,
|
||||
productName: 'Gemini Enterprise Business',
|
||||
waitInputValidator
|
||||
waitInputValidator,
|
||||
navigationHandler: handleAccountChooser
|
||||
});
|
||||
return { ...base, config };
|
||||
}
|
||||
@@ -202,10 +197,15 @@ async function generateImage(context, prompt, imgPaths, modelId, meta = {}) {
|
||||
throw new Error('GeminiBiz backend missing entry URL');
|
||||
}
|
||||
|
||||
// 开启新对话
|
||||
// 开启新对话 - 先等待可能正在进行的登录处理完成
|
||||
await waitForAuthComplete();
|
||||
|
||||
logger.info('适配器', '开启新会话', meta);
|
||||
await page.goto(targetUrl, { waitUntil: 'domcontentloaded' });
|
||||
|
||||
// 如果触发了账户选择跳转,等待全局处理器完成
|
||||
await waitForAuthComplete();
|
||||
|
||||
// 1. 等待输入框加载(使用公共函数处理账户选择)
|
||||
logger.debug('适配器', '正在寻找输入框...', meta);
|
||||
await waitForInputWithAccountChooser(page, { click: false });
|
||||
|
||||
@@ -21,13 +21,20 @@ const INPUT_SELECTOR = '.tiptap.ProseMirror';
|
||||
// 入口 URL
|
||||
const TARGET_URL = 'https://zai.is/';
|
||||
|
||||
|
||||
/**
|
||||
* 处理 Discord OAuth2 登录流程
|
||||
* @param {import('playwright-core').Page} page
|
||||
* @returns {Promise<boolean>} 是否处理了登录
|
||||
*/
|
||||
let isHandlingAuth = false;
|
||||
|
||||
/** 等待登录处理完成 */
|
||||
async function waitForAuthComplete() {
|
||||
while (isHandlingAuth) {
|
||||
await sleep(500, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDiscordAuth(page) {
|
||||
// 防止重复处理
|
||||
if (isHandlingAuth) return false;
|
||||
@@ -112,57 +119,43 @@ async function handleDiscordAuth(page) {
|
||||
async function waitForInputWithAuth(page, options = {}) {
|
||||
const { timeout = 60000, click = true } = options;
|
||||
|
||||
// 设置导航监听器,自动处理登录页面跳转
|
||||
const navigationHandler = async () => {
|
||||
await handleDiscordAuth(page);
|
||||
};
|
||||
page.on('framenavigated', navigationHandler);
|
||||
// 先检查一次当前页面 (全局监听器也会处理,但显式调用确保首次检查)
|
||||
await handleDiscordAuth(page);
|
||||
|
||||
try {
|
||||
// 先检查一次当前页面
|
||||
await handleDiscordAuth(page);
|
||||
|
||||
// 轮询等待输入框,同时处理登录
|
||||
const startTime = Date.now();
|
||||
while (Date.now() - startTime < timeout) {
|
||||
// 如果正在处理登录,暂停检测输入框,避免冲突
|
||||
if (isHandlingAuth) {
|
||||
await sleep(500, 1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (await handleDiscordAuth(page)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let inputHandle = null;
|
||||
try {
|
||||
inputHandle = await page.$(INPUT_SELECTOR);
|
||||
} catch (e) {
|
||||
// 忽略执行上下文销毁错误 (通常发生在页面刷新/跳转时)
|
||||
if (e.message.includes('Execution context was destroyed')) {
|
||||
inputHandle = null;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (inputHandle) break;
|
||||
|
||||
await sleep(1000, 1500);
|
||||
}
|
||||
|
||||
// 最终确认输入框存在
|
||||
await page.waitForSelector(INPUT_SELECTOR, { timeout: 5000 }).catch(() => {
|
||||
throw new Error('未找到输入框 (.tiptap.ProseMirror)');
|
||||
});
|
||||
|
||||
if (click) {
|
||||
await safeClick(page, INPUT_SELECTOR, { bias: 'input' });
|
||||
// 轮询等待输入框
|
||||
const startTime = Date.now();
|
||||
while (Date.now() - startTime < timeout) {
|
||||
// 如果正在处理登录,暂停检测输入框,避免冲突
|
||||
if (isHandlingAuth) {
|
||||
await sleep(500, 1000);
|
||||
continue;
|
||||
}
|
||||
} finally {
|
||||
page.off('framenavigated', navigationHandler);
|
||||
|
||||
let inputHandle = null;
|
||||
try {
|
||||
inputHandle = await page.$(INPUT_SELECTOR);
|
||||
} catch (e) {
|
||||
// 忽略执行上下文销毁错误 (通常发生在页面刷新/跳转时)
|
||||
if (e.message.includes('Execution context was destroyed')) {
|
||||
inputHandle = null;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (inputHandle) break;
|
||||
|
||||
await sleep(1000, 1500);
|
||||
}
|
||||
|
||||
// 最终确认输入框存在
|
||||
await page.waitForSelector(INPUT_SELECTOR, { timeout: 5000 }).catch(() => {
|
||||
throw new Error('未找到输入框 (.tiptap.ProseMirror)');
|
||||
});
|
||||
|
||||
if (click) {
|
||||
await safeClick(page, INPUT_SELECTOR, { bias: 'input' });
|
||||
await sleep(500, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +174,8 @@ async function initBrowser(config) {
|
||||
userDataDir: config.paths.userDataDir,
|
||||
targetUrl: TARGET_URL,
|
||||
productName: 'Zai.is',
|
||||
waitInputValidator
|
||||
waitInputValidator,
|
||||
navigationHandler: handleDiscordAuth
|
||||
});
|
||||
return { ...base, config };
|
||||
}
|
||||
@@ -199,10 +193,15 @@ async function generateImage(context, prompt, imgPaths, modelId, meta = {}) {
|
||||
const { page, config } = context;
|
||||
|
||||
try {
|
||||
// 开启新对话
|
||||
// 开启新对话 - 先等待可能正在进行的登录处理完成
|
||||
await waitForAuthComplete();
|
||||
|
||||
logger.info('适配器', '开启新会话', meta);
|
||||
await page.goto(TARGET_URL, { waitUntil: 'domcontentloaded' });
|
||||
|
||||
// 如果触发了登录跳转,等待全局处理器完成
|
||||
await waitForAuthComplete();
|
||||
|
||||
// 1. 等待输入框加载(使用公共函数处理登录)
|
||||
logger.debug('适配器', '正在寻找输入框...', meta);
|
||||
await waitForInputWithAuth(page, { click: false });
|
||||
|
||||
+15
-1
@@ -174,6 +174,7 @@ function getPersistentFingerprint(filePath) {
|
||||
* @param {string} options.productName - 产品名称(用于日志)
|
||||
* @param {boolean} [options.reuseExistingTab=false] - 是否复用已有特定域名的 tab
|
||||
* @param {Function} [options.waitInputValidator] - 自定义输入框等待验证函数
|
||||
* @param {Function} [options.navigationHandler] - 全局导航处理器,用于自动处理登录等跳转
|
||||
* @returns {Promise<{browser: object, page: object, client: object}>}
|
||||
*/
|
||||
export async function initBrowserBase(config, options) {
|
||||
@@ -181,7 +182,8 @@ export async function initBrowserBase(config, options) {
|
||||
userDataDir,
|
||||
targetUrl,
|
||||
productName,
|
||||
waitInputValidator = null
|
||||
waitInputValidator = null,
|
||||
navigationHandler = null
|
||||
} = options;
|
||||
|
||||
// 检测登录模式和 Xvfb 模式
|
||||
@@ -275,6 +277,18 @@ export async function initBrowserBase(config, options) {
|
||||
await page.setViewportSize(camoufoxLaunchOptions.viewport);
|
||||
}
|
||||
|
||||
// 注册全局导航处理器(用于自动处理登录等跳转)
|
||||
if (navigationHandler) {
|
||||
page.on('framenavigated', async () => {
|
||||
try {
|
||||
await navigationHandler(page);
|
||||
} catch (e) {
|
||||
logger.warn('浏览器', `全局导航处理器出错: ${e.message}`);
|
||||
}
|
||||
});
|
||||
logger.debug('浏览器', '已注册全局导航处理器');
|
||||
}
|
||||
|
||||
// 登录模式挂起逻辑
|
||||
if (isLoginMode) {
|
||||
// 尝试导航到目标页面方便用户登录
|
||||
|
||||
Reference in New Issue
Block a user