fix: account open launches Chrome/Edge directly like reveal-profile
All checks were successful
技能自动化发布 / release (push) Successful in 6s
All checks were successful
技能自动化发布 / release (push) Successful in 6s
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
"""
|
||||
Chromium/Edge browser profile opener.
|
||||
|
||||
Uses system Chrome/Edge channel, Playwright launch_persistent_context.
|
||||
Uses system Chrome/Edge executable with --user-data-dir (same profile as RPA shortcuts).
|
||||
Does NOT download Playwright Chromium.
|
||||
Does NOT check login status.
|
||||
Does NOT write secret to SQLite.
|
||||
@@ -11,20 +11,12 @@ import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from db.connection import get_conn, init_db
|
||||
from db.accounts_repo import get_account_by_id, get_platform_from_db
|
||||
from util.logging_config import (
|
||||
get_skill_logger,
|
||||
subprocess_env_with_trace,
|
||||
)
|
||||
from util.logging_config import get_skill_logger
|
||||
from util.platforms import PLATFORMS
|
||||
|
||||
# 子进程入口脚本与本模块同目录(独立解释器执行,非 import)
|
||||
_SERVICE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
_SCRIPTS_ROOT = os.path.dirname(_SERVICE_DIR)
|
||||
|
||||
|
||||
def _win_find_exe(candidates):
|
||||
for p in candidates:
|
||||
@@ -166,33 +158,11 @@ def cmd_reveal_profile(account_id):
|
||||
)
|
||||
|
||||
|
||||
def _spawn_browser_child(cmd: list[str], *, cwd: str, env: dict) -> subprocess.Popen:
|
||||
"""Detach Playwright child so `account open` can return immediately."""
|
||||
popen_kwargs: dict = {
|
||||
"cwd": cwd,
|
||||
"env": env,
|
||||
"stdin": subprocess.DEVNULL,
|
||||
"stdout": subprocess.DEVNULL,
|
||||
"stderr": subprocess.DEVNULL,
|
||||
"close_fds": True,
|
||||
}
|
||||
if sys.platform == "win32":
|
||||
# Keep browser window; detach from parent console / Skill Action wait.
|
||||
flags = subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.DETACHED_PROCESS
|
||||
create_no_window = getattr(subprocess, "CREATE_NO_WINDOW", 0)
|
||||
if create_no_window:
|
||||
flags |= create_no_window
|
||||
popen_kwargs["creationflags"] = flags
|
||||
else:
|
||||
popen_kwargs["start_new_session"] = True
|
||||
return subprocess.Popen(cmd, **popen_kwargs)
|
||||
|
||||
|
||||
def cmd_open(account_id):
|
||||
"""
|
||||
打开该账号的持久化浏览器,仅用于肉眼核对。
|
||||
不写数据库、不判定是否已登录。
|
||||
拉起浏览器子进程后立即返回(不等待窗口关闭),便于 Skill Action sync/direct。
|
||||
与 reveal-profile 同模式:本进程直接拉起系统 Chrome/Edge 后立即返回。
|
||||
不写数据库、不判定是否已登录、不起 Playwright / 额外 python 子进程。
|
||||
"""
|
||||
log = get_skill_logger()
|
||||
init_db()
|
||||
@@ -204,23 +174,23 @@ def cmd_open(account_id):
|
||||
print("ERROR:ACCOUNT_NOT_FOUND")
|
||||
sys.exit(1)
|
||||
|
||||
channel = resolve_chromium_channel()
|
||||
if not channel:
|
||||
_print_browser_install_hint()
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
import playwright # noqa: F401
|
||||
except ImportError:
|
||||
print("ERROR:需要 playwright Python 包。")
|
||||
sys.exit(1)
|
||||
browser_exe = resolve_chromium_executable()
|
||||
if not browser_exe:
|
||||
channel = resolve_chromium_channel()
|
||||
if sys.platform == "win32" or not channel:
|
||||
_print_browser_install_hint()
|
||||
sys.exit(1)
|
||||
# 非 Windows:退回 PATH 上的常见命令名
|
||||
browser_exe = "google-chrome" if channel == "chrome" else "microsoft-edge"
|
||||
|
||||
profile_dir = (target.get("profile_dir") or "").strip()
|
||||
if not profile_dir:
|
||||
log.warning("browser_open_error account_id=%s profile_dir_missing", account_id)
|
||||
print("ERROR:PROFILE_DIR_MISSING 账号中 profile_dir 为空。")
|
||||
sys.exit(1)
|
||||
os.makedirs(profile_dir, exist_ok=True)
|
||||
|
||||
profile_abs = os.path.abspath(profile_dir)
|
||||
os.makedirs(profile_abs, exist_ok=True)
|
||||
|
||||
conn = get_conn()
|
||||
try:
|
||||
@@ -228,41 +198,48 @@ def cmd_open(account_id):
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
# 与 profile_shortcut 一致:独立 user-data-dir 保存登录态
|
||||
cmd = [browser_exe, f"--user-data-dir={profile_abs}"]
|
||||
if url:
|
||||
cmd.append(url)
|
||||
|
||||
log.info(
|
||||
"browser_open_context profile_dir=%s url=%s channel=%s platform=%s",
|
||||
profile_dir,
|
||||
"browser_open_launch exe=%s profile_dir=%s url=%s platform=%s",
|
||||
browser_exe,
|
||||
profile_abs,
|
||||
url,
|
||||
channel,
|
||||
target.get("platform_key"),
|
||||
)
|
||||
|
||||
browser_name = "Google Chrome" if channel == "chrome" else "Microsoft Edge"
|
||||
cfg = {"channel": channel, "profile_dir": profile_dir, "url": url}
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode="w", suffix=".json", delete=False, encoding="utf-8"
|
||||
) as jf:
|
||||
json.dump(cfg, jf, ensure_ascii=False)
|
||||
cfg_path = jf.name
|
||||
|
||||
try:
|
||||
child = _spawn_browser_child(
|
||||
[sys.executable, "-m", "service.open_child_runner", cfg_path],
|
||||
cwd=_SCRIPTS_ROOT,
|
||||
env=subprocess_env_with_trace(),
|
||||
)
|
||||
except Exception as e:
|
||||
try:
|
||||
os.unlink(cfg_path)
|
||||
except OSError:
|
||||
pass
|
||||
log.error("browser_open_error account_id=%s error=%s", account_id, str(e))
|
||||
print(f"ERROR:BROWSER_OPEN_FAILED {e}")
|
||||
# GUI 浏览器:不挂控制台黑框;不等待关闭(与 reveal 一致立刻返回)
|
||||
popen_kwargs: dict = {
|
||||
"stdin": subprocess.DEVNULL,
|
||||
"stdout": subprocess.DEVNULL,
|
||||
"stderr": subprocess.DEVNULL,
|
||||
"close_fds": True,
|
||||
}
|
||||
if os.path.isabs(browser_exe):
|
||||
popen_kwargs["cwd"] = os.path.dirname(browser_exe)
|
||||
if sys.platform == "win32":
|
||||
popen_kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP
|
||||
else:
|
||||
popen_kwargs["start_new_session"] = True
|
||||
child = subprocess.Popen(cmd, **popen_kwargs)
|
||||
except OSError as exc:
|
||||
log.error("browser_open_error account_id=%s error=%s", account_id, str(exc))
|
||||
print(f"ERROR:BROWSER_OPEN_FAILED {exc}")
|
||||
sys.exit(1)
|
||||
|
||||
browser_name = (
|
||||
"Microsoft Edge"
|
||||
if os.path.basename(browser_exe).lower() == "msedge.exe"
|
||||
else "Google Chrome"
|
||||
)
|
||||
log.info(
|
||||
"browser_open_spawned account_id=%s channel=%s pid=%s",
|
||||
"browser_open_ok account_id=%s browser=%s pid=%s",
|
||||
account_id,
|
||||
channel,
|
||||
browser_name,
|
||||
child.pid,
|
||||
)
|
||||
print(
|
||||
@@ -273,10 +250,9 @@ def cmd_open(account_id):
|
||||
"account_id": int(target["id"]) if target.get("id") is not None else account_id,
|
||||
"account_label": target.get("account_label"),
|
||||
"browser": browser_name,
|
||||
"channel": channel,
|
||||
"profile_dir": os.path.abspath(profile_dir),
|
||||
"browser_exe": browser_exe,
|
||||
"profile_dir": profile_abs,
|
||||
"url": url,
|
||||
"detached": True,
|
||||
"pid": child.pid,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
"""Playwright 仅打开浏览器子进程:由 browser_service.cmd_open 启动,argv[1] 为 JSON 配置路径。
|
||||
|
||||
父进程拉起后即返回;本子进程自行维护浏览器生命周期,读完配置后删除临时 JSON。
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
from util.playwright_stealth import (
|
||||
STEALTH_INIT_SCRIPT,
|
||||
persistent_context_launch_parts,
|
||||
stealth_enabled,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
cfg_path = sys.argv[1]
|
||||
with open(cfg_path, encoding="utf-8") as f:
|
||||
c = json.load(f)
|
||||
try:
|
||||
os.unlink(cfg_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
with sync_playwright() as p:
|
||||
args, ignore_automation = persistent_context_launch_parts()
|
||||
lc_kw = dict(
|
||||
user_data_dir=c["profile_dir"],
|
||||
headless=False,
|
||||
channel=c["channel"],
|
||||
no_viewport=True,
|
||||
args=args,
|
||||
)
|
||||
if ignore_automation is not None:
|
||||
lc_kw["ignore_default_args"] = ignore_automation
|
||||
ctx = p.chromium.launch_persistent_context(**lc_kw)
|
||||
if stealth_enabled():
|
||||
ctx.add_init_script(STEALTH_INIT_SCRIPT)
|
||||
try:
|
||||
page = ctx.pages[0] if ctx.pages else ctx.new_page()
|
||||
page.goto(c["url"], wait_until="domcontentloaded", timeout=60000)
|
||||
ctx.wait_for_event("close", timeout=86400000)
|
||||
except Exception as e:
|
||||
print(e, file=sys.stderr)
|
||||
raise
|
||||
finally:
|
||||
try:
|
||||
ctx.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user