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>
This commit is contained in:
162
sdk/scripts/gen_wechat_capability_matrix.py
Normal file
162
sdk/scripts/gen_wechat_capability_matrix.py
Normal file
@@ -0,0 +1,162 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user