Files
account-manager/references/RPA_HELPERS.md

219 lines
7.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# rpa_helpers 库 — 业务 skill 同进程登录
`scripts/service/rpa_helpers/` 是 account-manager v2 提供给业务 skill 的 **同进程 Python 库**。业务代码调用 **`ensure_logged_in`**,库按账号记录的 **`auth_strategy`** 分发到对应登录流程(底层基于 Playwright
## 与 CLI 的关系
| 路径 | 职责 |
|---|---|
| **CLI subprocess** | 获取结构化账号 JSON、租约、`get-credential --reveal` 明文密码;进程隔离、易于跨语言 |
| **rpa_helpers** | 在已有 `BrowserContext`/`Page` 上执行登录编排与人类化输入 |
推荐组合:**CLI → pick / lease → get-credential → `launch_browser_with_profile` + `ensure_logged_in`**。
## 集成方式
### Import 路径
```python
import sys
AM_ROOT = r"D:\OpenClaw\client-commons\account-manager"
sys.path.insert(0, f"{AM_ROOT}/scripts")
from service.rpa_helpers import (
AuthStrategyError,
LoginResult,
close_browser,
ensure_logged_in,
launch_browser_with_profile,
)
```
**务必**`sys.path` 追加的是 **`…/account-manager/scripts`**,因此模块命名空间为 `service.rpa_helpers`**不要**写成 `from scripts.service.rpa_helpers …`)。
### 运行时依赖
- Python **3.10+**
- 已安装 **`playwright`** Python 包(宿主 runtime 提供)
- 本机已安装 **Chrome 或 Edge**(启动时使用 channel 模式,优先 Chrome失败回落 Edge——见 `service.browser_service.resolve_chromium_channel`
文档不提供 `pip install …` 指令;版本由匠厂宿主统一管理。
---
## 公开 API 一览
下列符号由 `service/rpa_helpers/__init__.py` **导出(共 19 个名字)**
| 分类 | 符号 |
|---|---|
| **dispatcher + 策略函数** | `ensure_logged_in`,以及 `ensure_logged_in_password_auto``ensure_logged_in_password_with_captcha``ensure_logged_in_password_plus_2fa``ensure_logged_in_qr_code_manual``ensure_logged_in_per_session_manual``ensure_logged_in_device_bound``ensure_logged_in_client_certificate``ensure_logged_in_api_token``ensure_logged_in_sso_redirect` |
| **结果 / 异常** | `LoginResult``AuthStrategyError` |
| **浏览器** | `launch_browser_with_profile``close_browser` |
| **人类化输入** | `pause``human_type``human_click``human_select``human_scroll_to` |
---
## `browser` 模块
### `launch_browser_with_profile(profile_dir, *, channel=None, headless=False, extra_args=None, locale="zh-CN") -> tuple[BrowserContext, Page]`
- 创建持久化上下文(`user_data_dir=profile_dir`),注入可选 stealth 脚本。
- `channel` 默认为宿主检测结果Chrome 优先)。
- **必须在 `finally` 中调用 `close_browser(context)`**,否则 playwright driver 可能泄漏。
### `close_browser(ctx) -> None`
关闭上下文并停止内部的 Playwright session manager。
---
## `human` 模块(人类化操作)
| 函数 | 行为 |
|---|---|
| `pause(min_ms=200, max_ms=800)` | 随机等待 |
| `human_type(locator, text, *, per_char_ms_range=(50, 200), pre_pause=True)` | 滚动进入视图 → 停顿 → `click` → 逐字符 `type` |
| `human_click(locator)` | 滚动 → 停顿 → `click` |
| `human_select(locator, value_or_label)` | 滚动 → 停顿 → `select_option`(依次尝试 value / label |
| `human_scroll_to(locator)` | 平滑滚动到元素 |
_locator_ 均为 Playwright `Locator`
---
## `auth_flows` 模块
### `ensure_logged_in(page, account, *, credentials=None, **kwargs) -> LoginResult`
1. 读取 `account["auth_strategy"]`。为空字符串 → 抛出 `AuthStrategyError(ERR_AUTH_STRATEGY_NOT_SUPPORTED)`
2. 若策略需要密码则要求 `credentials["plaintext"]`;缺失 → `ERR_CREDENTIAL_MISSING`
3. 分派到对应 `ensure_logged_in_*`
4. 未知策略 → `AuthStrategyError(ERR_AUTH_STRATEGY_NOT_SUPPORTED)`
附加 kwargs 会透传给具体策略(如 `captcha_value``wait_human_sec`)。
### `LoginResult`
| 字段 | 类型 | 说明 |
|---|---|---|
| `ok` | `bool` | 流程是否达到判定条件 |
| `needs_human` | `bool` | 是否需要人工协助(扫码 / OTP / SSO 等) |
| `message` | `str` | 描述信息(便于日志) |
### `AuthStrategyError`
属性:`code: str``message: str`。错误码与 CLI / [`ERRORS.md`](ERRORS.md) 对齐。
### 九个 `ensure_logged_in_*` 入口
业务代码通常只调用 `ensure_logged_in`。如需针对性 reuse可直接 import `auth_flows` 中的同名函数;签名见源码 `scripts/service/rpa_helpers/auth_flows.py`
---
## 已登录检测(`_is_already_logged_in`
核心逻辑:
1. 读取当前 `page.url`;空白页 → **False**
2. 仅在账号 `extra_json`(可为 dict 或 JSON 字符串)中出现以下 hook 之一才可能返回 True
- **`logged_in_url_pattern`**:正则,匹配当前 URL。
- **`logged_in_selector`**CSS/XPath selector`locator.first.is_visible(timeout=500)`
3. 若无任一 hook → **默认 False****除非已有持久化 session**,否则会完整跑登录流程。
### 接入建议
1. 首次上线:`auth_strategy=qr_code_manual` + 无 hook → 用户扫码登录。
2. 登录成功后记录典型 URL 片段或 DOM selector。
3. 写入账号 `extra_json`,例如:
```python
{
"logged_in_url_pattern": r"/user/",
"logged_in_selector": "div.avatar-wrapper"
}
```
4. 下次启动 → `_is_already_logged_in` 命中 → `needs_human` 可能降低。
### `per_session_manual` 的差异
该策略 **开场不会调用 `_is_already_logged_in`**,总是导航到业务 URL 并等待人工完成登录(适合强制会话)。
---
## 设备绑定策略(`device_bound`
如账号设置了 `device_fingerprint`,库会使用 `_current_machine_fingerprint()`hostname + MAC 派生)比对;不一致抛出 **`ERR_DEVICE_FINGERPRINT_MISMATCH`**。
---
## 客户端证书策略(`client_certificate`
直接抛出 **`ERR_AUTH_STRATEGY_NOT_AUTOMATABLE`**;请在宿主机手动装载证书后使用其它路径衔接。
---
## 完整示例CLI + rpa_helpers
```python
import json
import subprocess
import sys
AM = r"D:\OpenClaw\client-commons\account-manager"
PY = sys.executable
MAIN = f"{AM}/scripts/main.py"
sys.path.insert(0, f"{AM}/scripts")
from service.rpa_helpers import launch_browser_with_profile, close_browser, ensure_logged_in
def am_json(argv: list[str]) -> dict:
proc = subprocess.run([PY, MAIN, *argv], capture_output=True, text=True, check=False)
line = (proc.stdout or "").splitlines()[0]
return json.loads(line)
acc = am_json(
["account", "pick-web", "--platform", "icbc_sim", "--lease", "--holder", "monitor-rates"]
)
cred = am_json(
[
"account",
"get-credential",
str(acc["id"]),
"--reveal",
"--lease-token",
acc["lease_token"],
]
)
plaintext = cred.get("plaintext")
if plaintext in (None, ""):
raise RuntimeError("no plaintext returned; check secret_storage / lease")
ctx, page = launch_browser_with_profile(acc["profile_dir"])
try:
result = ensure_logged_in(page, acc, credentials={"plaintext": plaintext})
if not result.ok:
raise RuntimeError(result.message)
# …业务代码…
finally:
close_browser(ctx)
subprocess.run([PY, MAIN, "lease", "release", acc["lease_token"]], check=False)
```
---
## 测试建议
- account-manager 仓库内置 **107** 个 pytest截至 v2.0.0 文档修订时),覆盖 CLI / schema / rpa_helpers mock 路径。
- 业务 skill
- **单元测试**patch `ensure_logged_in` → 返回 `LoginResult(ok=True, …)`
- **端到端**:在 Windows + 真浏览器环境标记 `@pytest.mark.integration`,禁止在 CI 泄露明文密码。
更多 CLI 细节参见 [`CLI.md`](CLI.md)、[`INTEGRATION.md`](INTEGRATION.md)。