#!/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()