Skip to content

Commit e3ff488

Browse files
authored
Merge pull request #218 from theguy000/main
feat(auth): Implement auth profile management in GUI
2 parents 03d40b1 + f6f55fd commit e3ff488

3 files changed

Lines changed: 548 additions & 242 deletions

File tree

browser_utils/initialization.py

Lines changed: 64 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ async def _initialize_page_logic(browser: AsyncBrowser):
265265
launch_mode = os.environ.get('LAUNCH_MODE', 'debug')
266266
logger.info(f" 检测到启动模式: {launch_mode}")
267267
loop = asyncio.get_running_loop()
268-
268+
269269
if launch_mode == 'headless' or launch_mode == 'virtual_headless':
270270
auth_filename = os.environ.get('ACTIVE_AUTH_JSON_PATH')
271271
if auth_filename:
@@ -293,7 +293,7 @@ async def _initialize_page_logic(browser: AsyncBrowser):
293293
logger.info(" direct_debug_no_browser 模式:不加载 storage_state,不进行浏览器操作。")
294294
else:
295295
logger.warning(f" ⚠️ 警告: 未知的启动模式 '{launch_mode}'。不加载 storage_state。")
296-
296+
297297
try:
298298
logger.info("创建新的浏览器上下文...")
299299
context_options: Dict[str, Any] = {'viewport': {'width': 460, 'height': 800}}
@@ -302,18 +302,18 @@ async def _initialize_page_logic(browser: AsyncBrowser):
302302
logger.info(f" (使用 storage_state='{os.path.basename(storage_state_path_to_use)}')")
303303
else:
304304
logger.info(" (不使用 storage_state)")
305-
305+
306306
# 代理设置需要从server模块中获取
307307
import server
308308
if server.PLAYWRIGHT_PROXY_SETTINGS:
309309
context_options['proxy'] = server.PLAYWRIGHT_PROXY_SETTINGS
310310
logger.info(f" (浏览器上下文将使用代理: {server.PLAYWRIGHT_PROXY_SETTINGS['server']})")
311311
else:
312312
logger.info(" (浏览器上下文不使用显式代理配置)")
313-
313+
314314
context_options['ignore_https_errors'] = True
315315
logger.info(" (浏览器上下文将忽略 HTTPS 错误)")
316-
316+
317317
temp_context = await browser.new_context(**context_options)
318318

319319
# 设置网络拦截和脚本注入
@@ -325,10 +325,10 @@ async def _initialize_page_logic(browser: AsyncBrowser):
325325
target_full_url = f"{target_url_base}prompts/new_chat"
326326
login_url_pattern = 'accounts.google.com'
327327
current_url = ""
328-
328+
329329
# 导入_handle_model_list_response - 需要延迟导入避免循环引用
330330
from .operations import _handle_model_list_response
331-
331+
332332
for p_iter in pages:
333333
try:
334334
page_url_to_check = p_iter.url
@@ -346,7 +346,7 @@ async def _initialize_page_logic(browser: AsyncBrowser):
346346
logger.warning(f" 检查页面 URL 时出现属性错误: {attr_err_url}")
347347
except Exception as e_url_check:
348348
logger.warning(f" 检查页面 URL 时出现其他未预期错误: {e_url_check} (类型: {type(e_url_check).__name__})")
349-
349+
350350
if not found_page:
351351
logger.info(f"-> 未找到合适的现有页面,正在打开新页面并导航到 {target_full_url}...")
352352
found_page = await temp_context.new_page()
@@ -374,18 +374,22 @@ async def _initialize_page_logic(browser: AsyncBrowser):
374374
logger.error(" 5. 系统资源问题: 确保系统有足够的内存和 CPU 资源。")
375375
logger.error("="*74 + "\n")
376376
raise RuntimeError(f"导航新页面失败: {new_page_nav_err}") from new_page_nav_err
377-
377+
378378
if login_url_pattern in current_url:
379379
if launch_mode == 'headless':
380380
logger.error("无头模式下检测到重定向至登录页面,认证可能已失效。请更新认证文件。")
381381
raise RuntimeError("无头模式认证失败,需要更新认证文件。")
382382
else:
383383
print(f"\n{'='*20} 需要操作 {'='*20}", flush=True)
384384
login_prompt = " 检测到可能需要登录。如果浏览器显示登录页面,请在浏览器窗口中完成 Google 登录,然后在此处按 Enter 键继续..."
385-
print(USER_INPUT_START_MARKER_SERVER, flush=True)
386-
await loop.run_in_executor(None, input, login_prompt)
387-
print(USER_INPUT_END_MARKER_SERVER, flush=True)
388-
logger.info(" 用户已操作,正在检查登录状态...")
385+
# NEW: If SUPPRESS_LOGIN_WAIT is set, skip waiting for user input.
386+
if os.environ.get("SUPPRESS_LOGIN_WAIT", "").lower() in ("1", "true", "yes"):
387+
logger.info("检测到 SUPPRESS_LOGIN_WAIT 标志,跳过等待用户输入。")
388+
else:
389+
print(USER_INPUT_START_MARKER_SERVER, flush=True)
390+
await loop.run_in_executor(None, input, login_prompt)
391+
print(USER_INPUT_END_MARKER_SERVER, flush=True)
392+
logger.info(" 正在检查登录状态...")
389393
try:
390394
await found_page.wait_for_url(f"**/{AI_STUDIO_URL_PATTERN}**", timeout=180000)
391395
current_url = found_page.url
@@ -394,36 +398,39 @@ async def _initialize_page_logic(browser: AsyncBrowser):
394398
raise RuntimeError("手动登录尝试后仍在登录页面。")
395399
logger.info(" ✅ 登录成功!请不要操作浏览器窗口,等待后续提示。")
396400

397-
# 等待模型列表响应,确认登录成功
398-
await _wait_for_model_list_and_handle_auth_save(temp_context, launch_mode, loop)
401+
# 登录成功后,调用认证保存逻辑
402+
if os.environ.get('AUTO_SAVE_AUTH', 'false').lower() == 'true':
403+
await _wait_for_model_list_and_handle_auth_save(temp_context, launch_mode, loop)
404+
399405
except Exception as wait_login_err:
400406
from .operations import save_error_snapshot
401407
await save_error_snapshot("init_login_wait_fail")
402408
logger.error(f"登录提示后未能检测到 AI Studio URL 或保存状态时出错: {wait_login_err}", exc_info=True)
403409
raise RuntimeError(f"登录提示后未能检测到 AI Studio URL: {wait_login_err}") from wait_login_err
410+
404411
elif target_url_base not in current_url or "/prompts/" not in current_url:
405412
from .operations import save_error_snapshot
406413
await save_error_snapshot("init_unexpected_page")
407414
logger.error(f"初始导航后页面 URL 意外: {current_url}。期望包含 '{target_url_base}' 和 '/prompts/'。")
408415
raise RuntimeError(f"初始导航后出现意外页面: {current_url}。")
409-
416+
410417
logger.info(f"-> 确认当前位于 AI Studio 对话页面: {current_url}")
411418
await found_page.bring_to_front()
412-
419+
413420
try:
414421
input_wrapper_locator = found_page.locator('ms-prompt-input-wrapper')
415422
await expect_async(input_wrapper_locator).to_be_visible(timeout=35000)
416423
await expect_async(found_page.locator(INPUT_SELECTOR)).to_be_visible(timeout=10000)
417424
logger.info("-> ✅ 核心输入区域可见。")
418-
425+
419426
model_name_locator = found_page.locator('mat-select[data-test-ms-model-selector] div.model-option-content span.gmat-body-medium')
420427
try:
421428
model_name_on_page = await model_name_locator.first.inner_text(timeout=5000)
422429
logger.info(f"-> 🤖 页面检测到的当前模型: {model_name_on_page}")
423430
except PlaywrightAsyncError as e:
424431
logger.error(f"获取模型名称时出错 (model_name_locator): {e}")
425432
raise
426-
433+
427434
result_page_instance = found_page
428435
result_page_ready = True
429436

@@ -504,6 +511,19 @@ async def _wait_for_model_list_and_handle_auth_save(temp_context, launch_mode, l
504511
except asyncio.TimeoutError:
505512
logger.warning(" ⚠️ 等待模型列表响应超时,但继续处理认证保存...")
506513

514+
# 检查是否有预设的文件名用于保存
515+
save_auth_filename = os.environ.get('SAVE_AUTH_FILENAME', '').strip()
516+
if save_auth_filename:
517+
logger.info(f" 检测到 SAVE_AUTH_FILENAME 环境变量: '{save_auth_filename}'。将自动保存认证文件。")
518+
await _handle_auth_file_save_with_filename(temp_context, save_auth_filename)
519+
return
520+
521+
# If not auto-saving, proceed with interactive prompts
522+
await _interactive_auth_save(temp_context, launch_mode, loop)
523+
524+
525+
async def _interactive_auth_save(temp_context, launch_mode, loop):
526+
"""处理认证文件保存的交互式提示"""
507527
# 检查是否启用自动确认
508528
if AUTO_CONFIRM_LOGIN:
509529
print("\n" + "="*50, flush=True)
@@ -562,7 +582,6 @@ async def _handle_auth_file_save(temp_context, loop):
562582
finally:
563583
print(USER_INPUT_END_MARKER_SERVER, flush=True)
564584

565-
# 检查用户是否选择取消
566585
if chosen_auth_filename.strip().lower() == 'cancel':
567586
print(" 用户选择取消保存认证状态。", flush=True)
568587
return
@@ -575,12 +594,33 @@ async def _handle_auth_file_save(temp_context, loop):
575594

576595
try:
577596
await temp_context.storage_state(path=auth_save_path)
597+
logger.info(f" 认证状态已成功保存到: {auth_save_path}")
578598
print(f" ✅ 认证状态已成功保存到: {auth_save_path}", flush=True)
579599
except Exception as save_state_err:
580600
logger.error(f" ❌ 保存认证状态失败: {save_state_err}", exc_info=True)
581601
print(f" ❌ 保存认证状态失败: {save_state_err}", flush=True)
582602

583603

604+
async def _handle_auth_file_save_with_filename(temp_context, filename: str):
605+
"""处理认证文件保存(使用提供的文件名)"""
606+
os.makedirs(SAVED_AUTH_DIR, exist_ok=True)
607+
608+
# Clean the filename and add .json if needed
609+
final_auth_filename = filename.strip()
610+
if not final_auth_filename.endswith(".json"):
611+
final_auth_filename += ".json"
612+
613+
auth_save_path = os.path.join(SAVED_AUTH_DIR, final_auth_filename)
614+
615+
try:
616+
await temp_context.storage_state(path=auth_save_path)
617+
print(f" ✅ 认证状态已自动保存到: {auth_save_path}", flush=True)
618+
logger.info(f" 自动保存认证状态成功: {auth_save_path}")
619+
except Exception as save_state_err:
620+
logger.error(f" ❌ 自动保存认证状态失败: {save_state_err}", exc_info=True)
621+
print(f" ❌ 自动保存认证状态失败: {save_state_err}", flush=True)
622+
623+
584624
async def _handle_auth_file_save_auto(temp_context):
585625
"""处理认证文件保存(自动模式)"""
586626
os.makedirs(SAVED_AUTH_DIR, exist_ok=True)
@@ -592,8 +632,8 @@ async def _handle_auth_file_save_auto(temp_context):
592632

593633
try:
594634
await temp_context.storage_state(path=auth_save_path)
595-
print(f" ✅ 认证状态已自动保存到: {auth_save_path}", flush=True)
596-
logger.info(f" 自动保存认证状态成功: {auth_save_path}")
635+
logger.info(f" 认证状态已成功保存到: {auth_save_path}")
636+
print(f" ✅ 认证状态已成功保存到: {auth_save_path}", flush=True)
597637
except Exception as save_state_err:
598-
logger.error(f" ❌ 自动保存认证状态失败: {save_state_err}", exc_info=True)
599-
print(f" ❌ 自动保存认证状态失败: {save_state_err}", flush=True)
638+
logger.error(f" ❌ 保存认证状态失败: {save_state_err}", exc_info=True)
639+
print(f" ❌ 保存认证状态失败: {save_state_err}", flush=True)

0 commit comments

Comments
 (0)