Python实现微信防撤回:基于MsgId与MsgType的精准识别方案

向北走
向北走
发布时间:2026-10-15| 6人浏览过
原创

本文深入解析利用Python ItChat库实现微信防撤回的技术细节。通过对比普通消息与撤回消息的MsgType(1与10002)、Content字段及MsgId,文章提供了精确识别撤回事件的方法。核心逻辑在于利用撤回消息中的MsgId匹配本地缓存,从而精准还原被撤回内容,解决传统方案中仅能猜测最近一条消息的不确定性痛点。

一、之前解决方案

以往的处理逻辑通常较为粗糙:当短时间内同一位好友连续发送多条消息并撤回其中任意一条时,系统无法准确判断具体是哪一条被撤回。因此,常见的做法是假设用户撤回了最近发送的那条消息,并将之前的消息作为备选列表展示出来。其实现代码逻辑如下:


target_msg_pattern = '"{}" 撤回了一条消息'.format(sender_name)
if content == target_msg_pattern:
    return_msg = '【{}】撤回了一条消息:\n'.format(sender_name)
    if len(log[sender_name].items()) == 0:
        return_msg = '缓存信息列表为空!'
    else:
        return_msg += log[sender_name].items()[-1][-1] + '\n'
        if len(log[sender_name].items()) > 1:
            msgs = [msg for timestamp, msg in log[sender_name].items()[:-1]]
            return_msg += '也有可能是下列信息中的某一条:\n' + '\n'.join(msgs)

这种处理方式在实际运行中的效果并不理想:


 

这种存在不确定性的显示方式,对于追求精确性的用户来说难以接受。

二、分析msg信息

要精准定位被撤回的具体消息,必须深入理解普通消息与撤回消息在数据结构上的异同点。接下来将对比这两种情况下的msg对象结构,无需逐行阅读所有字段,后续将对关键字段进行详细剖析。

首先,通过机器人“小帮帮”发送普通消息时,获取到的msg数据结构如下:


{
	'MsgId': '2018511155698964390',
	'FromUserName': '@**********f511363f8200853d724137bb31236a7ea81e5183cc06cb4ec978e3',
	'ToUserName': '@**********c2e61fdb47b5c241553a2f',
	'MsgType': 1,
	'Content': 'msg里面到底有什么?',
	'Status': 3,
	'ImgStatus': 1,
	'CreateTime': 1578069291,
	'VoiceLength': 0,
	'PlayLength': 0,
	'FileName': '',
	'FileSize': '',
	'MediaId': '',
	'Url': '',
	'AppMsgType': 0,
	'StatusNotifyCode': 0,
	'StatusNotifyUserName': '',
	'RecommendInfo': {
		'UserName': '',
		'NickName': '',
		'QQNum': 0,
		'Province': '',
		'City': '',
		'Content': '',
		'Signature': '',
		'Alias': '',
		'Scene': 0,
		'VerifyFlag': 0,
		'AttrStatus': 0,
		'Sex': 0,
		'Ticket': '',
		'OpCode': 0
	},
	'ForwardFlag': 0,
	'AppInfo': {
		'AppID': '',
		'Type': 0
	},
	'HasProductId': 0,
	'Ticket': '',
	'ImgHeight': 0,
	'ImgWidth': 0,
	'SubMsgType': 0,
	'NewMsgId': 2018511155698964390,
	'OriContent': '',
	'EncryFileName': '',
	'User': < User: {
		'MemberList': < ContactList: [] > ,
		'Uin': 0,
		'UserName': '@**********f511363f8200853d724137bb31236a7ea81e5183cc06cb4ec978e3',
		'NickName': '小帮帮',
		'HeadImgUrl': '/cgi-bin/mmwebwx-bin/webwxgeticon?seq=699837854&username=@**********f511363f8200853d724137bb31236a7ea81e5183cc06cb4ec978e3&skey=@crypt_****c00c_92668c8ba7d285c221a85e**********',
		'ContactFlag': 2049,
		'MemberCount': 0,
		'RemarkName': '小帮帮',
		'HideInputBarFlag': 0,
		'Sex': 2,
		'Signature': '',
		'VerifyFlag': 0,
		'OwnerUin': 0,
		'PYInitial': 'XBB',
		'PYQuanPin': 'xiaobangbang',
		'RemarkPYInitial': 'XBB',
		'RemarkPYQuanPin': 'xiaobangbang',
		'StarFriend': 0,
		'AppAccountFlag': 0,
		'Statues': 0,
		'AttrStatus': 33658937,
		'Province': '浙江',
		'City': '台州',
		'Alias': '',
		'SnsFlag': 17,
		'UniFriend': 0,
		'DisplayName': '',
		'ChatRoomId': 0,
		'KeyWord': '',
		'EncryChatRoomId': '',
		'IsOwner': 0
	} > ,
	'Type': 'Text',
	'Text': 'msg里面到底有什么?'
}

随后,观察机器人撤回上述消息后,系统生成的msg数据结构:


{
	'MsgId': '4056955577161654067',
	'FromUserName': '@**********f511363f8200853d724137bb31236a7ea81e5183cc06cb4ec978e3',
	'ToUserName': '@**********c2e61fdb47b5c241553a2f',
	'MsgType': 10002,
	'Content': '<sysmsg type="revokemsg"><revokemsg><session>wxid_4gngrr04aqjn21</session><oldmsgid>1123721956</oldmsgid><msgid>2018511155698964390</msgid><replacemsg><![CDATA["小帮帮" 撤回了一条消息]]></replacemsg></revokemsg></sysmsg>',
	'Status': 4,
	'ImgStatus': 1,
	'CreateTime': 1578069381,
	'VoiceLength': 0,
	'PlayLength': 0,
	'FileName': '',
	'FileSize': '',
	'MediaId': '',
	'Url': '',
	'AppMsgType': 0,
	'StatusNotifyCode': 0,
	'StatusNotifyUserName': '',
	'RecommendInfo': {
		'UserName': '',
		'NickName': '',
		'QQNum': 0,
		'Province': '',
		'City': '',
		'Content': '',
		'Signature': '',
		'Alias': '',
		'Scene': 0,
		'VerifyFlag': 0,
		'AttrStatus': 0,
		'Sex': 0,
		'Ticket': '',
		'OpCode': 0
	},
	'ForwardFlag': 0,
	'AppInfo': {
		'AppID': '',
		'Type': 0
	},
	'HasProductId': 0,
	'Ticket': '',
	'ImgHeight': 0,
	'ImgWidth': 0,
	'SubMsgType': 0,
	'NewMsgId': 4056955577161654067,
	'OriContent': '',
	'EncryFileName': '',
	'User': < User: {
		'MemberList': < ContactList: [] > ,
		'Uin': 0,
		'UserName': '@**********f511363f8200853d724137bb31236a7ea81e5183cc06cb4ec978e3',
		'NickName': '小帮帮',
		'HeadImgUrl': '/cgi-bin/mmwebwx-bin/webwxgeticon?seq=699837854&username=@**********f511363f8200853d724137bb31236a7ea81e5183cc06cb4ec978e3&skey=@crypt_****c00c_92668c8ba7d285c221a85e**********',
		'ContactFlag': 2049,
		'MemberCount': 0,
		'RemarkName': '小帮帮',
		'HideInputBarFlag': 0,
		'Sex': 2,
		'Signature': '',
		'VerifyFlag': 0,
		'OwnerUin': 0,
		'PYInitial': 'XBB',
		'PYQuanPin': 'xiaobangbang',
		'RemarkPYInitial': 'XBB',
		'RemarkPYQuanPin': 'xiaobangbang',
		'StarFriend': 0,
		'AppAccountFlag': 0,
		'Statues': 0,
		'AttrStatus': 33658937,
		'Province': '浙江',
		'City': '台州',
		'Alias': '',
		'SnsFlag': 17,
		'UniFriend': 0,
		'DisplayName': '',
		'ChatRoomId': 0,
		'KeyWord': '',
		'EncryChatRoomId': '',
		'IsOwner': 0
	} > ,
	'Type': 'Note',
	'Text': '"小帮帮" 撤回了一条消息'
}

对比这两种类型的msg对象,高亮部分标示了关键差异(已省略部分相同字段,点击可查看大图):

现在对几个关键字段进行详细分析:

  • MsgId(与NewMsgId对应)
  • 消息唯一标识符。每条消息都拥有独一无二的编号,因此普通消息与撤回通知消息的MsgId不同是正常现象。若能获取好友撤回通知中的MsgId,即可通过该编号锁定原始消息。
  • MsgType(与Type对应)
  • 消息类型标识。如图所示,左侧为普通对话消息,右侧为系统提示类消息。我们可以通过此字段初步判断是否发生了消息撤回操作。

Content

消息具体内容。需注意与Text字段区分,这两种消息类型在Content字段的差异最为显著。

查看撤回消息的Content字段结构(已格式化以便阅读):


<sysmsg type="revokemsg">
    <revokemsg>
        <session>wxid_4gngrr04aqjn21</session>
        <oldmsgid>1123721956</oldmsgid>
        <msgid>2018511155698964390</msgid>
        <replacemsg><![CDATA["小帮帮" 撤回了一条消息]]></replacemsg>
    </revokemsg>
</sysmsg>

关键信息一目了然:撤回通知属于系统消息(sysmsg),类型为撤回消息(revokemsg),且包含了被撤回消息的编号2018511155698964390。

细心的读者会发现,这个MsgId恰好与左侧普通消息的MsgId一致。

由此推断,Content字段存储的是系统内部传输数据,而Text字段则是用户界面展示的内容。

三、确定消息类型

基于上述分析,我们可以通过以下三个维度综合判断收到的消息是否为撤回通知:

1. MsgType

值为1表示普通消息,值为10002则可能为撤回消息。

2. Content

若Content字段中包含type="revokemsg"字符串,则可能为撤回消息,否则不是。

3. Type

值为Text表示普通消息,值为Note则可能为撤回消息。

为确保准确性,必须同时满足上述三种条件,方可认定为有效的撤回消息。

四、锁定撤回的消息

由于确定撤回消息必须依赖MsgId字段,因此在存储临时消息时,必须将该字段纳入存储范围。


log[sender_name][cur_timestamp] = msg['MsgId'] + '|||' + content

为简化数据结构,我使用分隔符|||将MsgId直接拼接在内容前方。

基于此逻辑,锁定并发送撤回消息的代码实现如下:


content = str(msg['Text'])
revoke_info = msg['Content']
print('{}, {} 发来消息: {}'.format(formatted_timestamp, sender_name, content))
target_msg_pattern = '"{}" 撤回了一条消息'.format(sender_name)
if target_msg_pattern == content and msg['Type'] == 'Note' and str(msg['MsgType']) == '10002' and 'type="revokemsg"' in revoke_info:
    return_msg = ''
    return_msg_head = '{},【{}】撤回了一条消息:\n'.format(formatted_timestamp, sender_name)
    revoke_msg_id = revoke_info.split('<msgid>')[-1].split('</msgid>')[0]
    for _, value in log[sender_name].items():
        if value.split('|||')[0] == revoke_msg_id:
            return_msg = value.split('|||')[1]
    if return_msg == '':
        return_msg = '缓存信息列表为空!'
    return_msg = return_msg_head + return_msg
    print(return_msg)
    itchat.send_msg(return_msg, 'filehelper')

进行功能测试,为方便观察,将撤回提醒直接发送给机器人“小帮帮”:

 

至此,一个完善的微信防撤回脚本开发完成!

五、结语

Python拥有众多实用且有趣的库,值得深入挖掘。本期我们利用ItChat库编写了微信防撤回脚本。实际上,ItChat的功能远不止于此,它还能处理微信群消息及多种其他类型消息,本文仅涉及其基础应用,更多高级功能需读者自行探索。

关于Python微信防撤回脚本的教程到此结束,更多相关技术内容请查阅本站往期文章或继续浏览后续文章,感谢大家的支持!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。