@@ -8,4 +8,5 @@ config.json | |||||
QR.png | QR.png | ||||
nohup.out | nohup.out | ||||
tmp | tmp | ||||
plugins.json | |||||
plugins.json | |||||
itchat.pkl |
@@ -15,18 +15,16 @@ | |||||
# 更新日志 | # 更新日志 | ||||
>**2023.03.25:** 支持插件化开发,目前已实现 多角色切换、文字冒险游戏、管理员指令、Stable Diffusion等插件,使用参考 [#578](https://github.com/zhayujie/chatgpt-on-wechat/issues/578)。(contributed by [@lanvent](https://github.com/lanvent) in [#565](https://github.com/zhayujie/chatgpt-on-wechat/pull/565)) | |||||
>**2023.03.09:** 基于 `whisper API` 实现对微信语音消息的解析和回复,添加配置项 `"speech_recognition":true` 即可启用,使用参考 [#415](https://github.com/zhayujie/chatgpt-on-wechat/issues/415)。(contributed by [wanggang1987](https://github.com/wanggang1987) in [#385](https://github.com/zhayujie/chatgpt-on-wechat/pull/385)) | >**2023.03.09:** 基于 `whisper API` 实现对微信语音消息的解析和回复,添加配置项 `"speech_recognition":true` 即可启用,使用参考 [#415](https://github.com/zhayujie/chatgpt-on-wechat/issues/415)。(contributed by [wanggang1987](https://github.com/wanggang1987) in [#385](https://github.com/zhayujie/chatgpt-on-wechat/pull/385)) | ||||
>**2023.03.02:** 接入[ChatGPT API](https://platform.openai.com/docs/guides/chat) (gpt-3.5-turbo),默认使用该模型进行对话,需升级openai依赖 (`pip3 install --upgrade openai`)。网络问题参考 [#351](https://github.com/zhayujie/chatgpt-on-wechat/issues/351) | >**2023.03.02:** 接入[ChatGPT API](https://platform.openai.com/docs/guides/chat) (gpt-3.5-turbo),默认使用该模型进行对话,需升级openai依赖 (`pip3 install --upgrade openai`)。网络问题参考 [#351](https://github.com/zhayujie/chatgpt-on-wechat/issues/351) | ||||
>**2023.02.20:** 增加 [python-wechaty](https://github.com/wechaty/python-wechaty) 作为可选渠道,使用Pad协议,但Token收费 (使用参考[#244](https://github.com/zhayujie/chatgpt-on-wechat/pull/244),contributed by [ZQ7](https://github.com/ZQ7)) | |||||
>**2023.02.09:** 扫码登录存在封号风险,请谨慎使用,参考[#58](https://github.com/AutumnWhj/ChatGPT-wechat-bot/issues/158) | >**2023.02.09:** 扫码登录存在封号风险,请谨慎使用,参考[#58](https://github.com/AutumnWhj/ChatGPT-wechat-bot/issues/158) | ||||
>**2023.02.05:** 在openai官方接口方案中 (GPT-3模型) 实现上下文对话 | >**2023.02.05:** 在openai官方接口方案中 (GPT-3模型) 实现上下文对话 | ||||
>**2022.12.19:** 引入 [itchat-uos](https://github.com/why2lyj/ItChat-UOS) 替换 itchat,解决由于不能登录网页微信而无法使用的问题,且解决Python3.9的兼容问题 | |||||
>**2022.12.18:** 支持根据描述生成图片并发送,openai版本需大于0.25.0 | >**2022.12.18:** 支持根据描述生成图片并发送,openai版本需大于0.25.0 | ||||
>**2022.12.17:** 原来的方案是从 [ChatGPT页面](https://chat.openai.com/chat) 获取session_token,使用 [revChatGPT](https://github.com/acheong08/ChatGPT) 直接访问web接口,但随着ChatGPT接入Cloudflare人机验证,这一方案难以在服务器顺利运行。 所以目前使用的方案是调用 OpenAI 官方提供的 [API](https://beta.openai.com/docs/api-reference/introduction),回复质量上基本接近于ChatGPT的内容,劣势是暂不支持有上下文记忆的对话,优势是稳定性和响应速度较好。 | >**2022.12.17:** 原来的方案是从 [ChatGPT页面](https://chat.openai.com/chat) 获取session_token,使用 [revChatGPT](https://github.com/acheong08/ChatGPT) 直接访问web接口,但随着ChatGPT接入Cloudflare人机验证,这一方案难以在服务器顺利运行。 所以目前使用的方案是调用 OpenAI 官方提供的 [API](https://beta.openai.com/docs/api-reference/introduction),回复质量上基本接近于ChatGPT的内容,劣势是暂不支持有上下文记忆的对话,优势是稳定性和响应速度较好。 | ||||
@@ -54,7 +52,7 @@ | |||||
前往 [OpenAI注册页面](https://beta.openai.com/signup) 创建账号,参考这篇 [教程](https://www.pythonthree.com/register-openai-chatgpt/) 可以通过虚拟手机号来接收验证码。创建完账号则前往 [API管理页面](https://beta.openai.com/account/api-keys) 创建一个 API Key 并保存下来,后面需要在项目中配置这个key。 | 前往 [OpenAI注册页面](https://beta.openai.com/signup) 创建账号,参考这篇 [教程](https://www.pythonthree.com/register-openai-chatgpt/) 可以通过虚拟手机号来接收验证码。创建完账号则前往 [API管理页面](https://beta.openai.com/account/api-keys) 创建一个 API Key 并保存下来,后面需要在项目中配置这个key。 | ||||
> 项目中使用的对话模型是 davinci,计费方式是约每 750 字 (包含请求和回复) 消耗 $0.02,图片生成是每张消耗 $0.016,账号创建有免费的 $18 额度,使用完可以更换邮箱重新注册。 | |||||
> 项目中使用的对话模型是 davinci,计费方式是约每 750 字 (包含请求和回复) 消耗 $0.02,图片生成是每张消耗 $0.016,账号创建有免费的 $18 额度 (更新3.25: 最新注册的已经无免费额度了),使用完可以更换邮箱重新注册。 | |||||
#### 1.1 ChapGPT service On Azure | #### 1.1 ChapGPT service On Azure | ||||
一种替换以上的方法是使用Azure推出的[ChatGPT service](https://azure.microsoft.com/en-in/products/cognitive-services/openai-service/)。它host在公有云Azure上,因此不需要VPN就可以直接访问。不过目前仍然处于preview阶段。新用户可以通过Try Azure for free来薅一段时间的羊毛 | 一种替换以上的方法是使用Azure推出的[ChatGPT service](https://azure.microsoft.com/en-in/products/cognitive-services/openai-service/)。它host在公有云Azure上,因此不需要VPN就可以直接访问。不过目前仍然处于preview阶段。新用户可以通过Try Azure for free来薅一段时间的羊毛 | ||||
@@ -105,6 +103,7 @@ pip3 install --upgrade openai | |||||
"single_chat_reply_prefix": "[bot] ", # 私聊时自动回复的前缀,用于区分真人 | "single_chat_reply_prefix": "[bot] ", # 私聊时自动回复的前缀,用于区分真人 | ||||
"group_chat_prefix": ["@bot"], # 群聊时包含该前缀则会触发机器人回复 | "group_chat_prefix": ["@bot"], # 群聊时包含该前缀则会触发机器人回复 | ||||
"group_name_white_list": ["ChatGPT测试群", "ChatGPT测试群2"], # 开启自动回复的群名称列表 | "group_name_white_list": ["ChatGPT测试群", "ChatGPT测试群2"], # 开启自动回复的群名称列表 | ||||
"group_chat_in_one_session": ["ChatGPT测试群"], # 支持会话上下文共享的群名称 | |||||
"image_create_prefix": ["画", "看", "找"], # 开启图片回复的前缀 | "image_create_prefix": ["画", "看", "找"], # 开启图片回复的前缀 | ||||
"conversation_max_tokens": 1000, # 支持上下文记忆的最多字符数 | "conversation_max_tokens": 1000, # 支持上下文记忆的最多字符数 | ||||
"speech_recognition": false, # 是否开启语音识别 | "speech_recognition": false, # 是否开启语音识别 | ||||
@@ -124,6 +123,7 @@ pip3 install --upgrade openai | |||||
+ 群组聊天中,群名称需配置在 `group_name_white_list ` 中才能开启群聊自动回复。如果想对所有群聊生效,可以直接填写 `"group_name_white_list": ["ALL_GROUP"]` | + 群组聊天中,群名称需配置在 `group_name_white_list ` 中才能开启群聊自动回复。如果想对所有群聊生效,可以直接填写 `"group_name_white_list": ["ALL_GROUP"]` | ||||
+ 默认只要被人 @ 就会触发机器人自动回复;另外群聊天中只要检测到以 "@bot" 开头的内容,同样会自动回复(方便自己触发),这对应配置项 `group_chat_prefix` | + 默认只要被人 @ 就会触发机器人自动回复;另外群聊天中只要检测到以 "@bot" 开头的内容,同样会自动回复(方便自己触发),这对应配置项 `group_chat_prefix` | ||||
+ 可选配置: `group_name_keyword_white_list`配置项支持模糊匹配群名称,`group_chat_keyword`配置项则支持模糊匹配群消息内容,用法与上述两个配置项相同。(Contributed by [evolay](https://github.com/evolay)) | + 可选配置: `group_name_keyword_white_list`配置项支持模糊匹配群名称,`group_chat_keyword`配置项则支持模糊匹配群消息内容,用法与上述两个配置项相同。(Contributed by [evolay](https://github.com/evolay)) | ||||
+ `group_chat_in_one_session`:使群聊共享一个会话上下文,配置 `["ALL_GROUP"]` 则作用于所有群聊 | |||||
**3.语音识别** | **3.语音识别** | ||||
@@ -4,6 +4,7 @@ | |||||
wechat channel | wechat channel | ||||
""" | """ | ||||
import os | |||||
import itchat | import itchat | ||||
import json | import json | ||||
from itchat.content import * | from itchat.content import * | ||||
@@ -50,9 +51,20 @@ class WechatChannel(Channel): | |||||
pass | pass | ||||
def startup(self): | def startup(self): | ||||
# login by scan QRCode | |||||
itchat.auto_login(enableCmdQR=2, hotReload=conf().get('hot_reload', False)) | |||||
itchat.instance.receivingRetryCount = 600 # 修改断线超时时间 | |||||
# login by scan QRCode | |||||
hotReload = conf().get('hot_reload', False) | |||||
try: | |||||
itchat.auto_login(enableCmdQR=2, hotReload=hotReload) | |||||
except Exception as e: | |||||
if hotReload: | |||||
logger.error("Hot reload failed, try to login without hot reload") | |||||
itchat.logout() | |||||
os.remove("itchat.pkl") | |||||
itchat.auto_login(enableCmdQR=2, hotReload=hotReload) | |||||
else: | |||||
raise e | |||||
# start message listener | # start message listener | ||||
itchat.run() | itchat.run() | ||||
@@ -95,7 +107,7 @@ class WechatChannel(Channel): | |||||
return | return | ||||
if match_prefix: | if match_prefix: | ||||
content = content.replace(match_prefix, '', 1).strip() | content = content.replace(match_prefix, '', 1).strip() | ||||
else: | |||||
elif match_prefix is None: | |||||
return | return | ||||
context = Context() | context = Context() | ||||
context.kwargs = {'isgroup': False, 'msg': msg, 'receiver': other_user_id, 'session_id': other_user_id} | context.kwargs = {'isgroup': False, 'msg': msg, 'receiver': other_user_id, 'session_id': other_user_id} | ||||
@@ -176,7 +188,7 @@ class WechatChannel(Channel): | |||||
image_storage.write(block) | image_storage.write(block) | ||||
image_storage.seek(0) | image_storage.seek(0) | ||||
itchat.send_image(image_storage, toUserName=receiver) | itchat.send_image(image_storage, toUserName=receiver) | ||||
logger.info('[WX] sendImage url=, receiver={}'.format(img_url,receiver)) | |||||
logger.info('[WX] sendImage url={}, receiver={}'.format(img_url,receiver)) | |||||
elif reply.type == ReplyType.IMAGE: # 从文件读取图片 | elif reply.type == ReplyType.IMAGE: # 从文件读取图片 | ||||
image_storage = reply.content | image_storage = reply.content | ||||
image_storage.seek(0) | image_storage.seek(0) | ||||
@@ -1,16 +1,17 @@ | |||||
{ | { | ||||
"open_ai_api_key": "YOUR API KEY", | |||||
"model": "gpt-3.5-turbo", | |||||
"proxy": "", | |||||
"single_chat_prefix": ["bot", "@bot"], | |||||
"single_chat_reply_prefix": "[bot] ", | |||||
"group_chat_prefix": ["@bot"], | |||||
"group_name_white_list": ["ChatGPT测试群", "ChatGPT测试群2"], | |||||
"image_create_prefix": ["画", "看", "找"], | |||||
"speech_recognition": false, | |||||
"voice_reply_voice": false, | |||||
"conversation_max_tokens": 1000, | |||||
"expires_in_seconds": 3600, | |||||
"character_desc": "你是ChatGPT, 一个由OpenAI训练的大型语言模型, 你旨在回答并解决人们的任何问题,并且可以使用多种语言与人交流。", | |||||
"use_azure_chatgpt": false | |||||
"open_ai_api_key": "YOUR API KEY", | |||||
"model": "gpt-3.5-turbo", | |||||
"proxy": "", | |||||
"use_azure_chatgpt": false, | |||||
"single_chat_prefix": ["bot", "@bot"], | |||||
"single_chat_reply_prefix": "[bot] ", | |||||
"group_chat_prefix": ["@bot"], | |||||
"group_name_white_list": ["ChatGPT测试群", "ChatGPT测试群2"], | |||||
"group_chat_in_one_session": ["ChatGPT测试群"], | |||||
"image_create_prefix": ["画", "看", "找"], | |||||
"speech_recognition": false, | |||||
"voice_reply_voice": false, | |||||
"conversation_max_tokens": 1000, | |||||
"expires_in_seconds": 3600, | |||||
"character_desc": "你是ChatGPT, 一个由OpenAI训练的大型语言模型, 你旨在回答并解决人们的任何问题,并且可以使用多种语言与人交流。" | |||||
} | } |
@@ -1,19 +1,38 @@ | |||||
# 插件说明 | |||||
本项目主体是调用ChatGPT接口的Wechat自动回复机器人。之前未插件化的代码耦合程度高,很难定制一些个性化功能(如流量控制、接入本地的NovelAI画图平台等),多个功能的优先级顺序也难以调度。 | |||||
**插件化**: 在保证主体功能是ChatGPT的前提下,推荐将主体功能外的功能分离成不同的插件。有个性化需求的用户仅需按照插件提供的接口编写插件,无需了解程序主体的代码结构,同时也方便代码的测试和调试。(插件调用目前仅支持 itchat) | |||||
## 插件化初衷 | |||||
## 插件触发时机 | |||||
之前未插件化的代码耦合程度高,如果要定制一些个性化功能(如流量控制、接入`NovelAI`画图平台等),需要了解代码主体,避免影响到其他的功能。在实现多个功能后,不但无法调整功能的优先级顺序,功能的配置项也会变得非常混乱。 | |||||
此时插件化应声而出。 | |||||
**插件化**: 在保证主体功能是ChatGPT的前提下,我们推荐将主体功能外的功能利用插件的方式实现。 | |||||
- [x] 可根据功能需要,下载不同插件。 | |||||
- [x] 插件开发成本低,仅需了解插件触发事件,并按照插件定义接口编写插件。 | |||||
- [x] 插件化能够自由开关和调整优先级。 | |||||
- [x] 每个插件可在插件文件夹内维护独立的配置文件,方便代码的测试和调试,可以在独立的仓库开发插件。 | |||||
PS: 插件目前仅支持`itchat` | |||||
## 插件化实现 | |||||
插件化实现是在收到消息到发送回复的各个步骤之间插入触发事件实现的。 | |||||
### 消息处理过程 | ### 消息处理过程 | ||||
了解插件触发时机前,首先需要了解程序收到消息后的执行过程。插件化版本的消息处理过程如下: | |||||
在了解插件触发事件前,首先需要了解程序收到消息到发送回复的整个过程。 | |||||
插件化版本中,消息处理过程可以分为4个步骤: | |||||
``` | ``` | ||||
1.收到消息 ---> 2.产生回复 ---> 3.包装回复 ---> 4.发送回复 | 1.收到消息 ---> 2.产生回复 ---> 3.包装回复 ---> 4.发送回复 | ||||
``` | ``` | ||||
以下是它们的默认处理逻辑(太长不看,可跳过): | 以下是它们的默认处理逻辑(太长不看,可跳过): | ||||
- 1. 收到消息 | |||||
本过程接收到用户消息,根据用户设置,判断本条消息是否触发。若触发,则判断该消息的命令类型,如声音、聊天、画图等。之后,将消息包装成如下的 Context 交付给下一个步骤。 | |||||
```python | |||||
#### 1. 收到消息 | |||||
负责接收用户消息,根据用户的配置,判断本条消息是否触发机器人。如果触发,则会判断该消息的类型(声音、文本、画图命令等),将消息包装成如下的`Context`交付给下一个步骤。 | |||||
```python | |||||
class ContextType (Enum): | class ContextType (Enum): | ||||
TEXT = 1 # 文本消息 | TEXT = 1 # 文本消息 | ||||
VOICE = 2 # 音频消息 | VOICE = 2 # 音频消息 | ||||
@@ -25,14 +44,26 @@ | |||||
self.kwargs = kwargs | self.kwargs = kwargs | ||||
def __getitem__(self, key): | def __getitem__(self, key): | ||||
return self.kwargs[key] | return self.kwargs[key] | ||||
``` | |||||
`Context`中除了存放消息类型和内容外,还存放了与会话相关的参数。一个例子是,当收到用户私聊消息时,还会存放以下的会话参数,`isgroup`标识`Context`是否时群聊消息,`msg`是`itchat`中原始的消息对象,`receiver`是应回复消息的对象ID,`session_id`是会话ID(一般是触发bot的消息发送方,群聊中如果`conf`里设置了`group_chat_in_one_session`,那么此处便是群聊的ID) | |||||
``` | |||||
context.kwargs = {'isgroup': False, 'msg': msg, 'receiver': other_user_id, 'session_id': other_user_id} | |||||
``` | |||||
2. 产生回复 | |||||
本过程用于处理消息。目前默认处理逻辑如下,它根据`Context`的类型交付给对应的bot。如果本过程未产生任何回复,则会跳过之后的处理阶段。 | |||||
```python | |||||
``` | |||||
`Context`中除了存放消息类型和内容外,还存放了一些与会话相关的参数。 | |||||
例如,当收到用户私聊消息时,会存放以下的会话参数。 | |||||
```python | |||||
context.kwargs = {'isgroup': False, 'msg': msg, 'receiver': other_user_id, 'session_id': other_user_id} | |||||
``` | |||||
- `isgroup`: `Context`是否是群聊消息。 | |||||
- `msg`: `itchat`中原始的消息对象。 | |||||
- `receiver`: 需要回复消息的对象ID。 | |||||
- `session_id`: 会话ID(一般是发送触发bot消息的用户ID,如果在群聊中并且`conf`里设置了`group_chat_in_one_session`,那么此处便是群聊ID) | |||||
#### 2. 产生回复 | |||||
处理消息并产生回复。目前默认处理逻辑是根据`Context`的类型交付给对应的bot,并产生回复`Reply`。 如果本步骤没有产生任何回复,那么会跳过之后的所有步骤。 | |||||
```python | |||||
if context.type == ContextType.TEXT or context.type == ContextType.IMAGE_CREATE: | if context.type == ContextType.TEXT or context.type == ContextType.IMAGE_CREATE: | ||||
reply = super().build_reply_content(context.content, context) #文字跟画图交付给chatgpt | reply = super().build_reply_content(context.content, context) #文字跟画图交付给chatgpt | ||||
elif context.type == ContextType.VOICE: # 声音先进行语音转文字后,修改Context类型为文字后,再交付给chatgpt | elif context.type == ContextType.VOICE: # 声音先进行语音转文字后,修改Context类型为文字后,再交付给chatgpt | ||||
@@ -47,9 +78,11 @@ | |||||
if reply.type == ReplyType.TEXT: | if reply.type == ReplyType.TEXT: | ||||
if conf().get('voice_reply_voice'): | if conf().get('voice_reply_voice'): | ||||
reply = super().build_text_to_voice(reply.content) | reply = super().build_text_to_voice(reply.content) | ||||
``` | |||||
Bot可产生的回复如下所示,它允许Bot可以回复多类不同的消息,未来可能不止能返回文字,而是能根据文字回复音频/图片,这时候便能派上用场。同时也加入了`INFO`和`ERROR`消息类型区分系统提示和系统错误。 | |||||
```python | |||||
``` | |||||
回复`Reply`的定义如下所示,它允许Bot可以回复多类不同的消息。同时也加入了`INFO`和`ERROR`消息类型区分系统提示和系统错误。 | |||||
```python | |||||
class ReplyType(Enum): | class ReplyType(Enum): | ||||
TEXT = 1 # 文本 | TEXT = 1 # 文本 | ||||
VOICE = 2 # 音频文件 | VOICE = 2 # 音频文件 | ||||
@@ -62,11 +95,19 @@ | |||||
def __init__(self, type : ReplyType = None , content = None): | def __init__(self, type : ReplyType = None , content = None): | ||||
self.type = type | self.type = type | ||||
self.content = content | self.content = content | ||||
``` | |||||
3. 装饰回复 | |||||
本过程根据`Context`和回复的类型,对回复的内容进行装饰。目前的装饰有以下两种,如果是文本回复,会根据是否在群聊中来决定是否艾特收方或添加回复前缀。 | |||||
如果是`INFO`或`ERROR`类型,会在消息前添加对应字样。 | |||||
```python | |||||
``` | |||||
#### 3. 装饰回复 | |||||
根据`Context`和回复`Reply`的类型,对回复的内容进行装饰。目前的装饰有以下两种: | |||||
- `TEXT`文本回复,根据是否在群聊中来决定是艾特接收方还是添加回复的前缀。 | |||||
- `INFO`或`ERROR`类型,会在消息前添加对应的系统提示字样。 | |||||
如下是默认逻辑的代码: | |||||
```python | |||||
if reply.type == ReplyType.TEXT: | if reply.type == ReplyType.TEXT: | ||||
reply_text = reply.content | reply_text = reply.content | ||||
if context['isgroup']: | if context['isgroup']: | ||||
@@ -77,36 +118,40 @@ | |||||
reply.content = reply_text | reply.content = reply_text | ||||
elif reply.type == ReplyType.ERROR or reply.type == ReplyType.INFO: | elif reply.type == ReplyType.ERROR or reply.type == ReplyType.INFO: | ||||
reply.content = str(reply.type)+":\n" + reply.content | reply.content = str(reply.type)+":\n" + reply.content | ||||
``` | |||||
4. 发送回复 | |||||
本过程根据回复的类型来发送回复给接收方`context["receiver"]`。 | |||||
``` | |||||
#### 4. 发送回复 | |||||
根据`Reply`的类型,默认逻辑调用不同的发送函数发送回复给接收方`context["receiver"]`。 | |||||
### 插件触发事件 | ### 插件触发事件 | ||||
主程序会在各消息处理过程之间触发插件事件,插件可以监听相应事件编写相应的处理逻辑。 | |||||
``` | |||||
1.收到消息 ---> 2.产生回复 ---> 3.包装回复 ---> 4.发送回复 | |||||
``` | |||||
目前加入了三类事件的触发: | |||||
主程序目前会在各个消息步骤间触发事件,监听相应事件的插件会按照优先级,顺序调用事件处理函数。 | |||||
目前支持三类触发事件: | |||||
``` | ``` | ||||
1.收到消息 | 1.收到消息 | ||||
---> `ON_HANDLE_CONTEXT` | ---> `ON_HANDLE_CONTEXT` | ||||
2.产生回复 | 2.产生回复 | ||||
---> `ON_DECORATE_REPLY` | ---> `ON_DECORATE_REPLY` | ||||
3.包装回复 | |||||
3.装饰回复 | |||||
---> `ON_SEND_REPLY` | ---> `ON_SEND_REPLY` | ||||
4.发送回复 | 4.发送回复 | ||||
``` | ``` | ||||
触发事件会产生事件上下文`EventContext`,它包含了以下信息: | |||||
`EventContext(Event事件类型, {'channel' : 消息channel, 'context': context, 'reply': reply})` | |||||
插件的处理函数可以修改`Context`和`Reply`的内容来定制化处理逻辑。 | |||||
触发事件会产生事件的上下文`EventContext`,它包含了以下信息: | |||||
`EventContext(Event事件类型, {'channel' : 消息channel, 'context': Context, 'reply': Reply})` | |||||
插件处理函数可通过修改`EventContext`中的`context`和`reply`来实现功能。 | |||||
## 插件编写 | |||||
以`plugins/hello`为例,它编写了一个简单`Hello`插件。 | |||||
## 插件编写示例 | |||||
1. 创建插件 | |||||
在`plugins`目录下创建一个插件文件夹,例如`hello`。然后,在该文件夹中创建一个与文件夹同名的`.py`文件,例如`hello.py`。 | |||||
以`plugins/hello`为例,其中编写了一个简单的`Hello`插件。 | |||||
### 1. 创建插件 | |||||
在`plugins`目录下创建一个插件文件夹`hello`。然后,在该文件夹中创建一个与文件夹同名的`.py`文件`hello.py`。 | |||||
``` | ``` | ||||
plugins/ | plugins/ | ||||
└── hello | └── hello | ||||
@@ -114,8 +159,18 @@ plugins/ | |||||
└── hello.py | └── hello.py | ||||
``` | ``` | ||||
2. 编写插件类 | |||||
在`hello.py`文件中,创建插件类,它继承自Plugin类。在类定义之前使用`@plugins.register`装饰器注册插件,并填写插件的相关信息,其中`desire_priority`表示插件默认的优先级,越大优先级越高,扫描到插件后可在`plugins/plugins.json`中修改插件优先级。并在`__init__`中绑定你编写的事件处理函数: | |||||
### 2. 编写插件类 | |||||
在`hello.py`文件中,创建插件类,它继承自`Plugin`。 | |||||
在类定义之前需要使用`@plugins.register`装饰器注册插件,并填写插件的相关信息,其中`desire_priority`表示插件默认的优先级,越大优先级越高。初次加载插件后可在`plugins/plugins.json`中修改插件优先级。 | |||||
并在`__init__`中绑定你编写的事件处理函数。 | |||||
`Hello`插件为事件`ON_HANDLE_CONTEXT`绑定了一个处理函数`on_handle_context`,它表示之后每次生成回复前,都会由`on_handle_context`先处理。 | |||||
PS: `ON_HANDLE_CONTEXT`是最常用的事件,如果要根据不同的消息来生成回复,就用它。 | |||||
```python | ```python | ||||
@plugins.register(name="Hello", desc="A simple plugin that says hello", version="0.1", author="lanvent", desire_priority= -1) | @plugins.register(name="Hello", desc="A simple plugin that says hello", version="0.1", author="lanvent", desire_priority= -1) | ||||
class Hello(Plugin): | class Hello(Plugin): | ||||
@@ -125,17 +180,31 @@ class Hello(Plugin): | |||||
logger.info("[Hello] inited") | logger.info("[Hello] inited") | ||||
``` | ``` | ||||
3. 编写事件处理函数 | |||||
事件处理函数接收一个`EventContext`对象作为参数。`EventContext`对象包含了事件相关的信息,如消息内容和当前回复等。可以通过`e_context['key']`访问这些信息。 | |||||
### 3. 编写事件处理函数 | |||||
#### 修改事件上下文 | |||||
事件处理函数接收一个`EventContext`对象`e_context`作为参数。`e_context`包含了事件相关信息,利用`e_context['key']`来访问这些信息。 | |||||
`EventContext(Event事件类型, {'channel' : 消息channel, 'context': Context, 'reply': Reply})` | |||||
处理函数中通过修改`e_context`对象中的事件相关信息来实现所需功能,比如更改`e_context['reply']`中的内容可以修改回复。 | |||||
#### 决定是否交付给下个插件或默认逻辑 | |||||
在处理函数结束时,还需要设置`e_context`对象的`action`属性,它决定如何继续处理事件。目前有以下三种处理方式: | |||||
处理函数中,你可以修改`EventContext`对象的信息,比如更改回复内容。在处理函数结束时,需要设置`EventContext`对象的`action`属性,以决定如何继续处理事件。有以下三种处理方式: | |||||
- `EventAction.CONTINUE`: 事件未结束,继续交给下个插件处理,如果没有下个插件,则交付给默认的事件处理逻辑。 | - `EventAction.CONTINUE`: 事件未结束,继续交给下个插件处理,如果没有下个插件,则交付给默认的事件处理逻辑。 | ||||
- `EventAction.BREAK`: 事件结束,不再给下个插件处理,交付给默认的处理逻辑。 | - `EventAction.BREAK`: 事件结束,不再给下个插件处理,交付给默认的处理逻辑。 | ||||
- `EventAction.BREAK_PASS`: 事件结束,不再给下个插件处理,跳过默认的处理逻辑。 | - `EventAction.BREAK_PASS`: 事件结束,不再给下个插件处理,跳过默认的处理逻辑。 | ||||
以`Hello`插件为例,它处理`Context`类型为`TEXT`的消息: | |||||
- 如果内容是`Hello`,直接将回复设置为`Hello+用户昵称`,并跳过之后的插件和默认逻辑。 | |||||
- 如果内容是`End`,它会将`Context`的类型更改为`IMAGE_CREATE`,并让事件继续,如果最终交付到默认逻辑,会调用默认的画图Bot来画画。 | |||||
#### 示例处理函数 | |||||
`Hello`插件处理`Context`类型为`TEXT`的消息: | |||||
- 如果内容是`Hello`,就将回复设置为`Hello+用户昵称`,并跳过之后的插件和默认逻辑。 | |||||
- 如果内容是`End`,就将`Context`的类型更改为`IMAGE_CREATE`,并让事件继续,如果最终交付到默认逻辑,会调用默认的画图Bot来画画。 | |||||
```python | ```python | ||||
def on_handle_context(self, e_context: EventContext): | def on_handle_context(self, e_context: EventContext): | ||||
if e_context['context'].type != ContextType.TEXT: | if e_context['context'].type != ContextType.TEXT: | ||||
@@ -158,8 +227,9 @@ class Hello(Plugin): | |||||
e_context.action = EventAction.CONTINUE # 事件继续,交付给下个插件或默认逻辑 | e_context.action = EventAction.CONTINUE # 事件继续,交付给下个插件或默认逻辑 | ||||
``` | ``` | ||||
## 插件设计规范 | |||||
- 个性化功能推荐设计为插件。 | |||||
## 插件设计建议 | |||||
- 尽情将你想要的个性化功能设计为插件。 | |||||
- 一个插件目录建议只注册一个插件类。建议使用单独的仓库维护插件,便于更新。 | - 一个插件目录建议只注册一个插件类。建议使用单独的仓库维护插件,便于更新。 | ||||
- 插件的config文件、使用说明`README.md`、`requirement.txt`放置在插件目录中。 | |||||
- 默认优先级不要超过管理员插件`Godcmd`的优先级(999),`Godcmd`插件提供了配置管理、插件管理等功能。 | |||||
- 插件的config文件、使用说明`README.md`、`requirement.txt`等放置在插件目录中。 | |||||
- 默认优先级不要超过管理员插件`Godcmd`的优先级(999),`Godcmd`插件提供了配置管理、插件管理等功能。 |
@@ -1,9 +1,9 @@ | |||||
### 说明 | |||||
## 插件描述 | |||||
简易的敏感词插件,暂不支持分词,请自行导入词库到插件文件夹中的`banwords.txt`,每行一个词,一个参考词库是[1](https://github.com/cjh0613/tencent-sensitive-words/blob/main/sensitive_words_lines.txt)。 | 简易的敏感词插件,暂不支持分词,请自行导入词库到插件文件夹中的`banwords.txt`,每行一个词,一个参考词库是[1](https://github.com/cjh0613/tencent-sensitive-words/blob/main/sensitive_words_lines.txt)。 | ||||
`config.json`中能够填写默认的处理行为,目前行为有: | `config.json`中能够填写默认的处理行为,目前行为有: | ||||
- `ignore` : 无视这条消息。 | - `ignore` : 无视这条消息。 | ||||
- `replace` : 将消息中的敏感词替换成"*",并回复违规。 | - `replace` : 将消息中的敏感词替换成"*",并回复违规。 | ||||
### 致谢 | |||||
## 致谢 | |||||
搜索功能实现来自https://github.com/toolgood/ToolGood.Words | 搜索功能实现来自https://github.com/toolgood/ToolGood.Words |
@@ -1,3 +1,4 @@ | |||||
玩地牢游戏的聊天插件,触发方法如下: | 玩地牢游戏的聊天插件,触发方法如下: | ||||
- `$开始冒险 <背景故事>` - 以<背景故事>开始一个地牢游戏,不填写会使用默认背景故事。之后聊天中你的所有消息会帮助ai完善这个故事。 | - `$开始冒险 <背景故事>` - 以<背景故事>开始一个地牢游戏,不填写会使用默认背景故事。之后聊天中你的所有消息会帮助ai完善这个故事。 | ||||
- `$停止冒险` - 停止一个地牢游戏,回归正常的ai。 | |||||
- `$停止冒险` - 停止一个地牢游戏,回归正常的ai。 |
@@ -1,2 +1,12 @@ | |||||
管理员插件 | |||||
`#help` - 输出帮助文档。 | |||||
## 插件说明 | |||||
指令插件 | |||||
## 插件使用 | |||||
将`config.json.template`复制为`config.json`,并修改其中`password`的值为口令。 | |||||
在私聊中可使用`#auth`指令,输入口令进行管理员认证,详细指令请输入`#help`查看帮助文档: | |||||
`#auth <口令>` - 管理员认证。 | |||||
`#help` - 输出帮助文档,是否是管理员和是否是在群聊中会影响帮助文档的输出内容。 |
@@ -1,18 +1,14 @@ | |||||
用于让Bot扮演指定角色的聊天插件,触发方法如下: | 用于让Bot扮演指定角色的聊天插件,触发方法如下: | ||||
- `$角色/$role help/帮助` - 打印目前支持的角色列表。 | - `$角色/$role help/帮助` - 打印目前支持的角色列表。 | ||||
- `$角色/$role <角色名>` - 让AI扮演该角色,角色名支持模糊匹配。 | - `$角色/$role <角色名>` - 让AI扮演该角色,角色名支持模糊匹配。 | ||||
- `$停止扮演` - 停止角色扮演。 | - `$停止扮演` - 停止角色扮演。 | ||||
添加自定义角色请在`roles/roles.json`中添加。 | 添加自定义角色请在`roles/roles.json`中添加。 | ||||
(大部分prompt来自https://github.com/rockbenben/ChatGPT-Shortcut/blob/main/src/data/users.tsx) | |||||
以下为例子, | |||||
- `title`是角色名。 | |||||
- `description`是使用`$role`触发的英语prompt。 | |||||
- `descn`是使用`$角色`触发的中文prompt。 | |||||
- `wrapper`用于包装你的消息,可以起到强调的作用。 | |||||
- `remark`简短的描述该角色,在打印帮助时显示。 | |||||
(大部分prompt来自https://github.com/rockbenben/ChatGPT-Shortcut/blob/main/src/data/users.tsx) | |||||
以下为例子: | |||||
```json | ```json | ||||
{ | { | ||||
"title": "写作助理", | "title": "写作助理", | ||||
@@ -20,5 +16,11 @@ | |||||
"descn": "作为一名中文写作改进助理,你的任务是改进所提供文本的拼写、语法、清晰、简洁和整体可读性,同时分解长句,减少重复,并提供改进建议。请只提供文本的更正版本,避免包括解释。请把我之后的每一条消息都当作文本内容。", | "descn": "作为一名中文写作改进助理,你的任务是改进所提供文本的拼写、语法、清晰、简洁和整体可读性,同时分解长句,减少重复,并提供改进建议。请只提供文本的更正版本,避免包括解释。请把我之后的每一条消息都当作文本内容。", | ||||
"wrapper": "内容是:\n\"%s\"", | "wrapper": "内容是:\n\"%s\"", | ||||
"remark": "最常使用的角色,用于优化文本的语法、清晰度和简洁度,提高可读性。" | "remark": "最常使用的角色,用于优化文本的语法、清晰度和简洁度,提高可读性。" | ||||
}, | |||||
} | |||||
``` | ``` | ||||
- `title`: 角色名。 | |||||
- `description`: 使用`$role`触发时,使用英语prompt。 | |||||
- `descn`: 使用`$角色`触发时,使用中文prompt。 | |||||
- `wrapper`: 用于包装用户消息,可起到强调作用,避免回复离题。 | |||||
- `remark`: 简短描述该角色,在打印帮助文档时显示。 |
@@ -1,31 +1,43 @@ | |||||
### 插件描述 | |||||
## 插件描述 | |||||
本插件用于将画图请求转发给stable diffusion webui。 | 本插件用于将画图请求转发给stable diffusion webui。 | ||||
### 环境要求 | |||||
## 环境要求 | |||||
使用前先安装stable diffusion webui,并在它的启动参数中添加 "--api"。 | 使用前先安装stable diffusion webui,并在它的启动参数中添加 "--api"。 | ||||
具体信息,请参考[文章](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/API)。 | 具体信息,请参考[文章](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/API)。 | ||||
请**安装**本插件的依赖包```webuiapi``` | 请**安装**本插件的依赖包```webuiapi``` | ||||
``` | ``` | ||||
```pip install webuiapi``` | |||||
pip install webuiapi | |||||
``` | ``` | ||||
### 使用说明 | |||||
## 使用说明 | |||||
请将`config.json.template`复制为`config.json`,并修改其中的参数和规则。 | 请将`config.json.template`复制为`config.json`,并修改其中的参数和规则。 | ||||
#### 画图请求格式 | |||||
### 画图请求格式 | |||||
用户的画图请求格式为: | 用户的画图请求格式为: | ||||
``` | ``` | ||||
<画图触发词><关键词1> <关键词2> ... <关键词n>:<prompt> | <画图触发词><关键词1> <关键词2> ... <关键词n>:<prompt> | ||||
``` | ``` | ||||
- 本插件会对画图触发词后的关键词进行逐个匹配,如果触发了规则中的关键词,则会在画图请求中重载对应的参数。 | - 本插件会对画图触发词后的关键词进行逐个匹配,如果触发了规则中的关键词,则会在画图请求中重载对应的参数。 | ||||
- 规则的匹配顺序参考`config.json`中的顺序,每个关键词最多被匹配到1次,如果多个关键词触发了重复的参数,重复参数以最后一个关键词为准: | |||||
- 规则的匹配顺序参考`config.json`中的顺序,每个关键词最多被匹配到1次,如果多个关键词触发了重复的参数,重复参数以最后一个关键词为准。 | |||||
- 关键词中包含`help`或`帮助`,会打印出帮助文档。 | - 关键词中包含`help`或`帮助`,会打印出帮助文档。 | ||||
第一个"**:**"号之后的内容会作为附加的**prompt**,接在最终的prompt后 | 第一个"**:**"号之后的内容会作为附加的**prompt**,接在最终的prompt后 | ||||
例如: 画横版 高清 二次元:cat | 例如: 画横版 高清 二次元:cat | ||||
会触发三个关键词 "横版", "高清", "二次元",prompt为"cat" | 会触发三个关键词 "横版", "高清", "二次元",prompt为"cat" | ||||
若默认参数是: | 若默认参数是: | ||||
``` | |||||
```json | |||||
"width": 512, | "width": 512, | ||||
"height": 512, | "height": 512, | ||||
"enable_hr": false, | "enable_hr": false, | ||||
@@ -35,25 +47,30 @@ | |||||
``` | ``` | ||||
"横版"触发的规则参数为: | "横版"触发的规则参数为: | ||||
``` | |||||
```json | |||||
"width": 640, | "width": 640, | ||||
"height": 384, | "height": 384, | ||||
``` | ``` | ||||
"高清"触发的规则参数为: | "高清"触发的规则参数为: | ||||
``` | |||||
```json | |||||
"enable_hr": true, | "enable_hr": true, | ||||
"hr_scale": 1.6, | "hr_scale": 1.6, | ||||
``` | ``` | ||||
"二次元"触发的规则参数为: | "二次元"触发的规则参数为: | ||||
``` | |||||
```json | |||||
"negative_prompt": "(low quality, worst quality:1.4),(bad_prompt:0.8), (monochrome:1.1), (greyscale)", | "negative_prompt": "(low quality, worst quality:1.4),(bad_prompt:0.8), (monochrome:1.1), (greyscale)", | ||||
"steps": 20, | "steps": 20, | ||||
"prompt": "masterpiece, best quality", | "prompt": "masterpiece, best quality", | ||||
"sd_model_checkpoint": "meinamix_meinaV8" | "sd_model_checkpoint": "meinamix_meinaV8" | ||||
``` | ``` | ||||
最后将第一个":"后的内容cat连接在prompt后,得到最终参数为: | |||||
``` | |||||
以上这些规则的参数会和默认参数合并。第一个":"后的内容cat会连接在prompt后。 | |||||
得到最终参数为: | |||||
```json | |||||
"width": 640, | "width": 640, | ||||
"height": 384, | "height": 384, | ||||
"enable_hr": true, | "enable_hr": true, | ||||
@@ -64,6 +81,8 @@ | |||||
"sd_model_checkpoint": "meinamix_meinaV8" | "sd_model_checkpoint": "meinamix_meinaV8" | ||||
``` | ``` | ||||
PS: 参数分为两部分: | |||||
- 一部分是params,为画画的参数;参数名**必须**与webuiapi包中[txt2img api](https://github.com/mix1009/sdwebuiapi/blob/fb2054e149c0a4e25125c0cd7e7dca06bda839d4/webuiapi/webuiapi.py#L163)的参数名一致 | |||||
- 另一部分是options,指sdwebui的设置,使用的模型和vae需要写在里面。它和http://127.0.0.1:7860/sdapi/v1/options所返回的键一致。 | |||||
PS: 实际参数分为两部分: | |||||
- 一部分是`params`,为画画的参数;参数名**必须**与webuiapi包中[txt2img api](https://github.com/mix1009/sdwebuiapi/blob/fb2054e149c0a4e25125c0cd7e7dca06bda839d4/webuiapi/webuiapi.py#L163)的参数名一致 | |||||
- 另一部分是`options`,指sdwebui的设置,使用的模型和vae需写在里面。它和(http://127.0.0.1:7860/sdapi/v1/options)所返回的键一致。 |