Files
workphone-sdk/sdk/app/routers/wechat_full.py

964 lines
33 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.

"""
微信全量操作路由 — 补全统一路由缺失的端点
==========================================
对齐 agent 端 128 个 RPC 方法,通过 WechatSkillV2 统一分发。
所有操作通过 WiFi Frida 无线执行,无需 USB。
新增端点(统一路由已有的不重复):
- 视频号操作6个
- 小程序操作3个
- 收藏管理3个
- 公众号操作4个
- 账号安全8个
- 支付操作6个
- 浮窗管理2个
- 表情管理2个
- 搜索1个
- 设备信息3个
"""
from __future__ import annotations
import logging
from typing import Optional, List, Any, Dict
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel, Field
router = APIRouter(prefix="/wechat", tags=["微信全量操作"])
logger = logging.getLogger(__name__)
# ============================================================
# § 1 请求模型
# ============================================================
class WechatBaseRequest(BaseModel):
device_id: str
timeout: int = 30
class WxidRequest(WechatBaseRequest):
wxid: str
class SearchRequest(WechatBaseRequest):
keyword: str
limit: int = 50
class ChannelVideoRequest(WechatBaseRequest):
video_id: str
class ChannelCommentRequest(WechatBaseRequest):
video_id: str
comment: str
class ChannelFollowRequest(WechatBaseRequest):
channel_id: str
class ShareChannelRequest(WechatBaseRequest):
video_id: str
to_id: str
class MiniProgramRequest(WechatBaseRequest):
app_id: str
path: str = ""
class ShareMiniProgramRequest(WechatBaseRequest):
app_id: str
to_id: str
title: str = ""
class FavoriteRequest(WechatBaseRequest):
msg_svr_id: str = ""
local_id: str = ""
type: str = "message"
class OfficialAccountRequest(WechatBaseRequest):
account_id: str
class OfficialAccountArticlesRequest(WechatBaseRequest):
account_id: str
limit: int = 10
class PasswordRequest(WechatBaseRequest):
old_password: str
new_password: str
class PhoneRequest(WechatBaseRequest):
phone: str
class DeviceRemoveRequest(WechatBaseRequest):
target_device_id: str
class ToggleRequest(WechatBaseRequest):
enable: bool = True
class RedPacketRequest(WechatBaseRequest):
to_id: str
amount: str
message: str = "恭喜发财"
confirm: bool = False
dry_run: bool = True
class TransferRequest(WechatBaseRequest):
to_id: str
amount: str
description: str = ""
class ReceiveRequest(WechatBaseRequest):
msg_svr_id: str
confirm: bool = False
dry_run: bool = True
class FloatRequest(WechatBaseRequest):
wxid: str
class EmojiSendRequest(WechatBaseRequest):
to_id: str
emoji_md5: str
class EmojiAddRequest(WechatBaseRequest):
image_path: str
class ProfileSetRequest(WechatBaseRequest):
value: str
class AccountProfileUpdateRequest(WechatBaseRequest):
"""个人微信号统一维护请求;只传需要修改的字段。"""
nickname: Optional[str] = Field(None, max_length=32, description="微信昵称")
signature: Optional[str] = Field(None, max_length=120, description="个性签名")
avatar_path: Optional[str] = Field(None, description="手机可读取的头像绝对路径")
sex: Optional[str] = Field(None, pattern="^(0|1|2|unknown|male|female)$", description="0未知/1男/2女")
region: Optional[str] = Field(None, max_length=80, description="地区或我的地址,如福建 厦门")
status: Optional[str] = Field(None, max_length=80, description="状态/来电名/Whats Up")
phone: Optional[str] = Field(None, pattern=r"^1[3-9]\d{9}$", description="待绑定手机号")
wechat_id: Optional[str] = Field(None, min_length=6, max_length=20, description="新微信号")
dry_run: bool = Field(True, description="true仅生成执行计划false执行可直接下发的修改")
confirm_sensitive: bool = Field(False, description="绑定手机号等敏感项的显式确认")
model_config = {
"json_schema_extra": {
"examples": [{
"device_id": "xgfe65eimrrofyws",
"nickname": "卡若",
"signature": "自律、坚持、长期主义",
"region": "福建 厦门",
"status": "专注私域运营",
"dry_run": True,
"confirm_sensitive": False,
}]
}
}
class PrivacyRequest(WechatBaseRequest):
setting: str
value: bool = True
class NotificationRequest(WechatBaseRequest):
type: str = "all"
enable: bool = True
class ChatBgRequest(WechatBaseRequest):
wxid: str
image_path: str = ""
class DndRequest(WechatBaseRequest):
wxid: str
enable: bool = True
class PinChatRequest(WechatBaseRequest):
wxid: str
pin: bool = True
class QrScanRequest(WechatBaseRequest):
image_path: str = ""
class LabelContactRequest(WechatBaseRequest):
wxid: str
label_ids: List[str]
# ============================================================
# § 2 辅助函数
# ============================================================
async def _get_skill(device_id: str):
"""获取 WechatSkillV2 实例"""
# 服务端与设备端都存在 skills 包Frida 路由可能把 agent/ 放到
# sys.path 首位,直接 `from skills...` 会误载入设备端包。使用服务端
# 固定文件路径加载,保证 Hook 状态接口稳定。
import importlib.util
import sys
from pathlib import Path
module_name = "workphone_server_wechat_skill_v2"
module = sys.modules.get(module_name)
if module is None:
skill_path = Path(__file__).resolve().parents[1] / "skills" / "wechat" / "skill_v2.py"
spec = importlib.util.spec_from_file_location(module_name, skill_path)
if spec is None or spec.loader is None:
raise RuntimeError(f"服务端微信 Skill 文件不可加载: {skill_path}")
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
WechatSkillV2 = module.WechatSkillV2
return WechatSkillV2(device_id=device_id)
# ============================================================
# § 3 视频号操作H27
# ============================================================
@router.get("/channels/browse", response_model=dict)
async def browse_channels(device_id: str, limit: int = 10):
"""浏览视频号"""
skill = await _get_skill(device_id)
return await skill.browse_channels(limit)
@router.post("/channels/like", response_model=dict)
async def like_channel_video(req: ChannelVideoRequest):
"""视频号点赞"""
skill = await _get_skill(req.device_id)
return await skill.like_channel_video(req.video_id)
@router.post("/channels/comment", response_model=dict)
async def comment_channel_video(req: ChannelCommentRequest):
"""视频号评论"""
skill = await _get_skill(req.device_id)
return await skill.comment_channel_video(req.video_id, req.comment)
@router.post("/channels/follow", response_model=dict)
async def follow_channel(req: ChannelFollowRequest):
"""关注视频号"""
skill = await _get_skill(req.device_id)
return await skill.follow_channel(req.channel_id)
@router.post("/channels/unfollow", response_model=dict)
async def unfollow_channel(req: ChannelFollowRequest):
"""取消关注视频号"""
skill = await _get_skill(req.device_id)
return await skill.unfollow_channel(req.channel_id)
@router.post("/channels/share", response_model=dict)
async def share_channel_video(req: ShareChannelRequest):
"""分享视频号视频"""
skill = await _get_skill(req.device_id)
return await skill.share_channel_video(req.video_id, req.to_id)
# ============================================================
# § 4 小程序操作H32
# ============================================================
@router.post("/mini-program/open", response_model=dict)
async def open_mini_program(req: MiniProgramRequest):
"""打开小程序"""
skill = await _get_skill(req.device_id)
return await skill.open_mini_program(req.app_id, req.path)
@router.get("/mini-program/recent", response_model=dict)
async def get_recent_mini_programs(device_id: str):
"""获取最近使用的小程序"""
skill = await _get_skill(device_id)
return await skill.get_recent_mini_programs()
@router.post("/mini-program/share", response_model=dict)
async def share_mini_program(req: ShareMiniProgramRequest):
"""分享小程序"""
skill = await _get_skill(req.device_id)
return await skill.share_mini_program(req.app_id, req.to_id, req.title)
# ============================================================
# § 5 收藏管理H29
# ============================================================
@router.get("/favorites", response_model=dict)
async def get_favorites(device_id: str, limit: int = 50):
"""获取收藏列表"""
skill = await _get_skill(device_id)
return await skill.get_favorites(limit)
@router.post("/favorites/add", response_model=dict)
async def add_favorite(req: FavoriteRequest):
"""添加收藏"""
skill = await _get_skill(req.device_id)
return await skill.add_favorite(req.msg_svr_id, req.type)
@router.delete("/favorites/{local_id}", response_model=dict)
async def delete_favorite(local_id: str, device_id: str = ""):
"""删除收藏"""
skill = await _get_skill(device_id)
return await skill.delete_favorite(local_id)
# ============================================================
# § 6 公众号操作H36
# ============================================================
@router.get("/official-accounts", response_model=dict)
async def get_official_accounts(device_id: str, limit: int = 100):
"""获取关注的公众号列表"""
skill = await _get_skill(device_id)
return await skill.get_official_accounts(limit)
@router.post("/official-accounts/follow", response_model=dict)
async def follow_official_account(req: OfficialAccountRequest):
"""关注公众号"""
skill = await _get_skill(req.device_id)
return await skill.follow_official_account(req.account_id)
@router.post("/official-accounts/unfollow", response_model=dict)
async def unfollow_official_account(req: OfficialAccountRequest):
"""取消关注公众号"""
skill = await _get_skill(req.device_id)
return await skill.unfollow_official_account(req.account_id)
@router.get("/official-accounts/{account_id}/articles", response_model=dict)
async def get_official_account_articles(account_id: str, device_id: str = "", limit: int = 10):
"""获取公众号文章"""
skill = await _get_skill(device_id)
return await skill.get_official_account_articles(account_id, limit)
# ============================================================
# § 7 账号安全H24
# ============================================================
@router.post("/security/unblock", response_model=dict)
async def unblock_self(req: WechatBaseRequest):
"""自助解封"""
skill = await _get_skill(req.device_id)
return await skill.unblock_self()
@router.post("/security/change-password", response_model=dict)
async def change_password(req: PasswordRequest):
"""修改密码"""
skill = await _get_skill(req.device_id)
return await skill.change_password(req.old_password, req.new_password)
@router.post("/security/bind-phone", response_model=dict)
async def bind_phone(req: PhoneRequest):
"""绑定手机号"""
skill = await _get_skill(req.device_id)
return await skill.bind_phone(req.phone)
@router.post("/security/unbind-phone", response_model=dict)
async def unbind_phone(req: WechatBaseRequest):
"""解绑手机号"""
skill = await _get_skill(req.device_id)
return await skill.unbind_phone()
@router.get("/security/login-devices", response_model=dict)
async def get_login_devices(device_id: str):
"""获取登录设备列表"""
skill = await _get_skill(device_id)
return await skill.get_login_devices()
@router.delete("/security/login-device", response_model=dict)
async def remove_login_device(req: DeviceRemoveRequest):
"""移除登录设备"""
skill = await _get_skill(req.device_id)
return await skill.remove_login_device(req.target_device_id)
@router.post("/security/fingerprint", response_model=dict)
async def enable_fingerprint(req: ToggleRequest):
"""启用/禁用指纹"""
skill = await _get_skill(req.device_id)
return await skill.enable_fingerprint(req.enable)
@router.post("/security/account-protection", response_model=dict)
async def set_account_protection(req: ToggleRequest):
"""设置账号保护"""
skill = await _get_skill(req.device_id)
return await skill.set_account_protection(req.enable)
# ============================================================
# § 8 支付操作H25
# ============================================================
@router.post("/pay/red-packet", response_model=dict)
async def send_red_packet(req: RedPacketRequest):
"""发红包;默认返回确认前演练结果。"""
if req.dry_run or not req.confirm:
return {
"code": 200,
"success": True,
"verified": True,
"dry_run": True,
"confirm_required": True,
"action": "send_red_packet",
"to_id": req.to_id,
"amount": req.amount,
}
skill = await _get_skill(req.device_id)
return await skill.send_red_packet(req.to_id, req.amount, req.message)
@router.post("/pay/red-packet/receive", response_model=dict)
async def receive_red_packet(req: ReceiveRequest):
"""收红包;默认返回确认前演练结果。"""
if req.dry_run or not req.confirm:
return {
"code": 200,
"success": True,
"verified": True,
"dry_run": True,
"confirm_required": True,
"action": "receive_red_packet",
"msg_svr_id": req.msg_svr_id,
}
skill = await _get_skill(req.device_id)
return await skill.receive_red_packet(req.msg_svr_id)
@router.post("/pay/transfer", response_model=dict)
async def send_transfer(req: TransferRequest):
"""转账"""
skill = await _get_skill(req.device_id)
return await skill.send_transfer(req.to_id, req.amount, req.description)
@router.post("/pay/transfer/receive", response_model=dict)
async def receive_transfer(req: ReceiveRequest):
"""收转账"""
skill = await _get_skill(req.device_id)
return await skill.receive_transfer(req.msg_svr_id)
@router.get("/pay/balance", response_model=dict)
async def get_wallet_balance(device_id: str):
"""获取钱包余额"""
skill = await _get_skill(device_id)
return await skill.get_wallet_balance()
@router.get("/pay/transactions", response_model=dict)
async def get_transaction_history(device_id: str, limit: int = 20):
"""获取交易记录"""
skill = await _get_skill(device_id)
return await skill.get_transaction_history(limit)
# ============================================================
# § 9 浮窗管理H38
# ============================================================
@router.post("/float/add", response_model=dict)
async def add_to_float(req: FloatRequest):
"""添加到浮窗"""
skill = await _get_skill(req.device_id)
return await skill.add_to_float(req.wxid)
@router.post("/float/remove", response_model=dict)
async def remove_from_float(req: FloatRequest):
"""从浮窗移除"""
skill = await _get_skill(req.device_id)
return await skill.remove_from_float(req.wxid)
# ============================================================
# § 10 表情管理H37
# ============================================================
@router.post("/emoji/send-custom", response_model=dict)
async def send_emoji_custom(req: EmojiSendRequest):
"""发送自定义表情"""
skill = await _get_skill(req.device_id)
return await skill.send_emoji(req.to_id, req.emoji_md5)
@router.post("/emoji/add-custom", response_model=dict)
async def add_custom_emoji(req: EmojiAddRequest):
"""添加自定义表情"""
skill = await _get_skill(req.device_id)
return await skill.add_custom_emoji(req.image_path)
# ============================================================
# § 11 账号管理H23
# ============================================================
@router.get("/profile", response_model=dict)
async def get_profile(device_id: str):
"""获取个人资料"""
skill = await _get_skill(device_id)
return await skill.get_profile()
@router.get("/account/status", response_model=dict)
async def check_account_status(device_id: str):
"""检查账号状态"""
skill = await _get_skill(device_id)
return await skill.check_account_status()
@router.post("/profile/nickname", response_model=dict)
async def set_nickname(req: ProfileSetRequest):
"""设置昵称"""
skill = await _get_skill(req.device_id)
return await skill.set_nickname(req.value)
@router.post("/profile/signature", response_model=dict)
async def set_signature(req: ProfileSetRequest):
"""设置签名"""
skill = await _get_skill(req.device_id)
return await skill.set_signature(req.value)
@router.post("/profile/avatar", response_model=dict)
async def set_avatar(req: ProfileSetRequest):
"""设置头像"""
skill = await _get_skill(req.device_id)
return await skill.set_avatar(req.value)
@router.post("/profile/sex", response_model=dict)
async def set_sex(req: ProfileSetRequest):
"""设置性别"""
skill = await _get_skill(req.device_id)
return await skill.set_sex(req.value)
@router.post("/profile/region", response_model=dict)
async def set_region(req: ProfileSetRequest):
"""设置地区"""
skill = await _get_skill(req.device_id)
return await skill.set_region(req.value)
@router.post("/profile/status", response_model=dict)
async def set_what_up(req: ProfileSetRequest):
"""设置状态"""
skill = await _get_skill(req.device_id)
return await skill.set_what_up(req.value)
@router.get(
"/account/profile",
response_model=dict,
summary="读取个人微信号完整资料",
)
async def get_account_profile(device_id: str):
"""无界面读取昵称、微信内部ID、手机号、签名、性别、地区及版本。"""
skill = await _get_skill(device_id)
return await skill.get_profile()
@router.get(
"/account/maintenance/capabilities",
response_model=dict,
summary="获取个人微信号可维护能力",
)
async def get_account_maintenance_capabilities():
"""返回所有资料字段的读取、写入、验证方式及细分接口。"""
return {
"code": 200,
"success": True,
"data": {
"read_endpoint": "GET /api/v3/wechat/account/profile",
"update_endpoint": "POST /api/v3/wechat/account/profile/update",
"qr_endpoint": "GET /api/v3/wechat/account/qr",
"fields": {
"nickname": {"mode": "write", "endpoint": "/profile/nickname", "action": "set_nickname"},
"signature": {"mode": "write", "endpoint": "/profile/signature", "action": "set_signature"},
"avatar_path": {"mode": "write", "endpoint": "/profile/avatar", "action": "set_avatar"},
"sex": {"mode": "write", "endpoint": "/profile/sex", "action": "set_sex"},
"region": {"mode": "write", "endpoint": "/profile/region", "action": "set_region"},
"status": {"mode": "write", "endpoint": "/profile/status", "action": "set_what_up"},
"phone": {
"mode": "verification_required",
"endpoint": "/security/bind-phone",
"action": "bind_phone",
},
"wechat_id": {
"mode": "verification_required",
"note": "微信号修改受微信次数和账号状态约束,进入微信验证流程后确认",
},
"qr_code": {"mode": "read", "endpoint": "/qr/my", "action": "generate_my_qr_code"},
},
},
}
@router.post(
"/account/profile/update",
response_model=dict,
summary="统一维护个人微信号资料",
description=(
"无界面批量维护昵称、签名、头像、性别、地区、状态和手机号。"
"dry_run=true返回计划false逐项通过Frida执行并返回真实回执。"
),
)
async def update_account_profile(req: AccountProfileUpdateRequest):
"""按字段编排个人资料修改,保留逐项回执。"""
operations = [
("nickname", req.nickname, "set_nickname"),
("signature", req.signature, "set_signature"),
("avatar_path", req.avatar_path, "set_avatar"),
("sex", req.sex, "set_sex"),
("region", req.region, "set_region"),
("status", req.status, "set_what_up"),
]
plan = [
{"field": field, "value": value, "action": action}
for field, value, action in operations if value is not None
]
if req.phone is not None:
plan.append({
"field": "phone",
"value": req.phone,
"action": "bind_phone",
"requires_confirmation": True,
})
if req.wechat_id is not None:
plan.append({
"field": "wechat_id",
"value": req.wechat_id,
"action": "wechat_verification_flow",
"requires_confirmation": True,
})
if req.dry_run:
return {
"code": 200,
"success": True,
"dry_run": True,
"data": {"total": len(plan), "plan": plan},
"channel_used": "none",
}
skill = await _get_skill(req.device_id)
results: List[Dict[str, Any]] = []
method_map = {
"set_nickname": skill.set_nickname,
"set_signature": skill.set_signature,
"set_avatar": skill.set_avatar,
"set_sex": skill.set_sex,
"set_region": skill.set_region,
"set_what_up": skill.set_what_up,
}
for field, value, action in operations:
if value is None:
continue
result = await method_map[action](value)
results.append({"field": field, "action": action, "result": result})
if req.phone is not None:
if req.confirm_sensitive:
result = await skill.bind_phone(req.phone)
results.append({"field": "phone", "action": "bind_phone", "result": result})
else:
results.append({
"field": "phone",
"action": "bind_phone",
"result": {"success": False, "status": "confirmation_required"},
})
if req.wechat_id is not None:
results.append({
"field": "wechat_id",
"action": "wechat_verification_flow",
"result": {
"success": False,
"status": "verification_required",
"requested_wechat_id": req.wechat_id,
},
})
succeeded = sum(bool((item["result"] or {}).get("success")) for item in results)
return {
"code": 200,
"success": succeeded == len(results),
"dry_run": False,
"data": {
"total": len(results),
"succeeded": succeeded,
"failed": len(results) - succeeded,
"results": results,
},
"channel_used": "server/frida",
}
@router.get(
"/account/qr",
response_model=dict,
summary="获取个人微信二维码",
)
async def get_account_qr(device_id: str):
"""获取当前个人微信号二维码回执。"""
skill = await _get_skill(device_id)
return await skill.generate_my_qr_code()
# ============================================================
# § 12 搜索H31
# ============================================================
@router.get("/search", response_model=dict)
async def global_search(device_id: str, keyword: str, limit: int = 30):
"""全局搜索"""
skill = await _get_skill(device_id)
return await skill.global_search(keyword, limit)
# ============================================================
# § 13 设置H30
# ============================================================
@router.post("/settings/privacy", response_model=dict)
async def set_privacy(req: PrivacyRequest):
"""设置隐私"""
skill = await _get_skill(req.device_id)
return await skill.set_privacy(req.setting, req.value)
@router.post("/settings/notification", response_model=dict)
async def set_notification(req: NotificationRequest):
"""设置通知"""
skill = await _get_skill(req.device_id)
return await skill.set_notification(req.type, req.enable)
@router.post("/settings/clear-history", response_model=dict)
async def clear_chat_history(req: WxidRequest):
"""清除聊天记录"""
skill = await _get_skill(req.device_id)
return await skill.clear_chat_history(req.wxid)
@router.post("/settings/chat-background", response_model=dict)
async def set_chat_background(req: ChatBgRequest):
"""设置聊天背景"""
skill = await _get_skill(req.device_id)
return await skill.set_chat_background(req.wxid, req.image_path)
@router.post("/settings/dnd", response_model=dict)
async def set_do_not_disturb(req: DndRequest):
"""设置免打扰"""
skill = await _get_skill(req.device_id)
return await skill.set_do_not_disturb(req.wxid, req.enable)
@router.post("/settings/pin-chat", response_model=dict)
async def pin_chat(req: PinChatRequest):
"""置顶聊天"""
skill = await _get_skill(req.device_id)
return await skill.pin_chat(req.wxid, req.pin)
# ============================================================
# § 14 二维码H26
# ============================================================
@router.post("/qr/scan", response_model=dict)
async def scan_qr_code(req: QrScanRequest):
"""扫描二维码"""
skill = await _get_skill(req.device_id)
return await skill.scan_qr_code(req.image_path)
@router.get("/qr/my", response_model=dict)
async def generate_my_qr_code(device_id: str):
"""生成我的二维码"""
skill = await _get_skill(device_id)
return await skill.generate_my_qr_code()
@router.get("/qr/group/{group_id}", response_model=dict)
async def generate_group_qr_code(group_id: str, device_id: str = ""):
"""生成群二维码"""
skill = await _get_skill(device_id)
return await skill.generate_group_qr_code(group_id)
# ============================================================
# § 15 设备信息H39
# ============================================================
@router.get("/device-info", response_model=dict)
async def get_device_info(device_id: str):
"""获取设备信息"""
skill = await _get_skill(device_id)
return await skill.get_device_info()
@router.get("/storage-info", response_model=dict)
async def get_storage_info(device_id: str):
"""获取存储信息"""
skill = await _get_skill(device_id)
return await skill.get_storage_info()
@router.get("/network-info", response_model=dict)
async def get_network_info(device_id: str):
"""获取网络信息"""
skill = await _get_skill(device_id)
return await skill.get_network_info()
# ============================================================
# § 16 标签补全H28
# ============================================================
@router.post("/tag/set-contact", response_model=dict)
async def set_contact_label(req: LabelContactRequest):
"""设置联系人标签"""
skill = await _get_skill(req.device_id)
return await skill.set_contact_label(req.wxid, req.label_ids)
@router.get("/tag/contacts/{label_id}", response_model=dict)
async def get_contacts_by_label(label_id: str, device_id: str = ""):
"""按标签获取联系人"""
skill = await _get_skill(device_id)
return await skill.get_contacts_by_label(label_id)
# ============================================================
# § 17 Hook 状态
# ============================================================
@router.get("/hook/status", response_model=dict)
async def get_hook_status(device_id: str):
"""获取 Hook 状态"""
skill = await _get_skill(device_id)
return await skill.get_hook_status()
@router.get("/hook/process-info", response_model=dict)
async def get_process_info(device_id: str):
"""获取微信进程信息"""
skill = await _get_skill(device_id)
return await skill.get_process_info()
@router.get("/hook/wechat-version", response_model=dict)
async def get_wechat_version(device_id: str):
"""获取微信版本"""
skill = await _get_skill(device_id)
return await skill.get_wechat_version()
# ============================================================
# § 17 矩阵 H40 补全128 canonical 扩展)
# ============================================================
@router.get("/ping", response_model=dict)
async def ping(device_id: str):
skill = await _get_skill(device_id)
return await skill.ping()
@router.get("/safety-center", response_model=dict)
async def get_safety_center(device_id: str):
skill = await _get_skill(device_id)
return await skill.get_safety_center()
@router.get("/restrictions", response_model=dict)
async def check_restrictions(device_id: str):
skill = await _get_skill(device_id)
return await skill.check_restrictions()
@router.get("/top-stories", response_model=dict)
async def get_top_stories(device_id: str):
skill = await _get_skill(device_id)
return await skill.get_top_stories()
@router.get("/steps", response_model=dict)
async def get_wechat_steps(device_id: str):
skill = await _get_skill(device_id)
return await skill.get_wechat_steps()
@router.get("/stickers", response_model=dict)
async def get_sticker_list(device_id: str):
skill = await _get_skill(device_id)
return await skill.get_sticker_list()
@router.post("/pay/show-code", response_model=dict)
async def show_payment_code(device_id: str):
skill = await _get_skill(device_id)
return await skill.show_payment_code()
@router.post("/steps/like", response_model=dict)
async def like_wechat_steps(device_id: str, user_id: str = "filehelper"):
skill = await _get_skill(device_id)
return await skill.like_wechat_steps(user_id)
@router.post("/cache/clear", response_model=dict)
async def clear_cache(device_id: str):
skill = await _get_skill(device_id)
return await skill.clear_cache()
@router.get("/update/check", response_model=dict)
async def check_for_update(device_id: str):
skill = await _get_skill(device_id)
return await skill.check_for_update()
class CallRequest(WechatBaseRequest):
to_id: str
@router.post("/call/voice", response_model=dict)
async def voice_call(req: CallRequest):
skill = await _get_skill(req.device_id)
return await skill.voice_call(req.to_id)
@router.post("/call/video", response_model=dict)
async def video_call(req: CallRequest):
skill = await _get_skill(req.device_id)
return await skill.video_call(req.to_id)
@router.post("/location/share-realtime", response_model=dict)
async def share_real_time_location(req: CallRequest):
skill = await _get_skill(req.device_id)
return await skill.share_real_time_location(req.to_id)
class DownloadFileRequest(WechatBaseRequest):
url: str
save_path: str = "/sdcard/Download/wp_download.bin"
@router.post("/file/download", response_model=dict)
async def download_file(req: DownloadFileRequest):
skill = await _get_skill(req.device_id)
return await skill.download_file(req.url, req.save_path)
class MassSendRequest(WechatBaseRequest):
user_ids: List[str]
content: str
@router.post("/message/mass-send", response_model=dict)
async def mass_send(req: MassSendRequest):
skill = await _get_skill(req.device_id)
return await skill.mass_send(req.user_ids, req.content)
@router.post("/message/batch-send", response_model=dict)
async def batch_send(req: MassSendRequest):
skill = await _get_skill(req.device_id)
return await skill.batch_send(req.user_ids, req.content)
class ReplyCommentRequest(WechatBaseRequest):
sns_id: str
comment: str
@router.post("/moments/reply-comment", response_model=dict)
async def reply_comment(req: ReplyCommentRequest):
skill = await _get_skill(req.device_id)
return await skill.reply_comment(req.sns_id, req.comment)
class ReceivePaymentRequest(WechatBaseRequest):
msg_svr_id: str
@router.post("/pay/receive", response_model=dict)
async def receive_payment(req: ReceivePaymentRequest):
skill = await _get_skill(req.device_id)
return await skill.receive_payment(req.msg_svr_id)
# ============================================================
# § 18 统一执行入口
# ============================================================
class UnifiedExecuteRequest(BaseModel):
device_id: str
action: str
params: dict = Field(default_factory=dict)
timeout: int = 30
@router.post("/execute", response_model=dict)
async def unified_execute(req: UnifiedExecuteRequest):
"""
统一执行入口 — 支持所有 128 个微信 canonical action174 catalog 含 alias
通过 action 名直接调用对应的 Frida RPC 方法。
通道自动选择Hook > WebSocket Agent > UI 自动化
"""
skill = await _get_skill(req.device_id)
return await skill.execute(req.action, req.params)
@router.get("/actions", response_model=dict)
async def list_all_actions():
"""列出所有支持的微信操作128 canonical + 174 catalog"""
from skills.wechat.skill_v2 import WECHAT_ACTIONS, MODULES, WechatSkillV2
catalog = WechatSkillV2.get_catalog_action_list()
return {
"success": True,
"total_actions": len(WECHAT_ACTIONS),
"catalog_actions": len(catalog),
"total_modules": len(MODULES),
"modules": {k: v["name"] for k, v in MODULES.items()},
"actions": sorted(WECHAT_ACTIONS.keys()),
"catalog": catalog,
}