Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 75a332e7b8 | |||
| 4605d3d0e4 | |||
| beb95c1415 |
6
SKILL.md
6
SKILL.md
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: 账号管理
|
name: 账号管理
|
||||||
description: 多平台多账号管理。管理各平台账号与Chrome Profile的对应关系,供publisher类Skill调用获取账号信息。
|
description: 多平台多账号管理。管理各平台账号与Chrome Profile的对应关系,供publisher类Skill调用获取账号信息。
|
||||||
version: 1.0.6
|
version: 1.0.52
|
||||||
author: 深圳匠厂科技有限公司
|
author: 深圳匠厂科技有限公司
|
||||||
metadata:
|
metadata:
|
||||||
openclaw:
|
openclaw:
|
||||||
@@ -14,6 +14,10 @@ allowed-tools:
|
|||||||
|
|
||||||
# 账号管理
|
# 账号管理
|
||||||
|
|
||||||
|
## JIANGCHANG 共享 Python(匠厂桌面)
|
||||||
|
|
||||||
|
依赖由宿主安装到 `{JIANGCHANG_DATA_ROOT}/python-runtime/.venv`(技能市场安装时自动 `uv sync`)。勿在技能目录内创建 `.venv`。Playwright 使用本机 **Chrome / Edge**(`channel=chrome|msedge`),**不**下载自带 Chromium。
|
||||||
|
|
||||||
## 使用时机
|
## 使用时机
|
||||||
|
|
||||||
当用户发送以下内容的时候触发本Skill:
|
当用户发送以下内容的时候触发本Skill:
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
|
|
||||||
| 前缀 | 含义 |
|
| 前缀 | 含义 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| `ERROR:需要 playwright:pip install playwright && playwright install chromium` | 未安装 Playwright 或浏览器未安装 |
|
| `ERROR:需要 playwright Python 包:…` | 未安装 `playwright` 包,或本机无 Chrome/Edge(channel 模式,**不**需 `playwright install chromium`) |
|
||||||
| `ERROR:PROFILE_DIR_MISSING` | 账号记录中 `profile_dir` 为空 |
|
| `ERROR:PROFILE_DIR_MISSING` | 账号记录中 `profile_dir` 为空 |
|
||||||
|
|
||||||
## 非前缀输出
|
## 非前缀输出
|
||||||
|
|||||||
@@ -46,10 +46,10 @@
|
|||||||
| `JIANGCHANG_LOGIN_POLL_INTERVAL_SECONDS` | DOM 轮询间隔,默认 `1.5`,限制在 `0.5..10` |
|
| `JIANGCHANG_LOGIN_POLL_INTERVAL_SECONDS` | DOM 轮询间隔,默认 `1.5`,限制在 `0.5..10` |
|
||||||
| `JIANGCHANG_LOGIN_DOM_GRACE_SECONDS` | 打开 URL 后等待再检测,默认 `4`,减轻跳转误判 |
|
| `JIANGCHANG_LOGIN_DOM_GRACE_SECONDS` | 打开 URL 后等待再检测,默认 `4`,减轻跳转误判 |
|
||||||
|
|
||||||
依赖:**本机已安装 Chrome 或 Edge**;Python 环境需 **`playwright`** 且已执行 `playwright install chromium`(错误信息见 CLI 输出)。
|
依赖:**本机已安装 Chrome 或 Edge**;Python 环境需 **`playwright` 包**(匠厂共享 `python-runtime` 已包含)。运行时使用 **Playwright `channel=chrome|msedge`** 调系统浏览器,**不要**执行 `playwright install chromium`(避免下载自带 Chromium)。
|
||||||
|
|
||||||
## 常见问题(简要)
|
## 常见问题(简要)
|
||||||
|
|
||||||
- **`ERROR:需要 playwright`**:安装 `playwright` 并安装浏览器内核。
|
- **`ERROR:需要 playwright Python 包`**:确认共享 venv 已 `uv sync`;本机安装 Chrome 或 Edge。
|
||||||
- **`ERROR:PROFILE_DIR_MISSING`**:库中该账号 `profile_dir` 为空,需检查数据或重新添加账号流程。
|
- **`ERROR:PROFILE_DIR_MISSING`**:库中该账号 `profile_dir` 为空,需检查数据或重新添加账号流程。
|
||||||
- **登录检测一直失败**:关闭占用同一 profile 目录的其它浏览器;可调高超时与环境变量;查看统一日志文件。
|
- **登录检测一直失败**:关闭占用同一 profile 目录的其它浏览器;可调高超时与环境变量;查看统一日志文件。
|
||||||
|
|||||||
@@ -1,258 +1,137 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
JIANGCHANG_* 数据根与用户目录:解析规则 + 可选本地 CLI 默认值。
|
JIANGCHANG_* 数据根与用户目录:解析规则 + 可选本地 CLI 默认值。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
各技能 scripts/jiangchang_skill_core/ 为发布用副本,与 kit 保持同步。
|
各技能 scripts/jiangchang_skill_core/ 为发布用副本,与 kit 保持同步。
|
||||||
|
|
||||||
|
宿主在 skills.entries 与 Gateway 中注入:
|
||||||
|
- PATH 前缀:{JIANGCHANG_DATA_ROOT}/python-runtime/.venv/(Scripts|bin)
|
||||||
|
- VIRTUAL_ENV、JIANGCHANG_PYTHON_EXE
|
||||||
|
Playwright 使用本机 Chrome/Edge(launch 时 channel=chrome|msedge),不依赖宿主下载的 Chromium 包。
|
||||||
|
技能勿在仓库内维护独立 .venv;依赖以 jiangchang-platform-kit/python-runtime 锁文件为准。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 发版/嵌入宿主前改为 False,或仅通过环境变量 JIANGCHANG_CLI_LOCAL_DEV=1 开启
|
# 发版/嵌入宿主前改为 False,或仅通过环境变量 JIANGCHANG_CLI_LOCAL_DEV=1 开启
|
||||||
|
|
||||||
CLI_LOCAL_DEV_ENABLED = True
|
CLI_LOCAL_DEV_ENABLED = True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 本地开发且未设置 JIANGCHANG_USER_ID 时注入(与宿主约定一致即可修改)
|
# 本地开发且未设置 JIANGCHANG_USER_ID 时注入(与宿主约定一致即可修改)
|
||||||
|
|
||||||
DEFAULT_LOCAL_USER_ID = "10032"
|
DEFAULT_LOCAL_USER_ID = "10032"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Windows 下未设置 JIANGCHANG_DATA_ROOT 时的默认盘路径
|
# Windows 下未设置 JIANGCHANG_DATA_ROOT 时的默认盘路径
|
||||||
|
|
||||||
WIN_DEFAULT_DATA_ROOT = r"D:\jiangchang-data"
|
WIN_DEFAULT_DATA_ROOT = r"D:\jiangchang-data"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 匠厂桌面宿主应用根(未设置 JIANGCHANG_APP_ROOT 时 Windows 兜底;技能一般在 {APP}/skills/ 下并列)
|
# 匠厂桌面宿主应用根(未设置 JIANGCHANG_APP_ROOT 时 Windows 兜底;技能一般在 {APP}/skills/ 下并列)
|
||||||
|
|
||||||
WIN_DEFAULT_JIANGCHANG_APP_ROOT = r"D:\AI\jiangchang"
|
WIN_DEFAULT_JIANGCHANG_APP_ROOT = r"D:\AI\jiangchang"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def platform_default_data_root() -> str:
|
def platform_default_data_root() -> str:
|
||||||
|
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
|
|
||||||
return WIN_DEFAULT_DATA_ROOT
|
return WIN_DEFAULT_DATA_ROOT
|
||||||
|
|
||||||
return os.path.join(os.path.expanduser("~"), ".jiangchang-data")
|
return os.path.join(os.path.expanduser("~"), ".jiangchang-data")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_data_root() -> str:
|
def get_data_root() -> str:
|
||||||
|
|
||||||
env = (os.getenv("JIANGCHANG_DATA_ROOT") or "").strip()
|
env = (os.getenv("JIANGCHANG_DATA_ROOT") or "").strip()
|
||||||
|
|
||||||
if env:
|
if env:
|
||||||
|
|
||||||
return env
|
return env
|
||||||
|
|
||||||
return platform_default_data_root()
|
return platform_default_data_root()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_id() -> str:
|
def get_user_id() -> str:
|
||||||
|
|
||||||
return (os.getenv("JIANGCHANG_USER_ID") or "").strip() or "_anon"
|
return (os.getenv("JIANGCHANG_USER_ID") or "").strip() or "_anon"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _looks_like_skills_root(path: str) -> bool:
|
def _looks_like_skills_root(path: str) -> bool:
|
||||||
|
|
||||||
if not path or not os.path.isdir(path):
|
if not path or not os.path.isdir(path):
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
for marker in (
|
for marker in (
|
||||||
|
|
||||||
"llm-manager",
|
"llm-manager",
|
||||||
|
|
||||||
"content-manager",
|
"content-manager",
|
||||||
|
|
||||||
"account-manager",
|
"account-manager",
|
||||||
|
|
||||||
"sohu-publisher",
|
"sohu-publisher",
|
||||||
|
"toutiao-publisher",
|
||||||
|
"logistics-tracker",
|
||||||
"api-key-vault",
|
"api-key-vault",
|
||||||
|
|
||||||
):
|
):
|
||||||
|
|
||||||
if os.path.isdir(os.path.join(path, marker)):
|
if os.path.isdir(os.path.join(path, marker)):
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_skills_root() -> str:
|
def get_skills_root() -> str:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
并列技能安装目录(其下为「技能 slug」子目录)。
|
并列技能安装目录(其下为「技能 slug」子目录)。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
优先级:
|
优先级:
|
||||||
|
|
||||||
1) JIANGCHANG_SKILLS_ROOT
|
1) JIANGCHANG_SKILLS_ROOT
|
||||||
|
|
||||||
2) CLAW_SKILLS_ROOT
|
2) CLAW_SKILLS_ROOT
|
||||||
|
|
||||||
3) JIANGCHANG_APP_ROOT:若 {APP}/skills 存在且像技能根则用之;否则若 APP 下已有技能子目录则 APP 即根
|
3) JIANGCHANG_APP_ROOT:若 {APP}/skills 存在且像技能根则用之;否则若 APP 下已有技能子目录则 APP 即根
|
||||||
|
|
||||||
4) Windows:默认 D:\\AI\\jiangchang 同上规则
|
4) Windows:默认 D:\\AI\\jiangchang 同上规则
|
||||||
|
|
||||||
5) 其他平台:~/.openclaw/skills
|
5) 其他平台:~/.openclaw/skills
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for key in ("JIANGCHANG_SKILLS_ROOT", "CLAW_SKILLS_ROOT"):
|
for key in ("JIANGCHANG_SKILLS_ROOT", "CLAW_SKILLS_ROOT"):
|
||||||
|
|
||||||
v = (os.getenv(key) or "").strip()
|
v = (os.getenv(key) or "").strip()
|
||||||
|
|
||||||
if v:
|
if v:
|
||||||
|
|
||||||
return os.path.normpath(v)
|
return os.path.normpath(v)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
app = (os.getenv("JIANGCHANG_APP_ROOT") or "").strip()
|
app = (os.getenv("JIANGCHANG_APP_ROOT") or "").strip()
|
||||||
|
|
||||||
if sys.platform == "win32" and not app:
|
if sys.platform == "win32" and not app:
|
||||||
|
|
||||||
app = WIN_DEFAULT_JIANGCHANG_APP_ROOT
|
app = WIN_DEFAULT_JIANGCHANG_APP_ROOT
|
||||||
|
|
||||||
if app:
|
if app:
|
||||||
|
|
||||||
nested = os.path.join(app, "skills")
|
nested = os.path.join(app, "skills")
|
||||||
|
|
||||||
if _looks_like_skills_root(nested):
|
if _looks_like_skills_root(nested):
|
||||||
|
|
||||||
return os.path.normpath(nested)
|
return os.path.normpath(nested)
|
||||||
|
|
||||||
if _looks_like_skills_root(app):
|
if _looks_like_skills_root(app):
|
||||||
|
|
||||||
return os.path.normpath(app)
|
return os.path.normpath(app)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
|
|
||||||
nested = os.path.join(WIN_DEFAULT_JIANGCHANG_APP_ROOT, "skills")
|
nested = os.path.join(WIN_DEFAULT_JIANGCHANG_APP_ROOT, "skills")
|
||||||
|
|
||||||
if _looks_like_skills_root(nested):
|
if _looks_like_skills_root(nested):
|
||||||
|
|
||||||
return os.path.normpath(nested)
|
return os.path.normpath(nested)
|
||||||
|
|
||||||
if _looks_like_skills_root(WIN_DEFAULT_JIANGCHANG_APP_ROOT):
|
if _looks_like_skills_root(WIN_DEFAULT_JIANGCHANG_APP_ROOT):
|
||||||
|
|
||||||
return os.path.normpath(WIN_DEFAULT_JIANGCHANG_APP_ROOT)
|
return os.path.normpath(WIN_DEFAULT_JIANGCHANG_APP_ROOT)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return os.path.normpath(os.path.join(os.path.expanduser("~"), ".openclaw", "skills"))
|
return os.path.normpath(os.path.join(os.path.expanduser("~"), ".openclaw", "skills"))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_sibling_skills_root(skill_scripts_dir: str | None = None) -> str:
|
def get_sibling_skills_root(skill_scripts_dir: str | None = None) -> str:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
编排子进程调用兄弟技能时用:先根据本技能 scripts 目录推断并列根;若目录下能识别出兄弟技能
|
编排子进程调用兄弟技能时用:先根据本技能 scripts 目录推断并列根;若目录下能识别出兄弟技能
|
||||||
|
则用之(开发仓 D:\\OpenClaw 与安装目录 ~/.openclaw/skills 均满足「技能根之上一级 = 并列根」),
|
||||||
则用之(开发仓与安装目录均满足「技能根之上一级 = 并列根」),
|
避免全局 JIANGCHANG_SKILLS_ROOT / CLAW_SKILLS_ROOT 指向与当前检出不一致时错调兄弟进程。
|
||||||
|
|
||||||
避免全局 JIANGCHANG_SKILLS_ROOT / CLAW_SKILLS_ROOT 与当前检出不一致时错调兄弟进程。
|
|
||||||
|
|
||||||
推断失败时再读上述环境变量,最后回落 get_skills_root()。
|
推断失败时再读上述环境变量,最后回落 get_skills_root()。
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if skill_scripts_dir:
|
if skill_scripts_dir:
|
||||||
|
|
||||||
scripts = os.path.abspath(skill_scripts_dir)
|
scripts = os.path.abspath(skill_scripts_dir)
|
||||||
|
|
||||||
skill_root = os.path.dirname(scripts)
|
skill_root = os.path.dirname(scripts)
|
||||||
|
|
||||||
inferred = os.path.dirname(skill_root)
|
inferred = os.path.dirname(skill_root)
|
||||||
|
|
||||||
if _looks_like_skills_root(inferred):
|
if _looks_like_skills_root(inferred):
|
||||||
|
|
||||||
return os.path.normpath(inferred)
|
return os.path.normpath(inferred)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
for key in ("JIANGCHANG_SKILLS_ROOT", "CLAW_SKILLS_ROOT"):
|
for key in ("JIANGCHANG_SKILLS_ROOT", "CLAW_SKILLS_ROOT"):
|
||||||
|
|
||||||
v = (os.getenv(key) or "").strip()
|
v = (os.getenv(key) or "").strip()
|
||||||
|
|
||||||
if v:
|
if v:
|
||||||
|
|
||||||
return os.path.normpath(v)
|
return os.path.normpath(v)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return get_skills_root()
|
return get_skills_root()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def apply_cli_local_defaults() -> None:
|
def apply_cli_local_defaults() -> None:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
在 CLI 最早阶段调用(main.py 在 import 业务包之前)。
|
在 CLI 最早阶段调用(main.py 在 import 业务包之前)。
|
||||||
|
|
||||||
宿主已设置 JIANGCHANG_* 时不会覆盖。
|
宿主已设置 JIANGCHANG_* 时不会覆盖。
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
enabled = CLI_LOCAL_DEV_ENABLED
|
enabled = CLI_LOCAL_DEV_ENABLED
|
||||||
|
|
||||||
if not enabled:
|
if not enabled:
|
||||||
|
|
||||||
v = (os.getenv("JIANGCHANG_CLI_LOCAL_DEV") or "").strip().lower()
|
v = (os.getenv("JIANGCHANG_CLI_LOCAL_DEV") or "").strip().lower()
|
||||||
|
|
||||||
enabled = v in ("1", "true", "yes", "on")
|
enabled = v in ("1", "true", "yes", "on")
|
||||||
|
|
||||||
if not enabled:
|
if not enabled:
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if not (os.getenv("JIANGCHANG_DATA_ROOT") or "").strip():
|
if not (os.getenv("JIANGCHANG_DATA_ROOT") or "").strip():
|
||||||
|
|
||||||
os.environ["JIANGCHANG_DATA_ROOT"] = platform_default_data_root()
|
os.environ["JIANGCHANG_DATA_ROOT"] = platform_default_data_root()
|
||||||
|
|
||||||
if not (os.getenv("JIANGCHANG_USER_ID") or "").strip():
|
if not (os.getenv("JIANGCHANG_USER_ID") or "").strip():
|
||||||
|
|
||||||
os.environ["JIANGCHANG_USER_ID"] = DEFAULT_LOCAL_USER_ID.strip()
|
os.environ["JIANGCHANG_USER_ID"] = DEFAULT_LOCAL_USER_ID.strip()
|
||||||
|
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ def _run_login_browser_session(
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
log.error("login_aborted playwright_missing")
|
log.error("login_aborted playwright_missing")
|
||||||
if verbose_ui:
|
if verbose_ui:
|
||||||
print("ERROR:需要 playwright:pip install playwright && playwright install chromium")
|
print("ERROR:需要 playwright Python 包:请在匠厂共享环境中已含 playwright;本机需安装 Google Chrome 或 Microsoft Edge(channel 模式,无需 playwright install chromium)")
|
||||||
return False, "playwright_missing"
|
return False, "playwright_missing"
|
||||||
|
|
||||||
profile_dir = (target.get("profile_dir") or "").strip()
|
profile_dir = (target.get("profile_dir") or "").strip()
|
||||||
@@ -168,6 +168,13 @@ def _run_login_browser_session(
|
|||||||
if verbose_ui:
|
if verbose_ui:
|
||||||
print(f"正在为账号 [{target['name']}] 打开 {browser_name} …")
|
print(f"正在为账号 [{target['name']}] 打开 {browser_name} …")
|
||||||
print(f"访问地址:{url}")
|
print(f"访问地址:{url}")
|
||||||
|
if platform == "gemini":
|
||||||
|
print(
|
||||||
|
"Gemini / Google 登录:若提示「浏览器不安全」或「无法登录」,请关闭本窗口后,"
|
||||||
|
"用普通 Chrome 启动参数 --user-data-dir= 指向下面 profile 目录,在普通浏览器里完成"
|
||||||
|
" Google 登录后完全退出 Chrome,再执行本 login;详见 llm-manager Gemini 引擎说明。"
|
||||||
|
)
|
||||||
|
print(f" (本账号 profile 目录){profile_dir}")
|
||||||
print(
|
print(
|
||||||
"请在窗口内完成登录。系统根据页面是否仍出现「登录」等未登录控件判断;"
|
"请在窗口内完成登录。系统根据页面是否仍出现「登录」等未登录控件判断;"
|
||||||
"成功后自动关闭窗口并写入状态(无需手动关浏览器)。"
|
"成功后自动关闭窗口并写入状态(无需手动关浏览器)。"
|
||||||
@@ -355,7 +362,7 @@ def cmd_open(account_id):
|
|||||||
try:
|
try:
|
||||||
import playwright # noqa: F401
|
import playwright # noqa: F401
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print("ERROR:需要 playwright:pip install playwright && playwright install chromium")
|
print("ERROR:需要 playwright Python 包:请在匠厂共享环境中已含 playwright;本机需安装 Google Chrome 或 Microsoft Edge(channel 模式,无需 playwright install chromium)")
|
||||||
return
|
return
|
||||||
|
|
||||||
profile_dir = (target.get("profile_dir") or "").strip()
|
profile_dir = (target.get("profile_dir") or "").strip()
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ _scripts_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|||||||
if _scripts_dir not in sys.path:
|
if _scripts_dir not in sys.path:
|
||||||
sys.path.insert(0, _scripts_dir)
|
sys.path.insert(0, _scripts_dir)
|
||||||
|
|
||||||
|
from util.playwright_stealth import (
|
||||||
|
STEALTH_INIT_SCRIPT,
|
||||||
|
persistent_context_launch_parts,
|
||||||
|
stealth_enabled,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _is_context_closed_error(ex: BaseException) -> bool:
|
def _is_context_closed_error(ex: BaseException) -> bool:
|
||||||
s = str(ex).lower()
|
s = str(ex).lower()
|
||||||
@@ -224,13 +230,19 @@ def _run_login_child(c: dict) -> None:
|
|||||||
|
|
||||||
with sync_playwright() as p:
|
with sync_playwright() as p:
|
||||||
try:
|
try:
|
||||||
ctx = p.chromium.launch_persistent_context(
|
args, ignore_automation = persistent_context_launch_parts()
|
||||||
|
lc_kw = dict(
|
||||||
user_data_dir=profile_dir,
|
user_data_dir=profile_dir,
|
||||||
headless=False,
|
headless=False,
|
||||||
channel=c["channel"],
|
channel=c["channel"],
|
||||||
no_viewport=True,
|
no_viewport=True,
|
||||||
args=["--start-maximized"],
|
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)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if _is_context_closed_error(e):
|
if _is_context_closed_error(e):
|
||||||
end_reason = "user_closed"
|
end_reason = "user_closed"
|
||||||
|
|||||||
@@ -4,18 +4,30 @@ import sys
|
|||||||
|
|
||||||
from playwright.sync_api import sync_playwright
|
from playwright.sync_api import sync_playwright
|
||||||
|
|
||||||
|
from util.playwright_stealth import (
|
||||||
|
STEALTH_INIT_SCRIPT,
|
||||||
|
persistent_context_launch_parts,
|
||||||
|
stealth_enabled,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
with open(sys.argv[1], encoding="utf-8") as f:
|
with open(sys.argv[1], encoding="utf-8") as f:
|
||||||
c = json.load(f)
|
c = json.load(f)
|
||||||
with sync_playwright() as p:
|
with sync_playwright() as p:
|
||||||
ctx = p.chromium.launch_persistent_context(
|
args, ignore_automation = persistent_context_launch_parts()
|
||||||
|
lc_kw = dict(
|
||||||
user_data_dir=c["profile_dir"],
|
user_data_dir=c["profile_dir"],
|
||||||
headless=False,
|
headless=False,
|
||||||
channel=c["channel"],
|
channel=c["channel"],
|
||||||
no_viewport=True,
|
no_viewport=True,
|
||||||
args=["--start-maximized"],
|
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:
|
try:
|
||||||
page = ctx.pages[0] if ctx.pages else ctx.new_page()
|
page = ctx.pages[0] if ctx.pages else ctx.new_page()
|
||||||
page.goto(c["url"], wait_until="domcontentloaded", timeout=60000)
|
page.goto(c["url"], wait_until="domcontentloaded", timeout=60000)
|
||||||
|
|||||||
@@ -62,6 +62,12 @@ PLATFORMS = {
|
|||||||
"prefix": "元宝",
|
"prefix": "元宝",
|
||||||
"aliases": ["元宝"],
|
"aliases": ["元宝"],
|
||||||
},
|
},
|
||||||
|
"gemini": {
|
||||||
|
"url": "https://gemini.google.com",
|
||||||
|
"label": "Gemini",
|
||||||
|
"prefix": "Gemini",
|
||||||
|
"aliases": ["谷歌Gemini", "Google Gemini", "google gemini", "Bard"],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# 未登录页共性:主行动点含「登录」——submit 按钮、Element 主按钮、Semi 文案、标题、通栏主按钮等,Playwright :has-text 可覆盖多数站点。
|
# 未登录页共性:主行动点含「登录」——submit 按钮、Element 主按钮、Semi 文案、标题、通栏主按钮等,Playwright :has-text 可覆盖多数站点。
|
||||||
|
|||||||
40
scripts/util/playwright_stealth.py
Normal file
40
scripts/util/playwright_stealth.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
"""
|
||||||
|
淡化 Playwright 启动时的自动化指纹,减轻 Google 账号页「此浏览器或应用可能不安全」类拦截。
|
||||||
|
|
||||||
|
关闭:环境变量 OPENCLAW_PLAYWRIGHT_STEALTH=0(或 false/off/no)。
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
STEALTH_INIT_SCRIPT = """
|
||||||
|
(() => {
|
||||||
|
try {
|
||||||
|
Object.defineProperty(navigator, "webdriver", { get: () => undefined });
|
||||||
|
} catch (e) {}
|
||||||
|
})();
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def stealth_enabled() -> bool:
|
||||||
|
v = (os.environ.get("OPENCLAW_PLAYWRIGHT_STEALTH") or "1").strip().lower()
|
||||||
|
return v not in ("0", "false", "no", "off")
|
||||||
|
|
||||||
|
|
||||||
|
def persistent_context_launch_parts(
|
||||||
|
*,
|
||||||
|
extra_args: list[str] | None = None,
|
||||||
|
) -> tuple[list[str], list[str] | None]:
|
||||||
|
"""
|
||||||
|
返回 (args, ignore_default_args)。
|
||||||
|
ignore_default_args 为 None 时不应传给 launch_persistent_context。
|
||||||
|
"""
|
||||||
|
args = ["--start-maximized"]
|
||||||
|
if extra_args:
|
||||||
|
args = args + list(extra_args)
|
||||||
|
if not stealth_enabled():
|
||||||
|
return args, None
|
||||||
|
if "--disable-blink-features=AutomationControlled" not in args:
|
||||||
|
args.append("--disable-blink-features=AutomationControlled")
|
||||||
|
return args, ["--enable-automation"]
|
||||||
Reference in New Issue
Block a user