Files
workphone-sdk/sdk/scripts/gen_wechat_capability_matrix.py
Manus AI e7ba81ffad docs(微信): 174 action 能力矩阵(诚实口径·自动生成)
合并 catalog 探针+ACTION_TO_RPC+JS intent 分类,175动作分级(51已验直调/73companion/11UI/7资金/32别名);澄清 probe 假参数 success:false 属正常非缺陷。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-30 16:28:54 +08:00

163 lines
7.8 KiB
Python
Raw 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.

#!/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 parse_intent_funcs() -> set:
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):
body = js[mm.end():mm.end() + 1400]
if "_intentAction(" in body:
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()