很早以前,我就部署过这个项目。当时是本地部署,后续试了docker,docker也确实好用,不过我目前ubuntu的docker无法pull,代理配置都写了依然不行,这就让我很困惑了。
不过问题不大,对于服务器docker基本都是完善的,配置也没啥问题。以后或许会考虑全挂到服务器上面去,不过现在我还需要开发,等功能完善后再打包挂服务器得了。项目本身不难,娱乐性质更强(
本文来自开源项目 https://github.com/zhayujie/chatgpt-on-wechat ,对其插件开发。
部署配置
pull下来后,根据requirements.txt要求配置。具体参数配置自己找找文档都可以找到。我遇到了一个小问题,websocket版本过新导致函数已经被删掉了,卸载后指定下载指定版本websocket-client即可
开发plugins
基本逻辑
1.收到消息 ---> 2.产生回复 ---> 3.包装回复 ---> 4.发送回复
文档写的很好,但是可惜原版本过时了,我按照新代码整理一下
注册
对于下面这个,插件首先标识并注册,同时集成自Plugin
在类定义之前需要使用@plugins.register装饰器注册插件,并填写插件的相关信息,其中desire_priority表示插件默认的优先级,越大优先级越高。初次加载插件后可在plugins/plugins.json中修改插件优先级。
并在init中绑定你编写的事件处理函数。
Hello插件为事件ON_HANDLE_CONTEXT绑定了一个处理函数on_handle_context,它表示之后每次生成回复前,都会由on_handle_context先处理。
同时还支持以下几种回复,实际来说,收到回复最有用,一般也用这个就够了
1.收到消息
—> ON_HANDLE_CONTEXT
2.产生回复
—> ON_DECORATE_REPLY
3.装饰回复
—> ON_SEND_REPLY
@plugins.register(name="Hello", desc="A simple plugin that says hello", version="0.1", author="lanvent", desire_priority= -1)
class Hello(Plugin):
def __init__(self):
super().__init__()
self.handlers[Event.ON_HANDLE_CONTEXT] = self.on_handle_context
logger.info("[Hello] inited")
收到消息
def on_handle_context(self, e_context: EventContext):
if e_context['context'].type != ContextType.TEXT:
return
其余逻辑全部在on_handle_content里面,对于一个事物,收到消息后根据自己需求判断需要哪些类型,根据实际需要的去补充
- TEXT = 1 # 文字
- VOICE = 2 # 声音
- IMAGE = 3 # 图片
- FILE = 4 # 文件
- VIDEO = 5 # 视频
- SHARING = 6 # 分享
- IMAGE_CREATE = 10 # 创建图片命令
- ACCEPT_FRIEND = 19 # 同意好友需求
- JOIN_GROUP = 20 # 加入群聊
- PATPAT = 21 # 拍一拍
- FUNCTION = 22 # 函数调用
- EXIT_GROUP = 23 # 退出
对于得到其具体内容,则是在 e_context["context"].content
里面,文字会直接传递值即文本内容,不过我尚未尝试音频文件类内容,如果代码足够规范那应该携带的是原数据(根据下文传递猜测)
产生回复
事件处理函数接收一个EventContext对象e_context作为参数。e_context包含了事件相关信息,利用e_context[‘key’]来访问这些信息。
EventContext(Event事件类型, {‘channel’ : 消息channel, ‘context’: Context, ‘reply’: Reply})
处理函数中通过修改e_context对象中的事件相关信息来实现所需功能,比如更改e_context[‘reply’]中的内容可以修改回复。
def on_handle_context(self, e_context: EventContext):
if e_context['context'].type != ContextType.TEXT:
return
content = e_context['context'].content
if content == "Hello":
这里判断content是为给定值,但实际还有几个实用的接口,实际也是python里面的文字处理,比如说实用的开头包含,或者正则匹配,便于进一步匹配内容
if content.startswith("查询"):
horoscope_match = re.match(r'^([\u4e00-\u9fa5]{2}?)$', content)
if horoscope_match:
包装发送
以我自己写的一个简单获取图片URL为例子
if content == "来点那个":
url = "https://www.dmoe.cc/random.php"
reply_type = ReplyType.IMAGE_URL if self.is_valid_url(url) else ReplyType.TEXT//设定回复类型,判断url是否可获取
reply = self.create_reply(reply_type, url) //创建
e_context["reply"] = reply //设定恢复内容
e_context.action = EventAction.BREAK_PASS //事件结束,不再继续传递
return
def create_reply(self, reply_type, content):
reply = Reply()
reply.type = reply_type
reply.content = content
return reply
- EventAction.CONTINUE: 事件未结束,继续交给下个插件处理,如果没有下个插件,则交付给默认的事件处理逻辑。
- EventAction.BREAK: 事件结束,不再给下个插件处理,交付给默认的处理逻辑。
- EventAction.BREAK_PASS: 事件结束,不再给下个插件处理,跳过默认的处理逻辑。
return后完成
实战操练
插件支持热更新,配合#reloadp 即可。但有时莫名失败,失败一般是代码写错了,如果确认没错那么就重启项目吧
一张图
此链接link后会直接返回图片数据,总体逻辑简单
if content == "来点那个":
url = "https://www.dmoe.cc/random.php"
reply_type = ReplyType.IMAGE_URL if self.is_valid_url(url) else ReplyType.TEXT//设定回复类型,判断url是否可获取
reply = self.create_reply(reply_type, url) //创建
e_context["reply"] = reply //设定恢复内容
e_context.action = EventAction.BREAK_PASS //事件结束,不再继续传递
return
leetcode
python文件引用总是让我头大,对于py,引用貌似都是根据工作目录来的。分文件的py会导致引用失败问题。对此,我专门创建了一个utils文件夹,专门放我的爬虫
爬虫功夫还不到家,这里copy了他人的,后续数据由我处理。XML数据+get不知为何不包含我实际看见的,或许是什么反扒措施(?,下次再专门重新搞好爬虫(
逻辑获取网页数据,爬取对应内容。我再对数据处理,打包成消息发送的格式。外部调用此函数就直接获得了文本,我再返回即可
if content == "每日一题":
reply_type = ReplyType.TEXT
content = fetch_today_question()
reply = self.create_reply(reply_type , content)
e_context['reply'] = reply
e_context.action = EventAction.BREAK_PASS
return
效果可以,配合定时器按时做题(