免责声明
本文发布的内容,仅用于学习研究,请勿用于非法用途和商业用途!如因此产生任何法律纠纷,均与作者无关! 无任何后门、木马,也不获取、存储任何信息,请大家在国家法律、法规和腾讯相关原则下学习研究! 不对任何下载和使用者的任何行为负责,请于下载后24小时内删除!
前言
本文旨在进行技术交流,期待与各位交流。
先看下最终的效果
每次消息变化AI回复助理
就会更新建议列表。
点击右边的文字,内容就会输入到输入框。
微信窗口拖动也会AI回复助理
跟随
前言
最近在调研微信自动化脚本,希望能够实时读取历史记录,传递给AI,然后AI链接内部知识库,返回回复的建议。
技术方案的选择
目前有两种技术方案。首先微信端肯定是没有此类的开放接口的。
目前我查到的有
- 逆向网页版微信
封号概率一般,版本依赖中等,功能中等
ItChat
项目介绍:A complete and graceful API for Wechat. 微信个人号接口、微信机器人及命令行微信,三十行即可自定义个人号机器人。
库地址: https://github.com/littlecodersh/ItChat
- 逆向PC微信,注入脚本,实现hooks。
开源项目也挺多的,封号概率大,版本依赖高,功能多。
2024年11月7号,微信3.9.8.25,亲测可用。
wxbot - 微信聊天机器人
项目介绍:适用于微信(WeChat 3.9.8.25 | 3.9.8.15 | 3.9.7.29)
库地址:https://github.com/jwping/wxbot
- 利用GUI原理轮询本地微信客户端变化
封号概率小,版本依赖小,功能一般。
uiautomation
项目介绍:封装了微软UIAutomation API,支持自动化Win32,MFC,WPF,Modern UI(Metro UI), Qt, IE, Firefox(version<=56 or >=60
库地址:https://github.com/yinkaisheng/Python-UIAutomation-for-Windows
基于 uiautomation
二开的项目有
easyChat
项目介绍:PC端微信助手(非web微信版):定时发送信息;群发信息;自动回复等。直接下载文件目录内的exe即可使用。
库地址:https://github.com/LTEnjoy/easyChat
wxauto
项目介绍:Windows版本微信客户端自动化,可实现简单的发送、接收微信消息、保存聊天图片。
库地址:https://github.com/cluic/wxauto
本文是基于wxauto实现的,并加入了微信多开功能。
正文
以下是主要思路,代码非完整代码。
完整代码在文末。
1 修改源码
首先如果是多开微信需要修改wxauto源码。
修改如下:
https://github.com/PL-FE/wxauto/commit/fd523485170d1823672bf55d0fedc633ef46b924
def __init__(
self,
uia_api = uia.WindowControl(ClassName='WeChatMainWndForPC', searchDepth=1),
language: Literal['cn', 'cn_t', 'en'] = 'cn',
debug: bool = False
) -> None:
源码为初始化赋值,我们修改为可以传值覆盖。
将源码 SendMsg
方法拆成两步
def InputMsg(self, msg, who=None, clear=True, at=None):
"""输入文本消息,不发送
Args:
msg (str): 要发送的文本消息
who (str): 要发送给谁,如果为None,则发送到当前聊天页面。 *最好完整匹配,优先使用备注
clear (bool, optional): 是否清除原本的内容,
at (str|list, optional): 要@的人,可以是一个人或多个人,格式为str或list,例如:"张三"或["张三", "李四"]
"""
if FindWindow(name=who, classname='ChatWnd'):
chat = ChatWnd(who, self.language)
chat.SendMsg(msg, at=at)
return None
if not msg and not at:
return None
if who:
try:
editbox = self.ChatBox.EditControl(searchDepth=10)
if who in self.CurrentChat() and who in editbox.Name:
pass
else:
self.ChatWith(who)
editbox = self.ChatBox.EditControl(Name=who, searchDepth=10)
except:
self.ChatWith(who)
editbox = self.ChatBox.EditControl(Name=who, searchDepth=10)
else:
editbox = self.ChatBox.EditControl(searchDepth=10)
if clear:
editbox.SendKeys('{Ctrl}a', waitTime=0)
self._show()
if not editbox.HasKeyboardFocus:
editbox.Click(simulateMove=False)
if at:
if isinstance(at, str):
at = [at]
for i in at:
editbox.SendKeys('@'+i)
atwnd = self.UiaAPI.PaneControl(ClassName='ChatContactMenu')
if atwnd.Exists(maxSearchSeconds=0.1):
atwnd.SendKeys('{ENTER}')
if msg and not msg.startswith('\n'):
msg = '\n' + msg
if msg:
t0 = time.time()
while True:
if time.time() - t0 > 10:
raise TimeoutError(f'发送消息超时 --> {editbox.Name} - {msg}')
SetClipboardText(msg)
editbox.SendKeys('{Ctrl}v')
if editbox.GetValuePattern().Value:
break
return editbox
def SendMsg(self, msg, who=None, clear=True, at=None):
"""发送文本消息
Args:
msg (str): 要发送的文本消息
who (str): 要发送给谁,如果为None,则发送到当前聊天页面。 *最好完整匹配,优先使用备注
clear (bool, optional): 是否清除原本的内容,
at (str|list, optional): 要@的人,可以是一个人或多个人,格式为str或list,例如:"张三"或["张三", "李四"]
"""
editbox = self.InputMsg(msg, who, clear, at)
editbox.SendKeys('{Enter}')
2 构造多个微信实例
然后外部批量初始化,最后就拿到了多个微信GUI实例。
import uiautomation as uia
from wxauto import *
root = uia.GetRootControl()
child = root.GetChildren()
filtered_list = list(filter(lambda x: x.ClassName == 'WeChatMainWndForPC', child))
wxs = [WeChat(wx_window) for wx_window in filtered_list]
关于
WeChatMainWndForPC
这个就是一个类名,我们可以借助工具inspect
(点击下载)看到。
3 完整代码
main.python
import time
from wxauto_custom import *
import uiautomation as uia
import threading
import tkinter as tk
import assistant
import json
import win32gui
window_title = 'AI回复助理'
def check_window_visibility(window, wx):
# 获取微信窗口句柄和位置
wechat_hwnd = wx.UiaAPI.NativeWindowHandle
if wechat_hwnd:
# 获取当前鼠标点击的窗口的句柄的标题
current_title = win32gui.GetWindowText(win32gui.GetForegroundWindow())
# 判断微信窗口状态,显示或隐藏本窗口
if win32gui.GetForegroundWindow() == wechat_hwnd or current_title == window_title:
window.wm_attributes('-alpha', 1.0)
else:
window.wm_attributes('-alpha', 0.0)
else:
# 微信窗口未找到,隐藏本窗口
window.wm_attributes('-alpha', 0.0)
class WxAutoAssistant:
def __init__(self):
self.wechats = self.find_wechat_windows()
self.gpt_assistant = assistant.ChatGPTClient()
def find_wechat_windows(self):
root = uia.GetRootControl()
child = root.GetChildren()
filtered_list = list(filter(lambda x: x.ClassName == 'WeChatMainWndForPC', child))
return [WeChat(wx_window) for wx_window in filtered_list]
def create_assistant_window(self):
window = tk.Tk()
window.title(window_title)
window.overrideredirect(True)
window.wm_attributes('-topmost', True)
self.create_title_bar(window)
self.create_close_button(window)
return window
def create_title_bar(self, window):
title_frame = tk.Frame(window, height=30)
title_frame.pack(fill=tk.X)
title_label = tk.Label(title_frame, text=window.title())
title_label.pack(side=tk.LEFT, padx=5)
window.title_frame = title_frame
window.title_label = title_label
def create_close_button(self, window):
close_button = tk.Button(window.title_frame, text="X", command=window.destroy, height=1)
close_button.pack(side=tk.RIGHT, padx=5)
window.close_button = close_button
def handle_wechat_window(self, wx):
chats_list = []
labels = []
try:
assistant_window = self.create_assistant_window()
self.update_window_position(assistant_window, wx) # 开始时刻跟随微信窗口
self.update_chat_messages(assistant_window, wx, chats_list, labels) # 更新聊天内容
assistant_window.mainloop()
except Exception as e:
print(f"处理微信窗口失败: {e}")
def update_chat_messages(self, window, wx, chats_list, labels):
msgs = wx.GetAllMessage()
msgs.reverse()
chats_list_temp = []
for msg in msgs:
if msg.type == 'friend':
chats_list_temp.append({'nickname': msg.sender_remark, 'content': msg.content, 'is_send': False})
elif msg.type == 'self':
chats_list_temp.append({'nickname': '', 'content': msg.content, 'is_send': True})
if chats_list_temp != chats_list:
res_msg = self.gpt_assistant.get_response(chats_list_temp)
self.display_chat_tips(window, wx, res_msg, labels)
chats_list = chats_list_temp
print('新消息:', chats_list_temp)
window.after(3000, self.update_chat_messages, window, wx, chats_list, labels)
def display_chat_tips(self, window, wx, res_msg, labels):
for label in labels:
label.destroy()
labels.clear()
for msg in json.loads(res_msg):
label = tk.Label(window, text=msg, wraplength=200, justify=tk.LEFT)
label.pack()
label.bind("<Button-1>", lambda event, m=msg: wx.InputMsg(m))
labels.append(label)
def update_window_position(self, window, wx):
try:
check_window_visibility(window, wx)
rect = wx.UiaAPI.BoundingRectangle
wx_width = rect.width()
wx_height = rect.height()
wx_x = rect.left
wx_y = rect.top
window.geometry(f'200x{wx_height}+{wx_x + wx_width}+{wx_y}')
window.after(100, self.update_window_position, window, wx)
except IndexError:
print("未找到微信窗口,请确保微信已启动")
# 主程序
if __name__ == "__main__":
wxAutoAssistant = WxAutoAssistant()
threads = []
for wx in wxAutoAssistant.wechats:
thread = threading.Thread(target=wxAutoAssistant.handle_wechat_window, args=(wx,))
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
assistant.py
import openai
import json
class ChatGPTClient:
def __init__(self):
self.model = 'gpt-4o-mini'
# 替换成你的key,我这个是一个测试的key,请自行替换
openai.api_key = "sk-7EzKMSSKdTlNMy2C2e88C383E15b45BfA59228FaF2Fe382B11"
openai.base_url = "https://api.gpt.ge/v1/"
openai.default_headers = {"x-foo": "true"}
def get_response(self, history: list) -> str:
"""
传入历史记录,调用 ChatGPT 并返回下一句回复的建议。
Parameters:
history (list): 历史记录列表,每个元素是一个字典,包含角色和内容,例如:
[{"role": "user", "content": "你好,ChatGPT"}, {"role": "assistant", "content": "你好!有什么可以帮您的?"}]
Returns:
str: ChatGPT 的回复内容
"""
msg = [
{
"role": "user",
"content": "我会输入一段聊天记录给你,请你生成下一句回复给我。回复格式为列表,每项是一句回复,列表长度为5.要求返回格式只能是列表的JSON,不能包含md格式,以下是聊天记录:",
},
{
"role": "user",
"content": json.dumps(history, ensure_ascii=False, indent=4)
}
]
try:
response = openai.chat.completions.create(
model=self.model,
messages=msg,
)
print(response.choices[0].message.content)
return response.choices[0].message.content
except Exception as e:
print(f"Error: {e}")
return "Error: " + str(e)
# 使用示例
if __name__ == "__main__":
client = ChatGPTClient()
history = [
{"role": "user", "content": "你好,ChatGPT"},
{"role": "assistant", "content": "你好!有什么可以帮您的?"},
{"role": "user", "content": "请告诉我今天的天气。"}
]
reply = client.get_response(history)
print("ChatGPT 回复:", reply)