【微信回复建议助理】python实现自动化-微信回复建议助理-支持多开

免责声明

本文发布的内容,仅用于学习研究,请勿用于非法用途和商业用途!如因此产生任何法律纠纷,均与作者无关! 无任何后门、木马,也不获取、存储任何信息,请大家在国家法律、法规和腾讯相关原则下学习研究! 不对任何下载和使用者的任何行为负责,请于下载后24小时内删除!

前言

本文旨在进行技术交流,期待与各位交流。

先看下最终的效果

基于wxautoopenaitkinter实现

每次消息变化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)


仓库

https://github.com/PL-FE/wxauto

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_pengliang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值