使用文字识别构建的微信群聊天机器人

2023-11-01 09:10

本文主要是介绍使用文字识别构建的微信群聊天机器人,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 摘要
  • 实现思路
    • 实现代码
    • 遇到问题
      • pymouse报错缺少windows和win32api
      • module 'PIL.Image' has no attribute 'ANTIALIAS'
      • 获取openai api key
      • pip install CnOcr提示需要c++编译环境
    • 总结

摘要

本文实现了一种使用文字识别的方法来构建的微信群聊机器人,调用openai来实现智能聊天。众所周知,微信机器人的项目现在由于网页版微信被ban,大部分都已经不可用了。我的常用微信号和小号都无法登录网页版微信,于是寻找能够通过pc端来实现微信机器人的方法,找到诸如wechaty等项目。这些项目通过dll注入的方式调用微信客户端的内部方法来接受和发送消息。但可惜的是这些项目都是收费的,本身这就是个伪需求,只是做着玩的,要花钱就不太值当了。既然找不到现成的框架,那么试试自己能不能实现吧!本身我也没学过逆向,想要破解微信来实现dll注入对我来说太过困难。而且就算凭我的三脚猫功夫能实现需求,估计不超过一周就能被封号😥。只能想个更安全的方式了,就是使用模拟操作来直接操作微信客户端,当然这种方式实现的功能有着非常大的局限性,不过只是做着玩的话也勉强可以用。

实现思路

使用python来实现,有着强大的第三方库的支持,避免自己造轮子。

  1. 我们首先需要能够监听到群里发送的消息。使用pyautogui库的screenshot()方法对屏幕进行截图。
  2. 之后要从截图中获得最后一条消息框的位置,并对消息框进行截图。可知消息框的左边界是固定的位置(只要你不缩放微信界面)。那就从这条线最底下往上找rgb为(255,255,255)白色的坐标。就可以获得消息框左下角坐标。在从该坐标分别向上找和向右找白色的部分,就可以得到消息框的大小了。
    获得消息框3. 获得消息框的截图后,对其使用文字识别吧。一开始使用了tesseract来进行文字识别,但其中文的识别不出正确文字,不知到是我配置有问题吗?当把图片放大8倍的时候好歹能识别出几个字了😑。总之又换成了CnOcr来进行文字识别了,虽然有时候也识别错误,但也足够用了。
    识别图片
    识别文字
    4. 获得新消息后,调用openai的api来获得要回复的文本。在用pyperclipcopy()方法复制文字进剪切板,pyautogui来实现ctrl+v粘贴,alt+s发送消息。

实现代码

废话不多说了,这个代码是花了两天写的,仍有很多不足,但小心点使用还是可以的。

import pyautogui
from pynput.keyboard import Key, Controller
from pyperclip import copy,paste
from PIL import Image, ImageChops
import time
import io
from cnocr import CnOcr
from queue import Queue
import openai
import threading
import logging"""向队列中添加元素,并保持队列元素的个数。args:queue:队列limit:限制队列元素的个数item:要添加的元素
"""
def queue_limit_put(queue,limit,item):queue.put(item)if queue.qsize()>limit:queue.get()def send_msg_on_wechat(message,response):send_msg=message+"\n--------\n"+responsecopy(send_msg)pyautogui.hotkey('ctrl', 'v')pyautogui.hotkey('alt','s')class WeChatRobot:def __init__(self) -> None:self.snap_position=[300,60,980,825]#截图框的位置:left,top,length,heightself.dialog_left=340#消息框的左边界self.ocr=CnOcr()self.message_list=Queue()#新消息队列self.robot_name="%小马"#机器人的唤醒名字self.using_name=True#True时要带机器人名字的消息才会回复,False所有消息都回复self.snap_error_check=True"""对屏幕区域进行截图args:x,y:左上点的坐标m:长度n:高度"""def snap(self,x,y,m,n):try:im=pyautogui.screenshot(region=(x,y,m,n))self.snap_error_check=Trueexcept Exception as e:if self.snap_error_check:self.snap_error_check=False#防止发送太多的崩溃消息send_msg_on_wechat('我崩溃啦!','截图失败了!')return im#对比两张图片相似度def compare_image(self,screenshot1,screenshot2):image1=screenshot1image2=screenshot2diff=ImageChops.difference(image1,image2)diff_image = diff.convert('RGBA')diff_count = diff.getbbox()if diff_count is None:#与上一张截图相同return Falsesimilarity = 1 - ((diff_count[2]-diff_count[0]) * (diff_count[3]-diff_count[1])) / (image1.width * image1.height)if __debug__:print(f"相似度:{similarity}")if similarity>=0.95:#相似度大于0.95判定为相同,低于0.95判定为有新消息return Falsereturn True#寻找对话框的坐下点坐标def find_dialog_leftbott(self,im):x=80y=self.snap_position[3]-1while y>0:if im.getpixel((x,y))==(255,255,255):return (x,y)else:y=y-1return (-1,-1)#寻找对话框的长与高,(x,y)为左下点坐标def find_dialog_lenhei(self,im,x,y):right=xtop=ywhile right<self.snap_position[2]:if im.getpixel((right,y))!=(255,255,255):breakright=right+1while top>0:if im.getpixel((x,top))!=(255,255,255):breaktop=top-1if right==self.snap_position[2] or top==0:return (-1,-1)return (right-x,y-top)# def test_ai_reply(self):#     time.sleep(1)#     for i in range(2):#         self.message_list.put(f"你是谁")def run(self):privous_screenshot=Nonetime.sleep(5)#等我切换到微信页面while True:#对微信界面进行截图作为原始图im=self.snap(self.snap_position[0],self.snap_position[1],self.snap_position[2],self.snap_position[3])#寻找消息框并截图(dialog_x,dialog_y)=self.find_dialog_leftbott(im)if dialog_x==-1 or dialog_y==-1:logger.warning("查找对话框位置失败!")if __debug__:print(f"dialog_x,dialog_y:{dialog_x,dialog_y}")(dialog_len,dialog_hei)=self.find_dialog_lenhei(im,dialog_x,dialog_y)if dialog_len==-1 or dialog_hei==-1:logger.warning("查找对话框长宽失败!")if __debug__:print(f"dialog_len,dialog_hei{dialog_len,dialog_hei}")#根据获得的参数截取对话框的图片dialog_image=im.crop((dialog_x,dialog_y-dialog_hei,dialog_x+dialog_len,dialog_y))hasNews=Falseif privous_screenshot is not None:#与上一张截图进行对比,相似度是否在阈值内#此处只对比了消息框的内容,会导致用户连续发送相同的内容不会认为其是新消息,暂时没解决办法hasNews=self.compare_image(privous_screenshot,dialog_image)if hasNews:#使用cnocr进行识别,部分文字仍识别不正确,但正确率仍可以接受result=self.ocr.ocr(dialog_image)res_text=""for element in result:#拼接识别的文字res_text=res_text+element.get('text')logger.info(f"识别新消息:{res_text}")if self.using_name:if res_text.startswith(self.robot_name):res_text=res_text.replace(self.robot_name,"")#识别出机器人名字,然后从消息中去除机器人名字self.message_list.put(res_text)else:self.message_list.put(res_text)privous_screenshot=dialog_imagetime.sleep(0.5)
class AI_reply:def __init__(self,queue) -> None:self.queue=queueself.api_key='abcdefghijklmnopqrstuvwxyz'#换成你的openai api keyopenai.api_key=self.api_keyopenai.proxy = 'http://127.0.0.1:1234'#设置下代理,否则连不上网络self.model='gpt-3.5-turbo'self.temperature=0.7self.max_tokens=200#设置200回复字数上限,担心我的额度啊self.n=1self.msg_context=Queue()#保存的上下文self.msg_context_limit=5#上下文大小,影响ai能记住几句之前的内容self.keyborad=Controller()# def test_api(self):#     messages = [{ "role": "user", "content": '欢迎使用openai!' }]#     model = 'gpt-3.5-turbo'#     response = openai.ChatCompletion.create(#         model=model,#         messages=messages,#         temperature=self.temperature,#         n=self.n#     )#     queue_limit_put(self.msg_context,self.msg_context_limit,response.choices[0].message)#     if __debug__:#         print(f'\033[31m收到的数据:{response.choices[0].message.content}\033[0m')#制作消息放入上下文中def create_message(self,content):message={"role":"user","content":content}queue_limit_put(self.msg_context,self.msg_context_limit,message)return self.msg_context#请求api,获得返回结果def get_response(self,message):try:response=openai.ChatCompletion.create(model=self.model,messages=message,temperature=self.temperature,max_tokens=self.max_tokens)except openai.APIConnectionError  as e:logger.error(e)return ('error','网络又抽风了,这也是没办法的事。')queue_limit_put(self.msg_context,self.msg_context_limit,response.choices[0].message)#将返回的结果放入上下文中,让ai能记住说了什么logger.info(f"从openai获得回复:{response.choices[0].message.content}")return ('success',response.choices[0].message.content)def run(self):while True:logger.info("等待新消息。")msg=self.queue.get()logger.info(f"获得新消息,正在请求openai:{msg}")self.create_message(msg)logger.info(f"token:{list(self.msg_context.queue)}")(statue,res)=self.get_response(list(self.msg_context.queue))if statue=='success':self.send_msg_on_wechat(msg,res)elif statue=='error':self.send_msg_on_wechat('链接不上openai了',res)logger=logging.getLogger('robot_logger')
logger.setLevel("INFO")
file_handler = logging.FileHandler("C:\\Users\\22308\\Documents\\python file\\log\\robot.txt")
file_handler.setLevel("INFO")
fmt =logging.Formatter('%(filename)s-%(lineno)d-%(asctime)s-%(levelname)s-%(message)s')
file_handler.setFormatter(fmt)
logger.addHandler(file_handler)
logger.info("开始运行!")
robot=WeChatRobot()
ai=AI_reply(robot.message_list)
logger.info("初始化完成")
#创建线程监控是否有新消息,然后请求api
thread = threading.Thread(target=ai.run)
thread.start()
robot.run()
thread.join()
logger.info("运行结束!")

遇到问题

写一下遇到的一些小问题吧。

pymouse报错缺少windows和win32api

虽然最后也没用到这个库,但遇到了就写一下吧。
缺少windows:

pip install PyUserinput

缺少win32api:

pip install pypiwin32

module ‘PIL.Image’ has no attribute ‘ANTIALIAS’

在pillow的10.0.0版本中,ANTIALIAS方法被删除了,改为10.0版本许可的参数即可。

  • 方案一,修改ddddocr的_init_.py文件,将其中的ANTIALIAS替换为新方法:

    image = image.resize((int(image.size[0] * (64 / image.size[1])), 64), Image.LANCZOS).convert('L')
    
  • 方案二,降级Pillow的版本

    pip uninstall -y Pillow

    pip install Pillow==9.5.0

获取openai api key

在openai的官网上注册好后,到API文档中激活api key,每个月有5美刀的额度,持续三个月。不幸的是我的key不知道怎么回事已经申请过了,现在已经过期了😅。只好去买一个账号了,好在也不贵。

pip install CnOcr提示需要c++编译环境

找解决方法时有说安装anaconda可以,安装visual cpp build tool的。但总有地方卡住我。最后还是直接安装了virtual sudio,下了6个G😅。

总结

除了接入chatgpt以外,其实还可以使用一些固定指令来实现天气,咨询等内容,有了框架后这些也好写了。现在这些内容放到相亲相爱一家人里装个逼足够了。当然如果大家有更好用的技术或者其他建议的话欢迎告诉我。最后放一下我老婆。
老婆!

这篇关于使用文字识别构建的微信群聊天机器人的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/321954

相关文章

Python使用Tenacity一行代码实现自动重试详解

《Python使用Tenacity一行代码实现自动重试详解》tenacity是一个专为Python设计的通用重试库,它的核心理念就是用简单、清晰的方式,为任何可能失败的操作添加重试能力,下面我们就来看... 目录一切始于一个简单的 API 调用Tenacity 入门:一行代码实现优雅重试精细控制:让重试按我

MySQL中EXISTS与IN用法使用与对比分析

《MySQL中EXISTS与IN用法使用与对比分析》在MySQL中,EXISTS和IN都用于子查询中根据另一个查询的结果来过滤主查询的记录,本文将基于工作原理、效率和应用场景进行全面对比... 目录一、基本用法详解1. IN 运算符2. EXISTS 运算符二、EXISTS 与 IN 的选择策略三、性能对比

使用Python构建智能BAT文件生成器的完美解决方案

《使用Python构建智能BAT文件生成器的完美解决方案》这篇文章主要为大家详细介绍了如何使用wxPython构建一个智能的BAT文件生成器,它不仅能够为Python脚本生成启动脚本,还提供了完整的文... 目录引言运行效果图项目背景与需求分析核心需求技术选型核心功能实现1. 数据库设计2. 界面布局设计3

使用IDEA部署Docker应用指南分享

《使用IDEA部署Docker应用指南分享》本文介绍了使用IDEA部署Docker应用的四步流程:创建Dockerfile、配置IDEADocker连接、设置运行调试环境、构建运行镜像,并强调需准备本... 目录一、创建 dockerfile 配置文件二、配置 IDEA 的 Docker 连接三、配置 Do

Android Paging 分页加载库使用实践

《AndroidPaging分页加载库使用实践》AndroidPaging库是Jetpack组件的一部分,它提供了一套完整的解决方案来处理大型数据集的分页加载,本文将深入探讨Paging库... 目录前言一、Paging 库概述二、Paging 3 核心组件1. PagingSource2. Pager3.

深入浅出SpringBoot WebSocket构建实时应用全面指南

《深入浅出SpringBootWebSocket构建实时应用全面指南》WebSocket是一种在单个TCP连接上进行全双工通信的协议,这篇文章主要为大家详细介绍了SpringBoot如何集成WebS... 目录前言为什么需要 WebSocketWebSocket 是什么Spring Boot 如何简化 We

python使用try函数详解

《python使用try函数详解》Pythontry语句用于异常处理,支持捕获特定/多种异常、else/final子句确保资源释放,结合with语句自动清理,可自定义异常及嵌套结构,灵活应对错误场景... 目录try 函数的基本语法捕获特定异常捕获多个异常使用 else 子句使用 finally 子句捕获所

C++11右值引用与Lambda表达式的使用

《C++11右值引用与Lambda表达式的使用》C++11引入右值引用,实现移动语义提升性能,支持资源转移与完美转发;同时引入Lambda表达式,简化匿名函数定义,通过捕获列表和参数列表灵活处理变量... 目录C++11新特性右值引用和移动语义左值 / 右值常见的左值和右值移动语义移动构造函数移动复制运算符

Python对接支付宝支付之使用AliPay实现的详细操作指南

《Python对接支付宝支付之使用AliPay实现的详细操作指南》支付宝没有提供PythonSDK,但是强大的github就有提供python-alipay-sdk,封装里很多复杂操作,使用这个我们就... 目录一、引言二、准备工作2.1 支付宝开放平台入驻与应用创建2.2 密钥生成与配置2.3 安装ali

C#中lock关键字的使用小结

《C#中lock关键字的使用小结》在C#中,lock关键字用于确保当一个线程位于给定实例的代码块中时,其他线程无法访问同一实例的该代码块,下面就来介绍一下lock关键字的使用... 目录使用方式工作原理注意事项示例代码为什么不能lock值类型在C#中,lock关键字用于确保当一个线程位于给定实例的代码块中时