Compare commits

...

70 Commits

Author SHA1 Message Date
Soulter a78ebf2fd7 feat: plugin dev mode 2023-05-14 18:20:28 +08:00
Soulter bd11541678 perf: 优化插件更新缓存策略 2023-05-14 18:16:12 +08:00
Soulter 0d99aa81e6 Merge branch 'master' of https://github.com/Soulter/QQChannelChatGPT 2023-05-14 17:40:16 +08:00
Soulter f104d40d0a perf: 优化插件加载规则、更新插件接口规范
fix: 修复发言频率限制报错的问题
2023-05-14 17:40:13 +08:00
Soulter 0d69f8ab8a Update README.md 2023-05-13 17:03:32 +08:00
Soulter 66d1fc08b6 perf: 去除不必要的import 2023-05-13 14:25:16 +08:00
Soulter e32fc27728 perf: 优化插件的删除逻辑 2023-05-13 14:23:05 +08:00
Soulter eec890cd02 perf: 优化插件指令的卸载插件逻辑 2023-05-13 14:20:38 +08:00
Soulter d30881e59b perf: 补充插件指令的帮助信息 2023-05-13 14:10:00 +08:00
Soulter 9afaf83368 perf: 优化插件指令的身份组鉴定 2023-05-13 14:07:40 +08:00
Soulter 33f9a9cfa0 feat: 支持显示插件列表和插件帮助 2023-05-13 14:04:15 +08:00
Soulter bf72d5fa27 fix: 修复有依赖的插件拉取问题 2023-05-13 13:55:22 +08:00
Soulter 567c29bcd6 perf: 清除多余log 2023-05-13 13:45:04 +08:00
Soulter dcdfe453fb perf: 优化插件类鉴定规则 2023-05-13 13:44:14 +08:00
Soulter 0d23c0900b perf: 优化插件卸载逻辑 2023-05-13 13:28:18 +08:00
Soulter 86eda7bdf8 perf: 优化插件缓存逻辑 2023-05-13 13:25:56 +08:00
Soulter 1e46525b0f perf: 优化pip更新逻辑 2023-05-13 13:02:24 +08:00
Soulter 8d41efea4d Update README.md 2023-05-13 11:43:03 +08:00
Soulter f15d0eb0eb Update README.md 2023-05-13 11:10:56 +08:00
Soulter 1795362bcd perf: 优化插件调用逻辑 2023-05-13 11:03:16 +08:00
Soulter 2bf9c82617 perf: 更好的插件处理逻辑和更开放的插件功能 2023-05-13 10:54:57 +08:00
Soulter 33793a2053 chore: 更新版本号 2023-05-12 09:21:57 +08:00
Soulter 656fe14af4 perf: 优化身份组鉴定 2023-05-12 09:15:32 +08:00
Soulter 46197d49a4 perf: 调换语言模型启动顺序 2023-05-12 09:08:06 +08:00
Soulter 843ab56f50 perf: 优化身份组 2023-05-12 09:02:29 +08:00
Soulter 6b4b52f3c5 perf: 完善身份组功能 2023-05-11 22:56:38 +08:00
Soulter 392e5cd592 chore: 添加默认插件 2023-05-11 22:43:26 +08:00
Soulter d273019830 chore: 删除一些没必要的文件 2023-05-11 22:39:55 +08:00
Soulter fd59ec4b6c fix: 修复插件指令clone插件异常的问题 2023-05-11 22:12:39 +08:00
Soulter bf33ccafca fix: 修复插件指令创建文件夹出错的问题 2023-05-11 22:06:38 +08:00
Soulter 425936872d fix: 修复插件指令结果显示异常的问题 2023-05-11 22:01:57 +08:00
Soulter 6627b2e1e5 fix: 修复插件指令报错的问题 2023-05-11 22:00:18 +08:00
Soulter 323c2cecf8 feat: 新增插件指令 2023-05-11 21:52:44 +08:00
Soulter 5b1dd3dce9 feat: 插件支持 2023-05-11 21:35:25 +08:00
Soulter 54af770dfb fix: 修复keyword指令的一些问题 2023-05-08 20:30:36 +08:00
Soulter 30a48fea6e feat: QQ群的免@唤醒支持多个前缀(nick指令) #92 2023-05-08 20:17:51 +08:00
Soulter cfd5fb1452 perf: keyword指令支持删除关键词 2023-05-08 19:43:26 +08:00
Soulter a78984376f perf: 优化与go-cqhttp的通信 2023-04-26 14:52:09 +08:00
Soulter 9887cae43c fix: replit web fix 2023-04-25 20:57:22 +08:00
Soulter e63fe60f8d Merge branch 'master' of https://github.com/Soulter/QQChannelChatGPT 2023-04-25 20:48:08 +08:00
Soulter b0ac2d676c feat: Replit平台支持 2023-04-25 20:48:04 +08:00
Soulter 5ef515165c Merge pull request #94 from RockChinQ/patch-1
chore: 更换使用nakuru-project-idk
2023-04-25 19:43:02 +08:00
Rock Chin e21d43f920 chore: 更换使用nakuru-project-idk 2023-04-25 12:46:41 +08:00
Soulter 3a80ffad88 perf: 优化控制台信息显示 2023-04-25 10:42:03 +08:00
Soulter 47506d60cd perf: 优化pip检测 2023-04-25 10:29:16 +08:00
Soulter b999b712b7 perf: 优化逆向库的错误管理 2023-04-25 10:21:12 +08:00
Soulter 6860ba3f05 Merge branch 'master' of https://github.com/Soulter/QQChannelChatGPT 2023-04-25 09:38:21 +08:00
Soulter 02594867c0 fix: 修复QQ平台昵称后的消息前导空格问题 2023-04-25 09:38:17 +08:00
Soulter 250435f3e7 Update requirements.txt 2023-04-25 09:29:35 +08:00
Soulter 3c593fb6f7 Update README.md 2023-04-24 19:34:11 +08:00
Soulter 807cad5c48 fix: 删除启动时对qq频道appid不应该的检查 2023-04-24 08:00:45 +00:00
Soulter e92ecdd3f8 Update README.md 2023-04-23 17:16:31 +08:00
Soulter 1c91079d8f Merge branch 'master' of https://github.com/Soulter/QQChannelChatGPT 2023-04-23 09:05:32 +00:00
Soulter 376b2fef40 fix: 修复启动前检查依赖的问题 2023-04-23 09:05:30 +00:00
Soulter 300f3b6df8 Update README.md 2023-04-23 16:52:07 +08:00
Soulter 6e6f6d5cd4 Update README.md 2023-04-23 16:51:46 +08:00
Soulter 077e54d0f1 Merge branch 'master' of https://github.com/Soulter/QQChannelChatGPT 2023-04-23 08:35:02 +00:00
Soulter 18ffaa2b91 perf: 优化各种报错管理;
feat: 启动时检查依赖库
2023-04-23 08:31:22 +00:00
Soulter a6555681a0 Update requirements.txt 2023-04-23 15:31:33 +08:00
Soulter 43ac0ef87c fix: remove judge_res 2023-04-22 08:09:22 +00:00
Soulter 754842be7c Update README.md 2023-04-22 14:35:22 +08:00
Soulter 5b3ee2dbe8 fix: 修复回复内容屏蔽词无效的问题 2023-04-22 06:07:33 +00:00
Soulter ca5a1ddc0b perf: 优化bing模型达到单次会话上限后自动重置 2023-04-22 11:27:43 +08:00
Soulter c9821132ad fix: QQ频道停止信息来源显示 2023-04-21 11:11:16 +08:00
Soulter 0641dca2a6 fix: 修复qqAt的一些问题 2023-04-21 01:04:57 +08:00
Soulter fd983b9f5d fix: 修复了一些问题 2023-04-21 01:01:54 +08:00
Soulter 7e1e51c450 feat: QQ支持at发送方和画画指令支持 2023-04-21 01:00:31 +08:00
Soulter d912b990e4 fix: 修复画画指令失效的问题 2023-04-21 00:45:59 +08:00
Soulter 8224aa87a5 fix: 修复bing模型不想继续会话自动重置的一些问题 2023-04-20 09:07:55 +08:00
Soulter 4cb5abc7b6 fix: 修复bing会话超时过期的问题 2023-04-20 08:59:58 +08:00
17 changed files with 699 additions and 236 deletions
+43 -11
View File
@@ -1,33 +1,40 @@
<div align="center">
# QQChannelChatGPT
在QQ和QQ频道上使用ChatGPT、NewBing等语言模型,稳定,一次部署,同时使用。
<img src="https://socialify.git.ci/Soulter/QQChannelChatGPT/image?description=1&forks=1&issues=1&language=1&name=1&owner=1&pattern=Circuit%20Board&stargazers=1&theme=Light" alt="QQChannelChatGPT" width="600" height="300" />
教程:https://soulter.top/posts/qpdg.html
[![Language](https://img.shields.io/badge/language-python-green.svg?style=plastic)](https://www.python.org/)
[![License](https://img.shields.io/badge/license-AGPL3-orange.svg?style=plastic)](https://github.com/Soulter/QQChannelChatGPT/blob/master/LICENSE)
![Python](https://img.shields.io/badge/python-3.9+-blue)
_✨在QQ和QQ频道上使用ChatGPT、NewBing等语言模型,稳定,一次部署,同时使用✨_
欢迎体验😊(频道名: GPT机器人 | 频道号: x42d56aki2) | QQ群号:322154837):
_✨教程:https://github.com/Soulter/QQChannelChatGPT/wiki ✨_
_✨插件开发教程:https://github.com/Soulter/QQChannelChatGPT/wiki/%E5%9B%9B%E3%80%81%E5%BC%80%E5%8F%91%E6%8F%92%E4%BB%B6 ✨_
<img src="https://user-images.githubusercontent.com/37870767/230417115-9dd3c9d5-6b6b-4928-8fe3-82f559208aab.JPG" width="300"></img>
_✨欢迎体验😊(频道名: GPT机器人 | 频道号: x42d56aki2) | QQ群号:322154837)✨_
<!-- <img src="https://user-images.githubusercontent.com/37870767/230417115-9dd3c9d5-6b6b-4928-8fe3-82f559208aab.JPG" width="300"></img> -->
</div>
## ⭐功能:
通知:部署好后,如果使用的是bing或者逆向ChatGPT库,需要使用切换模型指令`/bing`或者'/revgpt'
近期新功能:
- 支持插件!https://github.com/Soulter/QQChannelChatGPT/wiki/%E5%9B%9B%E3%80%81%E5%BC%80%E5%8F%91%E6%8F%92%E4%BB%B6
- 支持一键切换语言模型(使用/bing /revgpt /gpt分别可以切换newbing、逆向ChatGPT、官方ChatGPT模型)
- 热更新
- 接入QQ,支持在QQ上和QQ频道上同时聊天!https://github.com/Soulter/QQChannelChatGPT/issues/82
- 更强大的Windows启动器,环境配置自动搞定。链接:https://github.com/Soulter/QQChatGPTLauncher/releases/latest
支持的AI语言模型(请在`configs/config.yaml`下配置):
- 逆向ChatGPT库
- 官方ChatGPT AI
- 文心一言(即将支持,链接https://github.com/Soulter/ERNIEBot 欢迎Star
- 文心一言(即将支持)
- NewBing
- Bard (即将支持)
部署QQ频道机器人教程链接:https://soulter.top/posts/qpdg.html
部署此项目的教程链接:https://github.com/Soulter/QQChannelChatGPT/wiki
### 基本功能
<details>
@@ -86,6 +93,18 @@
</details>
> 关于token:token就相当于是AI中的单词数(但是不等于单词数),`text-davinci-003`模型中最大可以支持`4097`个token。在发送信息时,这个机器人会将用户的历史聊天记录打包发送给ChatGPT,因此,`token`也会相应的累加,为了保证聊天的上下文的逻辑性,就有了缓存token。
### 🛠️ 插件支持
本项目支持接入插件。
插件开发教程:https://github.com/Soulter/QQChannelChatGPT/wiki/%E5%9B%9B%E3%80%81%E5%BC%80%E5%8F%91%E6%8F%92%E4%BB%B6
部分好用的插件:
`HuggingChat`: https://github.com/Soulter/HuggingChatForQQBot
### 指令功能
#### OpenAI官方API
@@ -129,17 +148,30 @@ pip install -r requirements.txt
```
> ⚠Python版本应>=3.9
### 配置
**详细部署教程链接**https://soulter.top/posts/qpdg.html
**详细部署教程链接**https://github.com/Soulter/QQChannelChatGPT/wiki
### 启动
- 启动main.py
## 感谢
本项目使用了一下项目:
[ChatGPT by acheong08](https://github.com/acheong08/ChatGPT)
[EdgeGPT by acheong08](https://github.com/acheong08/EdgeGPT)
[go-cqhttp by Mrs4s](https://github.com/Mrs4s/go-cqhttp)
[nakuru-project by Lxns-Network](https://github.com/Lxns-Network/nakuru-project)
## ⚙配置文件说明:
```yaml
# 如果你不知道怎么部署,请查看https://soulter.top/posts/qpdg.html
# 如果你不知道怎么部署,请查看https://github.com/Soulter/QQChannelChatGPT/wiki
# 不一定需要key了,如果你没有key但有openAI账号或者必应账号,可以考虑使用下面的逆向库
+5
View File
@@ -0,0 +1,5 @@
# helloworld
QQChannelChatGPT项目的测试插件
A test plugin for QQChannelChatGPT plugin feature
+65
View File
@@ -0,0 +1,65 @@
from nakuru.entities.components import *
from nakuru import (
GroupMessage,
FriendMessage
)
from botpy.message import Message, DirectMessage
class HelloWorldPlugin:
"""
初始化函数, 可以选择直接pass
"""
def __init__(self) -> None:
print("这是HelloWorld测试插件, 发送 helloworld 即可触发此插件。")
"""
入口函数,机器人会调用此函数。
参数规范: message: 消息文本; role: 身份; platform: 消息平台; message_obj: 消息对象
参数详情: role为admin或者member; platform为qqchan或者gocq; message_obj为nakuru的GroupMessage对象或者FriendMessage对象或者频道的Message, DirectMessage对象。
返回规范: bool: 是否hit到此插件(所有的消息均会调用每一个载入的插件, 如果没有hit到, 则应返回False)
Tuple: None或者长度为3的元组。当没有hit到时, 返回None. hit到时, 第1个参数为指令是否调用成功, 第2个参数为返回的消息文本或者gocq的消息链列表, 第3个参数为指令名称
例子:做一个名为"yuanshen"的插件;当接收到消息为“原神 可莉”, 如果不想要处理此消息,则返回False, None;如果想要处理,但是执行失败了,返回True, tuple([False, "请求失败啦~", "yuanshen"])
;执行成功了,返回True, tuple([True, "结果文本", "yuanshen"])
"""
def run(self, message: str, role: str, platform: str, message_obj):
if platform == "gocq":
"""
QQ平台指令处理逻辑
"""
img_url = "https://gchat.qpic.cn/gchatpic_new/905617992/720871955-2246763964-C6EE1A52CC668EC982453065C4FA8747/0?term=2&amp;is_origin=0"
if message == "helloworld":
return True, tuple([True, [Plain("Hello World!!"), Image.fromURL(url=img_url)], "helloworld"])
else:
return False, None
elif platform == "qqchan":
"""
频道处理逻辑(频道暂时只支持回复字符串类型的信息,返回的信息都会被转成字符串,如果不想处理某一个平台的信息,直接返回False, None就行)
"""
if message == "helloworld":
return True, tuple([True, "Hello World!!", "helloworld"])
else:
return False, None
"""
帮助函数,当用户输入 plugin v 插件名称 时,会调用此函数,返回帮助信息
返回参数要求(必填)dict{
"name": str, # 插件名称
"desc": str, # 插件简短描述
"help": str, # 插件帮助信息
"version": str, # 插件版本
"author": str, # 插件作者
}
"""
def info(self):
return {
"name": "helloworld",
"desc": "测试插件",
"help": "测试插件, 回复helloworld即可触发",
"version": "v1.0.1 beta",
"author": "Soulter"
}
# 热知识:检测消息开头指令,使用以下方法
# if message.startswith("原神"):
# pass
+140 -64
View File
@@ -1,8 +1,7 @@
import botpy
from botpy.message import Message
from botpy.message import Message, DirectMessage
from botpy.types.message import Reference
import re
from botpy.message import DirectMessage
import json
import threading
import asyncio
@@ -22,6 +21,7 @@ from nakuru import (
FriendMessage
)
from nakuru.entities.components import Plain,At
from model.command.command import Command
# QQBotClient实例
client = ''
@@ -62,7 +62,7 @@ direct_message_mode = True
abs_path = os.path.dirname(os.path.realpath(sys.argv[0])) + '/'
# 版本
version = '3.0'
version = '3.0.2'
# 语言模型
REV_CHATGPT = 'rev_chatgpt'
@@ -91,21 +91,40 @@ qqchan_loop = None
# QQ机器人
gocq_bot = None
PLATFORM_GOCQ = 'gocq'
gocq_app = CQHTTP(
host="127.0.0.1",
port=6700,
http_port=5700,
)
gocq_app = None
admin_qq = "123456"
gocq_loop = None
nick_qq = "ai "
nick_qq = "ai"
bing_cache_loop = None
# 插件
cached_plugins = {}
def gocq_runner():
global gocq_app
ok = False
while not ok:
try:
gocq_app = CQHTTP(
host="127.0.0.1",
port=6700,
http_port=5700,
)
ok = True
except BaseException as e:
print("[System-err] 连接到go-cqhttp异常, 5秒后重试。"+str(e))
threading.Thread(target=gocq_runner, daemon=True).start()
def new_sub_thread(func, args=()):
thread = threading.Thread(target=func, args=args, daemon=True)
thread.start()
# 写入统计信息
def toggle_count(at: bool, message):
global stat_file
@@ -168,14 +187,16 @@ def upload():
def initBot(cfg, prov):
global chatgpt, provider, rev_chatgpt, baidu_judge, rev_edgegpt, chosen_provider
global reply_prefix, gpt_config, config, uniqueSession, frequency_count, frequency_time,announcement, direct_message_mode, version
global command_openai_official, command_rev_chatgpt, command_rev_edgegpt,reply_prefix, keywords
global command_openai_official, command_rev_chatgpt, command_rev_edgegpt,reply_prefix, keywords, cached_plugins
provider = prov
config = cfg
if 'reply_prefix' in cfg:
reply_prefix = cfg['reply_prefix']
# 语言模型提供商
print("--------------------加载语言模型--------------------")
if REV_CHATGPT in prov:
print("- 逆向ChatGPT库 -")
if cfg['rev_ChatGPT']['enable']:
if 'account' in cfg['rev_ChatGPT']:
from model.provider.provider_rev_chatgpt import ProviderRevChatGPT
@@ -187,13 +208,18 @@ def initBot(cfg, prov):
input("[System-err] 请退出本程序, 然后在配置文件中填写rev_ChatGPT相关配置")
if REV_EDGEGPT in prov:
if cfg['rev_edgegpt']['enable']:
from model.provider.provider_rev_edgegpt import ProviderRevEdgeGPT
from model.command.command_rev_edgegpt import CommandRevEdgeGPT
rev_edgegpt = ProviderRevEdgeGPT()
command_rev_edgegpt = CommandRevEdgeGPT(rev_edgegpt)
chosen_provider = REV_EDGEGPT
print("- New Bing -")
if not os.path.exists('./cookies.json'):
input("[System-err] 导入Bing模型时发生错误, 没有找到cookies文件或者cookies文件放置位置错误。windows启动器启动的用户请把cookies.json文件放到和启动器相同的目录下。\n如何获取请看https://github.com/Soulter/QQChannelChatGPT仓库介绍。")
else:
if cfg['rev_edgegpt']['enable']:
from model.provider.provider_rev_edgegpt import ProviderRevEdgeGPT
from model.command.command_rev_edgegpt import CommandRevEdgeGPT
rev_edgegpt = ProviderRevEdgeGPT()
command_rev_edgegpt = CommandRevEdgeGPT(rev_edgegpt)
chosen_provider = REV_EDGEGPT
if OPENAI_OFFICIAL in prov:
print("- OpenAI ChatGPT官方API -")
if cfg['openai']['key'] is not None:
from model.provider.provider_openai_official import ProviderOpenAIOfficial
from model.command.command_openai_official import CommandOpenAIOfficial
@@ -201,6 +227,7 @@ def initBot(cfg, prov):
command_openai_official = CommandOpenAIOfficial(chatgpt)
chosen_provider = OPENAI_OFFICIAL
print("--------------------加载个性化配置--------------------")
# 得到关键词
if os.path.exists("keyword.json"):
with open("keyword.json", 'r', encoding='utf-8') as f:
@@ -261,7 +288,7 @@ def initBot(cfg, prov):
if 'dump_history_interval' in cfg:
print("[System] 历史记录转储时间周期: " + cfg['dump_history_interval'] + "分钟")
except BaseException:
print("[System-Error] 读取uniqueSessionMode/version/dump_history_interval配置文件失败, 使用默认值。")
pass
print(f"[System] QQ开放平台AppID: {cfg['qqbot']['appid']} 令牌: {cfg['qqbot']['token']}")
@@ -283,9 +310,42 @@ def initBot(cfg, prov):
thread_inst = None
print("--------------------加载插件--------------------")
# 加载插件
_command = Command(None)
ok, err = _command.plugin_reload(cached_plugins)
if ok:
print("加载插件完成")
else:
print(err)
print("--------------------加载平台--------------------")
# GOCQ
if 'gocqbot' in cfg and cfg['gocqbot']['enable']:
print("- 启用QQ机器人 -")
if os.path.exists("cmd_config.json"):
with open("cmd_config.json", 'r', encoding='utf-8') as f:
cmd_config = json.load(f)
global admin_qq
if "admin_qq" in cmd_config:
admin_qq = cmd_config['admin_qq']
print("[System] 管理者QQ号: " + admin_qq)
else:
admin_qq = input("[System] 请输入管理者QQ号(管理者QQ号才能使用update/plugin等指令): ")
print("[System] 管理者QQ号设置为: " + admin_qq)
cmd_config['admin_qq'] = admin_qq
with open("cmd_config.json", 'w', encoding='utf-8') as f:
json.dump(cmd_config, f, indent=4)
f.flush()
global gocq_app, gocq_bot, gocq_loop
gocq_bot = QQ()
gocq_loop = asyncio.new_event_loop()
thread_inst = threading.Thread(target=run_gocq_bot, args=(gocq_loop, gocq_bot, gocq_app), daemon=False)
thread_inst.start()
# QQ频道
if 'qqbot' in cfg and cfg['qqbot']['enable']:
print("[System] 启用QQ频道机器人")
print("- 启用QQ频道机器人 -")
global qqchannel_bot, qqchan_loop
qqchannel_bot = QQChan()
qqchan_loop = asyncio.new_event_loop()
@@ -293,15 +353,6 @@ def initBot(cfg, prov):
thread_inst.start()
# thread.join()
# GOCQ
if 'gocqbot' in cfg and cfg['gocqbot']['enable']:
print("[System] 启用QQ机器人")
global gocq_app, gocq_bot, gocq_loop
gocq_bot = QQ()
gocq_loop = asyncio.new_event_loop()
thread_inst = threading.Thread(target=run_gocq_bot, args=(gocq_loop, gocq_bot, gocq_app), daemon=False)
thread_inst.start()
if thread_inst == None:
input("[System-Error] 没有启用任何机器人,程序退出")
exit()
@@ -359,16 +410,25 @@ def save_provider_preference(chosen_provider):
def send_message(platform, message, res, msg_ref = None, image = None, gocq_loop = None, qqchannel_bot = None, gocq_bot = None):
if platform == PLATFORM_QQCHAN:
if image != None:
qqchannel_bot.send_qq_msg(message, res, image_mode=True, msg_ref=msg_ref)
qqchannel_bot.send_qq_msg(message, str(res), image_mode=True, msg_ref=msg_ref)
else:
qqchannel_bot.send_qq_msg(message, res, msg_ref=msg_ref)
if platform == PLATFORM_GOCQ: asyncio.run_coroutine_threadsafe(gocq_bot.send_qq_msg(message, res), gocq_loop).result()
qqchannel_bot.send_qq_msg(message, str(res), msg_ref=msg_ref)
if platform == PLATFORM_GOCQ:
if image != None:
asyncio.run_coroutine_threadsafe(gocq_bot.send_qq_msg(message, image, image_mode=True), gocq_loop).result()
else:
asyncio.run_coroutine_threadsafe(gocq_bot.send_qq_msg(message, res, False, ), gocq_loop).result()
'''
处理消息
group: 群聊模式
'''
def oper_msg(message, group=False, msg_ref = None, platform = None):
def oper_msg(message,
group: bool=False,
msg_ref: Reference = None,
platform: str = None):
"""
处理消息。
group: 群聊模式,
message: 频道是频道的消息对象, QQ是nakuru-gocq的消息对象
"""
global session_dict, provider
qq_msg = ''
session_id = ''
@@ -378,6 +438,7 @@ def oper_msg(message, group=False, msg_ref = None, platform = None):
role = "member" # 角色
hit = False # 是否命中指令
command_result = () # 调用指令返回的结果
global admin_qq, cached_plugins
if platform == PLATFORM_QQCHAN:
print("[QQCHAN-BOT] 接收到消息:"+ str(message.content))
@@ -399,7 +460,7 @@ def oper_msg(message, group=False, msg_ref = None, platform = None):
# 检查发言频率
if not check_frequency(user_id):
qqchannel_bot.send_qq_msg(message, f'{user_name}的发言超过频率限制(╯▔皿▔)╯。\n{frequency_time}秒内只能提问{frequency_count}次。')
send_message(platform, message, f'的发言超过频率限制(╯▔皿▔)╯。\n管理员设置{frequency_time}秒内只能提问{frequency_count}次。', msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
return
if platform == PLATFORM_QQCHAN:
@@ -429,7 +490,7 @@ def oper_msg(message, group=False, msg_ref = None, platform = None):
if platform == PLATFORM_GOCQ:
if group:
if isinstance(message.message[0], Plain):
qq_msg = str(message.message[0].text)
qq_msg = str(message.message[0].text).strip()
elif isinstance(message.message[0], At):
qq_msg = str(message.message[1].text).strip()
else:
@@ -438,9 +499,15 @@ def oper_msg(message, group=False, msg_ref = None, platform = None):
else:
qq_msg = message.message[0].text
session_id = message.user_id
# todo: 暂时将所有人设为管理员
role = "admin"
role = "member"
if str(message.sender.user_id) == admin_qq:
print("[GOCQ-BOT] 检测到管理员身份")
role = "admin"
if qq_msg == "":
send_message(platform, message, f"Hi~", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
return
logf.write("[QQBOT] "+ qq_msg+'\n')
logf.flush()
@@ -490,8 +557,8 @@ def oper_msg(message, group=False, msg_ref = None, platform = None):
chatgpt_res = ""
if chosen_provider == OPENAI_OFFICIAL:
hit, command_result = command_openai_official.check_command(qq_msg, session_id, user_name, role, platform=platform)
# hit: 是否触发了指令.
hit, command_result = command_openai_official.check_command(qq_msg, session_id, user_name, role, platform=platform, message_obj=message, cached_plugins=cached_plugins)
# hit: 是否触发了指令
if not hit:
# 请求ChatGPT获得结果
try:
@@ -499,19 +566,19 @@ def oper_msg(message, group=False, msg_ref = None, platform = None):
if OPENAI_OFFICIAL in reply_prefix:
chatgpt_res = reply_prefix[OPENAI_OFFICIAL] + chatgpt_res
except (BaseException) as e:
print("[System-Err] OpenAI API错误。原因如下:\n"+str(e))
send_message(platform, message, f"OpenAI API错误。原因如下:\n{str(e)} \n前往官方频道反馈~", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
print("[System-Err] OpenAI API请求错误, 原因: "+str(e))
send_message(platform, message, f"OpenAI API错误, 原因: {str(e)}", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
elif chosen_provider == REV_CHATGPT:
hit, command_result = command_rev_chatgpt.check_command(qq_msg, role, platform=platform)
hit, command_result = command_rev_chatgpt.check_command(qq_msg, role, platform=platform, message=message, cached_plugins=cached_plugins)
if not hit:
try:
chatgpt_res = str(rev_chatgpt.text_chat(qq_msg))
if REV_CHATGPT in reply_prefix:
chatgpt_res = reply_prefix[REV_CHATGPT] + chatgpt_res
except BaseException as e:
print("[System-Err] Rev ChatGPT API错误。原因如下:\n"+str(e))
send_message(platform, message, f"Rev ChatGPT API错误。原因如下:\n{str(e)} \n前往官方频道反馈~", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
print("[System-Err] RevChatGPT请求错误, 原因: "+str(e))
send_message(platform, message, f"RevChatGPT错误, 原因: \n{str(e)}", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
elif chosen_provider == REV_EDGEGPT:
if bing_cache_loop == None:
@@ -519,17 +586,17 @@ def oper_msg(message, group=False, msg_ref = None, platform = None):
bing_cache_loop = gocq_loop
elif platform == PLATFORM_QQCHAN:
bing_cache_loop = qqchan_loop
hit, command_result = command_rev_edgegpt.check_command(qq_msg, bing_cache_loop, role, platform=platform)
hit, command_result = command_rev_edgegpt.check_command(qq_msg, bing_cache_loop, role, platform=platform, message_obj=message, cached_plugins=cached_plugins)
if not hit:
try:
while rev_edgegpt.is_busy():
time.sleep(1)
res, res_code = asyncio.run_coroutine_threadsafe(rev_edgegpt.text_chat(qq_msg), bing_cache_loop).result()
res, res_code = asyncio.run_coroutine_threadsafe(rev_edgegpt.text_chat(qq_msg, platform), bing_cache_loop).result()
if res_code == 0: # bing不想继续话题,重置会话后重试。
send_message(platform, message, "Bing不想继续话题了, 正在自动重置会话并重试。", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
asyncio.run_coroutine_threadsafe(rev_edgegpt.forget(), bing_cache_loop).result()
res, res_code = asyncio.run_coroutine_threadsafe(rev_edgegpt.text_chat(qq_msg), bing_cache_loop).result()
res, res_code = asyncio.run_coroutine_threadsafe(rev_edgegpt.text_chat(qq_msg, platform), bing_cache_loop).result()
if res_code == 0: # bing还是不想继续话题,大概率说明提问有问题。
asyncio.run_coroutine_threadsafe(rev_edgegpt.forget(), bing_cache_loop).result()
send_message(platform, message, "Bing仍然不想继续话题, 会话已重置, 请检查您的提问后重试。", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
@@ -551,8 +618,9 @@ def oper_msg(message, group=False, msg_ref = None, platform = None):
if command_result != None:
command = command_result[2]
if command == "keyword":
with open("keyword.json", "r", encoding="utf-8") as f:
keywords = json.load(f)
if os.path.exists("keyword.json"):
with open("keyword.json", "r", encoding="utf-8") as f:
keywords = json.load(f)
# QQ昵称
if command == "nick":
@@ -562,18 +630,17 @@ def oper_msg(message, group=False, msg_ref = None, platform = None):
if command_result[0]:
# 是否是画图指令
if len(command_result) == 3 and command_result[2] == 'draw':
if isinstance(command_result, list) and len(command_result) == 3 and command_result[2] == 'draw':
for i in command_result[1]:
send_message(platform, message, i, msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
else:
send_message(platform, message, i, msg_ref=msg_ref, image=i, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
else:
try:
send_message(platform, message, command_result[1], msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
except BaseException as e:
t = command_result[1].replace(".", " . ")
send_message(platform, message, t, msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
send_message(platform, message, f"回复消息出错: {str(e)}", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
else:
send_message(platform, message, f"指令调用错误: \n{command_result[1]}", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
send_message(platform, message, f"指令调用错误: \n{str(command_result[1])}", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
return
@@ -586,12 +653,11 @@ def oper_msg(message, group=False, msg_ref = None, platform = None):
# 敏感过滤
# 过滤不合适的词
judged_res = chatgpt_res
for i in uw.unfit_words:
judged_res = re.sub(i, "***", judged_res)
chatgpt_res = re.sub(i, "***", chatgpt_res)
# 百度内容审核服务二次审核
if baidu_judge != None:
check, msg = baidu_judge.judge(judged_res)
check, msg = baidu_judge.judge(chatgpt_res)
if not check:
send_message(platform, message, f"你的提问得到的回复【百度内容审核】未通过,不予回复。\n\n{msg}", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
return
@@ -651,14 +717,24 @@ class gocqClient():
@gocq_app.receiver("GroupMessage")
async def _(app: CQHTTP, source: GroupMessage):
global nick_qq
# 将nick_qq转换为元组
if nick_qq == None:
nick_qq = ("ai",)
if isinstance(nick_qq, str):
nick_qq = (nick_qq,)
if isinstance(nick_qq, list):
nick_qq = tuple(nick_qq)
if isinstance(source.message[0], Plain):
if source.message[0].text.startswith(nick_qq):
source.message[0].text = source.message[0].text[len(nick_qq):]
_len = 0
for i in nick_qq:
if source.message[0].text.startswith(i):
_len = len(i)
source.message[0].text = source.message[0].text[_len:].strip()
new_sub_thread(oper_msg, (source, True, None, PLATFORM_GOCQ))
if isinstance(source.message[0], At):
if source.message[0].qq == source.self_id:
if source.message[1].text.startswith(nick_qq):
source.message[1].text = source.message[0].text[len(nick_qq):]
new_sub_thread(oper_msg, (source, True, None, PLATFORM_GOCQ))
else:
return
-56
View File
@@ -1,56 +0,0 @@
# -*- coding: utf-8 -*-
from git.repo import Repo
import git
import os
# import zipfile
if __name__ == "__main__":
try:
# 检测文件夹
if not os.path.exists('QQChannelChatGPT'):
os.mkdir('QQChannelChatGPT')
project_path = os.path.join('QQChannelChatGPT')
try:
repo = Repo(project_path)
# 检查当前commit的hash值
commit_hash = repo.head.object.hexsha
print("当前commit的hash值为: " + commit_hash)
# 得到远程仓库的origin的commit的列表
origin = repo.remotes.origin
try:
origin.fetch()
except:
pass
# 得到远程仓库的commit的hash值
remote_commit_hash = origin.refs.master.commit.hexsha
print("https://github.com/Soulter/QQChannelChatGPT的commit的hash值为: " + remote_commit_hash)
# 比较两个commit的hash值
if commit_hash != remote_commit_hash:
res = input("检测到项目有更新, 是否更新? (y/n): ")
if res == 'y':
repo.remotes.origin.pull()
print("项目更新完毕")
if res == 'n':
print("已取消更新")
except:
print("正在从https://github.com/Soulter/QQChannelChatGPT.git拉取项目...")
Repo.clone_from('https://github.com/Soulter/QQChannelChatGPT.git',to_path=project_path,branch='master')
print("项目拉取完毕")
print("【重要提醒】如果你没有Python(版本>=3.8)或者Git环境, 请先安装, 否则接下来的操作会造成闪退。")
print("【重要提醒】Python下载地址: https://npm.taobao.org/mirrors/python/3.9.7/python-3.9.7-amd64.exe ")
print("【重要提醒】Git下载地址: https://registry.npmmirror.com/-/binary/git-for-windows/v2.39.2.windows.1/Git-2.39.2-64-bit.exe")
print("【重要提醒】安装时, 请务必勾选“Add Python to PATH”选项。")
input("已确保安装了Python3.9+的版本,按下回车继续...")
print("正在安装依赖库")
os.system('python -m pip install -r QQChannelChatGPT\\requirements.txt')
print("依赖库安装完毕")
input("初次启动, 请先在QQChannelChatGPT/configs/config.yaml填写相关配置! 按任意键继续...")
finally:
print("正在启动项目...")
os.system('python QQChannelChatGPT\main.py')
except BaseException as e:
print(e)
input("程序出错。可以截图发给QQ:905617992.按下回车键退出...")
+66 -25
View File
@@ -1,14 +1,20 @@
import threading
import asyncio
import os, sys
from pip._internal import main as pipmain
abs_path = os.path.dirname(os.path.realpath(sys.argv[0])) + '/'
def main(loop, event):
import cores.qqbot.core as qqBot
import yaml
ymlfile = open(abs_path+"configs/config.yaml", 'r', encoding='utf-8')
cfg = yaml.safe_load(ymlfile)
try:
import cores.qqbot.core as qqBot
import yaml
ymlfile = open(abs_path+"configs/config.yaml", 'r', encoding='utf-8')
cfg = yaml.safe_load(ymlfile)
except BaseException as e:
print(e)
input("yaml库未导入或者配置文件格式错误,请退出程序重试。")
exit()
if 'http_proxy' in cfg:
os.environ['HTTP_PROXY'] = cfg['http_proxy']
@@ -39,28 +45,50 @@ def check_env():
print("请使用Python3.8运行本项目")
input("按任意键退出...")
exit()
# try:
# print("检查依赖库中...")
# if os.path.exists('requirements.txt'):
# os.system("pip3 install -r requirements.txt")
# elif os.path.exists('QQChannelChatGPT'+ os.sep +'requirements.txt'):
# os.system('pip3 install -r QQChannelChatGPT'+ os.sep +'requirements.txt')
# os.system("clear")
# print("安装依赖库完毕...")
# except BaseException as e:
# print("安装依赖库失败,请手动安装依赖库。")
# print(e)
# input("按任意键退出...")
# exit()
# 检查pip
# pip_tag = "pip"
# mm = os.system("pip -V")
# if mm != 0:
# mm1 = os.system("pip3 -V")
# if mm1 != 0:
# print("未检测到pip, 请安装Python(版本应>=3.9)")
# input("按任意键退出...")
# exit()
# else:
# pip_tag = "pip3"
# 检查key
with open(abs_path+"configs/config.yaml", 'r', encoding='utf-8') as ymlfile:
import yaml
cfg = yaml.safe_load(ymlfile)
if cfg['openai']['key'] == '' or cfg['openai']['key'] == None:
print("请先在configs/config.yaml下添加一个可用的OpenAI Key。详情请前往https://beta.openai.com/account/api-keys")
if cfg['qqbot']['appid'] == '' or cfg['qqbot']['token'] == '' or cfg['qqbot']['appid'] == None or cfg['qqbot']['token'] == None:
print("请先在configs/config.yaml下完善appid和token令牌(在https://q.qq.com/上注册一个QQ机器人即可获得)")
if os.path.exists('requirements.txt'):
pth = 'requirements.txt'
else:
pth = 'QQChannelChatGPT'+ os.sep +'requirements.txt'
print("正在更新三方依赖库...")
try:
pipmain(['install', '-r', pth])
print("依赖库安装完毕。")
except BaseException as e:
print(e)
while True:
res = input("依赖库可能安装失败了。\n如果是报错ValueError: check_hostname requires server_hostname,请尝试先关闭代理后重试。\n输入y回车重试\n输入c回车使用国内镜像源下载\n输入其他按键回车继续往下执行。")
if res == "y":
try:
pipmain(['install', '-r', pth])
print("依赖库安装完毕。")
break
except BaseException as e:
print(e)
continue
elif res == "c":
try:
pipmain(['install', '-r', pth, '-i', 'https://mirrors.aliyun.com/pypi/simple/'])
print("依赖库安装完毕。")
break
except BaseException as e:
print(e)
continue
else:
break
def get_platform():
import platform
@@ -76,6 +104,19 @@ def get_platform():
if __name__ == "__main__":
check_env()
# 获取参数
args = sys.argv
if len(args) > 1:
if args[1] == '-replit':
print("[System] 启动Replit Web保活服务...")
try:
from webapp_replit import keep_alive
keep_alive()
except BaseException as e:
print(e)
print(f"[System-err] Replit Web保活服务启动失败:{str(e)}")
bot_event = threading.Event()
loop = asyncio.get_event_loop()
main(loop, bot_event)
+206 -23
View File
@@ -1,4 +1,3 @@
import abc
import json
import git.exc
from git.repo import Repo
@@ -7,28 +6,204 @@ import sys
import requests
from model.provider.provider import Provider
import json
import util.plugin_util as putil
import shutil
import importlib
PLATFORM_QQCHAN = 'qqchan'
PLATFORM_GOCQ = 'gocq'
# 指令功能的基类,通用的(不区分语言模型)的指令就在这实现
class Command:
def __init__(self, provider: Provider):
self.provider = Provider
@abc.abstractmethod
def check_command(self, message, role, platform):
def get_plugin_modules(self):
plugins = []
try:
if os.path.exists("addons/plugins"):
plugins = putil.get_modules("addons/plugins")
return plugins
elif os.path.exists("QQChannelChatGPT/addons/plugins"):
plugins = putil.get_modules("QQChannelChatGPT/addons/plugins")
return plugins
else:
return None
except BaseException as e:
raise e
def check_command(self, message, role, platform, message_obj, cached_plugins: dict):
# 插件
for k, v in cached_plugins.items():
try:
hit, res = v["clsobj"].run(message, role, platform, message_obj)
if hit:
return True, res
except BaseException as e:
print(f"[Debug] {k}插件加载出现问题,原因: {str(e)}\n已安装插件: {cached_plugins.keys}\n如果你没有相关装插件的想法, 请直接忽略此报错, 不影响其他功能的运行。")
if self.command_start_with(message, "nick"):
return True, self.set_nick(message, platform)
if self.command_start_with(message, "plugin"):
return True, self.plugin_oper(message, role, cached_plugins)
return False, None
def plugin_reload(self, cached_plugins: dict, target: str = None, all: bool = False):
plugins = self.get_plugin_modules()
fail_rec = ""
if plugins != None:
for p in plugins:
try:
if p not in cached_plugins or p == target or all:
module = __import__("addons.plugins." + p + "." + p, fromlist=[p])
if p in cached_plugins:
module = importlib.reload(module)
cls = putil.get_classes(p, module)
obj = getattr(module, cls[0])()
try:
info = obj.info()
if 'name' not in info or 'desc' not in info or 'version' not in info or 'author' not in info:
fail_rec += f"载入插件{p}失败,原因: 插件信息不完整\n"
continue
if isinstance(info, dict) == False:
fail_rec += f"载入插件{p}失败,原因: 插件信息格式不正确\n"
continue
except BaseException as e:
fail_rec += f"调用插件{p} info失败, 原因: {str(e)}\n"
continue
cached_plugins[p] = {
"module": module,
"clsobj": obj,
"info": info
}
except BaseException as e:
fail_rec += f"加载{p}插件出现问题,原因{str(e)}\n"
if fail_rec == "":
return True, None
else:
return False, fail_rec
else:
return False, "未找到任何插件模块"
'''
插件指令
'''
def plugin_oper(self, message: str, role: str, cached_plugins: dict):
l = message.split(" ")
if len(l) < 2:
return True, "\n=====插件指令面板=====\n安装插件: \nplugin i 插件Github地址\n卸载插件: \nplugin i 插件名 \n重载插件: \nplugin reload\n查看插件列表:\nplugin l\n更新插件: plugin u 插件名\n===============", "plugin"
else:
ppath = ""
if os.path.exists("addons/plugins"):
ppath = "addons/plugins"
elif os.path.exists("QQChannelChatGPT/addons/plugins"):
ppath = "QQChannelChatGPT/addons/plugins"
else:
return False, "未找到插件目录", "plugin"
if l[1] == "i":
if role != "admin":
return False, f"你的身份组{role}没有权限安装插件", "plugin"
try:
# 得到url的最后一段
d = l[2].split("/")[-1]
# 创建文件夹
plugin_path = os.path.join(ppath, d)
if os.path.exists(plugin_path):
shutil.rmtree(plugin_path)
os.mkdir(plugin_path)
Repo.clone_from(l[2],to_path=plugin_path,branch='master')
# 读取插件的requirements.txt
if os.path.exists(os.path.join(plugin_path, "requirements.txt")):
with open(os.path.join(plugin_path, "requirements.txt"), "r", encoding="utf-8") as f:
for line in f.readlines():
mm = os.system(f"pip3 install {line.strip()}")
if mm != 0:
return False, "插件依赖安装失败,需要您手动pip安装对应插件的依赖。", "plugin"
# 加载没缓存的插件
ok, err = self.plugin_reload(cached_plugins, target=d)
if ok:
return True, "插件拉取并载入成功~", "plugin"
else:
# if os.path.exists(plugin_path):
# shutil.rmtree(plugin_path)
return False, f"插件拉取载入失败。\n跟踪: \n{err}", "plugin"
except BaseException as e:
return False, f"拉取插件失败,原因: {str(e)}", "plugin"
elif l[1] == "d":
if role != "admin":
return False, f"你的身份组{role}没有权限删除插件", "plugin"
try:
# 删除文件夹
shutil.rmtree(os.path.join(ppath, l[2]))
if l[2] in cached_plugins:
del cached_plugins[l[2]]
return True, "插件卸载成功~", "plugin"
except BaseException as e:
return False, f"卸载插件失败,原因: {str(e)}", "plugin"
elif l[1] == "u":
plugin_path = os.path.join(ppath, l[2])
try:
repo = Repo(path = plugin_path)
repo.remotes.origin.pull()
ok, err = self.plugin_reload(cached_plugins, target=l[2])
if ok:
return True, "\n更新插件成功!!", "plugin"
else:
return False, "更新插件成功,但是重载插件失败。\n问题跟踪: \n"+err, "plugin"
except BaseException as e:
return False, "更新插件失败, 请使用plugin i指令覆盖安装", "plugin"
elif l[1] == "l":
try:
plugin_list_info = "\n".join([f"{k}: \n名称: {v['info']['name']}\n简介: {v['info']['desc']}\n版本: {v['info']['version']}\n作者: {v['info']['author']}\n" for k, v in cached_plugins.items()])
return True, "\n=====已激活插件列表=====\n" + plugin_list_info + "\n使用plugin v 插件名 查看插件帮助\n=================", "plugin"
except BaseException as e:
return False, f"获取插件列表失败,原因: {str(e)}", "plugin"
elif l[1] == "v":
try:
if l[2] in cached_plugins:
info = cached_plugins[l[2]]["info"]
res = f"\n=====插件信息=====\n名称: {info['name']}\n{info['desc']}\n版本: {info['version']}作者: {info['author']}\n\n帮助:\n{info['help']}"
return True, res, "plugin"
else:
return False, "未找到该插件", "plugin"
except BaseException as e:
return False, f"获取插件信息失败,原因: {str(e)}", "plugin"
elif l[1] == "reload":
if role != "admin":
return False, f"你的身份组{role}没有权限重载插件", "plugin"
try:
ok, err = self.plugin_reload(cached_plugins, all = True)
if ok:
return True, "\n重载插件成功~", "plugin"
else:
# if os.path.exists(plugin_path):
# shutil.rmtree(plugin_path)
return False, f"插件重载失败。\n跟踪: \n{err}", "plugin"
except BaseException as e:
return False, f"插件重载失败,原因: {str(e)}", "plugin"
elif l[1] == "dev":
if role != "admin":
return False, f"你的身份组{role}没有权限开发者模式", "plugin"
return True, "cached_plugins: \n" + str(cached_plugins), "plugin"
'''
存储机器人的昵称
nick: 存储机器人的昵称
'''
def set_nick(self, message: str, platform: str):
if platform == PLATFORM_GOCQ:
nick = message.split(" ")[1]
l = message.split(" ")
if len(l) == 1:
return True, "【设置机器人昵称】示例:\n支持多昵称\nnick 昵称1 昵称2 昵称3", "nick"
nick = l[1:]
self.general_command_storer("nick_qq", nick)
return True, f"设置成功!现在你可以叫我{nick}来提问我啦~", "nick"
return True, f"设置成功!现在你可以叫我这些昵称来提问我啦~", "nick"
elif platform == PLATFORM_QQCHAN:
nick = message.split(" ")[2]
return False, "QQ频道平台不支持为机器人设置昵称。", "nick"
@@ -54,7 +229,7 @@ class Command:
"keyword": "设置关键词/关键指令回复",
"update": "更新面板",
"update latest": "更新到最新版本",
"update r": "重启程序",
"update r": "重启机器人",
"reset": "重置会话",
"nick": "设置机器人昵称",
"/bing": "切换到bing模型",
@@ -63,6 +238,7 @@ class Command:
"/bing 问题": "临时使用一次bing模型进行会话",
"/gpt 问题": "临时使用一次OpenAI ChatGPT API进行会话",
"/revgpt 问题": "临时使用一次网页版ChatGPT进行会话",
"plugin": "插件安装、卸载和重载"
}
def help_messager(self, commands: dict):
@@ -84,23 +260,42 @@ class Command:
return True
return False
# keyword: 关键字
def keyword(self, message: str, role: str):
if role != "admin":
return True, "你没有权限使用该指令", "keyword"
if len(message.split(" ")) != 3:
return True, "【设置关键词/关键指令回复】示例:\nkeyword hi 你好\n当发送hi的时候会回复你好\nkeyword /hi 你好\n当发送/hi时会回复你好", "keyword"
l = message.split(" ")
if len(l) < 3:
return True, "【设置关键词回复】示例:\nkeyword hi 你好\n当发送hi的时候会回复你好\nkeyword /hi 你好\n当发送/hi时会回复你好\n删除关键词: keyword d hi\n删除hi关键词的回复", "keyword"
del_mode = False
if l[1] == "d":
print("删除关键词: "+l[2])
del_mode = True
try:
if os.path.exists("keyword.json"):
with open("keyword.json", "r", encoding="utf-8") as f:
keyword = json.load(f)
keyword[l[1]] = l[2]
if del_mode:
# 删除关键词
if l[2] not in keyword:
return False, "该关键词不存在", "keyword"
else: del keyword[l[2]]
else:
keyword[l[1]] = l[2]
else:
if del_mode:
return False, "该关键词不存在", "keyword"
keyword = {l[1]: l[2]}
with open("keyword.json", "w", encoding="utf-8") as f:
print("设置指令: "+l[1]+" -> "+l[2])
json.dump(keyword, f, ensure_ascii=False, indent=4)
f.flush()
if del_mode:
return True, "删除成功: "+l[2], "keyword"
return True, "设置成功: "+l[1]+" -> "+l[2], "keyword"
except BaseException as e:
return False, "设置失败: "+str(e), "keyword"
@@ -143,18 +338,6 @@ class Command:
pash_tag = "QQChannelChatGPT"+os.sep
repo.remotes.origin.pull()
# 检查是否是windows环境
# if platform.system().lower() == "windows":
# if os.path.exists("launcher.exe"):
# os.system("start launcher.exe")
# elif os.path.exists("QQChannelChatGPT\\main.py"):
# os.system("start python QQChannelChatGPT\\main.py")
# else:
# return True, "更新成功,未发现启动项,因此需要手动重启程序。"
# exit()
# else:
# py = sys.executable
# os.execl(py, py, *sys.argv)
return True, "更新成功~是否重启?输入update r重启(重启指令不返回任何确认信息)。", "update"
except BaseException as e:
+10 -2
View File
@@ -5,9 +5,17 @@ from cores.qqbot.personality import personalities
class CommandOpenAIOfficial(Command):
def __init__(self, provider: ProviderOpenAIOfficial):
self.provider = provider
self.cached_plugins = {}
def check_command(self, message: str, session_id: str, user_name: str, role, platform: str):
hit, res = super().check_command(message, role, platform)
def check_command(self,
message: str,
session_id: str,
user_name: str,
role: str,
platform: str,
message_obj,
cached_plugins: dict):
hit, res = super().check_command(message, role, platform, message_obj=message_obj, cached_plugins=cached_plugins)
if hit:
return True, res
if self.command_start_with(message, "reset", "重置"):
+9 -3
View File
@@ -4,9 +4,15 @@ from model.provider.provider_rev_chatgpt import ProviderRevChatGPT
class CommandRevChatGPT(Command):
def __init__(self, provider: ProviderRevChatGPT):
self.provider = provider
def check_command(self, message: str, role, platform: str):
hit, res = super().check_command(message, role, platform)
self.cached_plugins = {}
def check_command(self,
message: str,
role: str,
platform: str,
message_obj,
cached_plugins: dict):
hit, res = super().check_command(message, role, platform, message_obj=message_obj, cached_plugins=cached_plugins)
if hit:
return True, res
if self.command_start_with(message, "help", "帮助"):
+10 -2
View File
@@ -4,9 +4,17 @@ import asyncio
class CommandRevEdgeGPT(Command):
def __init__(self, provider: ProviderRevEdgeGPT):
self.provider = provider
self.cached_plugins = {}
def check_command(self, message: str, loop, role, platform: str):
hit, res = super().check_command(message, role, platform)
def check_command(self,
message: str,
loop,
role: str,
platform: str,
message_obj,
cached_plugins: dict):
hit, res = super().check_command(message, role, platform, message_obj=message_obj, cached_plugins=cached_plugins)
if hit:
return True, res
if self.command_start_with(message, "reset"):
+37 -11
View File
@@ -1,18 +1,44 @@
from nakuru.entities.components import Plain
from nakuru.entities.components import Plain, At, Image
class QQ:
def run_bot(self, gocq):
self.client = gocq
self.client.run()
async def send_qq_msg(self, source, res):
print("[System-Info] 回复QQ消息中..."+res)
async def send_qq_msg(self,
source,
res,
image_mode: bool = False):
"""
res可以是一个数组,也就是gocq的消息链.
"""
# print(res)
print("[System-Info] 回复QQ消息中..."+str(res))
if isinstance(res, list) and len(res) > 0:
await self.client.sendGroupMessage(source.group_id, res)
return
# 通过消息链处理
if source.type == "GroupMessage":
await self.client.sendGroupMessage(source.group_id, [
Plain(text=res)
])
elif source.type == "FriendMessage":
await self.client.sendFriendMessage(source.user_id, [
Plain(text=res)
])
if not image_mode:
if source.type == "GroupMessage":
await self.client.sendGroupMessage(source.group_id, [
At(qq=source.user_id),
Plain(text=res)
])
elif source.type == "FriendMessage":
await self.client.sendFriendMessage(source.user_id, [
Plain(text=res)
])
else:
if source.type == "GroupMessage":
await self.client.sendGroupMessage(source.group_id, [
At(qq=source.user_id),
Plain(text="好的,我根据你的需要为你生成了一张图片😊"),
Image.fromURL(url=res)
])
elif source.type == "FriendMessage":
await self.client.sendFriendMessage(source.user_id, [
Plain(text="好的,我根据你的需要为你生成了一张图片😊"),
Image.fromURL(url=res)
])
+4 -3
View File
@@ -1,6 +1,7 @@
import io
import botpy
from PIL import Image
from botpy.message import Message, DirectMessage
import re
import asyncio
import requests
@@ -15,13 +16,13 @@ class QQChan():
self.client.run(appid=appid, token=token)
def send_qq_msg(self, message, res, image_mode=False, msg_ref = None):
print("[System-Info] 回复QQ频道消息中..."+res)
print("[System-Info] 回复QQ频道消息中..."+str(res))
if not image_mode:
try:
if msg_ref is not None:
reply_res = asyncio.run_coroutine_threadsafe(message.reply(content=res, message_reference = msg_ref), self.client.loop)
reply_res = asyncio.run_coroutine_threadsafe(message.reply(content=str(res), message_reference = msg_ref), self.client.loop)
else:
reply_res = asyncio.run_coroutine_threadsafe(message.reply(content=res), self.client.loop)
reply_res = asyncio.run_coroutine_threadsafe(message.reply(content=str(res)), self.client.loop)
reply_res.result()
except BaseException as e:
# 分割过长的消息
+35 -25
View File
@@ -1,4 +1,5 @@
from revChatGPT.V1 import Chatbot
from revChatGPT import typings
from model.provider.provider import Provider
class ProviderRevChatGPT(Provider):
@@ -6,7 +7,7 @@ class ProviderRevChatGPT(Provider):
self.rev_chatgpt = []
for i in range(0, len(config['account'])):
try:
print(f"[System] 创建rev_ChatGPT负载{str(i)}: " + str(config['account'][i]))
print(f"[System] 创建rev_ChatGPT负载{str(i)}中...")
if 'password' in config['account'][i]:
config['account'][i]['password'] = str(config['account'][i]['password'])
revstat = {
@@ -30,14 +31,25 @@ class ProviderRevChatGPT(Provider):
for data in bot.ask(prompt):
resp = data["message"]
break
except typings.Error as e:
if e.code == typings.ErrorType.RATE_LIMIT_ERROR:
raise e
if e.code == typings.ErrorType.INVALID_ACCESS_TOKEN_ERROR:
raise e
if e.code == typings.ErrorType.EXPIRED_ACCESS_TOKEN_ERROR:
raise e
if e.code == typings.ErrorType.PROHIBITED_CONCURRENT_QUERY_ERROR:
raise e
err_count += 1
print(f"[RevChatGPT] 请求出现问题: {str(e)} | 正在重试: {str(err_count)}")
if err_count >= retry_count:
raise e
except BaseException as e:
try:
print("[RevChatGPT] 请求出现了一些问题, 正在重试。次数"+str(err_count))
err_count += 1
if err_count >= retry_count:
raise e
except BaseException:
err_count += 1
err_count += 1
print(f"[RevChatGPT] 请求出现问题: {str(e)} | 正在重试: {str(err_count)}")
if err_count >= retry_count:
raise e
print("[RevChatGPT] "+str(resp))
return resp
@@ -45,26 +57,24 @@ class ProviderRevChatGPT(Provider):
def text_chat(self, prompt):
res = ''
print("[Debug] "+str(self.rev_chatgpt))
err_msg = ''
cursor = 0
for revstat in self.rev_chatgpt:
cursor += 1
if not revstat['busy']:
try:
revstat['busy'] = True
print("[Debug] 使用逆向ChatGPT回复ing", end='', flush=True)
res = self.request_text(prompt, revstat['obj'])
print("OK")
revstat['busy'] = False
# 处理结果文本
chatgpt_res = res.strip()
return res
except Exception as e:
print("[System-Error] 逆向ChatGPT回复失败" + str(e))
try:
if e.code == 2:
print("[System-Error] 频率限制,正在切换账号。"+ str(e))
continue
else:
res = '所有的非忙碌OpenAI账号经过测试都暂时出现问题,请稍后再试或者联系管理员~'
return res
except BaseException:
continue
res = '所有的OpenAI账号都有负载, 请稍后再试~'
return res.strip()
# todo: 细化错误管理
except BaseException as e:
revstat['busy'] = False
print(f"请求出现问题: {str(e)}")
err_msg += f"账号{cursor} - 错误原因: {str(e)}"
continue
else:
err_msg += f"账号{cursor} - 错误原因: 忙碌"
continue
res = f'回复失败。错误跟踪:{err_msg}'
return res
+20 -9
View File
@@ -1,6 +1,7 @@
from model.provider.provider import Provider
from EdgeGPT import Chatbot, ConversationStyle
import json
import os
class ProviderRevEdgeGPT(Provider):
def __init__(self):
@@ -20,7 +21,7 @@ class ProviderRevEdgeGPT(Provider):
except BaseException:
return False
async def text_chat(self, prompt):
async def text_chat(self, prompt, platform = 'none'):
if self.busy:
return
self.busy = True
@@ -32,6 +33,8 @@ class ProviderRevEdgeGPT(Provider):
try:
resp = await self.bot.ask(prompt=prompt, conversation_style=ConversationStyle.creative)
# print("[RevEdgeGPT] "+str(resp))
if 'messages' not in resp['item']:
await self.bot.reset()
msj_obj = resp['item']['messages'][len(resp['item']['messages'])-1]
reply_msg = msj_obj['text']
if 'sourceAttributions' in msj_obj:
@@ -43,7 +46,7 @@ class ProviderRevEdgeGPT(Provider):
# print(throttling)
else:
throttling = None
if 'I\'m sorry but I prefer not to continue this conversation. I\'m still learning so I appreciate your understanding and patience.' in resp:
if 'I\'m sorry but I prefer not to continue this conversation. I\'m still learning so I appreciate your understanding and patience.' in reply_msg:
self.busy = False
return '', 0
if reply_msg == prompt:
@@ -51,17 +54,25 @@ class ProviderRevEdgeGPT(Provider):
await self.forget()
err_count += 1
continue
if reply_msg is None:
if reply_source is None:
# 不想答复
return '', 0
else:
index = 1
if len(reply_source) > 0:
reply_msg += "\n\n信息来源:\n"
for i in reply_source:
reply_msg += f"[{str(index)}]: {i['seeMoreUrl']} | {i['providerDisplayName']}\n"
index += 1
if platform != 'qqchan':
index = 1
if len(reply_source) > 0:
reply_msg += "\n\n信息来源:\n"
for i in reply_source:
reply_msg += f"[{str(index)}]: {i['seeMoreUrl']} | {i['providerDisplayName']}\n"
index += 1
if throttling is not None:
if throttling['numUserMessagesInConversation'] == throttling['maxNumUserMessagesInConversation']:
# 达到上限,重置会话
await self.forget()
if throttling['numUserMessagesInConversation'] > throttling['maxNumUserMessagesInConversation']:
await self.forget()
err_count += 1
continue
reply_msg += f"\n{throttling['numUserMessagesInConversation']}/{throttling['maxNumUserMessagesInConversation']}"
break
except BaseException as e:
+2 -2
View File
@@ -1,10 +1,10 @@
requests~=2.28.1
openai~=0.27.4
qq-botpy~=1.1.2
revChatGPT~=4.0.8
revChatGPT~=5.0.0
baidu-aip~=4.16.9
EdgeGPT~=0.1.22.1
chardet~=5.1.0
Pillow~=9.4.0
GitPython~=3.1.31
git+https://github.com/Lxns-Network/nakuru-project.git
nakuru-project-idk~=0.0.2
+22
View File
@@ -0,0 +1,22 @@
import os
import inspect
# 找出模块里所有的类名
def get_classes(p_name, arg):
classes = []
clsmembers = inspect.getmembers(arg, inspect.isclass)
for (name, _) in clsmembers:
# print(name, p_name)
if p_name.lower() == name.lower().replace("plugin", ""):
classes.append(name)
break
return classes
# 获取一个文件夹下所有的模块
def get_modules(path):
modules = []
for root, dirs, files in os.walk(path):
for file in files:
if file.endswith(".py") and not file.startswith("__"):
modules.append(file[:-3])
return modules
+25
View File
@@ -0,0 +1,25 @@
from flask import Flask
from threading import Thread
import datetime
app = Flask(__name__)
@app.route('/')
def main_func():
content = "<h1>QQChannelChatGPT Web APP</h1>"
content += "<p>" + "Online @ " + str(datetime.datetime.now()) + "</p>"
content += "<p>欢迎Star本项目!!!</p>"
return content
def run():
app.run(host="0.0.0.0", port=8080)
def keep_alive():
server = Thread(target=run)
server.start()