feat: publish workphone SDK deployment and API docs
This commit is contained in:
335
sdk/scripts/matrix_hook_catalog_verify.py
Normal file
335
sdk/scripts/matrix_hook_catalog_verify.py
Normal file
@@ -0,0 +1,335 @@
|
||||
#!/usr/bin/env python3
|
||||
"""矩阵补全:Hook catalog 全 action 真机 hook/execute 探针。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
|
||||
BASE = os.environ.get("SDK_BASE_URL", "http://127.0.0.1:8899")
|
||||
DEVICE = os.environ.get("SDK_DEVICE_ID", "xgfe65eimrrofyws")
|
||||
TO = "filehelper"
|
||||
OUT = Path(__file__).resolve().parents[1] / "tmp"
|
||||
|
||||
SAFE_PARAMS: dict[str, dict] = {
|
||||
"ping": {},
|
||||
"send_message": {"to_id": TO, "content": "[catalog-probe]", "msg_type": "text"},
|
||||
"batch_send": {"user_ids": [TO], "content": "[catalog-probe]"},
|
||||
"mass_send": {"user_ids": [TO], "content": "[catalog-probe]"},
|
||||
"get_messages": {"conversation_id": TO, "limit": 3},
|
||||
"get_contacts": {"limit": 5},
|
||||
"get_contact_info": {"wxid": TO},
|
||||
"get_friend_info": {"user_id": TO},
|
||||
"search_contacts": {"keyword": "文件", "limit": 5},
|
||||
"search_contact": {"keyword": "文件", "limit": 5},
|
||||
"search_messages": {"keyword": "test", "limit": 5},
|
||||
"wechat_search": {"keyword": "微信", "limit": 5},
|
||||
"get_groups": {"limit": 5},
|
||||
"get_group_info": {"group_id": "test@chatroom"},
|
||||
"get_group_members": {"group_id": "test@chatroom"},
|
||||
"get_profile": {},
|
||||
"check_account_status": {},
|
||||
"get_favorites": {"limit": 5},
|
||||
"get_labels": {},
|
||||
"get_moments": {"limit": 3},
|
||||
"browse_channels": {"limit": 3},
|
||||
"global_search": {"keyword": "微信", "limit": 5},
|
||||
"generate_my_qr_code": {},
|
||||
"generate_group_qr_code": {"group_id": "test@chatroom"},
|
||||
"get_wallet_balance": {},
|
||||
"get_transaction_history": {"limit": 5},
|
||||
"get_wechat_steps": {},
|
||||
"get_sticker_list": {},
|
||||
"get_top_stories": {},
|
||||
"get_safety_center": {},
|
||||
"check_restrictions": {},
|
||||
"show_payment_code": {},
|
||||
"pin_chat": {"wxid": TO, "pin": False},
|
||||
"set_chat_top": {"user_id": TO, "enable": False},
|
||||
"set_do_not_disturb": {"wxid": TO, "enable": False},
|
||||
"add_favorite": {"content": "catalog-probe", "type": "text"},
|
||||
"add_to_favorites": {"content": "catalog-probe", "type": "text"},
|
||||
"set_privacy": {"setting": "moments_privacy", "value": "all"},
|
||||
"set_moments_privacy": {"privacy_type": "all", "days": 180},
|
||||
"set_moments_cover": {"image_path": "/sdcard/test.jpg"},
|
||||
"open_mini_program": {"name": "微信支付"},
|
||||
"open_miniprogram": {"name": "微信支付"},
|
||||
"send_location": {"to_id": TO, "latitude": 24.48, "longitude": 118.08, "label": "probe"},
|
||||
"send_emoji": {"to_id": TO, "emoji_name": "微笑"},
|
||||
"send_voice": {"to_id": TO, "duration": 1},
|
||||
"send_voice_message": {"to_id": TO, "duration": 1},
|
||||
"send_file": {"to_id": TO, "file_path": "/sdcard/test.txt"},
|
||||
"send_file_from_chat": {"to_id": TO, "file_path": "/sdcard/test.txt"},
|
||||
"send_image": {"to_id": TO, "image_path": "/sdcard/test.jpg"},
|
||||
"send_video": {"to_id": TO, "video_path": "/sdcard/test.mp4"},
|
||||
"send_link": {"to_id": TO, "url": "https://example.com", "title": "probe"},
|
||||
"send_card": {"to_id": TO, "card_wxid": TO},
|
||||
"send_group_message": {"group_id": "test@chatroom", "content": "probe"},
|
||||
"like_channel_video": {"index": 0},
|
||||
"like_video": {"index": 0},
|
||||
"like_wechat_steps": {"user_id": TO},
|
||||
"like_moments": {"sns_id": "probe_sns"},
|
||||
"comment_moments": {"sns_id": "probe_sns", "comment": "probe"},
|
||||
"comment_channel_video": {"video_id": "index_0", "comment": "probe"},
|
||||
"clear_cache": {},
|
||||
"check_for_update": {},
|
||||
"post_moments": {"content": "[catalog-probe]"},
|
||||
"forward_moments_link": {"content": "https://example.com probe"},
|
||||
"set_signature": {"signature": "catalog-probe"},
|
||||
"set_nickname": {"nickname": "probe"},
|
||||
"set_region": {"region": "福建 厦门"},
|
||||
"set_avatar": {"image_path": "/sdcard/test.jpg"},
|
||||
"set_remark": {"user_id": TO, "remark": "probe"},
|
||||
"set_friend_remark": {"wxid": TO, "remark": "probe"},
|
||||
"create_label": {"name": "catalog_probe"},
|
||||
"create_tag": {"tag_name": "catalog_probe"},
|
||||
"get_contacts_by_label": {"label_id": "1"},
|
||||
"get_users_by_tag": {"tag_name": "catalog_probe"},
|
||||
"add_friend": {"user_id": "28533368", "message": "catalog-probe"},
|
||||
"accept_friend": {"encrypt_username": "probe", "ticket": "probe"},
|
||||
"delete_friend": {"wxid": "invalid_wxid_probe"},
|
||||
"invite_to_group": {"group_id": "test@chatroom", "member_ids": [TO]},
|
||||
"remove_from_group": {"group_id": "test@chatroom", "member_ids": [TO]},
|
||||
"create_group": {"member_ids": [TO, TO], "group_name": "catalog-probe"},
|
||||
"set_group_announcement": {"group_id": "test@chatroom", "announcement": "probe"},
|
||||
"set_group_notice": {"group_id": "test@chatroom", "notice": "probe"},
|
||||
"set_group_name": {"group_id": "test@chatroom", "name": "probe"},
|
||||
"quit_group": {"group_id": "test@chatroom"},
|
||||
"set_contact_label": {"wxid": TO, "label_ids": []},
|
||||
"add_tag": {"wxid": TO, "tag_name": "catalog_probe"},
|
||||
"remove_tag": {"wxid": TO, "tag_name": "catalog_probe"},
|
||||
"delete_label": {"label_id": "1"},
|
||||
"delete_tag": {"label_id": "1"},
|
||||
"delete_moments": {"sns_id": "1"},
|
||||
"delete_favorite": {"local_id": "1"},
|
||||
"forward_message": {"to_id": TO, "msg_svr_id": "1779123054724"},
|
||||
"forward_multiple": {"to_id": TO, "msg_svr_ids": ["1779123054724"]},
|
||||
"recall_message": {"msg_svr_id": "1779123054724"},
|
||||
"revoke_message": {"msg_svr_id": "1779123054724"},
|
||||
"send_red_packet": {"to_id": TO, "amount": "0.01", "message": "probe"},
|
||||
"send_transfer": {"to_id": TO, "amount": "0.01", "description": "probe"},
|
||||
"transfer": {"to_id": TO, "amount": "0.01", "description": "probe"},
|
||||
"receive_red_packet": {"msg_svr_id": "1"},
|
||||
"receive_transfer": {"msg_svr_id": "1"},
|
||||
"receive_payment": {"msg_svr_id": "1"},
|
||||
"follow_channel": {"channel_id": "probe"},
|
||||
"unfollow_channel": {"channel_id": "probe"},
|
||||
"follow_official_account": {"account_id": "probe"},
|
||||
"unfollow_official_account": {"account_id": "probe"},
|
||||
"get_official_account_articles": {"account_id": "probe", "limit": 5},
|
||||
"share_channel_video": {"video_id": "index_0", "to_id": TO},
|
||||
"share_mini_program": {"app_id": "wxprobe", "to_id": TO, "title": "probe"},
|
||||
"add_friend_by_qr": {"qr_content": "probe"},
|
||||
"add_custom_emoji": {"image_path": "/sdcard/test.jpg"},
|
||||
"add_to_float": {"wxid": TO},
|
||||
"remove_from_float": {"wxid": TO},
|
||||
"clear_chat_history": {"wxid": TO},
|
||||
"clear_history": {"user_id": TO},
|
||||
"bind_phone": {"phone": "13800138000"},
|
||||
"change_password": {"old_password": "old", "new_password": "new"},
|
||||
"remove_login_device": {"device_id": "probe"},
|
||||
"register_account": {"phone": "13800138000", "nickname": "probe"},
|
||||
"login_by_password": {"phone": "13800138000", "password": "probe"},
|
||||
"login_by_sms": {"phone": "13800138000"},
|
||||
"switch_account": {"wxid": TO},
|
||||
"batch_execute": {"actions": [{"action": "getProfile", "params": {}}]},
|
||||
"get_hook_status": {},
|
||||
"get_wechat_version": {},
|
||||
"get_process_info": {},
|
||||
"voice_call": {"to_id": TO},
|
||||
"video_call": {"to_id": TO},
|
||||
"share_real_time_location": {"to_id": TO},
|
||||
"download_file": {"url": "https://example.com/probe.txt", "save_path": "/sdcard/Download/wp_probe.txt"},
|
||||
"reply_comment": {"sns_id": "probe_sns", "comment": "catalog-probe"},
|
||||
"set_what_up": {"content": "catalog-probe"},
|
||||
"logout": {},
|
||||
"get_device_info": {},
|
||||
"get_storage_info": {},
|
||||
"get_network_info": {},
|
||||
"get_recent_messages": {"limit": 5},
|
||||
"get_recent_mini_programs": {},
|
||||
"get_friend_requests": {},
|
||||
"get_official_accounts": {"limit": 5},
|
||||
"unblock_self": {},
|
||||
"unbind_phone": {},
|
||||
"enable_fingerprint": {},
|
||||
"set_account_protection": {"enable": True},
|
||||
"scan_qr_code": {},
|
||||
"set_sex": {"sex": 1},
|
||||
"set_notification": {"enable": True},
|
||||
"set_chat_background": {"wxid": TO},
|
||||
"auto_register": {},
|
||||
"check_login_state": {},
|
||||
"get_sim_phone": {},
|
||||
# ── 奥创协议补全 29 action 真机探针参数(读类空参安全 / 写类给最小 probe,真机验收才标 ✅)──
|
||||
"get_conversations": {"limit": 5},
|
||||
"get_message_by_id": {"msg_svr_id": "1779123054724"},
|
||||
"get_msg_ids_by_time": {"start_time": 0},
|
||||
"get_message_raw_xml": {"msg_svr_id": "1779123054724"},
|
||||
"add_friend_in_room": {"group_id": "test@chatroom", "wxid": TO, "verify_message": "catalog-probe"},
|
||||
"add_friend_from_phonebook": {"phone": "13800138000", "verify_message": "catalog-probe"},
|
||||
"add_friend_with_scene": {"wxid": TO, "scene": "30", "verify_message": "catalog-probe"},
|
||||
"add_friend_by_card": {"card_wxid": TO, "verify_message": "catalog-probe"},
|
||||
"join_group_by_qr": {"qr_content": "probe"},
|
||||
"approve_group_invite": {"group_id": "test@chatroom", "wxid": TO},
|
||||
"agree_join_group": {"group_id": "test@chatroom"},
|
||||
"mark_conversation_read": {"conversation_id": TO},
|
||||
"send_multi_image": {"to_id": TO, "images": ["/sdcard/test.jpg"]},
|
||||
"send_jielong": {"to_id": TO, "content": "catalog-probe 接龙"},
|
||||
"send_friend_verify": {"wxid": TO, "verify_message": "catalog-probe"},
|
||||
"voice_to_text": {"msg_svr_id": "1779123054724"},
|
||||
"get_friend_moments": {"wxid": TO},
|
||||
"get_moment_detail": {"sns_id": "probe_sns"},
|
||||
"delete_moment_comment": {"sns_id": "probe_sns", "comment_id": "1"},
|
||||
"reply_moment_comment": {"sns_id": "probe_sns", "content": "catalog-probe"},
|
||||
"stop_moments_praise": {},
|
||||
"start_nurture": {"plan": "default"},
|
||||
"stop_nurture": {},
|
||||
"detect_zombie_fans": {},
|
||||
"stop_detect_zombie_fans": {},
|
||||
"query_red_packet": {"red_packet_id": "1"},
|
||||
"get_wechat_location": {},
|
||||
"search_official_account": {"keyword": "微信"},
|
||||
"phone_action": {"op": "screenshot"},
|
||||
}
|
||||
|
||||
|
||||
def _load_actions():
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "app" / "agent"))
|
||||
from hook.hook_executor import ACTION_TO_RPC, ACTION_ALIASES, resolve_action, normalize_params # type: ignore
|
||||
actions = sorted(set(ACTION_TO_RPC.keys()) | set(ACTION_ALIASES.keys()))
|
||||
return actions, resolve_action, normalize_params
|
||||
|
||||
|
||||
async def probe_one(client: httpx.AsyncClient, device_id: str, action: str, resolve_action, normalize_params) -> dict:
|
||||
canonical = resolve_action(action)
|
||||
params = dict(SAFE_PARAMS.get(action) or SAFE_PARAMS.get(canonical, {}))
|
||||
params = normalize_params(action, params)
|
||||
t0 = time.time()
|
||||
try:
|
||||
resp = await client.post(
|
||||
f"{BASE}/api/v3/hook/execute",
|
||||
json={"device_id": device_id, "platform": "wechat", "action": action, "params": params, "hook_only": True},
|
||||
timeout=60,
|
||||
)
|
||||
body = resp.json()
|
||||
data = body.get("data") if isinstance(body.get("data"), dict) else body
|
||||
ok = bool(data.get("success")) or body.get("code") == 200 and data.get("success") is not False and "不支持" not in str(data.get("message", ""))
|
||||
if data.get("success") is False:
|
||||
ok = False
|
||||
err = "" if ok else str(data.get("message") or data.get("error") or body)[:120]
|
||||
ch = body.get("channel_used") or data.get("_channel_used") or data.get("channel") or "hook"
|
||||
except Exception as e:
|
||||
ok, err, ch = False, str(e)[:160], "error"
|
||||
# 间歇性 503 / frida_connected:False / WS 未连接 → 多次重试(frida session 繁忙或瞬断)
|
||||
retries = 0
|
||||
while not ok and retries < 3 and any(
|
||||
k in err for k in ("Frida", "frida_connected", "503", "未连接", "RPC 未成功")
|
||||
):
|
||||
retries += 1
|
||||
await asyncio.sleep(2.0 * retries)
|
||||
try:
|
||||
resp = await client.post(
|
||||
f"{BASE}/api/v3/hook/execute",
|
||||
json={"device_id": device_id, "platform": "wechat", "action": action, "params": params, "hook_only": True},
|
||||
timeout=60,
|
||||
)
|
||||
body = resp.json()
|
||||
data = body.get("data") if isinstance(body.get("data"), dict) else body
|
||||
ok = bool(data.get("success")) or (body.get("code") == 200 and data.get("success") is not False and "不支持" not in str(data.get("message", "")))
|
||||
if data.get("success") is False:
|
||||
ok = False
|
||||
err = "" if ok else str(data.get("message") or data.get("error") or body)[:160]
|
||||
ch = body.get("channel_used") or data.get("_channel_used") or "hook"
|
||||
except Exception as e2:
|
||||
err = str(e2)[:160]
|
||||
return {"action": action, "success": ok, "channel": str(ch), "elapsed_ms": int((time.time() - t0) * 1000), "error": err, "retries": retries}
|
||||
|
||||
|
||||
async def prefetch_context(client: httpx.AsyncClient, device_id: str) -> None:
|
||||
"""从真机拉取 group_id / label_id 等,写入 SAFE_PARAMS。"""
|
||||
try:
|
||||
r = await client.post(
|
||||
f"{BASE}/api/v3/hook/execute",
|
||||
json={"device_id": device_id, "platform": "wechat", "action": "get_groups", "params": {"limit": 5}, "hook_only": True},
|
||||
timeout=60,
|
||||
)
|
||||
groups = (r.json().get("data") or {}).get("groups") or []
|
||||
gid = None
|
||||
for g in groups:
|
||||
candidate = g.get("group_id") or g.get("wxid") or g.get("username")
|
||||
if not candidate:
|
||||
continue
|
||||
chk = await client.post(
|
||||
f"{BASE}/api/v3/hook/execute",
|
||||
json={"device_id": device_id, "platform": "wechat", "action": "get_group_info", "params": {"group_id": candidate}, "hook_only": True},
|
||||
timeout=60,
|
||||
)
|
||||
if (chk.json().get("data") or {}).get("success"):
|
||||
gid = candidate
|
||||
break
|
||||
if gid:
|
||||
for k in (
|
||||
"get_group_info", "get_group_members", "send_group_message",
|
||||
"generate_group_qr_code", "invite_to_group", "remove_from_group",
|
||||
"set_group_announcement", "set_group_notice", "set_group_name", "quit_group",
|
||||
):
|
||||
if k in SAFE_PARAMS:
|
||||
SAFE_PARAMS[k]["group_id"] = gid
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
r = await client.post(
|
||||
f"{BASE}/api/v3/hook/execute",
|
||||
json={"device_id": device_id, "platform": "wechat", "action": "get_labels", "params": {}, "hook_only": True},
|
||||
timeout=60,
|
||||
)
|
||||
labels = (r.json().get("data") or {}).get("labels") or []
|
||||
if labels:
|
||||
lid = str(labels[0].get("label_id") or labels[0].get("id") or "1")
|
||||
SAFE_PARAMS["get_contacts_by_label"]["label_id"] = lid
|
||||
SAFE_PARAMS["delete_tag"]["label_id"] = lid
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
async def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-d", "--device-id", default=DEVICE)
|
||||
args = parser.parse_args()
|
||||
actions, resolve_action, normalize_params = _load_actions()
|
||||
results = []
|
||||
async with httpx.AsyncClient(trust_env=False) as client:
|
||||
await prefetch_context(client, args.device_id)
|
||||
for action in actions:
|
||||
row = await probe_one(client, args.device_id, action, resolve_action, normalize_params)
|
||||
results.append(row)
|
||||
print(f"{'✓' if row['success'] else '✗'} {action:<32} {row['elapsed_ms']:>5}ms{' (retry %d)' % row['retries'] if row.get('retries') else ''}")
|
||||
await asyncio.sleep(0.6)
|
||||
passed = sum(1 for x in results if x["success"])
|
||||
report = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"device_id": args.device_id,
|
||||
"total": len(results),
|
||||
"passed": passed,
|
||||
"failed": len(results) - passed,
|
||||
"success_rate": f"{100 * passed / max(len(results), 1):.1f}%",
|
||||
"results": results,
|
||||
}
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
path = OUT / f"matrix_hook_catalog_{args.device_id}_{int(time.time())}.json"
|
||||
path.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
print(f"\nHook catalog: {passed}/{len(results)} → {path}")
|
||||
return 0 if passed == len(results) else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(asyncio.run(main()))
|
||||
Reference in New Issue
Block a user