想查看微信好友撤回的消息?Python帮你搞定

要说微信最让人恶心的发明,消息撤回绝对能上榜。

比如你现在正和女朋友用微信聊着天,或者跟自己喜欢的女孩子聊着天,一个不留神,你没注意到对方发的消息就被她及时撤回了,这时你很好奇,好奇她到底发了什么?于是你打算问问她发了什么,结果她回一句"没什么"。这一回复,让你的好奇心更加强烈了,顿时就感觉消息撤回这一功能就是用来折磨人的。

那么有没有什么办法能够知道你心爱的她(他)到底撤回了什么呢?不要着急,Python帮你搞定。

模块介绍

本篇文章将用Python实现微信的防撤回功能,针对微信操作,Python有一个十分强大的库:itchat。相信没有使用过也有所耳闻吧。官方是这样描述它的:

Project description
itchat is a open souce wechat api project for personal account.

It enables you to access your personal wechat account through command line.

翻译过来就是:itchat是一个针对个人帐户的开放式微信api项目,它使您可以通过命令行访问您的个人微信帐户。

既然是针对微信的开发,我们就离不开这个模块的协助,所以,首先下载该模块:

pip install itchat

也可以在开发工具Pycharm中直接导入该模块,Pycharm会提示你下载。

模块初体验

考虑到应该有些人从来没有使用过该模块,这里对该模块进行一个简单的入门。

1、如何登陆微信

既然要操作微信,那么摆在我们面前的问题就是如何登录微信,登录微信非常简单,直接看代码:

import itchat

itchat.login()

没错,一句代码即可完成登录,运行之后就会弹出一个二维码,扫描之后在手机上授权登录,控制台就会提示是否登录成功。

Login successfully as Y

这样就说明登录成功了。

这里需要注意一个问题,就是你会发现每次运行程序都要扫描二维码登录,这样未免太麻烦,有没有办法只扫描一次,以后就自动登录了呢?这当然是可以的。

import itchat

itchat.auto_login(hotReload=True)

通过函数名也能知道该方法可以实现自动登录,运行程序,扫码登录之后会在项目路径下创建一个itchat.pkl文件,该文件用于存储登录的状态,所以千万不要动它,如果你想换一个微信账号登录,就要先把这个文件删除,因为该文件记录的是上一个微信的状态,删除之后即可登录。

需要注意:这种方式只能保证你在短时间内无需重复登录,时间长了,还是需要重新扫码登录的。

进行到这里,有些人可能会发现自己的微信登录不上的情况,据我所知,有些新注册的微信和长期不使用的微信是无法登录网页版微信的,所以这里也会导致登录不上。如果登录不上,那也是没有办法的,下面的内容也就没有意义了。

2、获取好友列表

登录上微信之后,我们来用一用itchat模块提供的一些api,比如获取好友列表。

import itchat

itchat.auto_login(hotReload=True)
friends = itchat.get_friends()  # 好友列表
print(friends)

使用get_friends()函数即可获取到好友列表的所有好友信息,包括昵称、备注名、地址、个性签名、性别等等。

想查看微信好友撤回的消息?Python帮你搞定

这里我随意地复制了一个好友的个人信息,当然由于隐私问题,这里的部分信息我用"*"号代替了,我们重点是分析一下这些信息的内容。比如最开始的UserName,这是用户的唯一标识,相当于身份证号码,你的每个好友都会有这样一个标识,每个好友之间肯定都是不一样的;然后是NickName,这是好友的昵称;HeadImgUrl是好友的头像地址;RemarkName是你对好友的备注名;Province是省份等等,这里就不一一介绍了,感兴趣的话可以自己去了解一下。

3、如何发送消息给好友

如何发送一条消息给指定的好友呢?也非常简单:

import itchat

itchat.auto_login(hotReload=True)
itchat.send('Hello World', toUserName='@f9e42aafa1175b38b60a0be4d651a34c77f2528d9b7784e7aaf415090eca8fa6')

此时的UserName就派上用场了,也就是好友的唯一标识,这样,我们就给该标识对应的好友发送了一条消息,所以,我们可以这样改进程序:

import itchat

itchat.auto_login(hotReload=True)
friends = itchat.get_friends()
nickName = '诚信通授权渠道商-老曾'
for i in friends:
    if '诚信通授权渠道商-老曾' == i['NickName']:
        itchat.send('Hello World', toUserName=i['UserName'])
        break

这样,就可以指定发送给任意好友,通过好友的昵称在好友列表中进行检索,找到的话,就获取该好友的UserName,然后发送消息,也可以通过对好友的备注名(RemarkName)查找,大家可以自己尝试。

4、装饰器

关于itchat模块还有很多功能,这里就不作过多讲解了,我们只讲关于这次程序的知识点,这里是最后一个内容,装饰器。

关于装饰器,一时半会还讲不清楚,这里只是简单介绍一下,装饰器的作用就是用于拓展原来函数功能的一种函数,目的是在不改变原函数名(或类名)的情况下,给函数增加新的功能。

例如现在有一个函数fun(),你并不知晓函数的实现原理,你肯定也不能去修改这个函数的代码,而你需要给该函数添加一个输出开始运行时间和结束运行时间的功能,该如何实现呢?这个时候就可以使用装饰器。

import time
 
def show_time(fun):
    def inner():
        print(time.time())
        fun()
        print(time.time())
    return inner   
 
@show_time
def fun():
    pass
 
fun()

该如何理解这段程序呢?首先@show_time即是使用一个装饰器show_time,此时会将装饰的函数,也就是fun()作为参数传递给装饰器show_time(),我们知道函数作为返回值的话,执行的其实是该函数,所以程序会执行内部函数inner(),此时输出开始运行时间,然后调用fun()函数(原有的功能不能丢),最后输出结束运行时间。这样就通过装饰器实现了一个函数的功能扩展,这也是典型的面向切面编程思想。

如何获取好友发送的消息

准备工作做完了,接下来就进入正题了,对于上面的知识点,大家一定要掌握,如果不懂的话,接下来的代码你可能会很懵。

首先,我们看看该如何获取到好友发送的消息。

import itchat

itchat.auto_login(hotReload=True)


@itchat.msg_register(itchat.content.TEXT)
def resever_info(msg):
    print(msg)


itchat.run() #保持运行

itchat模块提供了@itchat.msg_register装饰器来监听消息,比如这里我们自定义了一个resever_info()函数,并用装饰器对消息进行监听,装饰器中传入了itchat.content.TEXT类型,这样监听的就是文本消息,监听到输入之后,装饰器就会将文本消息传入resever_info()的参数中。所以,msg就是监听到的消息内容。

对于@itchat.msg_register装饰器,它不仅可以监听文本,还可以监听语音、图片、地图、名片、视频等等,为了方便,这里我们导入itchat模块下的content模块中的全部内容,因为这些消息类型都是在该模块下声明的。

TEXT       = 'Text'
MAP        = 'Map'
CARD       = 'Card'
NOTE       = 'Note'
SHARING    = 'Sharing'
PICTURE    = 'Picture'
RECORDING  = VOICE = 'Recording'
ATTACHMENT = 'Attachment'
VIDEO      = 'Video'
FRIENDS    = 'Friends'
SYSTEM     = 'System'

INCOME_MSG = [TEXT, MAP, CARD, NOTE, SHARING, PICTURE,
    RECORDING, VOICE, ATTACHMENT, VIDEO, FRIENDS, SYSTEM]

还有要注意的地方,最后记得调用itchat的run()函数,保持程序运行,否则程序就直接结束了。

接下来我们就可以测试一下了,我让我的好友发了一条消息给我,控制台就输出了如下内容:
想查看微信好友撤回的消息?Python帮你搞定

内容很多,我们只挑重要的看。例如FromUserName,这是发送者的标识;ToUserName,这是接收者的标识;Content,这当然就是文本内容了;CreateTime,这是发送时间;注意最后的两个值:Type,这是消息类型,这里是文本类型Text,然后Text也是文本内容,所以如果想取出好友发送的消息内容的话,用Content和Text都可以。分析过后,取出内容就很简单了:

import itchat
import time
from itchat.content import *  # 导入itchat下的content模块

itchat.auto_login(hotReload=True)


@itchat.msg_register(TEXT)
def resever_info(msg):
    info = msg['Text']  # 取出文本消息
    info_type = msg['Type']  # 取出消息类型
    fromUser = itchat.search_friends(userName=msg['FromUserName'])['NickName']
    ticks = msg['CreateTime']  # 获取信息发送的时间
    time_local = time.localtime(ticks)
    dt = time.strftime("%Y-%m-%d %H:%M:%S", time_local)  # 格式化日期
    print("发送人:" + fromUser + '\n消息类型:' + info_type + '\n发送时间:' + dt + '\n消息内容:' + info)


itchat.run()

这里用到了time模块,用于格式化日期。

为了测试方便,我就自己发了一条消息给别人,自己发的消息也是会被监听的,看运行结果:

发送人:Y
消息类型:Text
发送时间:2019-11-28 16:19:13
消息内容:土鳖

再来试试语音和图片能获取到吗?我们回到刚才的代码:

import itchat
from itchat.content import *  # 导入itchat下的content模块

itchat.auto_login(hotReload=True)


@itchat.msg_register(TEXT)
def resever_info(msg):
    print(msg)


itchat.run()

运行之后,发送语音和图片试试,不管怎么发,控制台就是没反应,这是当然的了,我们还没对语音和图片进行监听呢,修改代码:

import itchat
from itchat.content import *  # 导入itchat下的content模块

itchat.auto_login(hotReload=True)


@itchat.msg_register([TEXT, PICTURE, RECORDING])    #添加了对图片和语音的监听
def resever_info(msg):
    print(msg)


itchat.run()

再运行试试,先发送一张图片,再发送一段语音,控制台输出了两段内容,由于篇幅过长,就不贴出来了,无非还是那些信息,发送者,接收者,日期,消息内容等等,这里只需注意图片和语音的内容:

'Type': 'Picture', 'Text': <function get_download_fn.<locals>.download_fn at 0x0000000003574158>
'Type': 'Recording', 'Text': <function get_download_fn.<locals>.download_fn at 0x0000000002CFED08>

这是一段地址,通过它我们就能够将图片和语音保存起来。

如何保存好友发送的图片和语音

下面我们对好友发送的图片和语音进行保存。

import itchat
import os
from itchat.content import *  # 导入itchat下的content模块

itchat.auto_login(hotReload=True)
temp = 'C:/Users/Administrator/Desktop/CrawlerDemo' + '/' + '撤回的消息'
# 如果不存在该文件夹,就创建
if not os.path.exists(temp):
    os.mkdir(temp)


@itchat.msg_register([TEXT, PICTURE, RECORDING])
def resever_info(msg):
    info = msg['Text']  # 取出文本消息
    info_type = msg['Type']  # 取出消息类型
    name = msg['FileName']  # 取出语音(图片)文件名

    if info_type == 'Recording':
        # 保存语音
        info(temp + '/' + name)
    elif info_type == 'Picture':
        # 保存图片
        info(temp + '/' + name)


itchat.run()

运行起来,然后发送一张图片和一条语音,就会在指定目录下生成两个文件:
想查看微信好友撤回的消息?Python帮你搞定

如何监听好友撤回了消息

到这里,我们其实已经完成了消息监听,只需要稍加修改即可,但是这个程序是有缺陷的,因为不是所有消息我们都需要去保存的,好友正常发送过来的消息我们直接就能看到,保存下来不是多此一举吗?我们的目的是想知道好友撤回了什么内容,这就涉及到如何监听好友是否撤回了消息这一问题了。其实也非常简单,Content模块为我们提供了NOTE类型,该类型指的是系统消息。

所以我们可以自定义一个函数用来监听系统消息:

import itchat
from itchat.content import *  # 导入itchat下的content模块


itchat.auto_login(hotReload=True)

@itchat.msg_register(NOTE)
def note_info(msg): # 监听系统消息
    print(msg)


itchat.run()

运行程序,我们撤回一条消息测试一下,输出结果如下:

......
'DisplayName': '', 'ChatRoomId': 0, 'KeyWord': '', 'EncryChatRoomId': '', 'IsOwner': 0}>, 'Type': 'Note', 'Text': '你撤回了一条消息'}
......

这里截取了部分内容,会发现,撤回消息的文本内容为"你撤回了一条消息",所以要想知道好友是否撤回了消息就非常简单了,判断msg['Text'] == '你撤回了一条消息'即可。

实现微信防撤回程序

关于程序每个步骤的代码到这里就分析完了,接下来是对所有代码的汇总,也是整个程序的完整代码:

import itchat
from itchat.content import *
import os
import time
import xml.dom.minidom    # 解析xml模块

# 这是保存撤回消息的文件目录(如:图片、语音等),这里已经写死了,大家可以自行修改
temp = 'C:/Users/Administrator/Desktop/CrawlerDemo' + '/' + '撤回的消息'
if not os.path.exists(temp):
    os.mkdir(temp)

itchat.auto_login(True)    # 自动登录

dict = {}    # 定义一个字典


# 这是一个装饰器,给下面的函数添加新功能
# 能够捕获好友发送的消息,并传递给函数参数msg
@itchat.msg_register([TEXT, PICTURE, FRIENDS, CARD, MAP, SHARING, RECORDING, ATTACHMENT, VIDEO])  # 文本,语音,图片
def resever_info(msg):
    global dict    # 声明全局变量

    info = msg['Text']  # 取出消息内容
    msgId = msg['MsgId']  # 取出消息标识
    info_type = msg['Type']  # 取出消息类型
    name = msg['FileName']  # 取出消息文件名
    # 取出消息发送者标识并从好友列表中检索
    fromUser = itchat.search_friends(userName=msg['FromUserName'])['NickName']
    ticks = msg['CreateTime']  # 获取信息发送的时间
    time_local = time.localtime(ticks)
    dt = time.strftime("%Y-%m-%d %H:%M:%S", time_local)  # 格式化日期
    # 将消息标识和消息内容添加到字典
    # 每一条消息的唯一标识作为键,消息的具体信息作为值,也是一个字典
    dict[msgId] = {"info": info, "info_type": info_type, "name": name, "fromUser": fromUser, "dt": dt}
    

@itchat.msg_register(NOTE)  # 监听系统提示
def note_info(msg):
    # 监听到好友撤回了一条消息
    if '撤回了一条消息' in msg['Text']:
        # 获取系统消息中的Content结点值
        content = msg['Content']
        # Content值为xml,解析xml
        doc = xml.dom.minidom.parseString(content)
        # 取出msgid标签的值
        result = doc.getElementsByTagName("msgid")
        # 该msgId就是撤回的消息标识,通过它可以在字典中找到撤回的消息信息
        msgId = result[0].childNodes[0].nodeValue
        # 从字典中取出对应消息标识的消息类型
        msg_type = dict[msgId]['info_type']
        if msg_type == 'Recording':    # 撤回的消息为语音
            recording_info = dict[msgId]['info']  # 取出消息标识对应的消息内容
            info_name = dict[msgId]['name'] # 取出消息文件名
            fromUser = dict[msgId]['fromUser'] # 取出发送者
            dt = dict[msgId]['dt'] # 取出发送时间
            recording_info(temp + '/' + info_name) # 保存语音
            # 拼接提示消息
            send_msg = '【发送人:】' + fromUser + '\n' + '发送时间:' + dt + '\n' + '撤回了一条语音'
            itchat.send(send_msg, 'filehelper') # 将提示消息发送给文件助手
            # 发送保存的语音
            itchat.send_file(temp + '/' + info_name, 'filehelper')
            del dict[msgId] # 删除字典中对应的消息
            print("保存语音")
        elif msg_type == 'Text':
            text_info = dict[msgId]['info'] # 取出消息标识对应的消息内容
            fromUser = dict[msgId]['fromUser'] # 取出发送者
            dt = dict[msgId]['dt'] # 取出发送时间
            # 拼接提示消息
            send_msg = '【发送人:】' + fromUser + '\n' + '发送时间:' + dt + '\n' + '撤回内容:' + text_info
            # 将提示消息发送给文件助手
            itchat.send(send_msg, 'filehelper')
            del dict[msgId] # 删除字典中对应的消息
            print("保存文本")
        elif msg_type == 'Picture':
            picture_info = dict[msgId]['info'] # 取出消息标识对应的消息内容
            fromUser = dict[msgId]['fromUser'] # 取出发送者
            dt = dict[msgId]['dt'] # 取出发送时间
            info_name = dict[msgId]['name'] # 取出文件名
            picture_info(temp + '/' + info_name) # 保存图片
            # 拼接提示消息
            send_msg = '【发送人:】' + fromUser + '\n' + '发送时间:' + dt + '\n' + '撤回了一张图片'
            itchat.send(send_msg, 'filehelper') # 将图片发送给文件助手
            # 发送保存的语音
            itchat.send_file(temp + '/' + info_name, 'filehelper')
            del dict[msgId] # 删除字典中对应的消息 
            print("保存图片")


itchat.run()

这样,一个完整的防撤回程序就完成了,如果你对于前面的铺垫能够掌握得很好的话,这个程序对你来说就是小菜一碟,每一句代码的注释我都有写,应该很容易看懂。

测试程序

到了激动人心的测试环节,我们来测试一下这个程序是否编写成功了。

想查看微信好友撤回的消息?Python帮你搞定

我向我的好友发送了三条消息,分别是文本、图片和语音,接着我一一撤回,然后,微信程序就自动向文件传输助手发送了三条消息:
想查看微信好友撤回的消息?Python帮你搞定

到这里,这个程序就基本完成了。你们在测试的时候也可以叫自己的好友、同学发给你几条消息,然后撤回看看是否能够成功获取到撤回的消息。

撤回的消息发给别人肯定不行,这样不仅泄露了隐私,也会骚扰到别人,所以这里我选择将撤回的消息发送给文件传输助手,如何将消息发送给文件传输助手也很简单:

itchat.send(send_msg, toUserName='filehelper')

toUserName传入filehelper即可,这样,如果对方撤回了消息,你就可以前往文件传输助手查看对方究竟撤回了什么。

说说我遇到的一些坑

这个程序说它难,其实并不难,但我也在编写的过程中遇到了一些坑,一开始我是一条消息一条消息地进行测试,发现程序是正常的,但我连续撤回几条消息,却发现程序出现了Bug。比如我一开始发送了一张图片和一段文字,结果我撤回这两条消息后,得到的却是两段文字。后面我才醒悟过来,是后面的消息覆盖了前面的消息,导致了这个结果,所以在程序中,我定义了一个字典,用于存放好友输入的消息,当监听到消息被撤回时,就通过撤回消息产生的内容中的msgId去和字典中的匹配,匹配到的就是被撤回的消息,然后进行操作即可。

使用教程

想使用该程序非常简单,实现微信防撤回程序节点下有程序的完整代码,直接复制粘贴到你自己的python文件,然后运行该文件即可,运行后会产生一个二维码,用手机验证登录即可。
当然,你也可以选择将该程序打包成可执行的exe文件,这样运行更加方便,打包方式:
首先打开cmd窗口,下载pyinstaller模块,有的话就不用下载了,下载指令:pip insall pyinstaller,此时我们通过cmd窗口进入到python文件目录,比如我这里
想查看微信好友撤回的消息?Python帮你搞定
那就进入到该目录下:
想查看微信好友撤回的消息?Python帮你搞定
然后执行下面这条指令:

pyinstaller -F wechat.py

后面是需要打包的文件名,执行命令后,就会在文件同级目录下生成一个dist文件夹。
想查看微信好友撤回的消息?Python帮你搞定
进入该文件夹,就看到我们的.exe文件了,然后双击执行即可。

最后

这个程序目前只实现了监听好友的文本、图片、语音类型的消息,对于其它类型的消息,还有群聊的消息都是无法监听到的,感兴趣的话大家可以自己试着实现一下。

因为自己也是刚刚接触这个模块,文中的程序可能会出现一些意想不到的Bug,但目前我测试来看是没有问题的,如有问题,欢迎评论区留言。

上一篇:"Hacked?"软件监控邮箱账号是否存在数据泄露情况


下一篇:C#进阶系列——WebApi 跨域问题解决方案:CORS