草庐IT

【Python】uiautomation+openai构建基于chatGPT的微信聊天机器人

一月的晨星 2023-06-10 原文

 目录

前言

一、uiautomation是什么?

二、openai怎么使用?

三、使用步骤

1.前期准备

2.引入库

3.主程序

4.演示效果

总结


前言

人工ai对话技术最近火热,相信很多人会想到做一款微信聊天机器人,让自己那些对程序不是很了解的亲朋好友使用体验一下。

目前大多数的微信聊天机器人,都是基于itchat或者wxautoapi 来操作微信,原理是监听和操作微信的网页版端口,来达到短信收发的目的。但腾讯官方对于这种办法卡得很严,很多微信账号(包括我的)已经不能使用了,就算使用也会面临封禁的风险。

也有用pyautogui来实现的,不过这种基于图像识别的技术的可移植性不是太好,也很难去实现某些功能。

在本文中,利用uiautomation去操作PC端微信,利用官方的openai库获得AI自动回复,实现微信聊天机器人的功能。

此微信聊天机器人的具有以下功能:

1.可以回复多名好友,能自动检测未读消息并回复。

2.配置上下文联系数量和每天问题字数限制,并且每天凌晨可以刷新字数限制。

3.可以给好友配置VIP权限,具有更多的上下文联系数量和问题限制。

4.可以回复群消息,只有当群友@聊天机器人的时候,才会回复。


一、uiautomation是什么?

uiautomation是python的一个模块,封装了微软UIAutomation API,可以对windows的桌面进行控件的搜索、点击等一些列操作,因为完全是桌面级操作,不涉及API接口问题,所以不容易被针对封禁——至少在微信操作上是这样。

uiautomation的原github地址链接

中文说明链接

二、openai怎么使用?

openai是官方提供的python库,通过API接口就可以去获取ChatGPT的回答,值得注意的是如何去实现上下文功能,下面是一段演示代码:

# Note: you need to be using OpenAI Python v0.27.0 for the code below to work
import openai

openai.ChatCompletion.create(
  model="gpt-3.5-turbo",
  messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Who won the world series in 2020?"},
        {"role": "assistant", "content": "The Los Angeles Dodgers won the World Series in 2020."},
        {"role": "user", "content": "Where was it played?"}
    ]
)

在messages中,{"role": "system", "content": "You are a helpful assistant."}算是表头,系统消息有助于设置助手的行为。在上面的例子中,助手被指示“你是一个有帮助的助手”。通常,对话首先使用系统消息格式化,然后是交替的用户和助理消息。

 {"role": "user", "content": "Who won the world series in 2020?"}就是第一次对话,"role": "user"表示是用户发起的,问的问题是"Who won the world series in 2020?"。

而{"role": "assistant", "content": "The Los Angeles Dodgers won the World Series in 2020."}就是第一次的回答,"role": "assistant"表示是AI的,回答内容是"The Los Angeles Dodgers won the World Series in 2020."

{"role": "user", "content": "Where was it played?"}就是第二次回答了,这样往复就能实现上下文的联系。

需要注意的是,message的发送也占用token数量,openai的chat-3.5模型收费价格是0.002美元/K token,虽然每个账号有18美元的免费额度,但是不限制上下文数量,估计没多久你的额度就爆了。

三、使用步骤

1.前期准备

首先推荐一个pytharm的插件,可以使用中文变量,像我这种英文不好的,最讨厌的就是去想变量名了,有了这个插件会方便好多,还减少了注释。直接在插件搜索Chinese就行:

其次,需要你把urllib3的版本回退到1.25.11,因为使用openai需要挂代理,但是1.26版以上会报错,而1.25.11版本就没有这个问题了。打开cmd,输入以下命令:

pip uninstall urllib3
pip install urllib3==1.25.11

2.引入库

import time
from datetime import datetime
import uiautomation as ui  # 导入控件控制库
import pyperclip  # 导入剪切板相关库
import pyautogui  # 导入自动操作库
import openai

3.主程序

openai.api_key = ""  # 这里填入你自己的OpenAi的API
ui.SetGlobalSearchTimeout(0)  # 设置全局搜索超时
info = {}
'''
用户配置,范例:
{'张':{
        '更新时间': '2023-04-01', 
        '是否VIP': '是”,
        '最大Token数': 5000,
        '已使用Token数': 0,
        '上下文联系数量': 5,
        'messages':[{'role': 'system', 'content': '张的对话'}]
        }
}
'''
VipList = ['赵某', '王某', '智障']  # vip名单


# 对用户创建一个专门的配置文件,用于存放权限以及对话上下文
def 用户配置(name, group=False):
    global info
    # 第一步:检索配置中是否存在该人
    if name in info:  # 如果在配置列表中
        if info[name]['更新时间'] != datetime.now().strftime('%y-%m-%d'):  # 如果当前日期和更新时间不同,更新频率为1天
            info[name]['更新时间'] = datetime.now().strftime('%y-%m-%d')  # 刷新更新时间
            info[name]['已使用Token数'] = 0  # 重置已使用Token数
            info[name]['messages'] = [{'role': 'system', 'content': name + '的对话'}]  # 重置messages
    else:  # 不在配置列表中
        info[name] = {}  # 以用户名创建配置列表
        info[name]['更新时间'] = datetime.now().strftime('%y-%m-%d')
        info[name]['messages'] = [{'role': 'system', 'content': name + '的对话'}]
        info[name]['已使用Token数'] = 0
    # 第二步:判断一下用户是否为vip
    if name in VipList:  # 如果用户为vip用户
        info[name]['是否VIP'] = '是'
        info[name]['最大Token数'] = 5000
        info[name]['上下文联系数量'] = 10
    else:  # 如果用户非vip用户
        info[name]['是否VIP'] = '否'
        info[name]['最大Token数'] = 1000
        info[name]['上下文联系数量'] = 1
    # 第三步:判断一下用户是否为群聊用户
    if group:
        info[name]['是否VIP'] = '否'
        info[name]['最大Token数'] = 100000  # 因为是在群聊中所有人一起使用,可以设适当设置大一些,谨慎使用!
        info[name]['上下文联系数量'] = 1  # 因为是在群聊中所有人一起使用,所以最好就设置成不联系上下文了


def getOpenAiMsg(name, question):
    """
    此函数这是向OpenAi发送对话,并返回AI的回答
    Args:
        name:用户的名称,用于读写对应的用户配置,str格式
        question:对话的具体内容,str格式
    Returns:
        answer:AI的回答,str格式
    """
    global info
    if question == "权限":  # 如果用户输入“权限”,
        answer = name + "\n" \
                 + "更新时间: " + info[name]['更新时间'] + "\n" \
                 + "是否VIP: " + info[name]['是否VIP'] + "\n" \
                 + "上下文联系数量: " + str(info[name]['上下文联系数量']) + "\n" \
                 + "最大Token数: " + str(info[name]['最大Token数']) + "\n" \
                 + "已使用Token数: " + str(info[name]['已使用Token数'])
        return answer  # 则返回用户配置信息,不会触发后续的OpenAi自动回复

    if info[name]['已使用Token数'] >= info[name]['最大Token数']:  # 查询是否超出配额
        answer = '很抱歉,你今天使用量超出最大配额,发送“权限”查看您的账号情况'
        return answer
    else:
        if len(info[name]['messages']) >= 2 * info[name]['上下文联系数量'] + 1:  # 如果上下文数量超出预设
            # 删除第一组对话
            info[name]['messages'].pop(1)
            info[name]['messages'].pop(1)

    if info[name]['上下文联系数量'] <= 1:  # 如果上下文数量设置为1
        info[name]['messages'] = [{'role': 'user', 'content': question}]  # 直接设置一条即可
    else:
        info[name]['messages'].append({'role': 'user', 'content': question})  # 增添对话信息

    try:
        response = openai.ChatCompletion.create(
            model='gpt-3.5-turbo',
            messages=info[name]['messages'],
            temperature=0.2
        )
        answer = response.choices[0].message.content  # 从返回值中提取回答
        # 把回答添加到用户配置中的上下文,为了防止tokens爆炸,限制了字数
        info[name]['messages'].append({'role': 'assistant', 'content': answer[:100]})
        info[name]['已使用Token数'] = info[name]['已使用Token数'] + response.usage.total_tokens
        return answer
    except:
        answer = '很抱歉,服务器出错,请重试'
        return answer


def 普通用户回复():
    """普通用户的回复"""
    wx = ui.WindowControl(Name="微信", searchDepth=1)  # 绑定名为微信的主窗口控件
    wx.SwitchToThisWindow()  # 主窗口置前显示
    hw = wx.ListControl(Name="会话")  # 绑定对话列表
    we = hw.EditControl(searchDepth=3)  # 查找新对话
    if we.Exists(0):
        好友 = we.GetParentControl().GetParentControl()
        姓名 = 好友.Name
        print("聊天对象:" + 姓名)
        we.Click(simulateMove=False)
        last_msg = wx.ListControl(Name="消息").GetChildren()[-1].Name  # 获取最新消息
        print("获得消息:" + last_msg)

        用户配置(姓名)
        response = getOpenAiMsg(姓名, last_msg)  # 将收到的信息转发给OpenAi
        print("你的回复:\n" + response)
        # print(info[姓名])     # 调试用,查看下用户配置
        print(
            '已消耗标记:' + str(info[姓名]['已使用Token数']) + "\n--------------------------------------------------\n")
        # 输入回复信息
        pyperclip.copy(response)  # 复制信息
        pyautogui.hotkey('ctrl', 'v')  # 粘贴
        pyautogui.press('enter')  # 回车

        # 隐藏对话
        好友.RightClick()
        右键菜单 = ui.MenuControl(ClassName="CMenuWnd")
        if 右键菜单.Exists(3, 0.1):
            右键菜单.TextControl(Name="不显示聊天").Click()


def 群聊用户回复():
    """群聊用户的回复,请将群消息接收设置为消息免打扰"""
    wx = ui.WindowControl(Name="微信", searchDepth=1)  # 绑定名为微信的主窗口控件
    wx.SwitchToThisWindow()  # 主窗口置前显示
    hw = wx.ListControl(Name="会话")  # 绑定名为对话列表
    we = hw.EditControl(SubName="有人@我", searchDepth=5)  # 查找新对话
    if we.Exists(0):
        群对象 = we.GetParentControl().GetParentControl().GetParentControl().GetParentControl()
        群名 = 群对象.Name
        print("聊天群对象:" + 群名)
        we.Click(simulateMove=False)
        last_msg = wx.ListControl(Name="消息").GetChildren()[-1].Name  # 获取最新消息
        last_msg = last_msg[7:len(last_msg)]  # 将@信息删除,注意这里要和你自身的微信名相匹配,尤其注意微信被@后会多一个‘\u2005’的字符
        # print(wx.ListControl(Name="消息").GetChildren()[-1]) # 感兴趣的可以用这行命令去看下原始消息文本
        # print(last_msg == '权限')  # 建议先在群里发送”权限“两个字,用这行命令比对一下
        print("获得群消息:" + last_msg)

        用户配置(群名, True)
        response = getOpenAiMsg(群名, last_msg)  # 将收到的信息转发给OpenAi
        print("你的回复:\n" + response)
        print(
            '已消耗标记:' + str(info[群名]['已使用Token数']) + "\n--------------------------------------------------\n")
        # 输入回复信息
        pyperclip.copy(response)  # 复制信息
        pyautogui.hotkey('ctrl', 'v')  # 粘贴
        pyautogui.press('enter')  # 回车

        # 隐藏对话
        群对象.RightClick()  # 隐藏权限
        右键菜单 = ui.MenuControl(ClassName="CMenuWnd")
        if 右键菜单.Exists(3, 0.1):
            右键菜单.TextControl(Name="不显示聊天").Click()


print("======================================\n开始\n======================================\n")
while True:
    普通用户回复()
    群聊用户回复()  # 记得把群消息接收设置为消息免打扰
    time.sleep(0.2)

4.演示效果

好友对话,发送”权限“时不会触发chatgpt,而是查看用户对应权限

好友对话,可以看到能正确触发上下文联系

 群聊,从成本考虑,群聊一般不设置上下文联系。


总结

本文仅仅简单介绍了uiautomation和openai的一些相关知识,以及相应的实现代码,后续考虑把这个程序直接部署在服务器上长期运行,可惜在阿里云、腾讯云等国内厂家产品中,可部署的境外云服务器并不便宜,境内服务器又需要挂代理,实际体验下来还是挺受网络波动影响,快的时候可以秒回,慢的时候要等十几秒钟。

最后,编程不易,文章也写的幸苦,为了踩坑颇花了一些钱,这里求个捐助,随便大家给多少~

赞助二维码

有关【Python】uiautomation+openai构建基于chatGPT的微信聊天机器人的更多相关文章

  1. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  2. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

    这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

  3. Python 相当于 Perl/Ruby ||= - 2

    这个问题在这里已经有了答案:关闭10年前。PossibleDuplicate:Pythonconditionalassignmentoperator对于这样一个简单的问题表示歉意,但是谷歌搜索||=并不是很有帮助;)Python中是否有与Ruby和Perl中的||=语句等效的语句?例如:foo="hey"foo||="what"#assignfooifit'sundefined#fooisstill"hey"bar||="yeah"#baris"yeah"另外,类似这样的东西的通用术语是什么?条件分配是我的第一个猜测,但Wikipediapage跟我想的不太一样。

  4. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  5. 亚特兰蒂斯的回声(中文版): chatGPT 的杰作 - 2

    英文版英文链接关注公众号在“亚特兰蒂斯的回声”中踏上一段难忘的冒险之旅,深入未知的海洋深处。足智多谋的考古学家AriaSeaborne偶然发现了一件古代神器,揭示了一张通往失落之城亚特兰蒂斯的隐藏地图。在她神秘的导师内森·兰登教授的指导和勇敢的冒险家亚历克斯·默瑟的帮助下,阿丽亚开始了一段危险的旅程,以揭开这座传说中城市的真相。他们的冒险之旅带领他们穿越险恶的大海、神秘的岛屿和充满陷阱和谜语的致命迷宫。随着Aria潜在的魔法能力的觉醒,她被睿智勇敢的QueenNeria的幻象所指引,她让她为即将到来的挑战做好准备。三人组揭开亚特兰蒂斯令人惊叹的隐藏文明,并了解到邪恶的巫师马拉卡勋爵试图利用其古

  6. ruby - 在 Ruby 中构建长字符串的简洁方法 - 2

    在编写Ruby(客户端脚本)时,我看到了三种构建更长字符串的方法,包括行尾,所有这些对我来说“闻起来”有点难看。有没有更干净、更好的方法?变量递增。ifrender_quote?quote="NowthatthereistheTec-9,acrappyspraygunfromSouthMiami."quote+="ThisgunisadvertisedasthemostpopularguninAmericancrime.Doyoubelievethatshit?"quote+="Itactuallysaysthatinthelittlebookthatcomeswithit:themo

  7. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  8. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  9. python - 如何读取 MIDI 文件、更改其乐器并将其写回? - 2

    我想解析一个已经存在的.mid文件,改变它的乐器,例如从“acousticgrandpiano”到“violin”,然后将它保存回去或作为另一个.mid文件。根据我在文档中看到的内容,该乐器通过program_change或patch_change指令进行了更改,但我找不到任何在已经存在的MIDI文件中执行此操作的库.他们似乎都只支持从头开始创建的MIDI文件。 最佳答案 MIDIpackage会为您完成此操作,但具体方法取决于midi文件的原始内容。一个MIDI文件由一个或多个音轨组成,每个音轨是十六个channel中任何一个上的

  10. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

随机推荐