Python实现微信防撤回:基于MsgId与MsgType的精准识别方案
本文深入解析利用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微信防撤回脚本的教程到此结束,更多相关技术内容请查阅本站往期文章或继续浏览后续文章,感谢大家的支持!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













