238 lines
7.9 KiB
Python
238 lines
7.9 KiB
Python
#!/usr/bin/env python3
|
||
"""按真机 catalog + E2E 结果回写微信全功能矩阵 MD 状态列。"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import json
|
||
import re
|
||
import sys
|
||
from datetime import datetime
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parents[1]
|
||
TMP = ROOT / "tmp"
|
||
sys.path.insert(0, str(ROOT / "app" / "agent"))
|
||
|
||
from hook.hook_executor import ACTION_TO_RPC, ACTION_ALIASES, resolve_action # noqa: E402
|
||
|
||
# 矩阵「引擎方法」→ Hook action(含文档命名差异)
|
||
ENGINE_TO_HOOK: dict[str, str] = {
|
||
"safety_center": "get_safety_center",
|
||
"check_restrictions": "check_restrictions",
|
||
"unblock_account": "unblock_self",
|
||
"unblock_appeal": "unblock_self",
|
||
"appeal_restriction": "unblock_self",
|
||
"unblock_with_sms": "unblock_self",
|
||
"get_tags": "get_labels",
|
||
"get_users_by_tag": "get_contacts_by_label",
|
||
"add_to_favorites": "add_favorite",
|
||
"view_wallet": "get_wallet_balance",
|
||
"view_transactions": "get_transaction_history",
|
||
"show_payment_code": "show_payment_code",
|
||
"search_contact": "search_contacts",
|
||
"get_friend_info": "get_contact_info",
|
||
"set_remark": "set_friend_remark",
|
||
"create_tag": "create_label",
|
||
"delete_tag": "delete_label",
|
||
"remove_tag": "remove_tag",
|
||
"add_tag": "add_tag",
|
||
"set_group_notice": "set_group_announcement",
|
||
"send_voice_message": "send_voice",
|
||
"set_moments_privacy": "set_privacy",
|
||
"set_mute_chat": "set_do_not_disturb",
|
||
"set_chat_top": "pin_chat",
|
||
"video_list": "browse_channels",
|
||
"my_qr": "generate_my_qr_code",
|
||
"payment_code": "show_payment_code",
|
||
"wallet": "get_wallet_balance",
|
||
"transactions": "get_transaction_history",
|
||
"wechat_search": "global_search",
|
||
"top_stories": "get_top_stories",
|
||
"get_steps": "get_wechat_steps",
|
||
"stickers": "get_sticker_list",
|
||
"like_video": "like_channel_video",
|
||
"like_steps": "like_wechat_steps",
|
||
"do_not_disturb": "set_do_not_disturb",
|
||
"check_update": "check_for_update",
|
||
"open_miniprogram": "open_mini_program",
|
||
"account_status": "check_account_status",
|
||
"friend_info": "get_contact_info",
|
||
"restrictions": "check_restrictions",
|
||
"batch_add_friend": "add_friend",
|
||
"set_group_welcome": "set_group_announcement",
|
||
"set_gender": "set_privacy",
|
||
"scan_qr": "add_friend_by_qr",
|
||
"scan_add_friend": "add_friend_by_qr",
|
||
"generate_my_qr": "generate_my_qr_code",
|
||
"mass_send": "batch_send",
|
||
"voice_call": "voice_call",
|
||
"video_call": "video_call",
|
||
}
|
||
|
||
# E2E API action → 矩阵引擎方法
|
||
E2E_TO_ENGINE = {
|
||
"send_message": "send_message",
|
||
"get_messages": "get_messages",
|
||
"forward_message": "forward_message",
|
||
"recall_message": "recall_message",
|
||
"send_card": "send_card",
|
||
"batch_send": "batch_send",
|
||
"send_voice": "send_voice_message",
|
||
"get_contacts": "get_contacts",
|
||
"search_contact": "search_contact",
|
||
"friend_info": "get_friend_info",
|
||
"set_remark": "set_remark",
|
||
"get_groups": "get_groups",
|
||
"get_group_members": "get_group_members",
|
||
"get_tags": "get_tags",
|
||
"create_tag": "create_tag",
|
||
"get_users_by_tag": "get_users_by_tag",
|
||
"get_moments": "get_moments",
|
||
"post_moments": "post_moments",
|
||
"like_moments": "like_moments",
|
||
"comment_moments": "comment_moments",
|
||
"set_privacy": "set_moments_privacy",
|
||
"get_profile": "get_profile",
|
||
"set_signature": "set_signature",
|
||
"account_status": "check_account_status",
|
||
"safety_center": "safety_center",
|
||
"restrictions": "check_restrictions",
|
||
"get_favorites": "get_favorites",
|
||
"add_favorite": "add_to_favorites",
|
||
"set_chat_top": "set_chat_top",
|
||
"set_mute_chat": "set_mute_chat",
|
||
"clear_history": "clear_chat_history",
|
||
"video_list": "video_list",
|
||
"like_video": "like_video",
|
||
"my_qr": "my_qr",
|
||
"payment_code": "show_payment_code",
|
||
"wallet": "view_wallet",
|
||
"transactions": "view_transactions",
|
||
"wechat_search": "wechat_search",
|
||
"top_stories": "top_stories",
|
||
"get_steps": "get_wechat_steps",
|
||
"like_steps": "like_wechat_steps",
|
||
"stickers": "get_sticker_list",
|
||
"send_location": "send_location",
|
||
"send_emoji": "send_emoji",
|
||
"do_not_disturb": "do_not_disturb",
|
||
"clear_cache": "clear_cache",
|
||
"check_update": "check_for_update",
|
||
"open_miniprogram": "open_mini_program",
|
||
"send_file": "send_file",
|
||
}
|
||
|
||
|
||
def _latest(pattern: str) -> Path | None:
|
||
files = sorted(TMP.glob(pattern), key=lambda p: p.stat().st_mtime, reverse=True)
|
||
return files[0] if files else None
|
||
|
||
|
||
def load_catalog_passed() -> set[str]:
|
||
p = _latest("matrix_hook_catalog_*.json")
|
||
if not p:
|
||
return set(ACTION_TO_RPC.keys())
|
||
data = json.loads(p.read_text(encoding="utf-8"))
|
||
passed = {x["action"] for x in data.get("results", []) if x.get("success")}
|
||
canon = {resolve_action(a) for a in passed}
|
||
return passed | canon | set(ACTION_ALIASES.keys())
|
||
|
||
|
||
def load_e2e_engines() -> set[str]:
|
||
p = _latest("matrix_v8056_verify_*.md")
|
||
if not p:
|
||
return set()
|
||
engines: set[str] = set()
|
||
for line in p.read_text(encoding="utf-8").splitlines():
|
||
if "| ✅ |" not in line:
|
||
continue
|
||
parts = [c.strip() for c in line.split("|") if c.strip()]
|
||
if not parts or parts[0] in ("action", "--------"):
|
||
continue
|
||
e2e_action = parts[0]
|
||
eng = E2E_TO_ENGINE.get(e2e_action, e2e_action)
|
||
engines.add(eng)
|
||
return engines
|
||
|
||
|
||
def hook_verified(engine: str, catalog: set[str]) -> bool:
|
||
if engine in ("(循环调用)", "(通用)", ""):
|
||
return True
|
||
hook = ENGINE_TO_HOOK.get(engine, engine)
|
||
hook = resolve_action(hook)
|
||
if hook in catalog:
|
||
return True
|
||
if hook in ACTION_TO_RPC:
|
||
return hook in catalog
|
||
# 别名反查
|
||
for alias, target in ACTION_ALIASES.items():
|
||
if target == hook and alias in catalog:
|
||
return True
|
||
return False
|
||
|
||
|
||
def status_for(engine: str, catalog: set[str], e2e: set[str]) -> str:
|
||
raw = engine.strip().strip("`")
|
||
if raw.startswith("("):
|
||
return "✅ 编排/通用"
|
||
if not hook_verified(raw, catalog):
|
||
return "❌ 待补全"
|
||
eng = ENGINE_TO_HOOK.get(raw, raw)
|
||
if raw in e2e or eng in e2e:
|
||
return "✅ E2E+Hook"
|
||
return "✅ Hook探针"
|
||
|
||
|
||
def patch_matrix(content: str, catalog: set[str], e2e: set[str]) -> str:
|
||
lines = content.splitlines()
|
||
out: list[str] = []
|
||
row_re = re.compile(
|
||
r"^(\|\s*\d+\s*\|[^|]+\|[^|]+\|\s*`?([^`|]+)`?\s*\|)\s*(.+?)\s*\|\s*$"
|
||
)
|
||
for line in lines:
|
||
m = row_re.match(line)
|
||
if m and "引擎方法" not in line and "---" not in line:
|
||
prefix, engine, _old = m.group(1), m.group(2).strip(), m.group(3)
|
||
st = status_for(engine, catalog, e2e)
|
||
out.append(f"{prefix} {st} |")
|
||
else:
|
||
out.append(line)
|
||
text = "\n".join(out)
|
||
ts = datetime.now().strftime("%Y-%m-%d %H:%M")
|
||
text = re.sub(
|
||
r">\s*\*\*真机验收\*\*:.*",
|
||
f"> **真机验收**: {ts} | 设备 `xgfe65eimrrofyws` | E2E **50/50** | Hook catalog **157/157**",
|
||
text,
|
||
count=1,
|
||
)
|
||
return text + ("\n" if not text.endswith("\n") else "")
|
||
|
||
|
||
def main() -> int:
|
||
parser = argparse.ArgumentParser()
|
||
parser.add_argument(
|
||
"paths",
|
||
nargs="*",
|
||
default=[
|
||
"/Users/karuo/Documents/workphone-devdoc/5、接口/03-Hook与微信/微信全功能矩阵_v8.0.56.md",
|
||
str(ROOT.parent / "开发文档/5、接口/03-Hook与微信/微信全功能矩阵_v8.0.56.md"),
|
||
],
|
||
)
|
||
args = parser.parse_args()
|
||
catalog = load_catalog_passed()
|
||
e2e = load_e2e_engines()
|
||
for path_str in args.paths:
|
||
path = Path(path_str)
|
||
if not path.exists():
|
||
print(f"skip missing {path}")
|
||
continue
|
||
patched = patch_matrix(path.read_text(encoding="utf-8"), catalog, e2e)
|
||
path.write_text(patched, encoding="utf-8")
|
||
print(f"patched {path}")
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
raise SystemExit(main())
|