feat: publish workphone SDK deployment and API docs

This commit is contained in:
Manus AI
2026-07-14 18:10:52 +08:00
commit 021d633cc1
534 changed files with 122391 additions and 0 deletions

View File

@@ -0,0 +1,189 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
微信 174 action 能力矩阵 生成器
把「Hook catalog 探针报告probe 假参数)」+ ACTION_TO_RPC 映射 + JS intent 分类
合成一张**诚实的能力矩阵**:每个 action → 模块 / 通道 / 可调用条件,
让存客宝 / 触客宝 / AI数智员工 / SuperAdmin 知道「这个动作怎么调、需要什么」。
诚实口径(真机铁律 #3/#14
- probe 用占位参数,写操作 success:False 是**正常**(需真实参数/上下文),非代码缺陷;
- 本矩阵据此区分「已验可直调 / 需真实参数 / 需UI或测试号 / companion模块」。
用法:
python3 sdk/scripts/gen_wechat_capability_matrix.py \
--report sdk/tmp/matrix_hook_catalog_xgfe65eimrrofyws_1780127048.json
输出:开发文档/5、接口/03-Hook与微信/微信174动作能力矩阵.md
"""
from __future__ import annotations
import argparse, datetime, glob, json, os, re
ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", ".."))
HOOK_EXEC = os.path.join(ROOT, "sdk", "agent", "hook", "hook_executor.py")
HOOK_JS = os.path.join(ROOT, "sdk", "agent", "hook", "wechat_hook_v2.js")
OUT = os.path.join(ROOT, "开发文档", "5、接口", "03-Hook与微信", "微信174动作能力矩阵.md")
# 资金/隐私敏感(需测试号 + 小额 + 审计)
MONEY = {"send_red_packet", "receive_red_packet", "transfer", "send_transfer",
"receive_transfer", "payment_receive", "receive_payment"}
# 需相机/系统 UI纯 Frida 难覆盖u2/companion
UI_HEAVY = {"set_avatar", "scan_qr_code", "scan_add_friend", "scan_extract_qr",
"extract_qr_from_image", "add_friend_by_qr", "show_payment_code",
"voice_call", "video_call", "send_voice", "send_voice_message"}
def parse_action_to_rpc() -> dict:
pairs, inb = {}, False
for ln in open(HOOK_EXEC, encoding="utf-8"):
if re.match(r"\s*ACTION_TO_RPC\s*[:=]", ln):
inb = True; continue
if inb:
if re.match(r"^\}", ln):
break
m = re.search(r'["\']([a-zA-Z0-9_]+)["\']\s*:\s*["\']([a-zA-Z0-9_]+)["\']', ln)
if m:
pairs[m.group(1)] = m.group(2)
return pairs
def _extract_func_body(js: str, brace_start: int) -> str:
"""从函数体起始 `{`brace_start 指向该 `{`)做花括号配平,返回函数体文本。
旧实现用固定 1400 字符窗口,会越界吃到相邻函数体,导致
sendVideo/sendFile/sendLink实为 _shareMediaToWechat 真实现)被误判为 companion。
"""
depth = 0
for i in range(brace_start, len(js)):
c = js[i]
if c == "{":
depth += 1
elif c == "}":
depth -= 1
if depth == 0:
return js[brace_start:i + 1]
return js[brace_start:]
def parse_intent_funcs() -> set:
"""只在每个函数**自身**体内判定是否为 _intentAction 占位(花括号配平,杜绝越界)。"""
js = open(HOOK_JS, encoding="utf-8").read()
intent = set()
for mm in re.finditer(r"([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*function[^{]*\{", js):
brace_start = js.rfind("{", mm.start(), mm.end())
body = _extract_func_body(js, brace_start)
# 仅当函数体内**唯一**返回路径是 _intentAction 才算占位;
# 若同时存在 _shareMediaToWechat/_shareTextToWechat/_sendMessageInternal/performNow 等真实现,则不算占位。
has_intent = "_intentAction(" in body
has_real = any(k in body for k in (
"_shareMediaToWechat", "_shareTextToWechat", "_sendMessageInternal",
"_execSQLWrite", "Java.performNow", "_sendMessageBySendMsgEvent",
))
if has_intent and not has_real:
intent.add(mm.group(1))
return intent
def latest_report() -> str:
files = sorted(glob.glob(os.path.join(ROOT, "sdk", "tmp", "matrix_hook_catalog_*.json")),
key=os.path.getmtime, reverse=True)
return files[0] if files else ""
def classify(action, passed, a2r, intent):
rpc = a2r.get(action, "")
if passed:
return "✅ 已验可直调", "Frida RPC", "probe 即通(读类/无副作用)"
if action in MONEY:
return "🟡 需测试号", "Frida RPC + 风控", "资金动作:测试号 + 小额 + 审计,真实参数"
if action in UI_HEAVY:
return "🟠 需UI/u2", "u2/companion", "相机/系统UI/录音,纯 Frida 难覆盖"
if rpc and rpc in intent:
return "🟠 companion", "companion intent", "设备端无障碍/广播模块;无模块诚实返 false"
if rpc:
return "🟢 需真实参数", "Frida RPC", "probe 假参数 success:false传真实 to_id/sns_id/msg_id 即可用"
return "⬜ 未映射", "u2 兜底", "无 ACTION_TO_RPC走 u2 或经 hook/execute 别名解析"
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--report", default="")
args = ap.parse_args()
report = args.report or latest_report()
rep = json.load(open(report, encoding="utf-8")) if report else {"results": []}
results = {r["action"]: bool(r.get("success")) for r in rep.get("results", [])}
a2r = parse_action_to_rpc()
intent = parse_intent_funcs()
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
# 全 action 集合catalog 结果 ACTION_TO_RPC
all_actions = sorted(set(results) | set(a2r))
rows = []
for a in all_actions:
passed = results.get(a, None)
status, channel, cond = classify(a, passed is True, a2r, intent)
rows.append((a, a2r.get(a, ""), status, channel, cond))
from collections import Counter
dist = Counter(r[2] for r in rows)
L = []
L.append("---")
L.append("tags: [工作手机, 接口, Hook, 微信, 能力矩阵, 自动生成]")
L.append("doc-type: 验收")
L.append("layer: 5、接口/03-Hook与微信")
L.append('parent: "[[5、接口/03-Hook与微信/README|03-Hook与微信]]"')
L.append('obsidian-color: "#EF6C00"')
L.append("---\n")
L.append("# 微信 Action 能力矩阵(诚实口径)")
L.append("")
L.append("> **自动生成**`python3 sdk/scripts/gen_wechat_capability_matrix.py` ")
L.append(f"> **数据源**catalog 探针 `{os.path.basename(report) or ''}` + `ACTION_TO_RPC` + JS intent 分类 · **生成**{now} ")
L.append("> **诚实口径**catalog 用**占位参数**探针,写操作 `success:false` 属**正常**(需真实参数/上下文),非代码缺陷;本矩阵据此分级。")
L.append("")
L.append("## 〇、能力分级总览")
L.append("")
L.append("| 分级 | 含义 | 数量 |")
L.append("|:---|:---|:---:|")
grade_desc = {
"✅ 已验可直调": "probe 即通,读类/无副作用,可直接调用",
"🟢 需真实参数": "有 Frida RPC传真实参数即可用probe 假参数才失败)",
"🟡 需测试号": "资金类,须测试号+小额+审计",
"🟠 需UI/u2": "相机/系统UI/录音,纯 Frida 难覆盖,走 u2",
"🟠 companion": "需设备端无障碍/广播模块,无模块诚实返 false",
"⬜ 未映射": "无直接 RPC经 hook/execute 别名解析或 u2",
}
for g, desc in grade_desc.items():
L.append(f"| {g} | {desc} | {dist.get(g, 0)} |")
L.append(f"| **合计** | | **{len(rows)}** |")
L.append("")
L.append("**结论**:✅+🟢 = **真实可用**(读类即调、写类传真实参数即调);🟡🟠 需对应前置(测试号/UI/模块);四端按本表选择调用方式,缺口走 `sdk/proxy` 或 COORD。")
L.append("")
L.append("## 一、动作明细")
L.append("")
L.append("| action | RPC | 分级 | 通道 | 可调用条件 |")
L.append("|:---|:---|:---|:---|:---|")
for a, rpc, status, channel, cond in rows:
L.append(f"| `{a}` | `{rpc}` | {status} | {channel} | {cond} |")
L.append("")
L.append("## 二、四端用法")
L.append("")
L.append("- **读类(✅)**`GET /api/v3/{module}/*` 或 `POST /api/v3/hook/execute`hook_only:1直接调。")
L.append("- **写类(🟢)**:传**真实** to_id/sns_id/msg_id/group_id经 BFF `/v1/workphone/*` 或 `hook/execute`。")
L.append("- **资金(🟡)**仅测试号存客宝侧加二次确认SDK 已入 `rate_limiter`。")
L.append("- **UI/companion🟠**:需设备端 u2 在线或无障碍模块;无则诚实返回 false不假成功")
L.append("")
L.append("## 三、关联")
L.append("- 需求真源:`1、需求/修改/工作手机_微信全量控机与私域_20260529.md`")
L.append("- 接口契约:`5、接口/03-Hook与微信/微信Frida_API契约.md`")
L.append("- 四端可直调清单:`5、接口/01-规范与统一层/四端可直调接口能力清单.md`")
L.append("- 静态对齐:`python3 sdk/scripts/wechat_interface_audit.py`0 缺失)")
L.append("")
os.makedirs(os.path.dirname(OUT), exist_ok=True)
open(OUT, "w", encoding="utf-8").write("\n".join(L))
print(f"[ok] 写入 {OUT}")
print(f" 动作 {len(rows)},分级分布:{dict(dist)}")
if __name__ == "__main__":
main()