mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-20 03:16:14 +00:00
+3
-1
@@ -4,4 +4,6 @@ __pycache__/
|
|||||||
database.db
|
database.db
|
||||||
qchatgpt.log
|
qchatgpt.log
|
||||||
config.py
|
config.py
|
||||||
banlist.py
|
banlist.py
|
||||||
|
plugins/
|
||||||
|
!plugins/__init__.py
|
||||||
@@ -99,13 +99,26 @@ python3 main.py
|
|||||||
- 如提示安装`uvicorn`或`hypercorn`请*不要*安装,这两个不是必需的,目前存在未知原因bug
|
- 如提示安装`uvicorn`或`hypercorn`请*不要*安装,这两个不是必需的,目前存在未知原因bug
|
||||||
- 如报错`TypeError: As of 3.10, the *loop* parameter was removed from Lock() since it is no longer necessary`, 请参考 [此处](https://github.com/RockChinQ/QChatGPT/issues/5)
|
- 如报错`TypeError: As of 3.10, the *loop* parameter was removed from Lock() since it is no longer necessary`, 请参考 [此处](https://github.com/RockChinQ/QChatGPT/issues/5)
|
||||||
|
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## 🚀使用
|
## 🚀使用
|
||||||
|
|
||||||
查看[Wiki功能使用页](https://github.com/RockChinQ/QChatGPT/wiki/%E5%8A%9F%E8%83%BD%E4%BD%BF%E7%94%A8#%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F)
|
查看[Wiki功能使用页](https://github.com/RockChinQ/QChatGPT/wiki/%E5%8A%9F%E8%83%BD%E4%BD%BF%E7%94%A8#%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F)
|
||||||
|
|
||||||
|
## 🧩插件生态
|
||||||
|
|
||||||
|
现已支持自行开发插件对功能进行扩展或自定义程序行为
|
||||||
|
详见[Wiki插件使用页](https://github.com/RockChinQ/QChatGPT/wiki/%E6%8F%92%E4%BB%B6%E4%BD%BF%E7%94%A8)
|
||||||
|
开发教程见[Wiki插件开发页](https://github.com/RockChinQ/QChatGPT/wiki/%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91)
|
||||||
|
|
||||||
|
### 示例插件
|
||||||
|
|
||||||
|
在`tests/plugin_examples`目录下,将其整个目录复制到`plugins`目录下即可使用
|
||||||
|
|
||||||
|
- `cmdcn` - 主程序指令中文形式
|
||||||
|
- `hello_plugin` - 在收到消息`hello`时回复相应消息
|
||||||
|
- `urlikethisijustsix` - 收到冒犯性消息时回复相应消息
|
||||||
|
|
||||||
## 👍赞赏
|
## 👍赞赏
|
||||||
|
|
||||||
<img alt="赞赏码" src="res/mm_reward_qrcode_1672840549070.png" width="400" height="400"/>
|
<img alt="赞赏码" src="res/mm_reward_qrcode_1672840549070.png" width="400" height="400"/>
|
||||||
@@ -39,6 +39,38 @@ def init_db():
|
|||||||
known_exception_caught = False
|
known_exception_caught = False
|
||||||
|
|
||||||
|
|
||||||
|
def reset_logging():
|
||||||
|
assert os.path.exists('config.py')
|
||||||
|
|
||||||
|
config = importlib.import_module('config')
|
||||||
|
|
||||||
|
import pkg.utils.context
|
||||||
|
|
||||||
|
if pkg.utils.context.context['logger_handler'] is not None:
|
||||||
|
logging.getLogger().removeHandler(pkg.utils.context.context['logger_handler'])
|
||||||
|
|
||||||
|
for handler in logging.getLogger().handlers:
|
||||||
|
logging.getLogger().removeHandler(handler)
|
||||||
|
|
||||||
|
logging.basicConfig(level=config.logging_level, # 设置日志输出格式
|
||||||
|
filename='qchatgpt.log', # log日志输出的文件位置和文件名
|
||||||
|
format="[%(asctime)s.%(msecs)03d] %(filename)s (%(lineno)d) - [%(levelname)s] : %(message)s",
|
||||||
|
# 日志输出的格式
|
||||||
|
# -8表示占位符,让输出左对齐,输出长度都为8位
|
||||||
|
datefmt="%Y-%m-%d %H:%M:%S" # 时间输出的格式
|
||||||
|
)
|
||||||
|
sh = logging.StreamHandler()
|
||||||
|
sh.setLevel(config.logging_level)
|
||||||
|
sh.setFormatter(colorlog.ColoredFormatter(
|
||||||
|
fmt="%(log_color)s[%(asctime)s.%(msecs)03d] %(filename)s (%(lineno)d) - [%(levelname)s] : "
|
||||||
|
"%(message)s",
|
||||||
|
datefmt="%Y-%m-%d %H:%M:%S",
|
||||||
|
log_colors=log_colors_config
|
||||||
|
))
|
||||||
|
logging.getLogger().addHandler(sh)
|
||||||
|
return sh
|
||||||
|
|
||||||
|
|
||||||
def main(first_time_init=False):
|
def main(first_time_init=False):
|
||||||
global known_exception_caught
|
global known_exception_caught
|
||||||
|
|
||||||
@@ -52,25 +84,7 @@ def main(first_time_init=False):
|
|||||||
import pkg.utils.context
|
import pkg.utils.context
|
||||||
pkg.utils.context.set_config(config)
|
pkg.utils.context.set_config(config)
|
||||||
|
|
||||||
if pkg.utils.context.context['logger_handler'] is not None:
|
sh = reset_logging()
|
||||||
logging.getLogger().removeHandler(pkg.utils.context.context['logger_handler'])
|
|
||||||
|
|
||||||
logging.basicConfig(level=config.logging_level, # 设置日志输出格式
|
|
||||||
filename='qchatgpt.log', # log日志输出的文件位置和文件名
|
|
||||||
format="[%(asctime)s.%(msecs)03d] %(filename)s (%(lineno)d) - [%(levelname)s] : %(message)s",
|
|
||||||
# 日志输出的格式
|
|
||||||
# -8表示占位符,让输出左对齐,输出长度都为8位
|
|
||||||
datefmt="%Y-%m-%d %H:%M:%S" # 时间输出的格式
|
|
||||||
)
|
|
||||||
sh = logging.StreamHandler()
|
|
||||||
sh.setLevel(config.logging_level)
|
|
||||||
sh.setFormatter(colorlog.ColoredFormatter(
|
|
||||||
fmt="%(log_color)s[%(asctime)s.%(msecs)03d] %(filename)s (%(lineno)d) - [%(levelname)s] : "
|
|
||||||
"%(message)s",
|
|
||||||
datefmt="%Y-%m-%d %H:%M:%S",
|
|
||||||
log_colors=log_colors_config
|
|
||||||
))
|
|
||||||
logging.getLogger().addHandler(sh)
|
|
||||||
|
|
||||||
# 检查是否设置了管理员
|
# 检查是否设置了管理员
|
||||||
if not (hasattr(config, 'admin_qq') and config.admin_qq != 0):
|
if not (hasattr(config, 'admin_qq') and config.admin_qq != 0):
|
||||||
@@ -117,9 +131,16 @@ def main(first_time_init=False):
|
|||||||
timeout=config.process_message_timeout, retry=config.retry_times,
|
timeout=config.process_message_timeout, retry=config.retry_times,
|
||||||
first_time_init=first_time_init)
|
first_time_init=first_time_init)
|
||||||
|
|
||||||
|
# 加载插件
|
||||||
|
import pkg.plugin.host
|
||||||
|
pkg.plugin.host.load_plugins()
|
||||||
|
|
||||||
|
pkg.plugin.host.initialize_plugins()
|
||||||
|
|
||||||
if first_time_init: # 不是热重载之后的启动,则不启动新的bot线程
|
if first_time_init: # 不是热重载之后的启动,则不启动新的bot线程
|
||||||
|
|
||||||
import mirai.exceptions
|
import mirai.exceptions
|
||||||
|
|
||||||
def run_bot_wrapper():
|
def run_bot_wrapper():
|
||||||
global known_exception_caught
|
global known_exception_caught
|
||||||
try:
|
try:
|
||||||
@@ -155,7 +176,7 @@ def main(first_time_init=False):
|
|||||||
known_exception_caught = True
|
known_exception_caught = True
|
||||||
else:
|
else:
|
||||||
logging.error(
|
logging.error(
|
||||||
"捕捉到未知异常:{}, 请前往 https://github.com/RockChinQ/issues 查找或提issue".format(e))
|
"捕捉到未知异常:{}, 请前往 https://github.com/RockChinQ/QChatGPT/issues 查找或提issue".format(e))
|
||||||
known_exception_caught = True
|
known_exception_caught = True
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
@@ -201,6 +222,9 @@ def stop():
|
|||||||
import pkg.qqbot.manager
|
import pkg.qqbot.manager
|
||||||
import pkg.openai.session
|
import pkg.openai.session
|
||||||
try:
|
try:
|
||||||
|
import pkg.plugin.host
|
||||||
|
pkg.plugin.host.unload_plugins()
|
||||||
|
|
||||||
qqbot_inst = pkg.utils.context.get_qqbot_manager()
|
qqbot_inst = pkg.utils.context.get_qqbot_manager()
|
||||||
assert isinstance(qqbot_inst, pkg.qqbot.manager.QQBotManager)
|
assert isinstance(qqbot_inst, pkg.qqbot.manager.QQBotManager)
|
||||||
|
|
||||||
@@ -230,6 +254,12 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
elif len(sys.argv) > 1 and sys.argv[1] == 'update':
|
elif len(sys.argv) > 1 and sys.argv[1] == 'update':
|
||||||
try:
|
try:
|
||||||
|
try:
|
||||||
|
import pkg.utils.pkgmgr
|
||||||
|
pkg.utils.pkgmgr.ensure_dulwich()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
from dulwich import porcelain
|
from dulwich import porcelain
|
||||||
|
|
||||||
repo = porcelain.open_repo('.')
|
repo = porcelain.open_repo('.')
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ class DataGatherer:
|
|||||||
except:
|
except:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def get_usage(self, key_md5):
|
||||||
|
return self.usage[key_md5] if key_md5 in self.usage else {}
|
||||||
|
|
||||||
def report_text_model_usage(self, model, text):
|
def report_text_model_usage(self, model, text):
|
||||||
key_md5 = pkg.utils.context.get_openai_manager().key_mgr.get_using_key_md5()
|
key_md5 = pkg.utils.context.get_openai_manager().key_mgr.get_using_key_md5()
|
||||||
|
|
||||||
|
|||||||
+12
-8
@@ -2,10 +2,8 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import pkg.database.manager
|
import pkg.plugin.host as plugin_host
|
||||||
import pkg.qqbot.manager
|
import pkg.plugin.models as plugin_models
|
||||||
import pkg.utils.context
|
|
||||||
|
|
||||||
|
|
||||||
class KeysManager:
|
class KeysManager:
|
||||||
api_key = {}
|
api_key = {}
|
||||||
@@ -39,13 +37,10 @@ class KeysManager:
|
|||||||
elif type(api_key) is list:
|
elif type(api_key) is list:
|
||||||
for i in range(len(api_key)):
|
for i in range(len(api_key)):
|
||||||
self.api_key[str(i)] = api_key[i]
|
self.api_key[str(i)] = api_key[i]
|
||||||
|
|
||||||
self.auto_switch()
|
|
||||||
# 从usage中删除未加载的api-key的记录
|
# 从usage中删除未加载的api-key的记录
|
||||||
# 不删了,也许会运行时添加曾经有记录的api-key
|
# 不删了,也许会运行时添加曾经有记录的api-key
|
||||||
|
|
||||||
if 'exceeded_keys' in pkg.utils.context.context and pkg.utils.context.context['exceeded_keys'] is not None:
|
self.auto_switch()
|
||||||
self.exceeded = pkg.utils.context.context['exceeded_keys']
|
|
||||||
|
|
||||||
# 根据tested自动切换到可用的api-key
|
# 根据tested自动切换到可用的api-key
|
||||||
# 返回是否切换成功, 切换后的api-key的别名
|
# 返回是否切换成功, 切换后的api-key的别名
|
||||||
@@ -53,7 +48,16 @@ class KeysManager:
|
|||||||
for key_name in self.api_key:
|
for key_name in self.api_key:
|
||||||
if self.api_key[key_name] not in self.exceeded:
|
if self.api_key[key_name] not in self.exceeded:
|
||||||
self.using_key = self.api_key[key_name]
|
self.using_key = self.api_key[key_name]
|
||||||
|
|
||||||
logging.info("使用api-key:" + key_name)
|
logging.info("使用api-key:" + key_name)
|
||||||
|
|
||||||
|
# 触发插件事件
|
||||||
|
args = {
|
||||||
|
"key_name": key_name,
|
||||||
|
"key_list": self.api_key.keys()
|
||||||
|
}
|
||||||
|
_ = plugin_host.emit(plugin_models.KeySwitched, **args)
|
||||||
|
|
||||||
return True, key_name
|
return True, key_name
|
||||||
|
|
||||||
self.using_key = list(self.api_key.values())[0]
|
self.using_key = list(self.api_key.values())[0]
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import pkg.openai.manager
|
|||||||
import pkg.database.manager
|
import pkg.database.manager
|
||||||
import pkg.utils.context
|
import pkg.utils.context
|
||||||
|
|
||||||
|
import pkg.plugin.host as plugin_host
|
||||||
|
import pkg.plugin.models as plugin_models
|
||||||
|
|
||||||
# 运行时保存的所有session
|
# 运行时保存的所有session
|
||||||
sessions = {}
|
sessions = {}
|
||||||
|
|
||||||
@@ -120,6 +123,17 @@ class Session:
|
|||||||
config = pkg.utils.context.get_config()
|
config = pkg.utils.context.get_config()
|
||||||
if int(time.time()) - self.last_interact_timestamp > config.session_expire_time:
|
if int(time.time()) - self.last_interact_timestamp > config.session_expire_time:
|
||||||
logging.info('session {} 已过期'.format(self.name))
|
logging.info('session {} 已过期'.format(self.name))
|
||||||
|
|
||||||
|
# 触发插件事件
|
||||||
|
args = {
|
||||||
|
'session_name': self.name,
|
||||||
|
'session': self,
|
||||||
|
'session_expire_time': config.session_expire_time
|
||||||
|
}
|
||||||
|
event = pkg.plugin.host.emit(plugin_models.SessionExpired, **args)
|
||||||
|
if event.is_prevented_default():
|
||||||
|
return
|
||||||
|
|
||||||
self.reset(expired=True, schedule_new=False)
|
self.reset(expired=True, schedule_new=False)
|
||||||
|
|
||||||
# 删除此session
|
# 删除此session
|
||||||
@@ -131,6 +145,18 @@ class Session:
|
|||||||
def append(self, text: str) -> str:
|
def append(self, text: str) -> str:
|
||||||
self.last_interact_timestamp = int(time.time())
|
self.last_interact_timestamp = int(time.time())
|
||||||
|
|
||||||
|
# 触发插件事件
|
||||||
|
if self.prompt == self.get_default_prompt():
|
||||||
|
args = {
|
||||||
|
'session_name': self.name,
|
||||||
|
'session': self,
|
||||||
|
'default_prompt': self.prompt,
|
||||||
|
}
|
||||||
|
|
||||||
|
event = pkg.plugin.host.emit(plugin_models.SessionFirstMessageReceived, **args)
|
||||||
|
if event.is_prevented_default():
|
||||||
|
return None
|
||||||
|
|
||||||
# max_rounds = config.prompt_submit_round_amount if hasattr(config, 'prompt_submit_round_amount') else 7
|
# max_rounds = config.prompt_submit_round_amount if hasattr(config, 'prompt_submit_round_amount') else 7
|
||||||
config = pkg.utils.context.get_config()
|
config = pkg.utils.context.get_config()
|
||||||
max_rounds = 1000 # 不再限制回合数
|
max_rounds = 1000 # 不再限制回合数
|
||||||
@@ -220,6 +246,15 @@ class Session:
|
|||||||
if self.prompt != self.get_default_prompt():
|
if self.prompt != self.get_default_prompt():
|
||||||
self.persistence()
|
self.persistence()
|
||||||
if explicit:
|
if explicit:
|
||||||
|
# 触发插件事件
|
||||||
|
args = {
|
||||||
|
'session_name': self.name,
|
||||||
|
'session': self
|
||||||
|
}
|
||||||
|
|
||||||
|
# 此事件不支持阻止默认行为
|
||||||
|
_ = pkg.plugin.host.emit(plugin_models.SessionExplicitReset, **args)
|
||||||
|
|
||||||
pkg.utils.context.get_database_manager().explicit_close_session(self.name, self.create_timestamp)
|
pkg.utils.context.get_database_manager().explicit_close_session(self.name, self.create_timestamp)
|
||||||
|
|
||||||
if expired:
|
if expired:
|
||||||
|
|||||||
@@ -0,0 +1,273 @@
|
|||||||
|
# 插件管理模块
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
import importlib
|
||||||
|
import os
|
||||||
|
import pkgutil
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
import pkg.utils.context as context
|
||||||
|
import pkg.plugin.switch as switch
|
||||||
|
|
||||||
|
from mirai import Mirai
|
||||||
|
|
||||||
|
__plugins__ = {}
|
||||||
|
"""
|
||||||
|
插件列表
|
||||||
|
|
||||||
|
示例:
|
||||||
|
{
|
||||||
|
"example": {
|
||||||
|
"path": "plugins/example/main.py",
|
||||||
|
"enabled: True,
|
||||||
|
"name": "example",
|
||||||
|
"description": "example",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"author": "RockChinQ",
|
||||||
|
"class": <class 'plugins.example.ExamplePlugin'>,
|
||||||
|
"hooks": {
|
||||||
|
"person_message": [
|
||||||
|
<function ExamplePlugin.person_message at 0x0000020E1D1B8D38>
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"instance": None
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
|
||||||
|
|
||||||
|
__current_module_path__ = ""
|
||||||
|
|
||||||
|
|
||||||
|
def walk_plugin_path(module, prefix='', path_prefix=''):
|
||||||
|
global __current_module_path__
|
||||||
|
"""遍历插件路径"""
|
||||||
|
for item in pkgutil.iter_modules(module.__path__):
|
||||||
|
if item.ispkg:
|
||||||
|
logging.debug("扫描插件包: plugins/{}".format(path_prefix + item.name))
|
||||||
|
walk_plugin_path(__import__(module.__name__ + '.' + item.name, fromlist=['']),
|
||||||
|
prefix + item.name + '.', path_prefix + item.name + '/')
|
||||||
|
else:
|
||||||
|
logging.debug("扫描插件模块: plugins/{}".format(path_prefix + item.name + '.py'))
|
||||||
|
logging.info('加载模块: plugins/{}'.format(path_prefix + item.name + '.py'))
|
||||||
|
__current_module_path__ = "plugins/"+path_prefix + item.name + '.py'
|
||||||
|
|
||||||
|
importlib.import_module(module.__name__ + '.' + item.name)
|
||||||
|
|
||||||
|
|
||||||
|
def load_plugins():
|
||||||
|
""" 加载插件 """
|
||||||
|
logging.info("加载插件")
|
||||||
|
PluginHost()
|
||||||
|
walk_plugin_path(__import__('plugins'))
|
||||||
|
|
||||||
|
logging.debug(__plugins__)
|
||||||
|
|
||||||
|
# 加载开关数据
|
||||||
|
switch.load_switch()
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_plugins():
|
||||||
|
""" 初始化插件 """
|
||||||
|
logging.info("初始化插件")
|
||||||
|
for plugin in __plugins__.values():
|
||||||
|
if not plugin['enabled']:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
plugin['instance'] = plugin["class"](plugin_host=context.get_plugin_host())
|
||||||
|
except:
|
||||||
|
logging.error("插件{}初始化时发生错误: {}".format(plugin['name'], sys.exc_info()))
|
||||||
|
|
||||||
|
|
||||||
|
def unload_plugins():
|
||||||
|
""" 卸载插件 """
|
||||||
|
for plugin in __plugins__.values():
|
||||||
|
if plugin['enabled'] and plugin['instance'] is not None:
|
||||||
|
if not hasattr(plugin['instance'], '__del__'):
|
||||||
|
logging.warning("插件{}没有定义析构函数".format(plugin['name']))
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
plugin['instance'].__del__()
|
||||||
|
logging.info("卸载插件: {}".format(plugin['name']))
|
||||||
|
except:
|
||||||
|
logging.error("插件{}卸载时发生错误: {}".format(plugin['name'], sys.exc_info()))
|
||||||
|
|
||||||
|
|
||||||
|
def install_plugin(repo_url: str):
|
||||||
|
""" 安装插件,从git储存库获取并解决依赖 """
|
||||||
|
try:
|
||||||
|
import pkg.utils.pkgmgr
|
||||||
|
pkg.utils.pkgmgr.ensure_dulwich()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
import dulwich
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
raise Exception("dulwich模块未安装,请查看 https://github.com/RockChinQ/QChatGPT/issues/77")
|
||||||
|
|
||||||
|
from dulwich import porcelain
|
||||||
|
|
||||||
|
logging.info("克隆插件储存库: {}".format(repo_url))
|
||||||
|
repo = porcelain.clone(repo_url, "plugins/"+repo_url.split(".git")[0].split("/")[-1]+"/", checkout=True)
|
||||||
|
|
||||||
|
# 检查此目录是否包含requirements.txt
|
||||||
|
if os.path.exists("plugins/"+repo_url.split(".git")[0].split("/")[-1]+"/requirements.txt"):
|
||||||
|
logging.info("检测到requirements.txt,正在安装依赖")
|
||||||
|
import pkg.utils.pkgmgr
|
||||||
|
pkg.utils.pkgmgr.install_requirements("plugins/"+repo_url.split(".git")[0].split("/")[-1]+"/requirements.txt")
|
||||||
|
|
||||||
|
import main
|
||||||
|
main.reset_logging()
|
||||||
|
|
||||||
|
|
||||||
|
class EventContext:
|
||||||
|
""" 事件上下文 """
|
||||||
|
eid = 0
|
||||||
|
"""事件编号"""
|
||||||
|
|
||||||
|
name = ""
|
||||||
|
|
||||||
|
__prevent_default__ = False
|
||||||
|
""" 是否阻止默认行为 """
|
||||||
|
|
||||||
|
__prevent_postorder__ = False
|
||||||
|
""" 是否阻止后续插件的执行 """
|
||||||
|
|
||||||
|
__return_value__ = {}
|
||||||
|
""" 返回值
|
||||||
|
示例:
|
||||||
|
{
|
||||||
|
"example": [
|
||||||
|
'value1',
|
||||||
|
'value2',
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
{
|
||||||
|
'key1': 'value1',
|
||||||
|
},
|
||||||
|
['value1', 'value2']
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def add_return(self, key: str, ret):
|
||||||
|
"""添加返回值"""
|
||||||
|
if key not in self.__return_value__:
|
||||||
|
self.__return_value__[key] = []
|
||||||
|
self.__return_value__[key].append(ret)
|
||||||
|
|
||||||
|
def get_return(self, key: str):
|
||||||
|
"""获取key的所有返回值"""
|
||||||
|
if key in self.__return_value__:
|
||||||
|
return self.__return_value__[key]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_return_value(self, key: str):
|
||||||
|
"""获取key的首个返回值"""
|
||||||
|
if key in self.__return_value__:
|
||||||
|
return self.__return_value__[key][0]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def prevent_default(self):
|
||||||
|
"""阻止默认行为"""
|
||||||
|
self.__prevent_default__ = True
|
||||||
|
|
||||||
|
def prevent_postorder(self):
|
||||||
|
"""阻止后续插件执行"""
|
||||||
|
self.__prevent_postorder__ = True
|
||||||
|
|
||||||
|
def is_prevented_default(self):
|
||||||
|
"""是否阻止默认行为"""
|
||||||
|
return self.__prevent_default__
|
||||||
|
|
||||||
|
def is_prevented_postorder(self):
|
||||||
|
"""是否阻止后序插件执行"""
|
||||||
|
return self.__prevent_postorder__
|
||||||
|
|
||||||
|
def __init__(self, name: str):
|
||||||
|
self.name = name
|
||||||
|
self.eid = EventContext.eid
|
||||||
|
self.__prevent_default__ = False
|
||||||
|
self.__prevent_postorder__ = False
|
||||||
|
self.__return_value__ = {}
|
||||||
|
EventContext.eid += 1
|
||||||
|
|
||||||
|
|
||||||
|
def emit(event_name: str, **kwargs) -> EventContext:
|
||||||
|
""" 触发事件 """
|
||||||
|
import pkg.utils.context as context
|
||||||
|
if context.get_plugin_host() is None:
|
||||||
|
return None
|
||||||
|
return context.get_plugin_host().emit(event_name, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginHost:
|
||||||
|
"""插件宿主"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
context.set_plugin_host(self)
|
||||||
|
|
||||||
|
def get_runtime_context(self) -> context:
|
||||||
|
"""获取运行时上下文(pkg.utils.context模块的对象)
|
||||||
|
|
||||||
|
此上下文用于和主程序其他模块交互(数据库、QQ机器人、OpenAI接口等)
|
||||||
|
详见pkg.utils.context模块
|
||||||
|
其中的context变量保存了其他重要模块的类对象,可以使用这些对象进行交互
|
||||||
|
"""
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_bot(self) -> Mirai:
|
||||||
|
"""获取机器人对象"""
|
||||||
|
return context.get_qqbot_manager().bot
|
||||||
|
|
||||||
|
def send_person_message(self, person, message):
|
||||||
|
"""发送私聊消息"""
|
||||||
|
asyncio.run(self.get_bot().send_friend_message(person, message))
|
||||||
|
|
||||||
|
def send_group_message(self, group, message):
|
||||||
|
"""发送群消息"""
|
||||||
|
asyncio.run(self.get_bot().send_group_message(group, message))
|
||||||
|
|
||||||
|
def notify_admin(self, message):
|
||||||
|
"""通知管理员"""
|
||||||
|
context.get_qqbot_manager().notify_admin(message)
|
||||||
|
|
||||||
|
def emit(self, event_name: str, **kwargs) -> EventContext:
|
||||||
|
""" 触发事件 """
|
||||||
|
event_context = EventContext(event_name)
|
||||||
|
logging.debug("触发事件: {} ({})".format(event_name, event_context.eid))
|
||||||
|
for plugin in __plugins__.values():
|
||||||
|
|
||||||
|
if not plugin['enabled']:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if plugin['instance'] is None:
|
||||||
|
# 从关闭状态切到开启状态之后,重新加载插件
|
||||||
|
try:
|
||||||
|
plugin['instance'] = plugin["class"]()
|
||||||
|
except:
|
||||||
|
logging.error("插件{}初始化时发生错误: {}".format(plugin['name'], sys.exc_info()))
|
||||||
|
continue
|
||||||
|
|
||||||
|
for hook in plugin['hooks'].get(event_name, []):
|
||||||
|
try:
|
||||||
|
already_prevented_default = event_context.is_prevented_default()
|
||||||
|
|
||||||
|
kwargs['host'] = context.get_plugin_host()
|
||||||
|
kwargs['event'] = event_context
|
||||||
|
|
||||||
|
hook(plugin['instance'], **kwargs)
|
||||||
|
|
||||||
|
if event_context.is_prevented_default() and not already_prevented_default:
|
||||||
|
logging.debug("插件 {} 已要求阻止事件 {} 的默认行为".format(plugin['name'], event_name))
|
||||||
|
|
||||||
|
if event_context.is_prevented_postorder():
|
||||||
|
logging.debug("插件 {} 阻止了后序插件的执行".format(plugin['name']))
|
||||||
|
break
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error("插件{}触发事件{}时发生错误".format(plugin['name'], event_name))
|
||||||
|
logging.error(traceback.format_exc())
|
||||||
|
|
||||||
|
return event_context
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import pkg.plugin.host as host
|
||||||
|
import pkg.utils.context
|
||||||
|
|
||||||
|
PersonMessageReceived = "person_message_received"
|
||||||
|
"""收到私聊消息时,在判断是否应该响应前触发
|
||||||
|
kwargs:
|
||||||
|
launcher_type: str 发起对象类型(group/person)
|
||||||
|
launcher_id: int 发起对象ID(群号/QQ号)
|
||||||
|
sender_id: int 发送者ID(QQ号)
|
||||||
|
message_chain: mirai.models.message.MessageChain 消息链
|
||||||
|
"""
|
||||||
|
|
||||||
|
GroupMessageReceived = "group_message_received"
|
||||||
|
"""收到群聊消息时,在判断是否应该响应前触发(所有群消息)
|
||||||
|
kwargs:
|
||||||
|
launcher_type: str 发起对象类型(group/person)
|
||||||
|
launcher_id: int 发起对象ID(群号/QQ号)
|
||||||
|
sender_id: int 发送者ID(QQ号)
|
||||||
|
message_chain: mirai.models.message.MessageChain 消息链
|
||||||
|
"""
|
||||||
|
|
||||||
|
PersonNormalMessageReceived = "person_normal_message_received"
|
||||||
|
"""判断为应该处理的私聊普通消息时触发
|
||||||
|
kwargs:
|
||||||
|
launcher_type: str 发起对象类型(group/person)
|
||||||
|
launcher_id: int 发起对象ID(群号/QQ号)
|
||||||
|
sender_id: int 发送者ID(QQ号)
|
||||||
|
text_message: str 消息文本
|
||||||
|
|
||||||
|
returns (optional):
|
||||||
|
alter: str 修改后的消息文本
|
||||||
|
reply: list 回复消息组件列表
|
||||||
|
"""
|
||||||
|
|
||||||
|
PersonCommandSent = "person_command_sent"
|
||||||
|
"""判断为应该处理的私聊指令时触发
|
||||||
|
kwargs:
|
||||||
|
launcher_type: str 发起对象类型(group/person)
|
||||||
|
launcher_id: int 发起对象ID(群号/QQ号)
|
||||||
|
sender_id: int 发送者ID(QQ号)
|
||||||
|
command: str 指令
|
||||||
|
params: list[str] 参数列表
|
||||||
|
text_message: str 完整指令文本
|
||||||
|
is_admin: bool 是否为管理员
|
||||||
|
|
||||||
|
returns (optional):
|
||||||
|
alter: str 修改后的完整指令文本
|
||||||
|
reply: list 回复消息组件列表
|
||||||
|
"""
|
||||||
|
|
||||||
|
GroupNormalMessageReceived = "group_normal_message_received"
|
||||||
|
"""判断为应该处理的群聊普通消息时触发
|
||||||
|
kwargs:
|
||||||
|
launcher_type: str 发起对象类型(group/person)
|
||||||
|
launcher_id: int 发起对象ID(群号/QQ号)
|
||||||
|
sender_id: int 发送者ID(QQ号)
|
||||||
|
text_message: str 消息文本
|
||||||
|
|
||||||
|
returns (optional):
|
||||||
|
alter: str 修改后的消息文本
|
||||||
|
reply: list 回复消息组件列表
|
||||||
|
"""
|
||||||
|
|
||||||
|
GroupCommandSent = "group_command_sent"
|
||||||
|
"""判断为应该处理的群聊指令时触发
|
||||||
|
kwargs:
|
||||||
|
launcher_type: str 发起对象类型(group/person)
|
||||||
|
launcher_id: int 发起对象ID(群号/QQ号)
|
||||||
|
sender_id: int 发送者ID(QQ号)
|
||||||
|
command: str 指令
|
||||||
|
params: list[str] 参数列表
|
||||||
|
text_message: str 完整指令文本
|
||||||
|
is_admin: bool 是否为管理员
|
||||||
|
|
||||||
|
returns (optional):
|
||||||
|
alter: str 修改后的完整指令文本
|
||||||
|
reply: list 回复消息组件列表
|
||||||
|
"""
|
||||||
|
|
||||||
|
NormalMessageResponded = "normal_message_responded"
|
||||||
|
"""获取到对普通消息的文字响应时触发
|
||||||
|
kwargs:
|
||||||
|
launcher_type: str 发起对象类型(group/person)
|
||||||
|
launcher_id: int 发起对象ID(群号/QQ号)
|
||||||
|
sender_id: int 发送者ID(QQ号)
|
||||||
|
session: pkg.openai.session.Session 会话对象
|
||||||
|
prefix: str 回复文字消息的前缀
|
||||||
|
response_text: str 响应文本
|
||||||
|
|
||||||
|
returns (optional):
|
||||||
|
prefix: str 修改后的回复文字消息的前缀
|
||||||
|
reply: list 替换回复消息组件列表
|
||||||
|
"""
|
||||||
|
|
||||||
|
SessionFirstMessageReceived = "session_first_message_received"
|
||||||
|
"""会话被第一次交互时触发
|
||||||
|
kwargs:
|
||||||
|
session_name: str 会话名称(<launcher_type>_<launcher_id>)
|
||||||
|
session: pkg.openai.session.Session 会话对象
|
||||||
|
default_prompt: str 预设值
|
||||||
|
"""
|
||||||
|
|
||||||
|
SessionExplicitReset = "session_reset"
|
||||||
|
"""会话被用户手动重置时触发,此事件不支持阻止默认行为
|
||||||
|
kwargs:
|
||||||
|
session_name: str 会话名称(<launcher_type>_<launcher_id>)
|
||||||
|
session: pkg.openai.session.Session 会话对象
|
||||||
|
"""
|
||||||
|
|
||||||
|
SessionExpired = "session_expired"
|
||||||
|
"""会话过期时触发
|
||||||
|
kwargs:
|
||||||
|
session_name: str 会话名称(<launcher_type>_<launcher_id>)
|
||||||
|
session: pkg.openai.session.Session 会话对象
|
||||||
|
session_expire_time: int 已设置的会话过期时间(秒)
|
||||||
|
"""
|
||||||
|
|
||||||
|
KeyExceeded = "key_exceeded"
|
||||||
|
"""api-key超额时触发
|
||||||
|
kwargs:
|
||||||
|
key_name: str 超额的api-key名称
|
||||||
|
usage: dict 超额的api-key使用情况
|
||||||
|
exceeded_keys: list[str] 超额的api-key列表
|
||||||
|
"""
|
||||||
|
|
||||||
|
KeySwitched = "key_switched"
|
||||||
|
"""api-key超额切换成功时触发,此事件不支持阻止默认行为
|
||||||
|
kwargs:
|
||||||
|
key_name: str 切换成功的api-key名称
|
||||||
|
key_list: list[str] api-key列表
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def on(event: str):
|
||||||
|
"""注册事件监听器
|
||||||
|
:param
|
||||||
|
event: str 事件名称
|
||||||
|
"""
|
||||||
|
return Plugin.on(event)
|
||||||
|
|
||||||
|
|
||||||
|
__current_registering_plugin__ = ""
|
||||||
|
|
||||||
|
|
||||||
|
class Plugin:
|
||||||
|
|
||||||
|
host: host.PluginHost
|
||||||
|
"""插件宿主,提供插件的一些基础功能"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def on(cls, event):
|
||||||
|
"""事件处理器装饰器
|
||||||
|
|
||||||
|
:param
|
||||||
|
event: 事件类型
|
||||||
|
:return:
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
|
||||||
|
def wrapper(func):
|
||||||
|
plugin_hooks = host.__plugins__[__current_registering_plugin__]["hooks"]
|
||||||
|
|
||||||
|
if event not in plugin_hooks:
|
||||||
|
plugin_hooks[event] = []
|
||||||
|
plugin_hooks[event].append(func)
|
||||||
|
|
||||||
|
host.__plugins__[__current_registering_plugin__]["hooks"] = plugin_hooks
|
||||||
|
|
||||||
|
return func
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def register(name: str, description: str, version: str, author: str):
|
||||||
|
"""注册插件, 此函数作为装饰器使用
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): 插件名称
|
||||||
|
description (str): 插件描述
|
||||||
|
version (str): 插件版本
|
||||||
|
author (str): 插件作者
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
global __current_registering_plugin__
|
||||||
|
|
||||||
|
__current_registering_plugin__ = name
|
||||||
|
|
||||||
|
host.__plugins__[name] = {
|
||||||
|
"name": name,
|
||||||
|
"description": description,
|
||||||
|
"version": version,
|
||||||
|
"author": author,
|
||||||
|
"hooks": {},
|
||||||
|
"path": host.__current_module_path__,
|
||||||
|
"enabled": True,
|
||||||
|
"instance": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
def wrapper(cls: Plugin):
|
||||||
|
cls.name = name
|
||||||
|
cls.description = description
|
||||||
|
cls.version = version
|
||||||
|
cls.author = author
|
||||||
|
cls.host = pkg.utils.context.get_plugin_host()
|
||||||
|
cls.enabled = True
|
||||||
|
cls.path = host.__current_module_path__
|
||||||
|
|
||||||
|
# 存到插件列表
|
||||||
|
host.__plugins__[name]["class"] = cls
|
||||||
|
|
||||||
|
logging.info("插件注册完成: n='{}', d='{}', v={}, a='{}' ({})".format(name, description, version, author, cls))
|
||||||
|
|
||||||
|
return cls
|
||||||
|
|
||||||
|
return wrapper
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# 控制插件的开关
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
import pkg.plugin.host as host
|
||||||
|
|
||||||
|
|
||||||
|
def wrapper_dict_from_plugin_list() -> dict:
|
||||||
|
""" 将插件列表转换为开关json """
|
||||||
|
switch = {}
|
||||||
|
|
||||||
|
for plugin_name in host.__plugins__:
|
||||||
|
plugin = host.__plugins__[plugin_name]
|
||||||
|
|
||||||
|
switch[plugin_name] = {
|
||||||
|
"path": plugin["path"],
|
||||||
|
"enabled": plugin["enabled"],
|
||||||
|
}
|
||||||
|
|
||||||
|
return switch
|
||||||
|
|
||||||
|
|
||||||
|
def apply_switch(switch: dict):
|
||||||
|
"""将开关数据应用到插件列表中"""
|
||||||
|
for plugin_name in switch:
|
||||||
|
host.__plugins__[plugin_name]["enabled"] = switch[plugin_name]["enabled"]
|
||||||
|
|
||||||
|
|
||||||
|
def dump_switch():
|
||||||
|
""" 保存开关数据 """
|
||||||
|
logging.debug("保存开关数据")
|
||||||
|
# 将开关数据写入plugins/switch.json
|
||||||
|
|
||||||
|
switch = wrapper_dict_from_plugin_list()
|
||||||
|
|
||||||
|
with open("plugins/switch.json", "w", encoding="utf-8") as f:
|
||||||
|
json.dump(switch, f, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
|
|
||||||
|
def load_switch():
|
||||||
|
""" 加载开关数据 """
|
||||||
|
logging.debug("加载开关数据")
|
||||||
|
# 读取plugins/switch.json
|
||||||
|
|
||||||
|
switch = {}
|
||||||
|
|
||||||
|
# 检查文件是否存在
|
||||||
|
if not os.path.exists("plugins/switch.json"):
|
||||||
|
# 不存在则创建
|
||||||
|
with open("plugins/switch.json", "w", encoding="utf-8") as f:
|
||||||
|
json.dump(switch, f, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
|
with open("plugins/switch.json", "r", encoding="utf-8") as f:
|
||||||
|
switch = json.load(f)
|
||||||
|
|
||||||
|
if switch is None:
|
||||||
|
switch = {}
|
||||||
|
|
||||||
|
switch_modified = False
|
||||||
|
|
||||||
|
switch_copy = switch.copy()
|
||||||
|
# 检查switch中多余的和path不相符的
|
||||||
|
for plugin_name in switch_copy:
|
||||||
|
if plugin_name not in host.__plugins__:
|
||||||
|
del switch[plugin_name]
|
||||||
|
switch_modified = True
|
||||||
|
elif switch[plugin_name]["path"] != host.__plugins__[plugin_name]["path"]:
|
||||||
|
# 删除此不相符的
|
||||||
|
del switch[plugin_name]
|
||||||
|
switch_modified = True
|
||||||
|
|
||||||
|
# 检查plugin中多余的
|
||||||
|
for plugin_name in host.__plugins__:
|
||||||
|
if plugin_name not in switch:
|
||||||
|
switch[plugin_name] = {
|
||||||
|
"path": host.__plugins__[plugin_name]["path"],
|
||||||
|
"enabled": host.__plugins__[plugin_name]["enabled"],
|
||||||
|
}
|
||||||
|
switch_modified = True
|
||||||
|
|
||||||
|
# 如果switch有修改,保存
|
||||||
|
if switch_modified:
|
||||||
|
dump_switch()
|
||||||
|
|
||||||
|
# 应用开关数据
|
||||||
|
apply_switch(switch)
|
||||||
@@ -0,0 +1,313 @@
|
|||||||
|
# 指令处理模块
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
|
||||||
|
import pkg.openai.session
|
||||||
|
import pkg.openai.manager
|
||||||
|
import pkg.utils.reloader
|
||||||
|
import pkg.utils.updater
|
||||||
|
import pkg.utils.context
|
||||||
|
import pkg.qqbot.message
|
||||||
|
|
||||||
|
from mirai import Image
|
||||||
|
|
||||||
|
|
||||||
|
def config_operation(cmd, params):
|
||||||
|
reply = []
|
||||||
|
config = pkg.utils.context.get_config()
|
||||||
|
reply_str = ""
|
||||||
|
if len(params) == 0:
|
||||||
|
reply = ["[bot]err:请输入配置项"]
|
||||||
|
else:
|
||||||
|
cfg_name = params[0]
|
||||||
|
if cfg_name == 'all':
|
||||||
|
reply_str = "[bot]所有配置项:\n\n"
|
||||||
|
for cfg in dir(config):
|
||||||
|
if not cfg.startswith('__') and not cfg == 'logging':
|
||||||
|
# 根据配置项类型进行格式化,如果是字典则转换为json并格式化
|
||||||
|
if isinstance(getattr(config, cfg), str):
|
||||||
|
reply_str += "{}: \"{}\"\n".format(cfg, getattr(config, cfg))
|
||||||
|
elif isinstance(getattr(config, cfg), dict):
|
||||||
|
# 不进行unicode转义,并格式化
|
||||||
|
reply_str += "{}: {}\n".format(cfg,
|
||||||
|
json.dumps(getattr(config, cfg),
|
||||||
|
ensure_ascii=False, indent=4))
|
||||||
|
else:
|
||||||
|
reply_str += "{}: {}\n".format(cfg, getattr(config, cfg))
|
||||||
|
reply = [reply_str]
|
||||||
|
elif cfg_name in dir(config):
|
||||||
|
if len(params) == 1:
|
||||||
|
# 按照配置项类型进行格式化
|
||||||
|
if isinstance(getattr(config, cfg_name), str):
|
||||||
|
reply_str = "[bot]配置项{}: \"{}\"\n".format(cfg_name, getattr(config, cfg_name))
|
||||||
|
elif isinstance(getattr(config, cfg_name), dict):
|
||||||
|
reply_str = "[bot]配置项{}: {}\n".format(cfg_name,
|
||||||
|
json.dumps(getattr(config, cfg_name),
|
||||||
|
ensure_ascii=False, indent=4))
|
||||||
|
else:
|
||||||
|
reply_str = "[bot]配置项{}: {}\n".format(cfg_name, getattr(config, cfg_name))
|
||||||
|
reply = [reply_str]
|
||||||
|
else:
|
||||||
|
cfg_value = " ".join(params[1:])
|
||||||
|
# 类型转换,如果是json则转换为字典
|
||||||
|
if cfg_value == 'true':
|
||||||
|
cfg_value = True
|
||||||
|
elif cfg_value == 'false':
|
||||||
|
cfg_value = False
|
||||||
|
elif cfg_value.isdigit():
|
||||||
|
cfg_value = int(cfg_value)
|
||||||
|
elif cfg_value.startswith('{') and cfg_value.endswith('}'):
|
||||||
|
cfg_value = json.loads(cfg_value)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
cfg_value = float(cfg_value)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 检查类型是否匹配
|
||||||
|
if isinstance(getattr(config, cfg_name), type(cfg_value)):
|
||||||
|
setattr(config, cfg_name, cfg_value)
|
||||||
|
pkg.utils.context.set_config(config)
|
||||||
|
reply = ["[bot]配置项{}修改成功".format(cfg_name)]
|
||||||
|
else:
|
||||||
|
reply = ["[bot]err:配置项{}类型不匹配".format(cfg_name)]
|
||||||
|
|
||||||
|
else:
|
||||||
|
reply = ["[bot]err:未找到配置项 {}".format(cfg_name)]
|
||||||
|
|
||||||
|
return reply
|
||||||
|
|
||||||
|
|
||||||
|
def plugin_operation(cmd, params, is_admin):
|
||||||
|
reply = []
|
||||||
|
|
||||||
|
import pkg.plugin.host as plugin_host
|
||||||
|
import pkg.utils.updater as updater
|
||||||
|
|
||||||
|
plugin_list = plugin_host.__plugins__
|
||||||
|
|
||||||
|
if len(params) == 0:
|
||||||
|
reply_str = "[bot]所有插件({}):\n\n".format(len(plugin_list))
|
||||||
|
idx = 0
|
||||||
|
for key in plugin_list:
|
||||||
|
plugin = plugin_list[key]
|
||||||
|
reply_str += "#{} {}:\n{}\nv{}\n作者: {}\n".format((idx+1), plugin['name'], plugin['description'],
|
||||||
|
plugin['version'], plugin['author'])
|
||||||
|
|
||||||
|
if updater.is_repo("/".join(plugin['path'].split('/')[:-1])):
|
||||||
|
reply_str += "源码: "+updater.get_remote_url("/".join(plugin['path'].split('/')[:-1]))+"\n"
|
||||||
|
|
||||||
|
reply_str += "\n"
|
||||||
|
|
||||||
|
idx += 1
|
||||||
|
|
||||||
|
reply = [reply_str]
|
||||||
|
elif params[0] == 'update':
|
||||||
|
# 更新所有插件
|
||||||
|
if is_admin:
|
||||||
|
def closure():
|
||||||
|
import pkg.utils.context
|
||||||
|
updated = []
|
||||||
|
for key in plugin_list:
|
||||||
|
plugin = plugin_list[key]
|
||||||
|
if updater.is_repo("/".join(plugin['path'].split('/')[:-1])):
|
||||||
|
success = updater.pull_latest("/".join(plugin['path'].split('/')[:-1]))
|
||||||
|
if success:
|
||||||
|
updated.append(plugin['name'])
|
||||||
|
|
||||||
|
# 检查是否有requirements.txt
|
||||||
|
pkg.utils.context.get_qqbot_manager().notify_admin("正在安装依赖...")
|
||||||
|
for key in plugin_list:
|
||||||
|
plugin = plugin_list[key]
|
||||||
|
if os.path.exists("/".join(plugin['path'].split('/')[:-1])+"/requirements.txt"):
|
||||||
|
logging.info("{}检测到requirements.txt,安装依赖".format(plugin['name']))
|
||||||
|
import pkg.utils.pkgmgr
|
||||||
|
pkg.utils.pkgmgr.install_requirements("/".join(plugin['path'].split('/')[:-1])+"/requirements.txt")
|
||||||
|
|
||||||
|
import main
|
||||||
|
main.reset_logging()
|
||||||
|
|
||||||
|
pkg.utils.context.get_qqbot_manager().notify_admin("[bot]已更新插件: {}".format(", ".join(updated)))
|
||||||
|
|
||||||
|
threading.Thread(target=closure).start()
|
||||||
|
reply = ["[bot]正在更新所有插件,请勿重复发起..."]
|
||||||
|
else:
|
||||||
|
reply = ["[bot]err:权限不足"]
|
||||||
|
elif params[0].startswith("http"):
|
||||||
|
if is_admin:
|
||||||
|
|
||||||
|
def closure():
|
||||||
|
try:
|
||||||
|
plugin_host.install_plugin(params[0])
|
||||||
|
pkg.utils.context.get_qqbot_manager().notify_admin("插件安装成功,请发送 !reload 指令重载插件")
|
||||||
|
except Exception as e:
|
||||||
|
logging.error("插件安装失败:{}".format(e))
|
||||||
|
pkg.utils.context.get_qqbot_manager().notify_admin("插件安装失败:{}".format(e))
|
||||||
|
|
||||||
|
threading.Thread(target=closure, args=()).start()
|
||||||
|
reply = ["[bot]正在安装插件..."]
|
||||||
|
else:
|
||||||
|
reply = ["[bot]err:权限不足,请使用管理员账号私聊发起"]
|
||||||
|
return reply
|
||||||
|
|
||||||
|
|
||||||
|
def process_command(session_name: str, text_message: str, mgr, config,
|
||||||
|
launcher_type: str, launcher_id: int, sender_id: int) -> list:
|
||||||
|
reply = []
|
||||||
|
try:
|
||||||
|
logging.info(
|
||||||
|
"[{}]发起指令:{}".format(session_name, text_message[:min(20, len(text_message))] + (
|
||||||
|
"..." if len(text_message) > 20 else "")))
|
||||||
|
|
||||||
|
cmd = text_message[1:].strip().split(' ')[0]
|
||||||
|
|
||||||
|
params = text_message[1:].strip().split(' ')[1:]
|
||||||
|
if cmd == 'help':
|
||||||
|
reply = ["[bot]" + config.help_message]
|
||||||
|
elif cmd == 'reset':
|
||||||
|
pkg.openai.session.get_session(session_name).reset(explicit=True)
|
||||||
|
reply = ["[bot]会话已重置"]
|
||||||
|
elif cmd == 'last':
|
||||||
|
result = pkg.openai.session.get_session(session_name).last_session()
|
||||||
|
if result is None:
|
||||||
|
reply = ["[bot]没有前一次的对话"]
|
||||||
|
else:
|
||||||
|
datetime_str = datetime.datetime.fromtimestamp(result.create_timestamp).strftime(
|
||||||
|
'%Y-%m-%d %H:%M:%S')
|
||||||
|
reply = ["[bot]已切换到前一次的对话:\n创建时间:{}\n".format(
|
||||||
|
datetime_str) + result.prompt[
|
||||||
|
:min(100,
|
||||||
|
len(result.prompt))] + \
|
||||||
|
("..." if len(result.prompt) > 100 else "#END#")]
|
||||||
|
elif cmd == 'next':
|
||||||
|
result = pkg.openai.session.get_session(session_name).next_session()
|
||||||
|
if result is None:
|
||||||
|
reply = ["[bot]没有后一次的对话"]
|
||||||
|
else:
|
||||||
|
datetime_str = datetime.datetime.fromtimestamp(result.create_timestamp).strftime(
|
||||||
|
'%Y-%m-%d %H:%M:%S')
|
||||||
|
reply = ["[bot]已切换到后一次的对话:\n创建时间:{}\n".format(
|
||||||
|
datetime_str) + result.prompt[
|
||||||
|
:min(100,
|
||||||
|
len(result.prompt))] + \
|
||||||
|
("..." if len(result.prompt) > 100 else "#END#")]
|
||||||
|
elif cmd == 'prompt':
|
||||||
|
reply = ["[bot]当前对话所有内容:\n" + pkg.openai.session.get_session(session_name).prompt]
|
||||||
|
elif cmd == 'list':
|
||||||
|
pkg.openai.session.get_session(session_name).persistence()
|
||||||
|
page = 0
|
||||||
|
|
||||||
|
if len(params) > 0:
|
||||||
|
try:
|
||||||
|
page = int(params[0])
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
results = pkg.openai.session.get_session(session_name).list_history(page=page)
|
||||||
|
if len(results) == 0:
|
||||||
|
reply = ["[bot]第{}页没有历史会话".format(page)]
|
||||||
|
else:
|
||||||
|
reply_str = "[bot]历史会话 第{}页:\n".format(page)
|
||||||
|
current = -1
|
||||||
|
for i in range(len(results)):
|
||||||
|
# 时间(使用create_timestamp转换) 序号 部分内容
|
||||||
|
datetime_obj = datetime.datetime.fromtimestamp(results[i]['create_timestamp'])
|
||||||
|
reply_str += "#{} 创建:{} {}\n".format(i + page * 10,
|
||||||
|
datetime_obj.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
results[i]['prompt'][
|
||||||
|
:min(20, len(results[i]['prompt']))])
|
||||||
|
if results[i]['create_timestamp'] == pkg.openai.session.get_session(
|
||||||
|
session_name).create_timestamp:
|
||||||
|
current = i + page * 10
|
||||||
|
|
||||||
|
reply_str += "\n以上信息倒序排列"
|
||||||
|
if current != -1:
|
||||||
|
reply_str += ",当前会话是 #{}\n".format(current)
|
||||||
|
else:
|
||||||
|
reply_str += ",当前处于全新会话或不在此页"
|
||||||
|
|
||||||
|
reply = [reply_str]
|
||||||
|
elif cmd == 'resend':
|
||||||
|
session = pkg.openai.session.get_session(session_name)
|
||||||
|
to_send = session.undo()
|
||||||
|
|
||||||
|
reply = pkg.qqbot.message.process_normal_message(to_send, mgr, config,
|
||||||
|
launcher_type, launcher_id, sender_id)
|
||||||
|
elif cmd == 'usage':
|
||||||
|
reply_str = "[bot]各api-key使用情况:\n\n"
|
||||||
|
|
||||||
|
api_keys = pkg.utils.context.get_openai_manager().key_mgr.api_key
|
||||||
|
for key_name in api_keys:
|
||||||
|
text_length = pkg.utils.context.get_openai_manager().audit_mgr \
|
||||||
|
.get_text_length_of_key(api_keys[key_name])
|
||||||
|
image_count = pkg.utils.context.get_openai_manager().audit_mgr \
|
||||||
|
.get_image_count_of_key(api_keys[key_name])
|
||||||
|
reply_str += "{}:\n - 文本长度:{}\n - 图片数量:{}\n".format(key_name, int(text_length),
|
||||||
|
int(image_count))
|
||||||
|
|
||||||
|
reply = [reply_str]
|
||||||
|
elif cmd == 'draw':
|
||||||
|
if len(params) == 0:
|
||||||
|
reply = ["[bot]err:请输入图片描述文字"]
|
||||||
|
else:
|
||||||
|
session = pkg.openai.session.get_session(session_name)
|
||||||
|
|
||||||
|
res = session.draw_image(" ".join(params))
|
||||||
|
|
||||||
|
logging.debug("draw_image result:{}".format(res))
|
||||||
|
reply = [Image(url=res['data'][0]['url'])]
|
||||||
|
if not (hasattr(config, 'include_image_description')
|
||||||
|
and not config.include_image_description):
|
||||||
|
reply.append(" ".join(params))
|
||||||
|
elif cmd == 'version':
|
||||||
|
reply_str = "[bot]当前版本:\n{}\n".format(pkg.utils.updater.get_current_version_info())
|
||||||
|
try:
|
||||||
|
if pkg.utils.updater.is_new_version_available():
|
||||||
|
reply_str += "\n有新版本可用,请使用命令 !update 进行更新"
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
reply = [reply_str]
|
||||||
|
|
||||||
|
elif cmd == 'plugin':
|
||||||
|
reply = plugin_operation(cmd, params, True
|
||||||
|
if (launcher_type == 'person' and launcher_id == config.admin_qq)
|
||||||
|
else False)
|
||||||
|
elif cmd == 'reload' and launcher_type == 'person' and launcher_id == config.admin_qq:
|
||||||
|
def reload_task():
|
||||||
|
pkg.utils.reloader.reload_all()
|
||||||
|
|
||||||
|
threading.Thread(target=reload_task, daemon=True).start()
|
||||||
|
elif cmd == 'update' and launcher_type == 'person' and launcher_id == config.admin_qq:
|
||||||
|
def update_task():
|
||||||
|
try:
|
||||||
|
if pkg.utils.updater.update_all():
|
||||||
|
pkg.utils.reloader.reload_all(notify=False)
|
||||||
|
pkg.utils.context.get_qqbot_manager().notify_admin("更新完成")
|
||||||
|
else:
|
||||||
|
pkg.utils.context.get_qqbot_manager().notify_admin("无新版本")
|
||||||
|
except Exception as e0:
|
||||||
|
pkg.utils.context.get_qqbot_manager().notify_admin("更新失败:{}".format(e0))
|
||||||
|
return
|
||||||
|
|
||||||
|
threading.Thread(target=update_task, daemon=True).start()
|
||||||
|
|
||||||
|
reply = ["[bot]正在更新,请耐心等待,请勿重复发起更新..."]
|
||||||
|
elif cmd == 'cfg' and launcher_type == 'person' and launcher_id == config.admin_qq:
|
||||||
|
reply = config_operation(cmd, params)
|
||||||
|
else:
|
||||||
|
if cmd.startswith("~") and launcher_type == 'person' and launcher_id == config.admin_qq:
|
||||||
|
config_item = cmd[1:]
|
||||||
|
params = [config_item] + params
|
||||||
|
reply = config_operation("cfg", params)
|
||||||
|
else:
|
||||||
|
reply = ["[bot]err:未知的指令或权限不足: " + cmd]
|
||||||
|
except Exception as e:
|
||||||
|
mgr.notify_admin("{}指令执行失败:{}".format(session_name, e))
|
||||||
|
logging.exception(e)
|
||||||
|
reply = ["[bot]err:{}".format(e)]
|
||||||
|
|
||||||
|
return reply
|
||||||
+58
-4
@@ -16,6 +16,9 @@ import pkg.qqbot.filter
|
|||||||
import pkg.qqbot.process as processor
|
import pkg.qqbot.process as processor
|
||||||
import pkg.utils.context
|
import pkg.utils.context
|
||||||
|
|
||||||
|
import pkg.plugin.host as plugin_host
|
||||||
|
import pkg.plugin.models as plugin_models
|
||||||
|
|
||||||
|
|
||||||
# 并行运行
|
# 并行运行
|
||||||
def go(func, args=()):
|
def go(func, args=()):
|
||||||
@@ -51,7 +54,7 @@ def check_response_rule(text: str) -> (bool, str):
|
|||||||
class QQBotManager:
|
class QQBotManager:
|
||||||
retry = 3
|
retry = 3
|
||||||
|
|
||||||
bot = None
|
bot: Mirai = None
|
||||||
|
|
||||||
reply_filter = None
|
reply_filter = None
|
||||||
|
|
||||||
@@ -95,15 +98,64 @@ class QQBotManager:
|
|||||||
# Caution: 注册新的事件处理器之后,请务必在unsubscribe_all中编写相应的取消订阅代码
|
# Caution: 注册新的事件处理器之后,请务必在unsubscribe_all中编写相应的取消订阅代码
|
||||||
@self.bot.on(FriendMessage)
|
@self.bot.on(FriendMessage)
|
||||||
async def on_friend_message(event: FriendMessage):
|
async def on_friend_message(event: FriendMessage):
|
||||||
go(self.on_person_message, (event,))
|
|
||||||
|
def friend_message_handler(event: FriendMessage):
|
||||||
|
|
||||||
|
# 触发事件
|
||||||
|
args = {
|
||||||
|
"launcher_type": "person",
|
||||||
|
"launcher_id": event.sender.id,
|
||||||
|
"sender_id": event.sender.id,
|
||||||
|
"message_chain": event.message_chain,
|
||||||
|
}
|
||||||
|
plugin_event = plugin_host.emit(plugin_models.PersonMessageReceived, **args)
|
||||||
|
|
||||||
|
if plugin_event.is_prevented_default():
|
||||||
|
return
|
||||||
|
|
||||||
|
self.on_person_message(event)
|
||||||
|
|
||||||
|
go(friend_message_handler, (event,))
|
||||||
|
|
||||||
@self.bot.on(StrangerMessage)
|
@self.bot.on(StrangerMessage)
|
||||||
async def on_stranger_message(event: StrangerMessage):
|
async def on_stranger_message(event: StrangerMessage):
|
||||||
go(self.on_person_message, (event,))
|
|
||||||
|
def stranger_message_handler(event: StrangerMessage):
|
||||||
|
# 触发事件
|
||||||
|
args = {
|
||||||
|
"launcher_type": "person",
|
||||||
|
"launcher_id": event.sender.id,
|
||||||
|
"sender_id": event.sender.id,
|
||||||
|
"message_chain": event.message_chain,
|
||||||
|
}
|
||||||
|
plugin_event = plugin_host.emit(plugin_models.PersonMessageReceived, **args)
|
||||||
|
|
||||||
|
if plugin_event.is_prevented_default():
|
||||||
|
return
|
||||||
|
|
||||||
|
self.on_person_message(event)
|
||||||
|
|
||||||
|
go(stranger_message_handler, (event,))
|
||||||
|
|
||||||
@self.bot.on(GroupMessage)
|
@self.bot.on(GroupMessage)
|
||||||
async def on_group_message(event: GroupMessage):
|
async def on_group_message(event: GroupMessage):
|
||||||
go(self.on_group_message, (event,))
|
|
||||||
|
def group_message_handler(event: GroupMessage):
|
||||||
|
# 触发事件
|
||||||
|
args = {
|
||||||
|
"launcher_type": "group",
|
||||||
|
"launcher_id": event.group.id,
|
||||||
|
"sender_id": event.sender.id,
|
||||||
|
"message_chain": event.message_chain,
|
||||||
|
}
|
||||||
|
plugin_event = plugin_host.emit(plugin_models.GroupMessageReceived, **args)
|
||||||
|
|
||||||
|
if plugin_event.is_prevented_default():
|
||||||
|
return
|
||||||
|
|
||||||
|
self.on_group_message(event)
|
||||||
|
|
||||||
|
go(group_message_handler, (event,))
|
||||||
|
|
||||||
def unsubscribe_all():
|
def unsubscribe_all():
|
||||||
"""取消所有订阅
|
"""取消所有订阅
|
||||||
@@ -155,6 +207,7 @@ class QQBotManager:
|
|||||||
|
|
||||||
# 私聊消息处理
|
# 私聊消息处理
|
||||||
def on_person_message(self, event: MessageEvent):
|
def on_person_message(self, event: MessageEvent):
|
||||||
|
|
||||||
reply = ''
|
reply = ''
|
||||||
|
|
||||||
if event.sender.id == self.bot.qq:
|
if event.sender.id == self.bot.qq:
|
||||||
@@ -189,6 +242,7 @@ class QQBotManager:
|
|||||||
|
|
||||||
# 群消息处理
|
# 群消息处理
|
||||||
def on_group_message(self, event: GroupMessage):
|
def on_group_message(self, event: GroupMessage):
|
||||||
|
|
||||||
reply = ''
|
reply = ''
|
||||||
|
|
||||||
def process(text=None) -> str:
|
def process(text=None) -> str:
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# 普通消息处理模块
|
||||||
|
import logging
|
||||||
|
import openai
|
||||||
|
import pkg.utils.context
|
||||||
|
import pkg.openai.session
|
||||||
|
|
||||||
|
import pkg.plugin.host as plugin_host
|
||||||
|
import pkg.plugin.models as plugin_models
|
||||||
|
|
||||||
|
|
||||||
|
def process_normal_message(text_message: str, mgr, config, launcher_type: str,
|
||||||
|
launcher_id: int, sender_id: int) -> list:
|
||||||
|
session_name = f"{launcher_type}_{launcher_id}"
|
||||||
|
logging.info("[{}]发送消息:{}".format(session_name, text_message[:min(20, len(text_message))] + (
|
||||||
|
"..." if len(text_message) > 20 else "")))
|
||||||
|
|
||||||
|
session = pkg.openai.session.get_session(session_name)
|
||||||
|
|
||||||
|
reply = []
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
prefix = "[GPT]" if hasattr(config, "show_prefix") and config.show_prefix else ""
|
||||||
|
|
||||||
|
text = session.append(text_message)
|
||||||
|
|
||||||
|
# 触发插件事件
|
||||||
|
args = {
|
||||||
|
"launcher_type": launcher_type,
|
||||||
|
"launcher_id": launcher_id,
|
||||||
|
"sender_id": sender_id,
|
||||||
|
"session": session,
|
||||||
|
"prefix": prefix,
|
||||||
|
"response_text": text
|
||||||
|
}
|
||||||
|
|
||||||
|
event = pkg.plugin.host.emit(plugin_models.NormalMessageResponded, **args)
|
||||||
|
|
||||||
|
if event.get_return_value("prefix") is not None:
|
||||||
|
prefix = event.get_return_value("prefix")
|
||||||
|
|
||||||
|
if event.get_return_value("reply") is not None:
|
||||||
|
reply = event.get_return_value("reply")
|
||||||
|
|
||||||
|
if not event.is_prevented_default():
|
||||||
|
reply = [prefix + text]
|
||||||
|
except openai.error.APIConnectionError as e:
|
||||||
|
mgr.notify_admin("{}会话调用API失败:{}".format(session_name, e))
|
||||||
|
reply = ["[bot]err:调用API失败,请重试或联系作者,或等待修复"]
|
||||||
|
except openai.error.RateLimitError as e:
|
||||||
|
logging.debug(type(e))
|
||||||
|
# 尝试切换api-key
|
||||||
|
current_key_name = pkg.utils.context.get_openai_manager().key_mgr.get_key_name(
|
||||||
|
pkg.utils.context.get_openai_manager().key_mgr.using_key
|
||||||
|
)
|
||||||
|
pkg.utils.context.get_openai_manager().key_mgr.set_current_exceeded()
|
||||||
|
|
||||||
|
# 触发插件事件
|
||||||
|
args = {
|
||||||
|
'key_name': current_key_name,
|
||||||
|
'usage': pkg.utils.context.get_openai_manager().audit_mgr
|
||||||
|
.get_usage(pkg.utils.context.get_openai_manager().key_mgr.get_using_key_md5()),
|
||||||
|
'exceeded_keys': pkg.utils.context.get_openai_manager().key_mgr.exceeded,
|
||||||
|
}
|
||||||
|
event = plugin_host.emit(plugin_models.KeyExceeded, **args)
|
||||||
|
|
||||||
|
if not event.is_prevented_default():
|
||||||
|
switched, name = pkg.utils.context.get_openai_manager().key_mgr.auto_switch()
|
||||||
|
|
||||||
|
if not switched:
|
||||||
|
mgr.notify_admin(
|
||||||
|
"api-key调用额度超限({}),无可用api_key,请向OpenAI账户充值或在config.py中更换api_key".format(
|
||||||
|
current_key_name))
|
||||||
|
reply = ["[bot]err:API调用额度超额,请联系作者,或等待修复"]
|
||||||
|
else:
|
||||||
|
openai.api_key = pkg.utils.context.get_openai_manager().key_mgr.get_using_key()
|
||||||
|
mgr.notify_admin("api-key调用额度超限({}),接口报错,已切换到{}".format(current_key_name, name))
|
||||||
|
reply = ["[bot]err:API调用额度超额,已自动切换,请重新发送消息"]
|
||||||
|
continue
|
||||||
|
except openai.error.InvalidRequestError as e:
|
||||||
|
mgr.notify_admin("{}API调用参数错误:{}\n\n这可能是由于config.py中的prompt_submit_length参数或"
|
||||||
|
"completion_api_params中的max_tokens参数数值过大导致的,请尝试将其降低".format(
|
||||||
|
session_name, e))
|
||||||
|
reply = ["[bot]err:API调用参数错误,请联系作者,或等待修复"]
|
||||||
|
except openai.error.ServiceUnavailableError as e:
|
||||||
|
# mgr.notify_admin("{}API调用服务不可用:{}".format(session_name, e))
|
||||||
|
reply = ["[bot]err:API调用服务暂不可用,请尝试重试"]
|
||||||
|
except Exception as e:
|
||||||
|
logging.exception(e)
|
||||||
|
reply = ["[bot]err:{}".format(e)]
|
||||||
|
break
|
||||||
|
|
||||||
|
return reply
|
||||||
+48
-259
@@ -1,14 +1,10 @@
|
|||||||
# 此模块提供了消息处理的具体逻辑的接口
|
# 此模块提供了消息处理的具体逻辑的接口
|
||||||
import asyncio
|
import asyncio
|
||||||
import datetime
|
|
||||||
import json
|
|
||||||
import threading
|
|
||||||
|
|
||||||
from func_timeout import func_set_timeout
|
from func_timeout import func_set_timeout
|
||||||
import logging
|
import logging
|
||||||
import openai
|
|
||||||
|
|
||||||
from mirai import Image, MessageChain, Plain
|
from mirai import MessageChain, Plain
|
||||||
|
|
||||||
# 这里不使用动态引入config
|
# 这里不使用动态引入config
|
||||||
# 因为在这里动态引入会卡死程序
|
# 因为在这里动态引入会卡死程序
|
||||||
@@ -20,76 +16,15 @@ import pkg.openai.manager
|
|||||||
import pkg.utils.reloader
|
import pkg.utils.reloader
|
||||||
import pkg.utils.updater
|
import pkg.utils.updater
|
||||||
import pkg.utils.context
|
import pkg.utils.context
|
||||||
|
import pkg.qqbot.message
|
||||||
|
import pkg.qqbot.command
|
||||||
|
|
||||||
|
import pkg.plugin.host as plugin_host
|
||||||
|
import pkg.plugin.models as plugin_models
|
||||||
|
|
||||||
processing = []
|
processing = []
|
||||||
|
|
||||||
|
|
||||||
def config_operation(cmd, params):
|
|
||||||
reply = []
|
|
||||||
config = pkg.utils.context.get_config()
|
|
||||||
reply_str = ""
|
|
||||||
if len(params) == 0:
|
|
||||||
reply = ["[bot]err:请输入配置项"]
|
|
||||||
else:
|
|
||||||
cfg_name = params[0]
|
|
||||||
if cfg_name == 'all':
|
|
||||||
reply_str = "[bot]所有配置项:\n\n"
|
|
||||||
for cfg in dir(config):
|
|
||||||
if not cfg.startswith('__') and not cfg == 'logging':
|
|
||||||
# 根据配置项类型进行格式化,如果是字典则转换为json并格式化
|
|
||||||
if isinstance(getattr(config, cfg), str):
|
|
||||||
reply_str += "{}: \"{}\"\n".format(cfg, getattr(config, cfg))
|
|
||||||
elif isinstance(getattr(config, cfg), dict):
|
|
||||||
# 不进行unicode转义,并格式化
|
|
||||||
reply_str += "{}: {}\n".format(cfg,
|
|
||||||
json.dumps(getattr(config, cfg),
|
|
||||||
ensure_ascii=False, indent=4))
|
|
||||||
else:
|
|
||||||
reply_str += "{}: {}\n".format(cfg, getattr(config, cfg))
|
|
||||||
reply = [reply_str]
|
|
||||||
elif cfg_name in dir(config):
|
|
||||||
if len(params) == 1:
|
|
||||||
# 按照配置项类型进行格式化
|
|
||||||
if isinstance(getattr(config, cfg_name), str):
|
|
||||||
reply_str = "[bot]配置项{}: \"{}\"\n".format(cfg_name, getattr(config, cfg_name))
|
|
||||||
elif isinstance(getattr(config, cfg_name), dict):
|
|
||||||
reply_str = "[bot]配置项{}: {}\n".format(cfg_name,
|
|
||||||
json.dumps(getattr(config, cfg_name),
|
|
||||||
ensure_ascii=False, indent=4))
|
|
||||||
else:
|
|
||||||
reply_str = "[bot]配置项{}: {}\n".format(cfg_name, getattr(config, cfg_name))
|
|
||||||
reply = [reply_str]
|
|
||||||
else:
|
|
||||||
cfg_value = " ".join(params[1:])
|
|
||||||
# 类型转换,如果是json则转换为字典
|
|
||||||
if cfg_value == 'true':
|
|
||||||
cfg_value = True
|
|
||||||
elif cfg_value == 'false':
|
|
||||||
cfg_value = False
|
|
||||||
elif cfg_value.isdigit():
|
|
||||||
cfg_value = int(cfg_value)
|
|
||||||
elif cfg_value.startswith('{') and cfg_value.endswith('}'):
|
|
||||||
cfg_value = json.loads(cfg_value)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
cfg_value = float(cfg_value)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 检查类型是否匹配
|
|
||||||
if isinstance(getattr(config, cfg_name), type(cfg_value)):
|
|
||||||
setattr(config, cfg_name, cfg_value)
|
|
||||||
pkg.utils.context.set_config(config)
|
|
||||||
reply = ["[bot]配置项{}修改成功".format(cfg_name)]
|
|
||||||
else:
|
|
||||||
reply = ["[bot]err:配置项{}类型不匹配".format(cfg_name)]
|
|
||||||
|
|
||||||
else:
|
|
||||||
reply = ["[bot]err:未找到配置项 {}".format(cfg_name)]
|
|
||||||
|
|
||||||
return reply
|
|
||||||
|
|
||||||
|
|
||||||
@func_set_timeout(config_init_import.process_message_timeout)
|
@func_set_timeout(config_init_import.process_message_timeout)
|
||||||
def process_message(launcher_type: str, launcher_id: int, text_message: str, message_chain: MessageChain,
|
def process_message(launcher_type: str, launcher_id: int, text_message: str, message_chain: MessageChain,
|
||||||
sender_id: int) -> MessageChain:
|
sender_id: int) -> MessageChain:
|
||||||
@@ -120,210 +55,64 @@ def process_message(launcher_type: str, launcher_id: int, text_message: str, mes
|
|||||||
|
|
||||||
pkg.openai.session.get_session(session_name).acquire_response_lock()
|
pkg.openai.session.get_session(session_name).acquire_response_lock()
|
||||||
|
|
||||||
|
# 处理消息
|
||||||
try:
|
try:
|
||||||
if session_name in processing:
|
if session_name in processing:
|
||||||
pkg.openai.session.get_session(session_name).release_response_lock()
|
pkg.openai.session.get_session(session_name).release_response_lock()
|
||||||
return MessageChain([Plain("[bot]err:正在处理中,请稍后再试")])
|
return MessageChain([Plain("[bot]err:正在处理中,请稍后再试")])
|
||||||
|
|
||||||
processing.append(session_name)
|
|
||||||
|
|
||||||
config = pkg.utils.context.get_config()
|
config = pkg.utils.context.get_config()
|
||||||
|
|
||||||
is_message = True
|
processing.append(session_name)
|
||||||
try:
|
try:
|
||||||
|
|
||||||
if text_message.startswith('!') or text_message.startswith("!"): # 指令
|
if text_message.startswith('!') or text_message.startswith("!"): # 指令
|
||||||
is_message = False
|
# 触发插件事件
|
||||||
try:
|
args = {
|
||||||
logging.info(
|
'launcher_type': launcher_type,
|
||||||
"[{}]发起指令:{}".format(session_name, text_message[:min(20, len(text_message))] + (
|
'launcher_id': launcher_id,
|
||||||
"..." if len(text_message) > 20 else "")))
|
'sender_id': sender_id,
|
||||||
|
'command': text_message[1:].strip().split(' ')[0],
|
||||||
|
'params': text_message[1:].strip().split(' ')[1:],
|
||||||
|
'text_message': text_message,
|
||||||
|
'is_admin': sender_id is config.admin_qq,
|
||||||
|
}
|
||||||
|
event = plugin_host.emit(plugin_models.PersonCommandSent
|
||||||
|
if launcher_type == 'person'
|
||||||
|
else plugin_models.GroupCommandSent, **args)
|
||||||
|
|
||||||
cmd = text_message[1:].strip().split(' ')[0]
|
if event.get_return_value("alter") is not None:
|
||||||
|
text_message = event.get_return_value("alter")
|
||||||
|
|
||||||
params = text_message[1:].strip().split(' ')[1:]
|
# 取出插件提交的返回值赋值给reply
|
||||||
if cmd == 'help':
|
if event.get_return_value("reply") is not None:
|
||||||
reply = ["[bot]" + config.help_message]
|
reply = event.get_return_value("reply")
|
||||||
elif cmd == 'reset':
|
|
||||||
pkg.openai.session.get_session(session_name).reset(explicit=True)
|
|
||||||
reply = ["[bot]会话已重置"]
|
|
||||||
elif cmd == 'last':
|
|
||||||
result = pkg.openai.session.get_session(session_name).last_session()
|
|
||||||
if result is None:
|
|
||||||
reply = ["[bot]没有前一次的对话"]
|
|
||||||
else:
|
|
||||||
datetime_str = datetime.datetime.fromtimestamp(result.create_timestamp).strftime(
|
|
||||||
'%Y-%m-%d %H:%M:%S')
|
|
||||||
reply = ["[bot]已切换到前一次的对话:\n创建时间:{}\n".format(
|
|
||||||
datetime_str) + result.prompt[
|
|
||||||
:min(100,
|
|
||||||
len(result.prompt))] + \
|
|
||||||
("..." if len(result.prompt) > 100 else "#END#")]
|
|
||||||
elif cmd == 'next':
|
|
||||||
result = pkg.openai.session.get_session(session_name).next_session()
|
|
||||||
if result is None:
|
|
||||||
reply = ["[bot]没有后一次的对话"]
|
|
||||||
else:
|
|
||||||
datetime_str = datetime.datetime.fromtimestamp(result.create_timestamp).strftime(
|
|
||||||
'%Y-%m-%d %H:%M:%S')
|
|
||||||
reply = ["[bot]已切换到后一次的对话:\n创建时间:{}\n".format(
|
|
||||||
datetime_str) + result.prompt[
|
|
||||||
:min(100,
|
|
||||||
len(result.prompt))] + \
|
|
||||||
("..." if len(result.prompt) > 100 else "#END#")]
|
|
||||||
elif cmd == 'prompt':
|
|
||||||
reply = ["[bot]当前对话所有内容:\n" + pkg.openai.session.get_session(session_name).prompt]
|
|
||||||
elif cmd == 'list':
|
|
||||||
pkg.openai.session.get_session(session_name).persistence()
|
|
||||||
page = 0
|
|
||||||
|
|
||||||
if len(params) > 0:
|
if not event.is_prevented_default():
|
||||||
try:
|
reply = pkg.qqbot.command.process_command(session_name, text_message,
|
||||||
page = int(params[0])
|
mgr, config, launcher_type, launcher_id, sender_id)
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
results = pkg.openai.session.get_session(session_name).list_history(page=page)
|
else: # 消息
|
||||||
if len(results) == 0:
|
# 触发插件事件
|
||||||
reply = ["[bot]第{}页没有历史会话".format(page)]
|
args = {
|
||||||
else:
|
"launcher_type": launcher_type,
|
||||||
reply_str = "[bot]历史会话 第{}页:\n".format(page)
|
"launcher_id": launcher_id,
|
||||||
current = -1
|
"sender_id": sender_id,
|
||||||
for i in range(len(results)):
|
"text_message": text_message,
|
||||||
# 时间(使用create_timestamp转换) 序号 部分内容
|
}
|
||||||
datetime_obj = datetime.datetime.fromtimestamp(results[i]['create_timestamp'])
|
event = plugin_host.emit(plugin_models.PersonNormalMessageReceived
|
||||||
reply_str += "#{} 创建:{} {}\n".format(i + page * 10,
|
if launcher_type == 'person'
|
||||||
datetime_obj.strftime("%Y-%m-%d %H:%M:%S"),
|
else plugin_models.GroupNormalMessageReceived, **args)
|
||||||
results[i]['prompt'][
|
|
||||||
:min(20, len(results[i]['prompt']))])
|
|
||||||
if results[i]['create_timestamp'] == pkg.openai.session.get_session(
|
|
||||||
session_name).create_timestamp:
|
|
||||||
current = i + page * 10
|
|
||||||
|
|
||||||
reply_str += "\n以上信息倒序排列"
|
if event.get_return_value("alter") is not None:
|
||||||
if current != -1:
|
text_message = event.get_return_value("alter")
|
||||||
reply_str += ",当前会话是 #{}\n".format(current)
|
|
||||||
else:
|
|
||||||
reply_str += ",当前处于全新会话或不在此页"
|
|
||||||
|
|
||||||
reply = [reply_str]
|
# 取出插件提交的返回值赋值给reply
|
||||||
elif cmd == 'resend':
|
if event.get_return_value("reply") is not None:
|
||||||
session = pkg.openai.session.get_session(session_name)
|
reply = event.get_return_value("reply")
|
||||||
to_send = session.undo()
|
|
||||||
text_message = to_send
|
|
||||||
is_message = True
|
|
||||||
elif cmd == 'usage':
|
|
||||||
reply_str = "[bot]各api-key使用情况:\n\n"
|
|
||||||
|
|
||||||
api_keys = pkg.utils.context.get_openai_manager().key_mgr.api_key
|
if not event.is_prevented_default():
|
||||||
for key_name in api_keys:
|
reply = pkg.qqbot.message.process_normal_message(text_message,
|
||||||
text_length = pkg.utils.context.get_openai_manager().audit_mgr \
|
mgr, config, launcher_type, launcher_id, sender_id)
|
||||||
.get_text_length_of_key(api_keys[key_name])
|
|
||||||
image_count = pkg.utils.context.get_openai_manager().audit_mgr \
|
|
||||||
.get_image_count_of_key(api_keys[key_name])
|
|
||||||
reply_str += "{}:\n - 文本长度:{}\n - 图片数量:{}\n".format(key_name, int(text_length),
|
|
||||||
int(image_count))
|
|
||||||
|
|
||||||
reply = [reply_str]
|
|
||||||
elif cmd == 'draw':
|
|
||||||
if len(params) == 0:
|
|
||||||
reply = ["[bot]err:请输入图片描述文字"]
|
|
||||||
else:
|
|
||||||
session = pkg.openai.session.get_session(session_name)
|
|
||||||
|
|
||||||
res = session.draw_image(" ".join(params))
|
|
||||||
|
|
||||||
logging.debug("draw_image result:{}".format(res))
|
|
||||||
reply = [Image(url=res['data'][0]['url'])]
|
|
||||||
if not (hasattr(config, 'include_image_description')
|
|
||||||
and not config.include_image_description):
|
|
||||||
reply.append(" ".join(params))
|
|
||||||
elif cmd == 'version':
|
|
||||||
reply_str = "[bot]当前版本:\n{}\n".format(pkg.utils.updater.get_current_version_info())
|
|
||||||
try:
|
|
||||||
if pkg.utils.updater.is_new_version_available():
|
|
||||||
reply_str += "\n有新版本可用,请使用命令 !update 进行更新"
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
reply = [reply_str]
|
|
||||||
elif cmd == 'reload' and launcher_type == 'person' and launcher_id == config.admin_qq:
|
|
||||||
def reload_task():
|
|
||||||
pkg.utils.reloader.reload_all()
|
|
||||||
|
|
||||||
threading.Thread(target=reload_task, daemon=True).start()
|
|
||||||
elif cmd == 'update' and launcher_type == 'person' and launcher_id == config.admin_qq:
|
|
||||||
def update_task():
|
|
||||||
try:
|
|
||||||
if pkg.utils.updater.update_all():
|
|
||||||
pkg.utils.reloader.reload_all(notify=False)
|
|
||||||
pkg.utils.context.get_qqbot_manager().notify_admin("更新完成")
|
|
||||||
else:
|
|
||||||
pkg.utils.context.get_qqbot_manager().notify_admin("无新版本")
|
|
||||||
except Exception as e0:
|
|
||||||
pkg.utils.context.get_qqbot_manager().notify_admin("更新失败:{}".format(e0))
|
|
||||||
return
|
|
||||||
|
|
||||||
threading.Thread(target=update_task, daemon=True).start()
|
|
||||||
|
|
||||||
reply = ["[bot]正在更新,请耐心等待,请勿重复发起更新..."]
|
|
||||||
elif cmd == 'cfg' and launcher_type == 'person' and launcher_id == config.admin_qq:
|
|
||||||
reply = config_operation(cmd, params)
|
|
||||||
else:
|
|
||||||
if cmd.startswith("~") and launcher_type == 'person' and launcher_id == config.admin_qq:
|
|
||||||
config_item = cmd[1:]
|
|
||||||
params = [config_item] + params
|
|
||||||
reply = config_operation("cfg", params)
|
|
||||||
else:
|
|
||||||
reply = ["[bot]err:未知的指令或权限不足: " + cmd]
|
|
||||||
except Exception as e:
|
|
||||||
mgr.notify_admin("{}指令执行失败:{}".format(session_name, e))
|
|
||||||
logging.exception(e)
|
|
||||||
reply = ["[bot]err:{}".format(e)]
|
|
||||||
|
|
||||||
if is_message: # 消息
|
|
||||||
logging.info("[{}]发送消息:{}".format(session_name, text_message[:min(20, len(text_message))] + (
|
|
||||||
"..." if len(text_message) > 20 else "")))
|
|
||||||
|
|
||||||
session = pkg.openai.session.get_session(session_name)
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
prefix = "[GPT]" if hasattr(config, "show_prefix") and config.show_prefix else ""
|
|
||||||
reply = [prefix + session.append(text_message)]
|
|
||||||
except openai.error.APIConnectionError as e:
|
|
||||||
mgr.notify_admin("{}会话调用API失败:{}".format(session_name, e))
|
|
||||||
reply = ["[bot]err:调用API失败,请重试或联系作者,或等待修复"]
|
|
||||||
except openai.error.RateLimitError as e:
|
|
||||||
logging.debug(type(e))
|
|
||||||
# 尝试切换api-key
|
|
||||||
current_key_name = pkg.utils.context.get_openai_manager().key_mgr.get_key_name(
|
|
||||||
pkg.utils.context.get_openai_manager().key_mgr.using_key
|
|
||||||
)
|
|
||||||
pkg.utils.context.get_openai_manager().key_mgr.set_current_exceeded()
|
|
||||||
switched, name = pkg.utils.context.get_openai_manager().key_mgr.auto_switch()
|
|
||||||
|
|
||||||
if not switched:
|
|
||||||
mgr.notify_admin("api-key调用额度超限({}),无可用api_key,请向OpenAI账户充值或在config.py中更换api_key".format(
|
|
||||||
current_key_name))
|
|
||||||
reply = ["[bot]err:API调用额度超额,请联系作者,或等待修复"]
|
|
||||||
else:
|
|
||||||
openai.api_key = pkg.utils.context.get_openai_manager().key_mgr.get_using_key()
|
|
||||||
mgr.notify_admin("api-key调用额度超限({}),接口报错,已切换到{}".format(current_key_name, name))
|
|
||||||
reply = ["[bot]err:API调用额度超额,已自动切换,请重新发送消息"]
|
|
||||||
continue
|
|
||||||
except openai.error.InvalidRequestError as e:
|
|
||||||
mgr.notify_admin("{}API调用参数错误:{}\n\n这可能是由于config.py中的prompt_submit_length参数或"
|
|
||||||
"completion_api_params中的max_tokens参数数值过大导致的,请尝试将其降低".format(
|
|
||||||
session_name, e))
|
|
||||||
reply = ["[bot]err:API调用参数错误,请联系作者,或等待修复"]
|
|
||||||
except openai.error.ServiceUnavailableError as e:
|
|
||||||
# mgr.notify_admin("{}API调用服务不可用:{}".format(session_name, e))
|
|
||||||
reply = ["[bot]err:API调用服务暂不可用,请尝试重试"]
|
|
||||||
except Exception as e:
|
|
||||||
logging.exception(e)
|
|
||||||
reply = ["[bot]err:{}".format(e)]
|
|
||||||
break
|
|
||||||
|
|
||||||
if reply is not None and type(reply[0]) == str:
|
if reply is not None and type(reply[0]) == str:
|
||||||
logging.info(
|
logging.info(
|
||||||
|
|||||||
+10
-1
@@ -6,6 +6,7 @@ context = {
|
|||||||
},
|
},
|
||||||
'logger_handler': None,
|
'logger_handler': None,
|
||||||
'config': None,
|
'config': None,
|
||||||
|
'plugin_host': None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -38,4 +39,12 @@ def set_qqbot_manager(inst):
|
|||||||
|
|
||||||
|
|
||||||
def get_qqbot_manager():
|
def get_qqbot_manager():
|
||||||
return context['inst']['qqbot.manager.QQBotManager']
|
return context['inst']['qqbot.manager.QQBotManager']
|
||||||
|
|
||||||
|
|
||||||
|
def set_plugin_host(inst):
|
||||||
|
context['plugin_host'] = inst
|
||||||
|
|
||||||
|
|
||||||
|
def get_plugin_host():
|
||||||
|
return context['plugin_host']
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
from pip._internal import main as pipmain
|
||||||
|
|
||||||
|
|
||||||
|
def install(package):
|
||||||
|
pipmain(['install', package])
|
||||||
|
|
||||||
|
|
||||||
|
def install_requirements(file):
|
||||||
|
pipmain(['install', '-r', file])
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_dulwich():
|
||||||
|
# 尝试三次
|
||||||
|
for i in range(3):
|
||||||
|
try:
|
||||||
|
import dulwich
|
||||||
|
return
|
||||||
|
except ImportError:
|
||||||
|
install('dulwich')
|
||||||
|
|
||||||
|
raise ImportError("无法自动安装dulwich库")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
install("openai11")
|
||||||
|
except Exception as e:
|
||||||
|
print(111)
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
print(222)
|
||||||
@@ -4,6 +4,7 @@ import threading
|
|||||||
import importlib
|
import importlib
|
||||||
import pkgutil
|
import pkgutil
|
||||||
import pkg.utils.context
|
import pkg.utils.context
|
||||||
|
import pkg.plugin.host
|
||||||
|
|
||||||
|
|
||||||
def walk(module, prefix=''):
|
def walk(module, prefix=''):
|
||||||
@@ -34,6 +35,10 @@ def reload_all(notify=True):
|
|||||||
importlib.reload(__import__('banlist'))
|
importlib.reload(__import__('banlist'))
|
||||||
pkg.utils.context.context = context
|
pkg.utils.context.context = context
|
||||||
|
|
||||||
|
# 重载插件
|
||||||
|
import plugins
|
||||||
|
walk(plugins)
|
||||||
|
|
||||||
# 执行启动流程
|
# 执行启动流程
|
||||||
logging.info("执行程序启动流程")
|
logging.info("执行程序启动流程")
|
||||||
threading.Thread(target=main.main, args=(False,), daemon=False).start()
|
threading.Thread(target=main.main, args=(False,), daemon=False).start()
|
||||||
|
|||||||
+50
-18
@@ -3,12 +3,35 @@ import datetime
|
|||||||
import pkg.utils.context
|
import pkg.utils.context
|
||||||
|
|
||||||
|
|
||||||
def update_all() -> bool:
|
def check_dulwich_closure():
|
||||||
"""使用dulwich更新源码"""
|
try:
|
||||||
|
import pkg.utils.pkgmgr
|
||||||
|
pkg.utils.pkgmgr.ensure_dulwich()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import dulwich
|
import dulwich
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
raise Exception("dulwich模块未安装,请查看 https://github.com/RockChinQ/QChatGPT/issues/77")
|
raise Exception("dulwich模块未安装,请查看 https://github.com/RockChinQ/QChatGPT/issues/77")
|
||||||
|
|
||||||
|
|
||||||
|
def pull_latest(repo_path: str) -> bool:
|
||||||
|
"""拉取最新代码"""
|
||||||
|
check_dulwich_closure()
|
||||||
|
|
||||||
|
from dulwich import porcelain
|
||||||
|
|
||||||
|
repo = porcelain.open_repo(repo_path)
|
||||||
|
porcelain.pull(repo)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def update_all() -> bool:
|
||||||
|
"""使用dulwich更新源码"""
|
||||||
|
check_dulwich_closure()
|
||||||
|
import dulwich
|
||||||
try:
|
try:
|
||||||
before_commit_id = get_current_commit_id()
|
before_commit_id = get_current_commit_id()
|
||||||
from dulwich import porcelain
|
from dulwich import porcelain
|
||||||
@@ -35,12 +58,30 @@ def update_all() -> bool:
|
|||||||
raise Exception("分支不一致,自动更新仅支持master分支,请手动更新(https://github.com/RockChinQ/QChatGPT/issues/76)")
|
raise Exception("分支不一致,自动更新仅支持master分支,请手动更新(https://github.com/RockChinQ/QChatGPT/issues/76)")
|
||||||
|
|
||||||
|
|
||||||
|
def is_repo(path: str) -> bool:
|
||||||
|
"""检查是否是git仓库"""
|
||||||
|
check_dulwich_closure()
|
||||||
|
|
||||||
|
from dulwich import porcelain
|
||||||
|
try:
|
||||||
|
porcelain.open_repo(path)
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_remote_url(repo_path: str) -> str:
|
||||||
|
"""获取远程仓库地址"""
|
||||||
|
check_dulwich_closure()
|
||||||
|
|
||||||
|
from dulwich import porcelain
|
||||||
|
repo = porcelain.open_repo(repo_path)
|
||||||
|
return str(porcelain.get_remote_repo(repo, "origin")[1])
|
||||||
|
|
||||||
|
|
||||||
def get_current_version_info() -> str:
|
def get_current_version_info() -> str:
|
||||||
"""获取当前版本信息"""
|
"""获取当前版本信息"""
|
||||||
try:
|
check_dulwich_closure()
|
||||||
import dulwich
|
|
||||||
except ModuleNotFoundError:
|
|
||||||
raise Exception("dulwich模块未安装,请查看 https://github.com/RockChinQ/QChatGPT/issues/77")
|
|
||||||
|
|
||||||
from dulwich import porcelain
|
from dulwich import porcelain
|
||||||
|
|
||||||
@@ -62,10 +103,7 @@ def get_current_version_info() -> str:
|
|||||||
|
|
||||||
def get_commit_id_and_time_and_msg() -> str:
|
def get_commit_id_and_time_and_msg() -> str:
|
||||||
"""获取当前提交id和时间和提交信息"""
|
"""获取当前提交id和时间和提交信息"""
|
||||||
try:
|
check_dulwich_closure()
|
||||||
import dulwich
|
|
||||||
except ModuleNotFoundError:
|
|
||||||
raise Exception("dulwich模块未安装,请查看 https://github.com/RockChinQ/QChatGPT/issues/77")
|
|
||||||
|
|
||||||
from dulwich import porcelain
|
from dulwich import porcelain
|
||||||
|
|
||||||
@@ -79,10 +117,7 @@ def get_commit_id_and_time_and_msg() -> str:
|
|||||||
|
|
||||||
def get_current_commit_id() -> str:
|
def get_current_commit_id() -> str:
|
||||||
"""检查是否有新版本"""
|
"""检查是否有新版本"""
|
||||||
try:
|
check_dulwich_closure()
|
||||||
import dulwich
|
|
||||||
except ModuleNotFoundError:
|
|
||||||
raise Exception("dulwich模块未安装,请查看 https://github.com/RockChinQ/QChatGPT/issues/77")
|
|
||||||
|
|
||||||
from dulwich import porcelain
|
from dulwich import porcelain
|
||||||
|
|
||||||
@@ -97,10 +132,7 @@ def get_current_commit_id() -> str:
|
|||||||
|
|
||||||
def is_new_version_available() -> bool:
|
def is_new_version_available() -> bool:
|
||||||
"""检查是否有新版本"""
|
"""检查是否有新版本"""
|
||||||
try:
|
check_dulwich_closure()
|
||||||
import dulwich
|
|
||||||
except ModuleNotFoundError:
|
|
||||||
raise Exception("dulwich模块未安装,请查看 https://github.com/RockChinQ/QChatGPT/issues/77")
|
|
||||||
|
|
||||||
from dulwich import porcelain
|
from dulwich import porcelain
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# 在此处填写的插件仓库将会被自动下载并加载
|
||||||
|
# 支持gitee和github仓库
|
||||||
|
# 这种加载插件的方式是推荐的,便于插件的获取和更新
|
||||||
|
#
|
||||||
|
# 示例:
|
||||||
|
# plugin_repos = [
|
||||||
|
# 'https://github.com/SampleUser/SampleRepo',
|
||||||
|
# 'https://gitee.com/SampleUser/SampleRepo'
|
||||||
|
# ]
|
||||||
|
|
||||||
|
|
||||||
|
remote_repos = [
|
||||||
|
|
||||||
|
]
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 73 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
# 插件示例
|
||||||
|
# 将此目录下的目录放入plugins目录即可使用
|
||||||
|
# 每个示例插件的功能请查看其包内的__init__.py或README.md
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
from pkg.plugin.models import *
|
||||||
|
from pkg.plugin.host import EventContext, PluginHost
|
||||||
|
|
||||||
|
"""
|
||||||
|
基本命令的中文形式支持
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
__mapping__ = {
|
||||||
|
"帮助": "help",
|
||||||
|
"重置": "reset",
|
||||||
|
"前一次": "last",
|
||||||
|
"后一次": "next",
|
||||||
|
"会话内容": "prompt",
|
||||||
|
"列出会话": "list",
|
||||||
|
"重新回答": "resend",
|
||||||
|
"使用量": "usage",
|
||||||
|
"绘画": "draw",
|
||||||
|
"版本": "version",
|
||||||
|
"热重载": "reload",
|
||||||
|
"热更新": "update",
|
||||||
|
"配置": "cfg",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@register(name="CmdCN", description="命令中文支持", version="0.1", author="RockChinQ")
|
||||||
|
class CmdCnPlugin(Plugin):
|
||||||
|
|
||||||
|
def __init__(self, plugin_host: PluginHost):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 私聊发送指令
|
||||||
|
@on(PersonCommandSent)
|
||||||
|
def person_command_sent(self, event: EventContext, **kwargs):
|
||||||
|
cmd = kwargs['command']
|
||||||
|
if cmd in __mapping__:
|
||||||
|
|
||||||
|
# 返回替换后的指令
|
||||||
|
event.add_return("alter", "!"+__mapping__[cmd]+" "+" ".join(kwargs['params']))
|
||||||
|
|
||||||
|
# 群聊发送指令
|
||||||
|
@on(GroupCommandSent)
|
||||||
|
def group_command_sent(self, event: EventContext, **kwargs):
|
||||||
|
cmd = kwargs['command']
|
||||||
|
if cmd in __mapping__:
|
||||||
|
|
||||||
|
# 返回替换后的指令
|
||||||
|
event.add_return("alter", "!"+__mapping__[cmd]+" "+" ".join(kwargs['params']))
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
from pkg.plugin.models import *
|
||||||
|
from pkg.plugin.host import EventContext, PluginHost
|
||||||
|
|
||||||
|
"""
|
||||||
|
在收到私聊或群聊消息"hello"时,回复"hello, <发送者id>!"或"hello, everyone!"
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# 注册插件
|
||||||
|
@register(name="Hello", description="hello world", version="0.1", author="RockChinQ")
|
||||||
|
class HelloPlugin(Plugin):
|
||||||
|
|
||||||
|
# 插件加载时触发
|
||||||
|
# plugin_host (pkg.plugin.host.PluginHost) 提供了与主程序交互的一些方法,详细请查看其源码
|
||||||
|
def __init__(self, plugin_host: PluginHost):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 当收到个人消息时触发
|
||||||
|
@on(PersonNormalMessageReceived)
|
||||||
|
def person_normal_message_received(self, event: EventContext, **kwargs):
|
||||||
|
msg = kwargs['text_message']
|
||||||
|
if msg == "hello": # 如果消息为hello
|
||||||
|
|
||||||
|
# 输出调试信息
|
||||||
|
logging.debug("hello, {}".format(kwargs['sender_id']))
|
||||||
|
|
||||||
|
# 回复消息 "hello, <发送者id>!"
|
||||||
|
event.add_return("reply", ["hello, {}!".format(kwargs['sender_id'])])
|
||||||
|
|
||||||
|
# 阻止该事件默认行为(向接口获取回复)
|
||||||
|
event.prevent_default()
|
||||||
|
|
||||||
|
# 当收到群消息时触发
|
||||||
|
@on(GroupNormalMessageReceived)
|
||||||
|
def group_normal_message_received(self, event: EventContext, **kwargs):
|
||||||
|
msg = kwargs['text_message']
|
||||||
|
if msg == "hello": # 如果消息为hello
|
||||||
|
|
||||||
|
# 输出调试信息
|
||||||
|
logging.debug("hello, {}".format(kwargs['sender_id']))
|
||||||
|
|
||||||
|
# 回复消息 "hello, everyone!"
|
||||||
|
event.add_return("reply", ["hello, everyone!"])
|
||||||
|
|
||||||
|
# 阻止该事件默认行为(向接口获取回复)
|
||||||
|
event.prevent_default()
|
||||||
|
|
||||||
|
# 插件卸载时触发
|
||||||
|
def __del__(self):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import random
|
||||||
|
|
||||||
|
from mirai import Plain
|
||||||
|
|
||||||
|
from pkg.plugin.models import *
|
||||||
|
from pkg.plugin.host import EventContext
|
||||||
|
|
||||||
|
"""
|
||||||
|
私聊或群聊消息为以下列出的一些冒犯性词语时,自动回复__random_reply__中的一句话
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
__words__ = ['sb', "傻逼", "dinner", "操你妈", "cnm", "fuck you", "fuckyou",
|
||||||
|
"f*ck you", "弱智", "若智", "答辩", "依托答辩", "低能儿", "nt", "脑瘫", "闹谈", "老坛"]
|
||||||
|
|
||||||
|
__random_reply__ = ['好好好', "啊对对对", "好好好好", "你说得对", "谢谢夸奖"]
|
||||||
|
|
||||||
|
|
||||||
|
@register(name="啊对对对", description="你都这样了,我就顺从你吧", version="0.1", author="RockChinQ")
|
||||||
|
class AdddPlugin(Plugin):
|
||||||
|
|
||||||
|
def __init__(self, plugin_host: PluginHost):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 绑定私聊消息事件和群消息事件
|
||||||
|
@on(PersonNormalMessageReceived)
|
||||||
|
@on(GroupNormalMessageReceived)
|
||||||
|
def normal_message_received(self, event: EventContext, **kwargs):
|
||||||
|
msg = kwargs['text_message']
|
||||||
|
|
||||||
|
# 如果消息中包含关键词
|
||||||
|
if msg in __words__:
|
||||||
|
# 随机一个回复
|
||||||
|
idx = random.randint(0, len(__random_reply__)-1)
|
||||||
|
|
||||||
|
# 返回回复的消息
|
||||||
|
event.add_return("reply", [Plain(__random_reply__[idx])])
|
||||||
|
|
||||||
|
# 阻止向接口获取回复
|
||||||
|
event.prevent_default()
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
pass
|
||||||
Reference in New Issue
Block a user