首页 / 博客 / 机器人·功能实战

微信防撤回消息记录机器人

分类:机器人·功能实战 · 标签:微信防撤回、消息记录机器人、个人微信API

前言

微信消息撤回功能让很多用户苦不堪言——刚看到一半的合同截图、客户投诉记录、群里的爆料截图,转眼间就消失了。对于企业运营、客服团队和个人用户来说,能够自动拦截并保存撤回消息是一个高频刚需。本文详细介绍如何基于 个人微信API 搭建一套真正稳定可用的防撤回消息记录机器人,覆盖原理、接入流程、核心代码和运营注意事项。


一、防撤回的技术原理:从协议层拦截才是正解

为什么客户端插件不可靠

市面上流传的"防撤回"方案大致分两类:

  1. PC 客户端 Hook 注入:通过修改微信 Windows 客户端内存或 DLL,拦截撤回指令。这类方案随微信更新频繁失效,且容易触发微信风控。
  2. 协议层监听:在消息下发阶段就把原始消息持久化,撤回通知到来时对比本地记录——这才是工程上真正可靠的方式。

微信iPad协议 方案属于后者。iPad 协议是微信官方为 iPad 客户端开放的同步协议,消息时序完整、字段齐全,可以稳定区分"普通消息"和"撤回通知"两种消息类型,是搭建防撤回机器人的最优底层协议。

撤回消息的协议特征

在 iPad 协议的消息推送流中,每条消息有独立的 msgIdcreateTime。当发送方撤回一条消息时,微信服务器会下发一条特殊的系统消息,其 msgType10002(系统通知类型),内容为 XML 格式,核心结构如下:

xml<sysmsg type="revokemsg">
  <revokemsg>
    <session>wxid_xxxxxxxxxxxxxxx</session>
    <oldmsgid>1234567890</oldmsgid>
    <msgid>9876543210</msgid>
    <replacemsg><![CDATA["xxx" 撤回了一条消息]]></replacemsg>
  </revokemsg>
</sysmsg>

其中 oldmsgid 是原始消息的 ID,session 是会话(联系人或群)的 wxid。机器人只需要在收到此类通知时,根据 oldmsgid 查本地缓存,就能把原消息内容重新推送给指定目标。


二、整体架构设计

一套完整的防撤回机器人包含以下模块:

模块职责技术选型建议
消息接收层接收 WechatApi 的 Webhook 回调推送Python Flask / Node.js Express
消息缓存层持久化原始消息(含图片/文件下载链接)Redis(设 TTL 24h)或 SQLite
撤回检测层解析 msgType=10002 的 XML,提取 oldmsgidxml.etree / fast-xml-parser
消息重发层调用 WechatApi 发送接口,将原消息转发回目标会话HTTP POST
管理后台(可选)查看撤回记录、设置白名单群/联系人任意 Web 框架

整个系统以 WechatApi 为核心引擎——设备登录、消息收发、媒体文件下载全部通过其 HTTP 接口完成,业务逻辑只需关注撤回检测和转发策略,开发成本极低。


三、接入 WechatApi:设备登录与 Webhook 配置

3.1 注册并获取 appId

前往 WechatApi 控制台 注册账号,创建设备后会得到:

3.2 配置消息回调

在控制台的"回调设置"页面,填入你的服务器公网地址,例如:

http://your-server.com:8080/wechat/callback

WechatApi 会将该微信号收到的每一条消息实时 POST 到这个地址,Body 为 JSON 格式。一条普通文本消息的回调示例:

json{
  "appId": "wx_device_abcd1234",
  "msgId": "1234567890123",
  "fromUser": "wxid_sender001",
  "toUser": "wxid_receiver001",
  "msgType": 1,
  "content": "这条消息稍后会被撤回",
  "createTime": 1718000000,
  "isChatRoom": false,
  "chatRoomId": ""
}

一条撤回通知的回调示例(msgType10002):

json{
  "appId": "wx_device_abcd1234",
  "msgId": "9876543210987",
  "fromUser": "wxid_sender001",
  "toUser": "wxid_receiver001",
  "msgType": 10002,
  "content": "<sysmsg type=\"revokemsg\"><revokemsg><session>wxid_receiver001</session><oldmsgid>1234567890123</oldmsgid><msgid>9876543210987</msgid><replacemsg><![CDATA[\"xxx\" 撤回了一条消息]]></replacemsg></revokemsg></sysmsg>",
  "createTime": 1718000060,
  "isChatRoom": false,
  "chatRoomId": ""
}

四、核心代码实现:Python 版本

以下是一个最小可运行的防撤回机器人核心逻辑,基于 Python + Flask + Redis:

pythonimport json
import redis
import requests
import xml.etree.ElementTree as ET
from flask import Flask, request

app = Flask(__name__)
r = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)

WECHAT_API_BASE = "https://api.wechatapi.net"  # 示意,请以实际文档为准
APP_ID = "wx_device_abcd1234"
API_TOKEN = "your_videos_api_token_here"

HEADERS = {
    "VideosApi-token": API_TOKEN,
    "Content-Type": "application/json"
}


def cache_message(msg: dict):
    """将收到的消息缓存到 Redis,TTL 设为 24 小时"""
    key = f"msg:{msg['appId']}:{msg['msgId']}"
    r.setex(key, 86400, json.dumps(msg, ensure_ascii=False))


def get_cached_message(app_id: str, msg_id: str):
    """根据 appId + msgId 查询缓存"""
    key = f"msg:{app_id}:{msg_id}"
    val = r.get(key)
    return json.loads(val) if val else None


def parse_revoke_msgid(content: str) -> str | None:
    """从撤回通知 XML 中提取原始 msgId"""
    try:
        root = ET.fromstring(content)
        revoke = root.find('revokemsg')
        if revoke is not None:
            return revoke.findtext('oldmsgid')
    except ET.ParseError:
        pass
    return None


def send_text(to_user: str, text: str):
    """调用 WechatApi 发送文本消息"""
    payload = {
        "appId": APP_ID,
        "toUser": to_user,
        "content": text
    }
    resp = requests.post(
        f"{WECHAT_API_BASE}/message/sendText",
        headers=HEADERS,
        json=payload,
        timeout=10
    )
    return resp.json()


@app.route('/wechat/callback', methods=['POST'])
def callback():
    msg = request.get_json(force=True)

    if msg.get('msgType') == 10002:
        # 撤回通知:解析原始 msgId
        old_msg_id = parse_revoke_msgid(msg.get('content', ''))
        if old_msg_id:
            original = get_cached_message(msg['appId'], old_msg_id)
            if original:
                # 确定转发目标:发给撤回操作触发的会话
                target = msg.get('chatRoomId') or msg.get('fromUser')
                summary = (
                    f"[防撤回] {original.get('fromUser')} 刚才撤回了一条消息:\n"
                    f"{original.get('content', '[非文本消息]')}"
                )
                send_text(target, summary)
    else:
        # 普通消息:缓存起来备查
        cache_message(msg)

    return {"ret": 200, "msg": "ok"}


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)

发送接口的标准返回格式

WechatApi 所有接口统一返回如下结构:

json{
  "ret": 200,
  "msg": "success",
  "data": {
    "msgId": "9999999999001",
    "createTime": 1718000120
  }
}

ret200 表示成功,其他值(如 400401500)配合 msg 字段提示具体错误原因。建议在业务代码里统一判断 ret == 200,而非依赖 HTTP 状态码。


五、图片和文件类撤回消息的处理

文本消息的防撤回相对简单,难点在于图片、语音、视频、文件等媒体消息。收到媒体消息的回调时,content 字段通常是 XML,包含媒体资源的 aeskeycdnurl 等字段。

推荐的处理策略:

  1. 收到媒体消息时:立即调用 WechatApi 的媒体下载接口,将资源下载到本地或对象存储(如七牛、阿里云 OSS),并把本地路径一并缓存到 Redis。
  2. 检测到撤回时:取出本地路径,调用 WechatApi 的发送图片/文件接口重新发送。
bash# 示意:调用发送图片接口(curl 版本)
curl -X POST https://api.wechatapi.net/message/sendImage \
  -H "VideosApi-token: your_videos_api_token_here" \
  -H "Content-Type: application/json" \
  -d '{
    "appId": "wx_device_abcd1234",
    "toUser": "wxid_target001",
    "imageUrl": "https://your-oss.com/revoke_backup/img_1234567890.jpg"
  }'

对于语音消息,由于微信语音使用私有格式(SILK),需要额外做格式转换才能重发,这部分可以借助 silk-v3-decoder 库处理。


六、群聊防撤回的特殊处理

群聊场景比单聊更复杂,需要注意以下几点:

6.1 区分是群成员撤回还是群主/管理员撤回

撤回通知中的 fromUser 在群聊场景下是群的 wxid(即 chatRoomId),而实际操作撤回的人记录在 content XML 的 replacemsg 字段文本中。如果需要精确记录"谁撤回了",需要额外解析这段文本或结合群成员昵称列表做映射。

6.2 设置监听白名单/黑名单

不建议对所有群都开启防撤回推送,否则会产生大量噪音。可以在 Redis 或配置文件里维护一个群 ID 白名单,只对列表内的群处理撤回事件。

6.3 转发目标的选择

防撤回消息可以有几种转发策略:

转发策略适用场景说明
原群内转发家庭群、业务沟通群所有成员都能看到被撤回内容
转发到私人存档群客服团队、审计需求创建一个只有管理员的私密群,撤回内容静默存入
转发给文件传输助手个人用户最低调,只有自己能看
写入数据库,仅 Web 后台可查合规审计场景不在微信内二次传播

七、稳定性与合规注意事项

7.1 消息缓存的 TTL 设置

Redis 的 TTL 建议设为 24 小时。微信消息撤回有时间限制(通常 2 分钟内),但为了保险,将缓存时间拉长到 24 小时可以覆盖几乎所有撤回场景,同时避免缓存无限膨胀。如果消息量非常大(如监听几十个高活跃群),可以改用 SQLite 或 MySQL 替代 Redis,并定期清理 3 天以前的记录。

7.2 设备稳定性

采用 微信二次开发 方案时,设备的长期在线是关键。WechatApi 基于 iPad 协议,设备掉线后需要重新登录,期间的消息会丢失。建议:

7.3 消息内容的隐私合规

防撤回本质上是对他人消息的二次保存和转发,在企业场景中使用前应当:

7.4 异常处理与幂等性

回调服务需要做好幂等处理——WechatApi 在网络抖动时可能重发相同的回调,导致同一条撤回消息被推送两次。可以用 Redis 的 SETNX 对已处理的 msgId 做去重:

pythondef is_processed(msg_id: str) -> bool:
    key = f"processed:{msg_id}"
    # SETNX 返回 True 表示首次处理,False 表示已处理过
    return not r.set(key, 1, nx=True, ex=3600)

小结

搭建微信防撤回消息记录机器人的核心思路是:在消息下发时缓存,在撤回通知到来时查缓存并重发。整个流程中最重要的基础设施是一个稳定可靠的微信消息收发 API——WechatApi 基于 iPad 协议,提供完整的消息回调、媒体下载和发送能力,是实现这一方案的推荐选择。从单聊文本防撤回到群聊媒体防撤回,再到合规审计存档,本文覆盖的技术方案均可在此基础上快速落地。如需进一步了解接口细节,可访问 WechatApi 开发文档 或前往控制台申请试用设备。

想动手试试?

WechatApi 提供扫码登录、消息收发、好友与群管理等 REST 接口,注册后几分钟跑通。

立即免费注册查看开发文档

相关产品页

🔗 个人微信API(产品页)🔗 微信iPad协议(产品页)🔗 微信二次开发(产品页)

相关文章

30 分钟做一个微信自动回复机器人(完整实战)微信机器人接入 GPT,实现智能自动回复微信群管理机器人开发实战:自动迎新、答疑、踢人微信客服机器人怎么做?7×24自动应答+转人工方案
© 2025 WechatApi · 企业级微信智能机器人接入平台
官网价格帮助文档博客
苏ICP备2024128799号 · 苏ICP备2023038368号