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

2350 lines
89 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.

"""
工作手机SDK v3.0 - 统一接口路由
存客宝重点对接的接口
功能模块:
1. 消息管理 - 发送/获取消息
2. 好友管理 - 添加/通过好友请求
3. 群聊管理 - 创建群/邀请入群/群发消息
4. 标签管理 - 添加/删除/查询标签
5. 朋友圈管理 - 发布/点赞/评论
6. 联系人管理 - 获取联系人列表
"""
from fastapi import APIRouter, HTTPException
from typing import Optional, List
from pydantic import BaseModel, Field
from enum import Enum
import asyncio
import logging
import re
from services.ws_hub import ws_hub
from services.device_manager import device_manager
from services.adb_device import adb_manager
from services import ai_agent as ai_agent_service
from services.rate_limiter import rate_limiter, DailyLimitExceeded
from services.content_guard import diversify_content, filter_sensitive, has_sensitive_words
from services.account_lifecycle import account_lifecycle
from config import settings
router = APIRouter()
logger = logging.getLogger(__name__)
# ========== 枚举定义 ==========
class Platform(str, Enum):
"""支持的平台"""
WECHAT = "wechat"
DOUYIN = "douyin"
XHS = "xhs"
XIANYU = "xianyu"
SOUL = "soul"
class MessageType(str, Enum):
"""消息类型"""
TEXT = "text"
IMAGE = "image"
VIDEO = "video"
VOICE = "voice"
FILE = "file"
LINK = "link"
MINI_PROGRAM = "mini_program"
class Channel(str, Enum):
"""执行通道"""
OFFICIAL_API = "official_api"
SDK_CONTROL = "sdk_control"
AI_AGENT = "ai_agent"
HOOK = "hook"
# ========== 消息相关模型 ==========
class SendMessageRequest(BaseModel):
"""发送消息请求"""
device_id: str
platform: Platform
to_id: str
content: str
msg_type: MessageType = MessageType.TEXT
media_url: Optional[str] = None
at_list: Optional[List[str]] = None # @列表(群聊时使用)
timeout_seconds: Optional[int] = None # 本次请求超时(秒),不传则用 MESSAGE_SEND_TIMEOUT
channel: Optional[str] = None # 强制通道hook/sdk_control/ai_agent/official_api
hook_config: Optional[dict] = None # Hook配置script_id/method/timeout
class SendMessageResponse(BaseModel):
"""发送消息响应"""
success: bool
message_id: Optional[str] = None
channel_used: Channel
error: Optional[str] = None
class GetMessagesRequest(BaseModel):
"""获取消息请求"""
device_id: str
platform: Platform
conversation_id: Optional[str] = None
limit: int = 20
since_time: Optional[int] = None
class Message(BaseModel):
"""消息"""
message_id: str
from_id: str
to_id: str
content: str
msg_type: MessageType
timestamp: int
is_self: bool
# ========== 好友相关模型 ==========
class AddFriendRequest(BaseModel):
"""添加好友请求"""
device_id: str
platform: Platform
user_id: str
message: str = ""
source: Optional[str] = None # 来源说明
class AcceptFriendRequest(BaseModel):
"""通过好友请求"""
device_id: str
platform: Platform
user_id: str
class SetRemarkRequest(BaseModel):
"""设置备注请求"""
device_id: str
platform: Platform
user_id: str
remark: str
class DeleteFriendRequest(BaseModel):
"""删除好友请求"""
device_id: str
platform: Platform
user_id: str
# ========== 群聊相关模型 ==========
class CreateGroupRequest(BaseModel):
"""创建群聊请求"""
device_id: str
platform: Platform
group_name: str
member_ids: List[str] # 群成员ID列表
class InviteToGroupRequest(BaseModel):
"""邀请入群请求"""
device_id: str
platform: Platform
group_id: str # 群ID或群名
member_ids: List[str] # 邀请的成员ID
class RemoveFromGroupRequest(BaseModel):
"""移出群聊请求"""
device_id: str
platform: Platform
group_id: str
member_ids: List[str]
class SetGroupNoticeRequest(BaseModel):
"""设置群公告请求"""
device_id: str
platform: Platform
group_id: str
notice: str
class SetGroupNameRequest(BaseModel):
"""设置群名请求"""
device_id: str
platform: Platform
group_id: str
group_name: str
class GroupMessageRequest(BaseModel):
"""群发消息请求"""
device_id: str
platform: Platform
group_id: str
content: str
msg_type: MessageType = MessageType.TEXT
media_url: Optional[str] = None
at_all: bool = False # 是否@所有人
at_list: Optional[List[str]] = None
class SetGroupWelcomeRequest(BaseModel):
"""设置群欢迎语请求"""
device_id: str
platform: Platform
group_id: str
welcome_text: str
welcome_image: Optional[str] = None
# ========== 标签相关模型 ==========
class AddTagRequest(BaseModel):
"""添加标签请求"""
device_id: str
platform: Platform
user_id: str
tags: List[str]
class RemoveTagRequest(BaseModel):
"""移除标签请求"""
device_id: str
platform: Platform
user_id: str
tags: List[str]
class CreateTagRequest(BaseModel):
"""创建标签请求"""
device_id: str
platform: Platform
tag_name: str
class DeleteTagRequest(BaseModel):
"""删除标签请求"""
device_id: str
platform: Platform
tag_name: str
class GetUsersByTagRequest(BaseModel):
"""根据标签获取用户请求"""
device_id: str
platform: Platform
tag_name: str
limit: int = 100
# ========== 朋友圈相关模型 ==========
class PostMomentsRequest(BaseModel):
"""发布朋友圈请求"""
device_id: str
platform: Platform
content: str
images: Optional[List[str]] = None # 图片URL列表
video_url: Optional[str] = None
location: Optional[str] = None # 位置
visible_list: Optional[List[str]] = None # 可见名单
invisible_list: Optional[List[str]] = None # 不可见名单
class LikeMomentsRequest(BaseModel):
"""点赞朋友圈请求"""
device_id: str
platform: Platform
user_id: str # 朋友圈所属用户
post_index: int = 0 # 朋友圈索引(第几条)
class CommentMomentsRequest(BaseModel):
"""评论朋友圈请求"""
device_id: str
platform: Platform
user_id: str
post_index: int = 0
comment: str
reply_to: Optional[str] = None # 回复某人
class GetMomentsRequest(BaseModel):
"""获取朋友圈请求"""
device_id: str
platform: Platform
user_id: Optional[str] = None # 不指定则获取自己的
limit: int = 10
class ReplyCommentRequest(BaseModel):
"""回复评论请求(抖音/小红书等)"""
device_id: str
platform: Platform
video_id: Optional[str] = None
comment_id: str
content: str
# ========== 批量操作模型 ==========
class BatchSendMessageRequest(BaseModel):
"""批量发送消息请求"""
device_id: str
platform: Platform
to_ids: List[str]
content: str
msg_type: MessageType = MessageType.TEXT
media_url: Optional[str] = None
interval: float = 2.0 # 发送间隔(秒)
class BatchAddFriendRequest(BaseModel):
"""批量添加好友请求"""
device_id: str
platform: Platform
user_ids: List[str]
message: str = ""
interval: float = 5.0 # 添加间隔(秒)
# ========== 通道路由器 ==========
class ChannelRouter:
"""通道路由器 - 选择最优执行通道"""
# 官方API能力矩阵
OFFICIAL_API_SUPPORT = {
Platform.DOUYIN: ["send_message", "get_messages", "get_fans", "reply_comment"],
Platform.XIANYU: ["send_message", "get_messages"],
}
@staticmethod
def route(platform: Platform, action: str, device_online: bool) -> Channel:
"""选择执行通道"""
# 1. 检查官方API
if platform in ChannelRouter.OFFICIAL_API_SUPPORT:
if action in ChannelRouter.OFFICIAL_API_SUPPORT[platform]:
return Channel.OFFICIAL_API
# 2. 设备在线用SDK控制
if device_online:
return Channel.SDK_CONTROL
# 3. 兜底用AI Agent
return Channel.AI_AGENT
# ========== 辅助函数 ==========
def _check_device_online(device_id: str):
"""检查设备是否在线按优先级Hook > WebSocket > ADB"""
from services.connection_priority import connection_priority
best = connection_priority.get_best_mode(device_id)
if best:
return best.to_dict()["id"] # hook / agent / adb
raise HTTPException(status_code=503, detail=f"设备不在线: {device_id}")
def _get_device_mode(device_id: str) -> str:
"""获取设备最优连接模式"""
from services.connection_priority import connection_priority
channel = connection_priority.choose_execution_channel(device_id)
if channel == "agent":
return "websocket"
return channel
def _parse_messages_from_xml(xml: str, limit: int) -> list:
"""从 UI 树 XML 解析 text 节点作为消息列表ADB 模式用)"""
out = []
if not xml:
return out
skip = {"搜索", "发送", "输入", "消息", "私信", "通讯录"}
for i, m in enumerate(re.finditer(r'\btext="([^"]{1,200})"', xml)):
if i >= limit:
break
text = m.group(1).strip()
if text and text not in skip:
out.append({
"message_id": f"adb_ui_{i}",
"content": text,
"from_id": "",
"to_id": "",
"timestamp": 0,
"is_self": False,
})
return out
def _parse_contacts_from_xml(xml: str, limit: int) -> list:
"""从 UI 树 XML 解析 text 节点作为联系人列表ADB 模式用)"""
out = []
if not xml:
return out
skip = {"通讯录", "搜索", "添加朋友", "新的朋友", "群聊", "标签", "公众号", "微信"}
for i, m in enumerate(re.finditer(r'\btext="([^"]{1,100})"', xml)):
if i >= limit:
break
text = m.group(1).strip()
if text and text not in skip:
out.append({"user_id": f"adb_contact_{i}", "nickname": text, "remark": ""})
return out
async def _anti_ban_guard(device_id: str, platform: str, action: str, content: Optional[str] = None) -> dict:
"""
防封守卫:在执行任何操作前进行防封检查。
返回 {"pass": True/False, "reason": str, "content": str(处理后的内容)}
检查链:时段 → 生命周期 → L1全局 → L2设备 → L3动作 → 敏感词 → 内容差异化
"""
from services.anti_ban_alert import (
alert_daily_limit, alert_outside_hours, alert_sensitive_content,
)
from services.rate_limiter import OutsideOperationHours
try:
lifecycle_check = await account_lifecycle.check_allowed(device_id, platform, action)
if not lifecycle_check["allowed"]:
return {"pass": False, "reason": lifecycle_check["reason"], "content": content}
except Exception as e:
logger.warning(f"[anti_ban] 生命周期检查异常(跳过): {e}")
try:
is_new = await account_lifecycle.is_new_account(device_id, platform)
wait = await rate_limiter.check_and_wait(device_id, platform, action, is_new_account=is_new)
if wait > 0:
logger.info(f"[anti_ban] {device_id}/{platform}.{action} 等待了 {wait:.1f}s")
except OutsideOperationHours as e:
asyncio.ensure_future(alert_outside_hours(device_id, action))
return {"pass": False, "reason": str(e), "content": content}
except DailyLimitExceeded as e:
asyncio.ensure_future(alert_daily_limit(device_id, platform, action, -1, -1))
return {"pass": False, "reason": str(e), "content": content}
except Exception as e:
logger.warning(f"[anti_ban] 限流检查异常(跳过): {e}")
safe_content = content
if content:
sensitive = has_sensitive_words(content)
if sensitive:
logger.warning(f"[anti_ban] 检测到敏感词: {sensitive}")
asyncio.ensure_future(alert_sensitive_content(device_id, platform, sensitive))
safe_content = diversify_content(content, add_invisible=True, add_emoji=False)
return {"pass": True, "reason": "", "content": safe_content}
async def _execute_skill(device_id: str, platform: str, action: str, params: dict, timeout: int = 30) -> dict:
"""执行技能并返回结果WebSocket或ADB"""
mode = _get_device_mode(device_id)
if mode == "websocket":
# 通过WebSocket发送到手机Agent执行
result = await ws_hub.send_command(device_id, {
"type": "execute",
"data": {
"script": platform,
"action": action,
"params": params
}
}, timeout=timeout)
return result
elif mode == "adb":
# 通过ADB直接执行
adb_device = adb_manager.get_device(device_id)
if not adb_device:
return {"success": False, "error": "ADB设备不可用"}
return await _execute_via_adb(adb_device, platform, action, params)
else:
return {"success": False, "error": "设备离线"}
async def _execute_via_adb(device, platform: str, action: str, params: dict) -> dict:
"""通过ADB执行技能操作 — 微信走专用引擎,其他平台走通用 UI 自动化"""
try:
if platform == "wechat":
from services.wechat_adb_engine import WeChatADBEngine
engine = WeChatADBEngine(device)
loop = asyncio.get_event_loop()
result = await loop.run_in_executor(None, lambda: engine.execute(action, params))
return result
app_packages = {
"douyin": "com.ss.android.ugc.aweme",
"xhs": "com.xingin.xhs",
"xianyu": "com.taobao.idlefish",
"soul": "cn.soulapp.android",
}
package = app_packages.get(platform)
if action == "screenshot":
return device.screenshot()
if action == "send_message":
import time as _time
to_id = params.get("to_id", "")
content = params.get("content", "")
if package:
device.start_app(package)
_time.sleep(2)
device.click_text("搜索")
_time.sleep(0.5)
device.input_text(to_id)
_time.sleep(1)
device.click_text(to_id)
_time.sleep(1)
device.input_text(content, clear=False)
_time.sleep(0.3)
device.click_text("发送")
return {"success": True, "message_id": f"adb_{int(_time.time()*1000)}", "mode": "adb"}
logger.info(f"ADB通用执行: {platform}.{action} params={params}")
return {"success": True, "action": action, "params": params, "mode": "adb",
"note": "通过ADB UI自动化执行"}
except Exception as e:
logger.error(f"ADB执行失败: {e}")
return {"success": False, "error": str(e), "mode": "adb"}
# =============================================================================
# 、Hawk Hook 统一执行入口(丝滑控制)
# =============================================================================
class HookExecuteRequest(BaseModel):
"""Hawk Hook 格式 — 单端点执行任意操作,任何应用可直连"""
device_id: str
platform: Platform = Platform.WECHAT
action: str # send_message | get_contacts | post_moments | ... 共 96 个
params: dict = Field(default_factory=dict, description="action 所需参数")
@router.post("/hook/execute", response_model=dict, tags=["Hook模块管理"])
async def hook_execute(req: HookExecuteRequest):
"""
Hawk Hook 统一执行 — 单接口控制整台手机
任意应用只需调此端点,传 action + params 即可执行 96 种操作。
支持 WebSocket(Agent) 与 ADB 双通道,执行更丝滑。
示例:
```json
{"device_id":"xgfe65eimrrofyws","platform":"wechat","action":"send_message","params":{"to_id":"阿猫","content":"你好"}}
```
"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, req.action, req.params or {}
)
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": "sdk_control"}
# =============================================================================
# 一、消息管理接口
# =============================================================================
@router.post("/message/send", response_model=dict, tags=["消息管理"])
async def send_message(req: SendMessageRequest):
"""
发送消息(统一接口)
自动选择最优通道:
1. 有官方API → 用API
2. 设备在线 → 用SDK控制
3. 兜底 → 用AI Agent
超时:使用 MESSAGE_SEND_TIMEOUT默认60s超时返回 200 + success=false + error=timeout
"""
# ---- 防封守卫 ----
guard = await _anti_ban_guard(req.device_id, req.platform.value, "send_message", req.content)
if not guard["pass"]:
return {"code": 200, "data": {"success": False, "error": guard["reason"], "error_code": "anti_ban_blocked"}, "channel_used": "none"}
req.content = guard["content"]
mode = _get_device_mode(req.device_id)
device_online = mode != "offline"
forced_channel = (req.channel or "").strip().lower()
if forced_channel:
try:
channel = Channel(forced_channel)
except ValueError:
raise HTTPException(status_code=400, detail=f"无效 channel: {req.channel}")
else:
channel = ChannelRouter.route(req.platform, "send_message", device_online)
logger.info(f"[message/send] device_id={req.device_id} platform={req.platform.value} to_id={req.to_id} channel={channel.value}")
try:
if channel == Channel.OFFICIAL_API:
result = await _send_via_official_api(req)
elif channel == Channel.HOOK:
result = await _send_via_hook(req)
elif channel == Channel.SDK_CONTROL:
result = await _send_via_sdk(req)
else:
result = await _send_via_agent(req)
logger.info(f"[message/send] result success={result.get('success')} error={result.get('error')}")
await device_manager.log_command(
req.device_id,
f"{req.platform.value}.send_message",
req.dict(),
result
)
err = result.get("error") or ""
data = {
"success": result.get("success", False),
"message_id": result.get("message_id"),
"error": err or None
}
if err and "未找到" in str(err):
data["error_code"] = "contact_not_found"
elif err and str(err) == "timeout":
data["error_code"] = "timeout"
return {"code": 200, "data": data, "channel_used": channel.value}
except Exception as e:
logger.warning(f"[message/send] exception channel={channel.value} e={e}")
if channel == Channel.SDK_CONTROL:
try:
result = await _send_via_agent(req)
return {
"code": 200,
"data": result,
"channel_used": Channel.AI_AGENT.value
}
except Exception:
pass
raise HTTPException(status_code=500, detail=str(e))
@router.post("/message/list", response_model=dict, tags=["消息管理"])
async def get_messages(req: GetMessagesRequest):
"""获取消息列表"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "get_messages",
{"conversation_id": req.conversation_id, "limit": req.limit}
)
# 兼容 WebSocket 返回 { code, data: { messages } } 与 ADB 返回 { success, messages }
payload = result.get("data") if result.get("data") is not None else result
messages = payload.get("messages", [])
return {
"code": 200,
"data": {"messages": messages},
"channel_used": Channel.SDK_CONTROL.value
}
@router.post("/message/batch-send", response_model=dict, tags=["消息管理"])
async def batch_send_message(req: BatchSendMessageRequest):
"""
批量发送消息(服务端逐条下发,间隔防风控,单条超时可控)
"""
guard = await _anti_ban_guard(req.device_id, req.platform.value, "batch_send", req.content)
if not guard["pass"]:
return {"code": 200, "data": {"success": False, "error": guard["reason"], "error_code": "anti_ban_blocked"}, "channel_used": "none"}
req.content = guard["content"]
_check_device_online(req.device_id)
per_msg_timeout = min(60, max(15, getattr(settings, "MESSAGE_SEND_TIMEOUT", 60)))
sent, failed = [], []
for i, to_id in enumerate(req.to_ids):
if i > 0:
await asyncio.sleep(max(0.5, min(float(req.interval), 30)))
result = await _execute_skill(
req.device_id, req.platform.value, "send_message",
{"to_id": to_id, "content": req.content, "msg_type": req.msg_type.value, **({"media_url": req.media_url} if req.media_url else {})},
timeout=per_msg_timeout
)
payload = result.get("data") if result.get("data") is not None else result
if payload.get("success"):
sent.append({"to_id": to_id, "message_id": payload.get("message_id")})
else:
failed.append({"to_id": to_id, "error": payload.get("error") or result.get("message") or "unknown"})
return {
"code": 200,
"data": {"sent": sent, "failed": failed, "total": len(req.to_ids)},
"channel_used": Channel.SDK_CONTROL.value
}
@router.post("/comment/reply", response_model=dict, tags=["消息管理"])
async def reply_comment(req: ReplyCommentRequest):
"""回复评论(抖音/小红书等)"""
_check_device_online(req.device_id)
guard = await _anti_ban_guard(req.device_id, req.platform.value, "comment", req.content)
if not guard["pass"]:
return {"code": 200, "success": False, "error": guard["reason"], "error_code": "anti_ban_blocked"}
result = await _execute_skill(
req.device_id, req.platform.value, "reply_comment",
{"video_id": req.video_id, "comment_id": req.comment_id, "content": guard.get("content", req.content)}
)
payload = result.get("data") if result.get("data") is not None else result
return {
"code": 200,
"data": payload,
"channel_used": Channel.SDK_CONTROL.value
}
# =============================================================================
# 二、好友管理接口
# =============================================================================
@router.post("/friend/add", response_model=dict, tags=["好友管理"])
async def add_friend(req: AddFriendRequest):
"""添加好友"""
guard = await _anti_ban_guard(req.device_id, req.platform.value, "add_friend", req.message)
if not guard["pass"]:
return {"code": 200, "data": {"success": False, "error": guard["reason"], "error_code": "anti_ban_blocked"}, "channel_used": "none"}
req.message = guard["content"] or req.message
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "add_friend",
{"user_id": req.user_id, "message": req.message, "source": req.source}
)
return {
"code": 200,
"data": result.get("data", {}),
"channel_used": Channel.SDK_CONTROL.value
}
@router.post("/friend/accept", response_model=dict, tags=["好友管理"])
async def accept_friend(req: AcceptFriendRequest):
"""通过好友请求"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "accept_friend",
{"user_id": req.user_id}
)
return {
"code": 200,
"data": result.get("data", {}),
"channel_used": Channel.SDK_CONTROL.value
}
@router.post("/friend/set-remark", response_model=dict, tags=["好友管理"])
async def set_friend_remark(req: SetRemarkRequest):
"""设置好友备注"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "set_remark",
{"user_id": req.user_id, "remark": req.remark}
)
return {
"code": 200,
"data": result.get("data", {}),
"channel_used": Channel.SDK_CONTROL.value
}
@router.post("/friend/delete", response_model=dict, tags=["好友管理"])
async def delete_friend(req: DeleteFriendRequest):
"""删除好友"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "delete_friend",
{"user_id": req.user_id}
)
return {
"code": 200,
"data": result.get("data", {}),
"channel_used": Channel.SDK_CONTROL.value
}
@router.post("/friend/batch-add", response_model=dict, tags=["好友管理"])
async def batch_add_friend(req: BatchAddFriendRequest):
"""批量添加好友"""
_check_device_online(req.device_id)
guard = await _anti_ban_guard(req.device_id, req.platform.value, "add_friend", req.message)
if not guard["pass"]:
return {"code": 200, "success": False, "error": guard["reason"], "error_code": "anti_ban_blocked"}
result = await _execute_skill(
req.device_id, req.platform.value, "batch_add_friend",
{
"user_ids": req.user_ids,
"message": guard.get("content", req.message),
"interval": req.interval
},
timeout=len(req.user_ids) * 15
)
return {
"code": 200,
"data": result.get("data", {}),
"channel_used": Channel.SDK_CONTROL.value
}
@router.get("/contacts", response_model=dict, tags=["好友管理"])
async def get_contacts(device_id: str, platform: Platform, limit: int = 100):
"""获取联系人列表"""
_check_device_online(device_id)
result = await _execute_skill(
device_id, platform.value, "get_contacts",
{"limit": limit}
)
payload = result.get("data") if result.get("data") is not None else result
contacts = payload.get("contacts", [])
return {
"code": 200,
"data": {"contacts": contacts},
"channel_used": Channel.SDK_CONTROL.value
}
# =============================================================================
# 三、群聊管理接口
# =============================================================================
@router.post("/group/create", response_model=dict, tags=["群聊管理"])
async def create_group(req: CreateGroupRequest):
"""创建群聊"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "create_group",
{"group_name": req.group_name, "member_ids": req.member_ids}
)
return {
"code": 200,
"data": result.get("data", {}),
"channel_used": Channel.SDK_CONTROL.value
}
@router.post("/group/invite", response_model=dict, tags=["群聊管理"])
async def invite_to_group(req: InviteToGroupRequest):
"""邀请入群"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "invite_to_group",
{"group_id": req.group_id, "member_ids": req.member_ids}
)
return {
"code": 200,
"data": result.get("data", {}),
"channel_used": Channel.SDK_CONTROL.value
}
@router.post("/group/remove", response_model=dict, tags=["群聊管理"])
async def remove_from_group(req: RemoveFromGroupRequest):
"""移出群聊"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "remove_from_group",
{"group_id": req.group_id, "member_ids": req.member_ids}
)
return {
"code": 200,
"data": result.get("data", {}),
"channel_used": Channel.SDK_CONTROL.value
}
@router.post("/group/set-notice", response_model=dict, tags=["群聊管理"])
async def set_group_notice(req: SetGroupNoticeRequest):
"""设置群公告"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "set_group_notice",
{"group_id": req.group_id, "notice": req.notice}
)
return {
"code": 200,
"data": result.get("data", {}),
"channel_used": Channel.SDK_CONTROL.value
}
@router.post("/group/set-name", response_model=dict, tags=["群聊管理"])
async def set_group_name(req: SetGroupNameRequest):
"""设置群名"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "set_group_name",
{"group_id": req.group_id, "group_name": req.group_name}
)
return {
"code": 200,
"data": result.get("data", {}),
"channel_used": Channel.SDK_CONTROL.value
}
@router.post("/group/send-message", response_model=dict, tags=["群聊管理"])
async def send_group_message(req: GroupMessageRequest):
"""发送群消息"""
_check_device_online(req.device_id)
guard = await _anti_ban_guard(req.device_id, req.platform.value, "send_group_message", req.content)
if not guard["pass"]:
return {"code": 200, "success": False, "error": guard["reason"], "error_code": "anti_ban_blocked"}
result = await _execute_skill(
req.device_id, req.platform.value, "send_group_message",
{
"group_id": req.group_id,
"content": guard.get("content", req.content),
"msg_type": req.msg_type.value,
"media_url": req.media_url,
"at_all": req.at_all,
"at_list": req.at_list
}
)
return {
"code": 200,
"data": result.get("data", {}),
"channel_used": Channel.SDK_CONTROL.value
}
@router.post("/group/set-welcome", response_model=dict, tags=["群聊管理"])
async def set_group_welcome(req: SetGroupWelcomeRequest):
"""设置群欢迎语"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "set_group_welcome",
{
"group_id": req.group_id,
"welcome_text": req.welcome_text,
"welcome_image": req.welcome_image
}
)
return {
"code": 200,
"data": result.get("data", {}),
"channel_used": Channel.SDK_CONTROL.value
}
@router.get("/group/list", response_model=dict, tags=["群聊管理"])
async def get_group_list(device_id: str, platform: Platform, limit: int = 100):
"""获取群聊列表"""
_check_device_online(device_id)
result = await _execute_skill(
device_id, platform.value, "get_groups",
{"limit": limit}
)
return {
"code": 200,
"data": result.get("data", {}),
"channel_used": Channel.SDK_CONTROL.value
}
@router.get("/group/members", response_model=dict, tags=["群聊管理"])
async def get_group_members(device_id: str, platform: Platform, group_id: str):
"""获取群成员列表"""
_check_device_online(device_id)
result = await _execute_skill(
device_id, platform.value, "get_group_members",
{"group_id": group_id}
)
return {
"code": 200,
"data": result.get("data", {}),
"channel_used": Channel.SDK_CONTROL.value
}
# =============================================================================
# 四、标签管理接口
# =============================================================================
@router.post("/tag/add", response_model=dict, tags=["标签管理"])
async def add_tag(req: AddTagRequest):
"""给好友添加标签"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "add_tag",
{"user_id": req.user_id, "tags": req.tags}
)
return {
"code": 200,
"data": result.get("data", {}),
"channel_used": Channel.SDK_CONTROL.value
}
@router.post("/tag/remove", response_model=dict, tags=["标签管理"])
async def remove_tag(req: RemoveTagRequest):
"""移除好友标签"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "remove_tag",
{"user_id": req.user_id, "tags": req.tags}
)
return {
"code": 200,
"data": result.get("data", {}),
"channel_used": Channel.SDK_CONTROL.value
}
@router.post("/tag/create", response_model=dict, tags=["标签管理"])
async def create_tag(req: CreateTagRequest):
"""创建标签"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "create_tag",
{"tag_name": req.tag_name}
)
return {
"code": 200,
"data": result.get("data", {}),
"channel_used": Channel.SDK_CONTROL.value
}
@router.post("/tag/delete", response_model=dict, tags=["标签管理"])
async def delete_tag(req: DeleteTagRequest):
"""删除标签"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "delete_tag",
{"tag_name": req.tag_name}
)
return {
"code": 200,
"data": result.get("data", {}),
"channel_used": Channel.SDK_CONTROL.value
}
@router.get("/tag/list", response_model=dict, tags=["标签管理"])
async def get_tag_list(device_id: str, platform: Platform):
"""获取标签列表"""
_check_device_online(device_id)
result = await _execute_skill(
device_id, platform.value, "get_tags", {}
)
return {
"code": 200,
"data": result.get("data", {}),
"channel_used": Channel.SDK_CONTROL.value
}
@router.post("/tag/users", response_model=dict, tags=["标签管理"])
async def get_users_by_tag(req: GetUsersByTagRequest):
"""根据标签获取好友列表"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "get_users_by_tag",
{"tag_name": req.tag_name, "limit": req.limit}
)
return {
"code": 200,
"data": result.get("data", {}),
"channel_used": Channel.SDK_CONTROL.value
}
# =============================================================================
# 五、朋友圈管理接口
# =============================================================================
@router.post("/moments/post", response_model=dict, tags=["朋友圈管理"])
async def post_moments(req: PostMomentsRequest):
"""发布朋友圈/瞬间(统一返回 200业务失败用 success=false 表示,避免 502"""
guard = await _anti_ban_guard(req.device_id, req.platform.value, "post_moments", req.content)
if not guard["pass"]:
return {"code": 200, "success": False, "error": guard["reason"], "data": {}, "channel_used": "none"}
req.content = guard["content"]
try:
_check_device_online(req.device_id)
except HTTPException:
raise
try:
result = await _execute_skill(
req.device_id, req.platform.value, "post_moments",
{
"content": req.content,
"images": req.images,
"video_url": req.video_url,
"location": req.location,
"visible_list": req.visible_list,
"invisible_list": req.invisible_list
},
timeout=60 # 发布朋友圈可能需要较长时间
)
rc = result.get("code", 200)
if rc != 200:
return {
"code": 200,
"success": False,
"error": result.get("message", result.get("error", "发布失败")),
"data": result.get("data", {}),
"channel_used": Channel.SDK_CONTROL.value
}
return {
"code": 200,
"success": True,
"data": result.get("data", {}),
"channel_used": Channel.SDK_CONTROL.value
}
except Exception as e:
logger.exception(f"moments/post 异常: {e}")
return {
"code": 200,
"success": False,
"error": str(e),
"data": {},
"channel_used": Channel.SDK_CONTROL.value
}
@router.post("/moments/like", response_model=dict, tags=["朋友圈管理"])
async def like_moments(req: LikeMomentsRequest):
"""点赞朋友圈"""
_check_device_online(req.device_id)
guard = await _anti_ban_guard(req.device_id, req.platform.value, "like_moments")
if not guard["pass"]:
return {"code": 200, "success": False, "error": guard["reason"], "error_code": "anti_ban_blocked"}
result = await _execute_skill(
req.device_id, req.platform.value, "like_moments",
{"user_id": req.user_id, "post_index": req.post_index}
)
return {
"code": 200,
"data": result.get("data", {}),
"channel_used": Channel.SDK_CONTROL.value
}
@router.post("/moments/comment", response_model=dict, tags=["朋友圈管理"])
async def comment_moments(req: CommentMomentsRequest):
"""评论朋友圈"""
_check_device_online(req.device_id)
guard = await _anti_ban_guard(req.device_id, req.platform.value, "comment_moments", req.comment)
if not guard["pass"]:
return {"code": 200, "success": False, "error": guard["reason"], "error_code": "anti_ban_blocked"}
result = await _execute_skill(
req.device_id, req.platform.value, "comment_moments",
{
"user_id": req.user_id,
"post_index": req.post_index,
"comment": guard.get("content", req.comment),
"reply_to": req.reply_to
}
)
return {
"code": 200,
"data": result.get("data", {}),
"channel_used": Channel.SDK_CONTROL.value
}
@router.post("/moments/list", response_model=dict, tags=["朋友圈管理"])
async def get_moments(req: GetMomentsRequest):
"""获取朋友圈列表"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "get_moments",
{"user_id": req.user_id, "limit": req.limit}
)
return {
"code": 200,
"data": result.get("data", {}),
"channel_used": Channel.SDK_CONTROL.value
}
# =============================================================================
# 六、消息高级操作
# =============================================================================
class ForwardMessageRequest(BaseModel):
device_id: str
platform: Platform
to_id: str
content: str
class SendCardRequest(BaseModel):
device_id: str
platform: Platform
to_id: str
card_wxid: str
@router.post("/message/forward", response_model=dict, tags=["消息管理"])
async def forward_message(req: ForwardMessageRequest):
"""转发消息"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "forward_message",
{"to_id": req.to_id, "content": req.content}
)
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/message/recall", response_model=dict, tags=["消息管理"])
async def recall_message(device_id: str, platform: Platform):
"""撤回最近一条消息"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "recall_message", {})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/message/send-card", response_model=dict, tags=["消息管理"])
async def send_card(req: SendCardRequest):
"""发送名片"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "send_card",
{"to_id": req.to_id, "card_wxid": req.card_wxid}
)
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
# =============================================================================
# 七、个人设置接口
# =============================================================================
class SetNicknameRequest(BaseModel):
device_id: str
platform: Platform
nickname: str
class SetSignatureRequest(BaseModel):
device_id: str
platform: Platform
signature: str
class SetAvatarRequest(BaseModel):
device_id: str
platform: Platform
image_path: Optional[str] = None
class SetGenderRequest(BaseModel):
device_id: str
platform: Platform
gender: str
class SetRegionRequest(BaseModel):
device_id: str
platform: Platform
region: str
@router.get("/profile/get", response_model=dict, tags=["个人设置"])
async def get_profile(device_id: str, platform: Platform):
"""获取当前微信账号资料"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "get_profile", {})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/profile/set-nickname", response_model=dict, tags=["个人设置"])
async def set_nickname(req: SetNicknameRequest):
"""修改微信昵称"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "set_nickname",
{"nickname": req.nickname}
)
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/profile/set-signature", response_model=dict, tags=["个人设置"])
async def set_signature(req: SetSignatureRequest):
"""修改个性签名"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "set_signature",
{"signature": req.signature}
)
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/profile/set-avatar", response_model=dict, tags=["个人设置"])
async def set_avatar(req: SetAvatarRequest):
"""修改头像"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "set_avatar",
{"image_path": req.image_path}
)
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/profile/set-gender", response_model=dict, tags=["个人设置"])
async def set_gender(req: SetGenderRequest):
"""设置性别"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "set_gender",
{"gender": req.gender}
)
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/profile/set-region", response_model=dict, tags=["个人设置"])
async def set_region(req: SetRegionRequest):
"""设置地区"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "set_region",
{"region": req.region}
)
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
# =============================================================================
# 八、账号安全接口
# =============================================================================
class UnblockAccountRequest(BaseModel):
device_id: str
platform: Platform
helper_wxid: Optional[str] = None
class ChangePasswordRequest(BaseModel):
device_id: str
platform: Platform
old_pwd: str
new_pwd: str
@router.get("/account/status", response_model=dict, tags=["账号安全"])
async def check_account_status(device_id: str, platform: Platform):
"""检查账号状态"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "check_account_status", {})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/account/unblock", response_model=dict, tags=["账号安全"])
async def unblock_account(req: UnblockAccountRequest):
"""微信解封"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "unblock_account",
{"helper_wxid": req.helper_wxid}
)
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.get("/account/safety-center", response_model=dict, tags=["账号安全"])
async def safety_center(device_id: str, platform: Platform):
"""打开微信安全中心"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "safety_center", {})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/account/change-password", response_model=dict, tags=["账号安全"])
async def change_password(req: ChangePasswordRequest):
"""修改微信密码"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "change_password",
{"old_pwd": req.old_pwd, "new_pwd": req.new_pwd}
)
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
# =============================================================================
# 九、收藏管理接口
# =============================================================================
class AddFavoriteRequest(BaseModel):
device_id: str
platform: Platform
content_desc: Optional[str] = None
@router.post("/favorites/add", response_model=dict, tags=["收藏管理"])
async def add_favorite(req: AddFavoriteRequest):
"""收藏消息"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "add_to_favorites",
{"content_desc": req.content_desc}
)
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.get("/favorites/list", response_model=dict, tags=["收藏管理"])
async def get_favorites(device_id: str, platform: Platform, limit: int = 20):
"""获取收藏列表"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "get_favorites", {"limit": limit})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
# =============================================================================
# 十、聊天设置接口
# =============================================================================
class ChatSettingRequest(BaseModel):
device_id: str
platform: Platform
user_id: str
enable: bool = True
@router.post("/chat/set-top", response_model=dict, tags=["聊天设置"])
async def set_chat_top(req: ChatSettingRequest):
"""置顶/取消置顶聊天"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "set_chat_top",
{"user_id": req.user_id, "enable": req.enable}
)
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/chat/set-mute", response_model=dict, tags=["聊天设置"])
async def set_mute_chat(req: ChatSettingRequest):
"""消息免打扰"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "set_mute_chat",
{"user_id": req.user_id, "enable": req.enable}
)
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/chat/clear-history", response_model=dict, tags=["聊天设置"])
async def clear_chat_history(device_id: str, platform: Platform, user_id: str):
"""清空聊天记录"""
_check_device_online(device_id)
result = await _execute_skill(
device_id, platform.value, "clear_chat_history",
{"user_id": user_id}
)
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
# =============================================================================
# 十一、联系人搜索接口
# =============================================================================
@router.get("/contacts/search", response_model=dict, tags=["好友管理"])
async def search_contacts(device_id: str, platform: Platform, keyword: str):
"""搜索联系人"""
_check_device_online(device_id)
result = await _execute_skill(
device_id, platform.value, "search_contact",
{"keyword": keyword}
)
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.get("/friend/info", response_model=dict, tags=["好友管理"])
async def get_friend_info(device_id: str, platform: Platform, user_id: str):
"""获取好友详细资料"""
_check_device_online(device_id)
result = await _execute_skill(
device_id, platform.value, "get_friend_info",
{"user_id": user_id}
)
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
# =============================================================================
# 十二、群聊高级操作
# =============================================================================
@router.post("/group/quit", response_model=dict, tags=["群聊管理"])
async def quit_group(device_id: str, platform: Platform, group_id: str):
"""退出群聊"""
_check_device_online(device_id)
result = await _execute_skill(
device_id, platform.value, "quit_group",
{"group_id": group_id}
)
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
# =============================================================================
# 十三、朋友圈高级操作
# =============================================================================
@router.post("/moments/delete", response_model=dict, tags=["朋友圈管理"])
async def delete_moments(device_id: str, platform: Platform, post_index: int = 0):
"""删除朋友圈"""
_check_device_online(device_id)
result = await _execute_skill(
device_id, platform.value, "delete_moments",
{"post_index": post_index}
)
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
# =============================================================================
# 十四、支付接口
# =============================================================================
class RedPacketRequest(BaseModel):
device_id: str
platform: Platform
to_id: str
amount: str
message: str = "恭喜发财"
class TransferRequest(BaseModel):
device_id: str
platform: Platform
to_id: str
amount: str
message: str = ""
@router.post("/payment/red-packet", response_model=dict, tags=["支付"])
async def send_red_packet(req: RedPacketRequest):
"""发红包"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "send_red_packet",
{"to_id": req.to_id, "amount": req.amount, "message": req.message}
)
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/payment/transfer", response_model=dict, tags=["支付"])
async def send_transfer(req: TransferRequest):
"""转账"""
_check_device_online(req.device_id)
result = await _execute_skill(
req.device_id, req.platform.value, "transfer",
{"to_id": req.to_id, "amount": req.amount, "message": req.message}
)
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
# =============================================================================
# 十五、小程序 & 公众号
# =============================================================================
@router.post("/miniprogram/open", response_model=dict, tags=["小程序"])
async def open_mini_program(device_id: str, platform: Platform, name: str):
"""打开小程序"""
_check_device_online(device_id)
result = await _execute_skill(
device_id, platform.value, "open_mini_program", {"name": name}
)
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/official-account/follow", response_model=dict, tags=["公众号"])
async def follow_official_account(device_id: str, platform: Platform, account_name: str):
"""关注公众号"""
_check_device_online(device_id)
result = await _execute_skill(
device_id, platform.value, "follow_official_account",
{"account_name": account_name}
)
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
# =============================================================================
# 十六、解封与限制管理
# =============================================================================
@router.post("/account/unblock-self", response_model=dict, tags=["账号安全"])
async def unblock_self(device_id: str, platform: Platform):
"""自助解封"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "unblock_self", {})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/account/unblock-appeal", response_model=dict, tags=["账号安全"])
async def unblock_appeal(device_id: str, platform: Platform):
"""申诉解封"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "unblock_appeal", {})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.get("/account/restrictions", response_model=dict, tags=["账号安全"])
async def check_restrictions(device_id: str, platform: Platform):
"""检查当前功能限制"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "check_restrictions", {})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/account/appeal-restriction", response_model=dict, tags=["账号安全"])
async def appeal_restriction(device_id: str, platform: Platform):
"""申诉功能限制"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "appeal_restriction", {})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/account/unblock-sms", response_model=dict, tags=["账号安全"])
async def unblock_with_sms(device_id: str, platform: Platform, phone: str = ""):
"""短信验证解封"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "unblock_with_sms", {"phone": phone})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
# =============================================================================
# 十七、视频号
# =============================================================================
@router.get("/video-channel/list", response_model=dict, tags=["视频号"])
async def get_video_list(device_id: str, platform: Platform, limit: int = 10):
"""获取视频号列表"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "get_video_list", {"limit": limit})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/video-channel/like", response_model=dict, tags=["视频号"])
async def like_video(device_id: str, platform: Platform, index: int = 0):
"""点赞视频"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "like_video", {"index": index})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/video-channel/comment", response_model=dict, tags=["视频号"])
async def comment_video(device_id: str, platform: Platform, index: int = 0, comment: str = ""):
"""评论视频"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "comment_video", {"index": index, "comment": comment})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/video-channel/follow", response_model=dict, tags=["视频号"])
async def follow_video_creator(device_id: str, platform: Platform, index: int = 0):
"""关注视频号创作者"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "follow_video_creator", {"index": index})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/video-channel/share", response_model=dict, tags=["视频号"])
async def share_video(device_id: str, platform: Platform, index: int = 0, to_id: str = ""):
"""分享视频"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "share_video", {"index": index, "to_id": to_id})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
# =============================================================================
# 十八、扫一扫
# =============================================================================
@router.post("/scan/qr-code", response_model=dict, tags=["扫一扫"])
async def scan_qr_code(device_id: str, platform: Platform):
"""扫描二维码"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "scan_qr_code", {})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/scan/add-friend", response_model=dict, tags=["扫一扫"])
async def scan_add_friend(device_id: str, platform: Platform):
"""扫码加好友"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "scan_add_friend", {})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.get("/scan/my-qr", response_model=dict, tags=["扫一扫"])
async def show_my_qr(device_id: str, platform: Platform):
"""显示我的二维码"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "show_my_qr", {})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/scan/extract-qr", response_model=dict, tags=["扫一扫"])
async def extract_qr_from_image(device_id: str, platform: Platform):
"""从图片识别二维码"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "extract_qr_from_image", {})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
# =============================================================================
# 十九、支付增强
# =============================================================================
@router.get("/payment/code", response_model=dict, tags=["支付"])
async def show_payment_code(device_id: str, platform: Platform):
"""显示付款码"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "show_payment_code", {})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/payment/receive", response_model=dict, tags=["支付"])
async def receive_payment(device_id: str, platform: Platform, amount: str = "", desc: str = ""):
"""收款"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "receive_payment", {"amount": amount, "desc": desc})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.get("/payment/wallet", response_model=dict, tags=["支付"])
async def view_wallet(device_id: str, platform: Platform):
"""查看钱包"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "view_wallet", {})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.get("/payment/transactions", response_model=dict, tags=["支付"])
async def view_transactions(device_id: str, platform: Platform, limit: int = 20):
"""查看账单"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "view_transactions", {"limit": limit})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/payment/receive-red-packet", response_model=dict, tags=["支付"])
async def receive_red_packet(device_id: str, platform: Platform, from_id: str = ""):
"""领取红包"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "receive_red_packet", {"from_id": from_id})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
# =============================================================================
# 二十、通话
# =============================================================================
@router.post("/call/voice", response_model=dict, tags=["通话"])
async def voice_call(device_id: str, platform: Platform, user_id: str = ""):
"""语音通话"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "voice_call", {"user_id": user_id})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/call/video", response_model=dict, tags=["通话"])
async def video_call(device_id: str, platform: Platform, user_id: str = ""):
"""视频通话"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "video_call", {"user_id": user_id})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
# =============================================================================
# 二十一、群发助手
# =============================================================================
class MassSendRequest(BaseModel):
device_id: str
platform: Platform
content: str
user_ids: List[str]
@router.post("/mass-send", response_model=dict, tags=["群发助手"])
async def mass_send(req: MassSendRequest):
"""群发消息"""
_check_device_online(req.device_id)
guard = await _anti_ban_guard(req.device_id, req.platform.value, "batch_send", req.content)
if not guard["pass"]:
return {"code": 200, "success": False, "error": guard["reason"], "error_code": "anti_ban_blocked"}
result = await _execute_skill(
req.device_id, req.platform.value, "mass_send",
{"content": guard.get("content", req.content), "user_ids": req.user_ids}
)
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
# =============================================================================
# 二十二、搜一搜/看一看
# =============================================================================
@router.get("/search/wechat", response_model=dict, tags=["搜一搜"])
async def wechat_search(device_id: str, platform: Platform, keyword: str = ""):
"""搜一搜"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "wechat_search", {"keyword": keyword})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.get("/discover/top-stories", response_model=dict, tags=["看一看"])
async def top_stories(device_id: str, platform: Platform):
"""看一看"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "top_stories", {})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
# =============================================================================
# 二十三、微信运动
# =============================================================================
@router.get("/wechat-sport/steps", response_model=dict, tags=["微信运动"])
async def get_steps(device_id: str, platform: Platform):
"""获取步数"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "get_steps", {})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/wechat-sport/like-steps", response_model=dict, tags=["微信运动"])
async def like_steps(device_id: str, platform: Platform, user_id: str = ""):
"""点赞步数"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "like_steps", {"user_id": user_id})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
# =============================================================================
# 二十四、位置分享
# =============================================================================
@router.post("/location/send", response_model=dict, tags=["位置"])
async def send_location(device_id: str, platform: Platform, user_id: str = ""):
"""发送位置"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "send_location", {"user_id": user_id})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/location/share-realtime", response_model=dict, tags=["位置"])
async def share_real_time_location(device_id: str, platform: Platform, user_id: str = ""):
"""共享实时位置"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "share_real_time_location", {"user_id": user_id})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
# =============================================================================
# 二十五、表情管理
# =============================================================================
@router.post("/emoji/send", response_model=dict, tags=["表情"])
async def send_emoji(device_id: str, platform: Platform, user_id: str = "", emoji_name: str = ""):
"""发送表情"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "send_emoji", {"user_id": user_id, "emoji_name": emoji_name})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.get("/emoji/stickers", response_model=dict, tags=["表情"])
async def get_sticker_list(device_id: str, platform: Platform):
"""获取表情包列表"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "get_sticker_list", {})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
# =============================================================================
# 二十六、朋友圈增强
# =============================================================================
@router.post("/moments/set-cover", response_model=dict, tags=["朋友圈管理"])
async def set_moments_cover(device_id: str, platform: Platform):
"""设置朋友圈封面"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "set_moments_cover", {})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/moments/set-privacy", response_model=dict, tags=["朋友圈管理"])
async def set_moments_privacy(device_id: str, platform: Platform, days: int = 180):
"""设置朋友圈可见天数"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "set_moments_privacy", {"days": days})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/moments/share-link", response_model=dict, tags=["朋友圈管理"])
async def share_link_to_moments(device_id: str, platform: Platform, url: str = "", title: str = ""):
"""分享链接到朋友圈"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "forward_moments_link", {"url": url, "title": title})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
# =============================================================================
# 二十七、语音消息
# =============================================================================
@router.post("/message/voice", response_model=dict, tags=["消息管理"])
async def send_voice_message(device_id: str, platform: Platform, user_id: str = "", duration: int = 3):
"""发送语音消息"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "send_voice_message", {"user_id": user_id, "duration": duration})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
# =============================================================================
# 二十八、文件管理
# =============================================================================
@router.post("/file/send", response_model=dict, tags=["文件管理"])
async def send_file(device_id: str, platform: Platform, user_id: str = ""):
"""发送文件"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "send_file_from_chat", {"user_id": user_id})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/file/download", response_model=dict, tags=["文件管理"])
async def download_file(device_id: str, platform: Platform, user_id: str = ""):
"""下载文件"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "download_file", {"user_id": user_id})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
# =============================================================================
# 二十九、设置管理
# =============================================================================
@router.post("/settings/do-not-disturb", response_model=dict, tags=["设置"])
async def toggle_dnd(device_id: str, platform: Platform, enable: bool = True):
"""勿扰模式"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "toggle_do_not_disturb", {"enable": enable})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/settings/clear-cache", response_model=dict, tags=["设置"])
async def clear_cache(device_id: str, platform: Platform):
"""清理缓存"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "clear_cache", {})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/settings/check-update", response_model=dict, tags=["设置"])
async def check_update(device_id: str, platform: Platform):
"""检查更新"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "check_for_update", {})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/settings/logout", response_model=dict, tags=["设置"])
async def logout(device_id: str, platform: Platform):
"""退出登录"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "logout", {})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
@router.post("/settings/switch-account", response_model=dict, tags=["设置"])
async def switch_account(device_id: str, platform: Platform):
"""切换账号"""
_check_device_online(device_id)
result = await _execute_skill(device_id, platform.value, "switch_account", {})
payload = result.get("data") if result.get("data") is not None else result
return {"code": 200, "data": payload, "channel_used": Channel.SDK_CONTROL.value}
# =============================================================================
# 内部实现方法
# =============================================================================
async def _execute_actions_via_adb(device, actions: list) -> tuple:
"""
通过 ADB 设备顺序执行 AI 解析出的 action 列表。
返回 (success: bool, error: Optional[str])
"""
import asyncio
import time
for item in actions:
act = item.get("action") or item.get("action_type")
params = item.get("params") or {}
try:
if act == "open_app":
pkg = params.get("package", "")
if pkg:
device.start_app(pkg)
await asyncio.sleep(2)
elif act == "click":
x, y = params.get("x", 0), params.get("y", 0)
device.click(x, y)
await asyncio.sleep(0.3)
elif act == "swipe":
x1, y1 = params.get("x1", 540), params.get("y1", 1500)
x2, y2 = params.get("x2", 540), params.get("y2", 500)
duration = params.get("duration", 300)
device._shell(f"input swipe {x1} {y1} {x2} {y2} {duration}")
await asyncio.sleep(0.3)
elif act == "input_text":
text = params.get("text", "")
if text:
device.input_text(text)
await asyncio.sleep(0.2)
elif act == "key_event":
keycode = params.get("keycode", "KEYCODE_BACK")
device._shell(f"input keyevent {keycode}")
await asyncio.sleep(0.2)
elif act == "back":
device.press_key("back")
await asyncio.sleep(0.2)
elif act == "home":
device.press_key("home")
await asyncio.sleep(0.2)
elif act == "wait":
sec = params.get("seconds", 1)
await asyncio.sleep(max(0.5, min(sec, 10)))
except Exception as e:
logger.warning(f"ADB 执行 action 失败: {act} {params} -> {e}")
return False, str(e)
return True, None
async def _send_via_official_api(req: SendMessageRequest) -> dict:
"""通过官方API发送抖音/闲鱼等开放平台 API 对接后启用)"""
platform_msg = {
Platform.DOUYIN: "抖音开放平台私信 API 暂未对接",
Platform.XIANYU: "闲鱼开放平台消息 API 暂未对接",
}
err = platform_msg.get(req.platform, "该平台官方API暂未实现")
logger.info(f"官方API通道: {req.platform.value} -> {err}")
return {"success": False, "error": err}
async def _send_via_hook(req: SendMessageRequest) -> dict:
"""
Hook 通道发送 — 优先走 Frida RPC失败自动降级到 SDK/ADB 通道
路由策略:
1. 检查设备是否有活跃的 Frida Hook 会话
2. 有 → 通过 WebSocket 下发 hook_execute 指令Agent 端 HookExecutor 执行)
3. 无 → 降级到 _send_via_sdkADB/u2 通道)
"""
script_id = (req.hook_config or {}).get("script_id")
method = (req.hook_config or {}).get("method", "send_message")
logger.info(f"[_send_via_hook] script_id={script_id} method={method} device_id={req.device_id}")
mode = _get_device_mode(req.device_id)
if mode == "websocket":
try:
hook_params = {
"action": "send_message",
"params": {
"to_id": req.to_id,
"content": req.content,
"msg_type": req.msg_type.value if hasattr(req.msg_type, "value") else str(req.msg_type),
},
}
if script_id:
hook_params["script_id"] = script_id
timeout = req.timeout_seconds or settings.MESSAGE_SEND_TIMEOUT
result = await ws_hub.send_command(
req.device_id,
"hook_execute",
hook_params,
timeout=timeout,
)
if result and result.get("success"):
result["channel_used"] = "hook"
return result
logger.warning(f"[_send_via_hook] Frida RPC 失败,降级到 SDK: {result}")
except Exception as e:
logger.warning(f"[_send_via_hook] Hook 通道异常,降级到 SDK: {e}")
if mode == "adb":
device_serial = req.device_id
try:
from services.adb_device import adb_manager
hook_result = await asyncio.get_event_loop().run_in_executor(
None,
lambda: _try_local_frida_hook(device_serial, req),
)
if hook_result and hook_result.get("success"):
hook_result["channel_used"] = "hook"
return hook_result
logger.warning(f"[_send_via_hook] 本地 Frida 失败,降级到 ADB: {hook_result}")
except Exception as e:
logger.warning(f"[_send_via_hook] 本地 Frida 异常: {e}")
result = await _send_via_sdk(req)
if result.get("success"):
result["channel_used"] = "hook(degraded_to_sdk)"
return result
def _try_local_frida_hook(device_serial: str, req) -> dict:
"""尝试通过本地 Frida 直连设备执行 HookADB 模式专用)"""
try:
import sys, os
agent_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "agent")
if agent_dir not in sys.path:
sys.path.insert(0, agent_dir)
from hook.frida_manager import FridaManager
from hook.hook_executor import HookExecutor
mgr = FridaManager(device_serial=device_serial)
if not mgr.start():
return {"success": False, "error": "Frida 连接失败"}
executor = HookExecutor(mgr)
result = executor.execute("send_message", {
"to_id": req.to_id,
"content": req.content,
"msg_type": req.msg_type.value if hasattr(req.msg_type, "value") else str(req.msg_type),
})
mgr.stop()
return result
except ImportError:
return {"success": False, "error": "frida 未安装"}
except Exception as e:
return {"success": False, "error": str(e)}
async def _send_via_sdk(req: SendMessageRequest) -> dict:
"""通过SDK控制发送WebSocket或ADB超时由请求 timeout_seconds 或 MESSAGE_SEND_TIMEOUT 控制"""
params = {
"to_id": req.to_id,
"content": req.content,
"msg_type": req.msg_type.value
}
if req.media_url:
params["media_url"] = req.media_url
if req.at_list:
params["at_list"] = req.at_list
timeout = req.timeout_seconds if req.timeout_seconds is not None and req.timeout_seconds > 0 else (getattr(settings, "MESSAGE_SEND_TIMEOUT", 60) or 60)
mode = _get_device_mode(req.device_id)
if mode == "websocket":
logger.info(f"[_send_via_sdk] 下发 execute send_message timeout={timeout}s")
result = await ws_hub.send_command(req.device_id, {
"type": "execute",
"data": {
"script": req.platform.value,
"action": "send_message",
"params": params
}
}, timeout=timeout)
if result.get("code") == 200:
return {
"success": True,
"message_id": result.get("data", {}).get("message_id")
}
err = result.get("message", "发送失败")
if result.get("code") == 408:
err = "timeout"
logger.warning(f"[_send_via_sdk] 设备响应超时 device_id={req.device_id} to_id={req.to_id}")
return {"success": False, "error": err}
elif mode == "adb":
adb_device = adb_manager.get_device(req.device_id)
if not adb_device:
return {"success": False, "error": "ADB设备不可用"}
return await _execute_via_adb(adb_device, req.platform.value, "send_message", params)
return {"success": False, "error": "设备不在线"}
async def _send_via_agent(req: SendMessageRequest) -> dict:
"""通过AI Agent发送WebSocket 下发 agent_executeADB 模式由服务端解析后执行 action 序列"""
platform_names = {
Platform.WECHAT: "微信",
Platform.DOUYIN: "抖音",
Platform.XHS: "小红书",
Platform.XIANYU: "闲鱼",
Platform.SOUL: "Soul"
}
task = f"打开{platform_names.get(req.platform, req.platform.value)},找到联系人'{req.to_id}',发送消息:{req.content}"
mode = _get_device_mode(req.device_id)
if mode == "websocket":
# 设备端已有 agent_execute 处理,直接下发任务
result = await ws_hub.send_command(req.device_id, {
"type": "agent_execute",
"data": {"task": task, "llm_provider": "deepseek", "max_steps": 30}
}, timeout=120)
if result.get("code") == 200 and result.get("data"):
return {
"success": True,
"message_id": result.get("data", {}).get("message_id") or f"agent_{int(__import__('time').time() * 1000)}"
}
return {"success": False, "error": result.get("message", "AI Agent 执行失败")}
if mode == "adb":
# ADB 模式:服务端解析意图后通过 ADB 执行 action 序列
parse_result = await ai_agent_service.process_voice_command(task, req.device_id)
if not parse_result.get("success") or not parse_result.get("actions"):
return {"success": False, "error": parse_result.get("message", "无法解析指令")}
adb_device = adb_manager.get_device(req.device_id)
if not adb_device or not adb_device.is_online():
return {"success": False, "error": "ADB 设备不可用"}
ok, err = await _execute_actions_via_adb(adb_device, parse_result["actions"])
if ok:
return {"success": True, "message_id": f"agent_adb_{int(__import__('time').time() * 1000)}"}
return {"success": False, "error": err or "ADB 执行失败"}
return {"success": False, "error": "设备离线"}
# ========== AF12: 风控中心监控 ==========
@router.get("/anti-ban/dashboard")
async def anti_ban_dashboard():
"""防封风控中心:汇总全局限流/账号生命周期/指纹碰撞状态"""
import time as _time
from services.rate_limiter import rate_limiter as _rl
devices = await device_manager.get_all_devices(limit=500)
total_devices = len(devices)
online = sum(1 for d in devices if d.get("status") == "online")
seen_fp: dict = {}
fp_collision_devices = []
for d in devices:
fp = d.get("fingerprint_hash", "")
if not fp:
continue
did = d.get("device_id", "")
seen_fp.setdefault(fp, []).append(did)
for fp, dids in seen_fp.items():
if len(dids) > 1:
for did in dids:
fp_collision_devices.append({
"device_id": did,
"collided_with": [x for x in dids if x != did],
})
lifecycle_summary = []
for d in devices[:50]:
did = d.get("device_id", "")
plat = d.get("platform", "wechat")
phase_info = await account_lifecycle.get_phase(did, plat)
rules = await account_lifecycle.get_rules(did, plat)
lifecycle_summary.append({
"device_id": did,
"phase": phase_info.value if hasattr(phase_info, "value") else str(phase_info),
"max_daily_add_friend": rules.get("max_daily_add_friend", "N/A"),
"max_daily_message": rules.get("max_daily_send_message", "N/A"),
})
return {
"timestamp": int(_time.time()),
"devices": {"total": total_devices, "online": online},
"fingerprint_collisions": fp_collision_devices,
"account_lifecycle": lifecycle_summary,
}
@router.get("/anti-ban/device/{device_id}")
async def anti_ban_device_detail(device_id: str):
"""单设备防封详情"""
device = await device_manager.get_device(device_id)
if not device:
raise HTTPException(status_code=404, detail="设备不存在")
plat = device.get("platform", "wechat")
phase = await account_lifecycle.get_phase(device_id, plat)
rules = await account_lifecycle.get_rules(device_id, plat)
fp = device.get("fingerprint_hash", "")
collision_result = {"collision": False, "collided_with": []}
if fp and device_manager.db:
collision_result = await device_manager.check_fingerprint_collision(
device_id, device.get("fingerprint_info", {})
)
return {
"device_id": device_id,
"status": device.get("status", "unknown"),
"fingerprint_hash": fp,
"fingerprint_collision": collision_result,
"account_phase": phase.value if hasattr(phase, "value") else str(phase),
"phase_rules": rules,
"last_heartbeat": str(device.get("last_heartbeat", "")),
}