大家好,欢迎来到《分享本周所学》第十一期。本人是一名人工智能初学者(虽然我最近写的东西都跟人工智能没什么关系),刚刚上完大一。最近在公司实习摸鱼的时候突然觉得微信没法把消息发进Windows通知中心,特别不好用。于是就决定写一个基于wxpy和win11toast的自己的微信。
这是我第一次用CSDN的MD编辑器,所以可能格式和往期不太一样。
9月8日更新
我在GitHub仓库上的项目更新了,更新后的内容和这篇教程没法匹配上,所以我新建了一个叫tutorial
分支,把项目的最初版本(也就是这篇文章对应的版本)放了进去。大家在阅读或学习的时候可以参考tutorial
分支,如果想实际体验的话,可以用main
分支上的最新版本,体验会更好。
上期文章连接
分享本周所学——在Windows上搭建自己的Git服务器并支持互联网远程访问
本期封面
项目安装与运行
我已经把整个项目上传到GitHub了。大家可以到我的GitHub仓库上把整个项目打包下载到本地。千万别忘先Star一下我的项目!求你了! 项目还在不断更新,以后可能会推出更多美观而且实用的功能。
安装
- 项目必须在Windows 11系统上运行。如果你还没有更新到Windows 11,请务必先进行更新。我使用的Windows版本是25330.1000,在其他版本的系统上也大概率能成功运行,但不能保证一定成功,毕竟我没有做过测试。
- 安装Python。我使用的版本是Python 3.8,其他版本上也大概率能成功运行,但不能保证一定成功,毕竟我没有做过测试。
- 使用以下命令来安装所依赖的包(
win11toast
和wxpy
):
pip install wxpy win11toast -i https://pypi.tuna.tsinghua.edu.cn/simple
运行
下载并解压项目文件之后,里面应该有一个叫run.bat
的文件。直接双击run.bat
即可运行。
使用方法
开始运行后,程序会先下载一个微信网页版的二维码。你需要先扫描二维码进行登录。登录之后,程序就会不断地将收到的微信转发到系统的通知中心。具体效果是这样的:
- 文字信息:
- 图片:
- 文件:
是不是还挺好看的?而且和Windows的通知中心集成得非常好。你可以在“Reply to this message”那个文本框里直接输入你想回复的内容,然后点击“Send”回复。非常方便。除此之外,你也可以在run.bat
开启的那个终端窗口里发送信息,下面是一个示例:
In [1]: switch_context('RFdragon') # 开始与RFdragon的聊天。
In [2]: show_context() # 显示当前聊天对象。
Current context: [RFdragon].
In [3]: send('Hello world!') # 发送文字信息“Hello world!”。
In [4]: send(r'D:\Downloads\download.zip', msg_type='file') # 发送文件。
至于这些命令具体的功能和使用方法,我会在下面代码讲解的部分提到。
代码讲解
首先我们导入需要的包:
import os # 处理文件路径。
from typing import Union # 这个是专门写注释用的,没啥实际作用。
import win11toast # 发送Windows通知。
import wxpy # 收发微信。
这个wxpy
里有一个非常重要的Bot
类。我们需要创建一个Bot
类的变量,用于收发微信:
bot = wxpy.Bot(cache_path=True)
其中,这个cache_path
表示微信登录信息的缓存路径。如果设置为True
,它就会在程序的同级目录下创建一个叫wxpy.pkl
的文件,这样就不用每次运行的时候都扫一次码了,只有当缓存的登录信息过期时,才需要重新扫码。但是使用这个参数也有一个缺点,就是每次重新运行程序的时候都会收到一些重复的信息。初始化Bot
的时候还可以使用一些其他参数,具体可以参考wxpy的官方文档。
接下来,就是要处理bot
接收到的所有微信消息。我们可以用Bot.register
装饰器来处理这些消息:
@bot.register(except_self=False)
def echo(msg: wxpy.Message) -> None:
装饰器中,except_self
参数表示是否处理自己发送的消息。如果为True
,将只处理别人发送的消息。而装饰器下方函数的参数是一个wxpy.Message
类的变量。关于这个变量的具体信息也可以参考wxpy的官方文档。
现在,我们要分别处理收到的文字、图片和文件信息。首先是文字信息:
if msg.type == wxpy.TEXT: # 判断信息类型为文字信息。
display = f'{msg.chat.name}({msg.sender.name if msg.member is None else msg.member.name}): {msg.text}'
res = win11toast.toast('WeChat',
display,
input='Reply to this message...',
button={
'activationType': 'protocol',
'arguments': 'http:',
'content': 'Send',
'hint-inputId': 'Reply to this message...'
},
duration='long')
这里主要要说一下这个win11toast.toast
函数。这个函数是用来发送通知的,函数的前两个参数分别是通知的标题和内容,之后有一些关键字参数。button
参数表示这个通知上有一个按钮,duration
参数可以延长通知显示的时间。这个函数会返回一个字典,其中包含了用户想要回复的信息。
图片和文件信息的处理其实大同小异,这里就不赘述了。接下来是处理用户的回复:
try:
reply = res['user_input']['Reply to this message...'] # 获取用户的回复。
if reply:
msg.chat.send_msg(reply) # 发送回复。
except Exception:
pass
这样就完成了整个信息处理的步骤,说实话还挺简单的,主要是wxpy
和win11toast
这两个库封装得太好了。我在编写这段代码的时候其实也没遇到什么问题,唯一的问题就是感觉win11toast的文档写得有点不清不楚。
但现在的问题是,我们只能被动地回复收到的信息,不能主动向别人发送信息。为了解决这个问题,我写了三个函数用于在Windows终端中主动发送信息。它们分别是:
switch_context
,用于选择聊天对象。
def switch_context(chat: Union[str, None] = None) -> None:
# 参数chat表示想要选择的聊天对象,可以为对象的昵称、备注、群名等信息。如果为None,则会切换到上一个向你发送消息的聊天对象。
global context # 全局变量context记录着当前的聊天对象。
if chat is None:
if last is None:
print('Warning: No existing chat found.') # 聊天对象为None,并且当前没有收到任何消息时报错。
else:
context = last
else:
chat = bot.search(chat) # 搜索聊天对象。
try:
context = wxpy.ensure_one(chat) # 确保搜索结果唯一。
except ValueError:
print(
'Warning: Zero or more than one chat with the given keyword found. Skipping these chats.'
) # 当搜索结果不唯一时报错。
return
show_context
,用于显示当前聊天对象。
def show_context() -> None:
if context is None: # 当前无聊天对象。
print('No chat context is available.')
return
print(f'Current context: [{context.name}].')
send
,用于发送消息。
def send(msg: str,
chat: Union[str, None] = None,
msg_type: str = 'msg') -> None:
# msg为待发送的信息,可以为文字信息或文件名。
# chat为发往的聊天对象。可以为对象的昵称、备注、群名等信息。如果为None,则会使用当前context中的聊天对象。
# msg_type为消息类型,可以为“msg”(文字消息)、“image”(图片)、“file”(文件)或“video”(视频)。
if chat is None:
if context is None:
# 当chat为None并且当前无聊天对象时报错。
print('Warning: No chat context is available.')
return
else:
chat = context
else:
# 搜索聊天对象。
chat = bot.search(chat)
try:
chat = wxpy.ensure_one(chat)
except ValueError:
print(
'Warning: Zero or more than one chat with the given keyword found. Skipping these chats.'
)
return
if msg_type == 'msg':
chat.send_msg(msg)
elif msg_type == 'image':
chat.send_image(msg)
elif msg_type == 'file':
chat.send_file(msg)
elif msg_type == 'video':
chat.send_video(msg)
else:
print('Warning: Invalid message type.')