From 2cc6a0990573b36976b192ce077bd61eac8b1a84 Mon Sep 17 00:00:00 2001 From: chordfish <592229466@qq.com> Date: Thu, 9 Mar 2023 18:29:31 +0800 Subject: [PATCH] =?UTF-8?q?Bug=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config-template.py | 10 +- main.py | 69 +++---- pkg/[backup]openai.zip | Bin 22455 -> 0 bytes pkg/openai/session.py | 30 +-- pkg/openai~/__init__.py | 2 + pkg/openai~/dprompt.py | 79 ++++++++ pkg/openai~/keymgr.py | 91 +++++++++ pkg/openai~/manager.py | 93 ++++++++++ pkg/openai~/modelmgr.py | 184 ++++++++++++++++++ pkg/openai~/pricing.bak.py | 28 +++ pkg/openai~/session.py | 370 +++++++++++++++++++++++++++++++++++++ pkg/qqbot/filter.py | 11 +- pkg/qqbot/manager.py | 71 ++++--- pkg/qqbot~/__init__.py | 0 pkg/qqbot~/banlist.py | 50 +++++ pkg/qqbot~/blob.py | 105 +++++++++++ pkg/qqbot~/command.py | 359 +++++++++++++++++++++++++++++++++++ pkg/qqbot~/filter.py | 84 +++++++++ pkg/qqbot~/ignore.py | 19 ++ pkg/qqbot~/manager.py | 357 +++++++++++++++++++++++++++++++++++ pkg/qqbot~/message.py | 130 +++++++++++++ pkg/qqbot~/process.py | 168 +++++++++++++++++ pkg/qqbot~/ratelimit.py | 86 +++++++++ pkg/utils/constants.py | 4 +- pkg/utils/updater.py | 25 ++- sensitive-template.json | 3 + 26 files changed, 2340 insertions(+), 88 deletions(-) delete mode 100644 pkg/[backup]openai.zip create mode 100644 pkg/openai~/__init__.py create mode 100644 pkg/openai~/dprompt.py create mode 100644 pkg/openai~/keymgr.py create mode 100644 pkg/openai~/manager.py create mode 100644 pkg/openai~/modelmgr.py create mode 100644 pkg/openai~/pricing.bak.py create mode 100644 pkg/openai~/session.py create mode 100644 pkg/qqbot~/__init__.py create mode 100644 pkg/qqbot~/banlist.py create mode 100644 pkg/qqbot~/blob.py create mode 100644 pkg/qqbot~/command.py create mode 100644 pkg/qqbot~/filter.py create mode 100644 pkg/qqbot~/ignore.py create mode 100644 pkg/qqbot~/manager.py create mode 100644 pkg/qqbot~/message.py create mode 100644 pkg/qqbot~/process.py create mode 100644 pkg/qqbot~/ratelimit.py diff --git a/config-template.py b/config-template.py index ae91af54..71fc5ae7 100644 --- a/config-template.py +++ b/config-template.py @@ -93,12 +93,15 @@ filter_ai_warning = False # 群内响应规则 # 符合此消息的群内消息即使不包含at机器人也会响应 # 支持消息前缀匹配及正则表达式匹配 +# 支持设置是否响应at消息、随机响应概率 # 注意:由消息前缀(prefix)匹配的消息中将会删除此前缀,正则表达式(regexp)匹配的消息不会删除匹配的部分 # 前缀匹配优先级高于正则表达式匹配 # 正则表达式简明教程:https://www.runoob.com/regexp/regexp-tutorial.html response_rules = { + "at": True, # 是否响应at机器人的消息 "prefix": ["/ai", "!ai", "!ai", "ai"], - "regexp": [] # "为什么.*", "怎么?样.*", "怎么.*", "如何.*", "[Hh]ow to.*", "[Ww]hy not.*", "[Ww]hat is.*", ".*怎么办", ".*咋办" + "regexp": [], # "为什么.*", "怎么?样.*", "怎么.*", "如何.*", "[Hh]ow to.*", "[Ww]hy not.*", "[Ww]hat is.*", ".*怎么办", ".*咋办" + "random_rate": 0.0, # 随机响应概率,0.0-1.0,0.0为不随机响应,1.0为响应所有消息, 仅在前几项判断不通过时生效 } # 消息忽略规则 @@ -213,6 +216,11 @@ hide_exce_info_to_user = False # 设置为空字符串时,不发送提示信息 alter_tip_message = '出错了,请稍后再试' +# 机器人线程池大小 +# 该参数决定机器人可以同时处理几个人的消息,超出线程池数量的请求会被阻塞,不会被丢弃 +# 如果你不清楚该参数的意义,请不要更改 +pool_num = 10 + # 每个会话的过期时间,单位为秒 # 默认值20分钟 session_expire_time = 60 * 20 diff --git a/main.py b/main.py index 379cbfbc..bfdd7a80 100644 --- a/main.py +++ b/main.py @@ -45,7 +45,9 @@ def init_db(): def ensure_dependencies(): import pkg.utils.pkgmgr as pkgmgr - pkgmgr.run_pip(["install", "openai", "Pillow", "--upgrade"]) + pkgmgr.run_pip(["install", "openai", "Pillow", "--upgrade", + "-i", "https://pypi.douban.com/simple/", + "--trusted-host", "pypi.douban.com"]) known_exception_caught = False @@ -105,6 +107,8 @@ def reset_logging(): def main(first_time_init=False): + """启动流程,reload之后会被执行""" + global known_exception_caught import config @@ -127,13 +131,26 @@ def main(first_time_init=False): config = importlib.import_module('config') - import pkg.utils.context - pkg.utils.context.set_config(config) - init_runtime_log_file() sh = reset_logging() + # 配置完整性校验 + is_integrity = True + config_template = importlib.import_module('config-template') + for key in dir(config_template): + if not key.startswith("__") and not hasattr(config, key): + setattr(config, key, getattr(config_template, key)) + logging.warning("[{}]不存在".format(key)) + is_integrity = False + if not is_integrity: + logging.warning("配置文件不完整,请依据config-template.py检查config.py") + logging.warning("以上配置已被设为默认值,将在5秒后继续启动... ") + time.sleep(5) + + import pkg.utils.context + pkg.utils.context.set_config(config) + # 检查是否设置了管理员 if not (hasattr(config, 'admin_qq') and config.admin_qq != 0): # logging.warning("未设置管理员QQ,管理员权限指令及运行告警将无法使用,如需设置请修改config.py中的admin_qq字段") @@ -180,7 +197,7 @@ def main(first_time_init=False): # 初始化qq机器人 qqbot = pkg.qqbot.manager.QQBotManager(mirai_http_api_config=config.mirai_http_api_config, timeout=config.process_message_timeout, retry=config.retry_times, - first_time_init=first_time_init) + first_time_init=first_time_init, pool_num=config.pool_num) # 加载插件 import pkg.plugin.host @@ -188,7 +205,7 @@ def main(first_time_init=False): pkg.plugin.host.initialize_plugins() - if first_time_init: # 不是热重载之后的启动,则不启动新的bot线程 + if first_time_init: # 不是热重载之后的启动,则启动新的bot线程 import mirai.exceptions @@ -277,17 +294,7 @@ def main(first_time_init=False): except Exception as e: logging.warning("检查更新失败:{}".format(e)) - while True: - try: - time.sleep(10) - if qqbot != pkg.utils.context.get_qqbot_manager(): # 已经reload了 - logging.info("以前的main流程由于reload退出") - break - except KeyboardInterrupt: - stop() - - print("程序退出") - sys.exit(0) + return qqbot def stop(): @@ -340,19 +347,9 @@ if __name__ == '__main__': sys.exit(0) elif len(sys.argv) > 1 and sys.argv[1] == 'update': - try: - try: - import pkg.utils.pkgmgr - pkg.utils.pkgmgr.ensure_dulwich() - except: - pass - - from dulwich import porcelain - - repo = porcelain.open_repo('.') - porcelain.pull(repo) - except ModuleNotFoundError: - print("dulwich模块未安装,请查看 https://github.com/RockChinQ/QChatGPT/issues/77") + print("正在进行程序更新...") + import pkg.utils.updater as updater + updater.update_all(cli=True) sys.exit(0) # import pkg.utils.configmgr @@ -360,4 +357,14 @@ if __name__ == '__main__': # pkg.utils.configmgr.set_config_and_reload("quote_origin", False) requests.packages.urllib3.disable_warnings(InsecureRequestWarning) - main(True) + qqbot = main(True) + + import pkg.utils.context + while True: + try: + time.sleep(10) + except KeyboardInterrupt: + stop() + + print("程序退出") + sys.exit(0) diff --git a/pkg/[backup]openai.zip b/pkg/[backup]openai.zip deleted file mode 100644 index 1b5291345edf9edba2d32c0cbf98b593434abde6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22455 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 BS2O?s diff --git a/pkg/openai/session.py b/pkg/openai/session.py index 5d41a116..7f97b761 100644 --- a/pkg/openai/session.py +++ b/pkg/openai/session.py @@ -4,7 +4,6 @@ """ import logging -import os import threading import time import json @@ -21,8 +20,6 @@ import pkg.plugin.models as plugin_models sessions = {} - - class SessionOfflineStatus: ON_GOING = 'on_going' EXPLICITLY_CLOSED = 'explicitly_closed' @@ -131,15 +128,13 @@ class Session: logging.debug('{},lock release successfully,{}'.format(self.name, self.response_lock)) # 从配置文件获取会话预设信息 - # get_only: 仅返回初始prompt, 不更改该session的bot_name和bot_filter def get_default_prompt(self, use_default: str = None, get_only = False): - config = pkg.utils.context.get_config() + import pkg.openai.dprompt as dprompt if use_default is None: - use_default = dprompt.get_current() - + use_default = dprompt.get_current() current_default_prompt = \ [ { @@ -150,15 +145,15 @@ class Session: 'content': 'ok' } ] - # 根据设置进行prompt预设模式 if config.preset_mode == "full_scenario": - + import os + logging.info("A") ## dir = os.path.join(os.getcwd(), config.full_prompt_dir) json_file = os.path.join(dir, use_default) + '.json' - + logging.info("B") logging.info("try to load json: {}".format(json_file)) try: @@ -189,8 +184,6 @@ class Session: self.schedule() self.response_lock = threading.Lock() - self.bot_name = 'ai' - self.bot_filter = None self.prompt = self.get_default_prompt() # 设定检查session最后一次对话是否超过过期时间的计时器 @@ -264,23 +257,10 @@ class Session: del (res_ans_spt[0]) res_ans = '\n\n'.join(res_ans_spt) - #检测是否包含ai人格否定 - logging.debug('bot_filter: {}'.format(self.bot_filter)) - if config.filter_ai_warning and self.bot_filter: - import re - match = re.search(self.bot_filter['reg'], res_ans) - logging.debug(self.bot_filter) - logging.debug(res_ans) - if match: - logging.debug('回复:{}, 检测到人格否定,替换中。。'.format(res_ans)) - res_ans = self.bot_filter['replace'] - logging.debug('替换为: {}'.format(res_ans)) - # 将此次对话的双方内容加入到prompt中 self.prompt.append({'role': 'user', 'content': text}) self.prompt.append({'role': 'assistant', 'content': res_ans}) - if self.just_switched_to_exist_session: self.just_switched_to_exist_session = False self.set_ongoing() diff --git a/pkg/openai~/__init__.py b/pkg/openai~/__init__.py new file mode 100644 index 00000000..e6a669c8 --- /dev/null +++ b/pkg/openai~/__init__.py @@ -0,0 +1,2 @@ +"""OpenAI 接口处理及会话管理相关 +""" diff --git a/pkg/openai~/dprompt.py b/pkg/openai~/dprompt.py new file mode 100644 index 00000000..3aba31cb --- /dev/null +++ b/pkg/openai~/dprompt.py @@ -0,0 +1,79 @@ +# 多情景预设值管理 + +__current__ = "default" +"""当前默认使用的情景预设的名称 + +由管理员使用`!default <名称>`指令切换 +""" + +__prompts_from_files__ = {} +"""从文件中读取的情景预设值""" + + +def read_prompt_from_file() -> str: + """从文件读取预设值""" + # 读取prompts/目录下的所有文件,以文件名为键,文件内容为值 + # 保存在__prompts_from_files__中 + global __prompts_from_files__ + import os + + __prompts_from_files__ = {} + for file in os.listdir("prompts"): + with open(os.path.join("prompts", file), encoding="utf-8") as f: + __prompts_from_files__[file] = f.read() + + +def get_prompt_dict() -> dict: + """获取预设值字典""" + import config + default_prompt = config.default_prompt + if type(default_prompt) == str: + default_prompt = {"default": default_prompt} + elif type(default_prompt) == dict: + pass + else: + raise TypeError("default_prompt must be str or dict") + + # 将文件中的预设值合并到default_prompt中 + for key in __prompts_from_files__: + default_prompt[key] = __prompts_from_files__[key] + + return default_prompt + + +def set_current(name): + global __current__ + for key in get_prompt_dict(): + if key.lower().startswith(name.lower()): + __current__ = key + return + raise KeyError("未找到情景预设: " + name) + + +def get_current(): + global __current__ + return __current__ + + +def set_to_default(): + global __current__ + default_dict = get_prompt_dict() + + if "default" in default_dict: + __current__ = "default" + else: + __current__ = list(default_dict.keys())[0] + + +def get_prompt(name: str = None) -> str: + """获取预设值""" + if name is None: + name = get_current() + + default_dict = get_prompt_dict() + + for key in default_dict: + if key.lower().startswith(name.lower()): + return default_dict[key] + + raise KeyError("未找到情景预设: " + name) diff --git a/pkg/openai~/keymgr.py b/pkg/openai~/keymgr.py new file mode 100644 index 00000000..7127db8c --- /dev/null +++ b/pkg/openai~/keymgr.py @@ -0,0 +1,91 @@ +# 此模块提供了维护api-key的各种功能 +import hashlib +import logging + +import pkg.plugin.host as plugin_host +import pkg.plugin.models as plugin_models + + +class KeysManager: + api_key = {} + """所有api-key""" + + using_key = "" + """当前使用的api-key + """ + + alerted = [] + """已提示过超额的key + + 记录在此以避免重复提示 + """ + + exceeded = [] + """已超额的key + + 供自动切换功能识别 + """ + + def get_using_key(self): + return self.using_key + + def get_using_key_md5(self): + return hashlib.md5(self.using_key.encode('utf-8')).hexdigest() + + def __init__(self, api_key): + + if type(api_key) is dict: + self.api_key = api_key + elif type(api_key) is str: + self.api_key = { + "default": api_key + } + elif type(api_key) is list: + for i in range(len(api_key)): + self.api_key[str(i)] = api_key[i] + # 从usage中删除未加载的api-key的记录 + # 不删了,也许会运行时添加曾经有记录的api-key + + self.auto_switch() + + def auto_switch(self) -> (bool, str): + """尝试切换api-key + + Returns: + 是否切换成功, 切换后的api-key的别名 + """ + + for key_name in self.api_key: + if self.api_key[key_name] not in self.exceeded: + self.using_key = self.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 + + self.using_key = list(self.api_key.values())[0] + logging.info("使用api-key:" + list(self.api_key.keys())[0]) + + return False, "" + + def add(self, key_name, key): + self.api_key[key_name] = key + + def set_current_exceeded(self): + """设置当前使用的api-key使用量超限 + """ + self.exceeded.append(self.using_key) + + def get_key_name(self, api_key): + """根据api-key获取其别名""" + for key_name in self.api_key: + if self.api_key[key_name] == api_key: + return key_name + return "" \ No newline at end of file diff --git a/pkg/openai~/manager.py b/pkg/openai~/manager.py new file mode 100644 index 00000000..4a3ceabd --- /dev/null +++ b/pkg/openai~/manager.py @@ -0,0 +1,93 @@ +import logging + +import openai + +import pkg.openai.keymgr +import pkg.utils.context +import pkg.audit.gatherer +from pkg.openai.modelmgr import ModelRequest, create_openai_model_request + + +class OpenAIInteract: + """OpenAI 接口封装 + + 将文字接口和图片接口封装供调用方使用 + """ + + key_mgr: pkg.openai.keymgr.KeysManager = None + + audit_mgr: pkg.audit.gatherer.DataGatherer = None + + default_image_api_params = { + "size": "256x256", + } + + def __init__(self, api_key: str): + + self.key_mgr = pkg.openai.keymgr.KeysManager(api_key) + self.audit_mgr = pkg.audit.gatherer.DataGatherer() + + logging.info("文字总使用量:%d", self.audit_mgr.get_total_text_length()) + + openai.api_key = self.key_mgr.get_using_key() + + pkg.utils.context.set_openai_manager(self) + + # 请求OpenAI Completion + def request_completion(self, prompts) -> str: + """请求补全接口回复 + + Parameters: + prompts (str): 提示语 + + Returns: + str: 回复 + """ + + config = pkg.utils.context.get_config() + + # 根据模型选择使用的接口 + ai: ModelRequest = create_openai_model_request( + config.completion_api_params['model'], + 'user', + config.openai_config["http_proxy"] if "http_proxy" in config.openai_config else None + ) + ai.request( + prompts, + **config.completion_api_params + ) + response = ai.get_response() + + logging.debug("OpenAI response: %s", response) + + if 'model' in config.completion_api_params: + self.audit_mgr.report_text_model_usage(config.completion_api_params['model'], + ai.get_total_tokens()) + elif 'engine' in config.completion_api_params: + self.audit_mgr.report_text_model_usage(config.completion_api_params['engine'], + response['usage']['total_tokens']) + + return ai.get_message() + + def request_image(self, prompt) -> dict: + """请求图片接口回复 + + Parameters: + prompt (str): 提示语 + + Returns: + dict: 响应 + """ + config = pkg.utils.context.get_config() + params = config.image_api_params if hasattr(config, "image_api_params") else self.default_image_api_params + + response = openai.Image.create( + prompt=prompt, + n=1, + **params + ) + + self.audit_mgr.report_image_model_usage(params['size']) + + return response + diff --git a/pkg/openai~/modelmgr.py b/pkg/openai~/modelmgr.py new file mode 100644 index 00000000..e67f98c1 --- /dev/null +++ b/pkg/openai~/modelmgr.py @@ -0,0 +1,184 @@ +"""OpenAI 接口底层封装 + +目前使用的对话接口有: +ChatCompletion - gpt-3.5-turbo 等模型 +Completion - text-davinci-003 等模型 +此模块封装此两个接口的请求实现,为上层提供统一的调用方式 +""" +import openai, logging, threading, asyncio +import openai.error as aiE + +COMPLETION_MODELS = { + 'text-davinci-003', + 'text-davinci-002', + 'code-davinci-002', + 'code-cushman-001', + 'text-curie-001', + 'text-babbage-001', + 'text-ada-001', +} + +CHAT_COMPLETION_MODELS = { + 'gpt-3.5-turbo', + 'gpt-3.5-turbo-0301', +} + +EDIT_MODELS = { + +} + +IMAGE_MODELS = { + +} + + +class ModelRequest: + """模型接口请求父类""" + + can_chat = False + runtime: threading.Thread = None + ret = {} + proxy: str = None + request_ready = True + error_info: str = "若在没有任何错误的情况下看到这句话,请带着配置文件上报Issues" + + def __init__(self, model_name, user_name, request_fun, http_proxy:str = None, time_out = None): + self.model_name = model_name + self.user_name = user_name + self.request_fun = request_fun + self.time_out = time_out + if http_proxy != None: + self.proxy = http_proxy + openai.proxy = self.proxy + self.request_ready = False + + async def __a_request__(self, **kwargs): + """异步请求""" + + try: + self.ret:dict = await self.request_fun(**kwargs) + self.request_ready = True + except aiE.APIConnectionError as e: + self.error_info = "{}\n请检查网络连接或代理是否正常".format(e) + raise ConnectionError(self.error_info) + except ValueError as e: + self.error_info = "{}\n该错误可能是由于http_proxy格式设置错误引起的" + except Exception as e: + self.error_info = "{}\n由于请求异常产生的未知错误,请查看日志".format(e) + raise Exception(self.error_info) + + def request(self, **kwargs): + """向接口发起请求""" + + if self.proxy != None: #异步请求 + self.request_ready = False + loop = asyncio.new_event_loop() + self.runtime = threading.Thread( + target=loop.run_until_complete, + args=(self.__a_request__(**kwargs),) + ) + self.runtime.start() + else: #同步请求 + self.ret = self.request_fun(**kwargs) + + def __msg_handle__(self, msg): + """将prompt dict转换成接口需要的格式""" + return msg + + def ret_handle(self): + ''' + API消息返回处理函数 + 若重写该方法,应检查异步线程状态,或在需要检查处super该方法 + ''' + if self.runtime != None and isinstance(self.runtime, threading.Thread): + self.runtime.join(self.time_out) + if self.request_ready: + return + raise Exception(self.error_info) + + def get_total_tokens(self): + try: + return self.ret['usage']['total_tokens'] + except: + return 0 + + def get_message(self): + return self.message + + def get_response(self): + return self.ret + + +class ChatCompletionModel(ModelRequest): + """ChatCompletion接口的请求实现""" + + Chat_role = ['system', 'user', 'assistant'] + def __init__(self, model_name, user_name, http_proxy:str = None, **kwargs): + if http_proxy == None: + request_fun = openai.ChatCompletion.create + else: + request_fun = openai.ChatCompletion.acreate + self.can_chat = True + super().__init__(model_name, user_name, request_fun, http_proxy, **kwargs) + + def request(self, prompts, **kwargs): + prompts = self.__msg_handle__(prompts) + kwargs['messages'] = prompts + super().request(**kwargs) + self.ret_handle() + + def __msg_handle__(self, msgs): + temp_msgs = [] + # 把msgs拷贝进temp_msgs + for msg in msgs: + temp_msgs.append(msg.copy()) + return temp_msgs + + def get_message(self): + return self.ret["choices"][0]["message"]['content'] #需要时直接加载加快请求速度,降低内存消耗 + + +class CompletionModel(ModelRequest): + """Completion接口的请求实现""" + + def __init__(self, model_name, user_name, http_proxy:str = None, **kwargs): + if http_proxy == None: + request_fun = openai.Completion.create + else: + request_fun = openai.Completion.acreate + super().__init__(model_name, user_name, request_fun, http_proxy, **kwargs) + + def request(self, prompts, **kwargs): + prompts = self.__msg_handle__(prompts) + kwargs['prompt'] = prompts + super().request(**kwargs) + self.ret_handle() + + def __msg_handle__(self, msgs): + prompt = '' + for msg in msgs: + prompt = prompt + "{}: {}\n".format(msg['role'], msg['content']) + # for msg in msgs: + # if msg['role'] == 'assistant': + # prompt = prompt + "{}\n".format(msg['content']) + # else: + # prompt = prompt + "{}:{}\n".format(msg['role'] , msg['content']) + prompt = prompt + "assistant: " + return prompt + + def get_message(self): + return self.ret["choices"][0]["text"] + + +def create_openai_model_request(model_name: str, user_name: str = 'user', http_proxy:str = None) -> ModelRequest: + """使用给定的模型名称创建模型请求对象""" + if model_name in CHAT_COMPLETION_MODELS: + model = ChatCompletionModel(model_name, user_name, http_proxy) + elif model_name in COMPLETION_MODELS: + model = CompletionModel(model_name, user_name, http_proxy) + else : + log = "找不到模型[{}],请检查配置文件".format(model_name) + logging.error(log) + raise IndexError(log) + logging.debug("使用接口[{}]创建模型请求[{}]".format(model.__class__.__name__, model_name)) + return model diff --git a/pkg/openai~/pricing.bak.py b/pkg/openai~/pricing.bak.py new file mode 100644 index 00000000..8a46978b --- /dev/null +++ b/pkg/openai~/pricing.bak.py @@ -0,0 +1,28 @@ +# 计费模块 +# 已弃用 https://github.com/RockChinQ/QChatGPT/issues/81 + +import logging + +pricing = { + "base": { # 文字模型单位是1000字符 + "text-davinci-003": 0.02, + }, + "image": { + "256x256": 0.016, + "512x512": 0.018, + "1024x1024": 0.02, + } +} + + +def language_base_price(model, text): + salt_rate = 0.93 + length = ((len(text.encode('utf-8')) - len(text)) / 2 + len(text)) * salt_rate + logging.debug("text length: %d" % length) + + return pricing["base"][model] * length / 1000 + + +def image_price(size): + logging.debug("image size: %s" % size) + return pricing["image"][size] diff --git a/pkg/openai~/session.py b/pkg/openai~/session.py new file mode 100644 index 00000000..38a629d1 --- /dev/null +++ b/pkg/openai~/session.py @@ -0,0 +1,370 @@ +"""主线使用的会话管理模块 + +每个人、每个群单独一个session,session内部保留了对话的上下文, +""" + +import logging +import threading +import time +import json + +import pkg.openai.manager +import pkg.openai.modelmgr +import pkg.database.manager +import pkg.utils.context + +import pkg.plugin.host as plugin_host +import pkg.plugin.models as plugin_models + +# 运行时保存的所有session +sessions = {} + + +class SessionOfflineStatus: + ON_GOING = 'on_going' + EXPLICITLY_CLOSED = 'explicitly_closed' + + +# 重置session.prompt +def reset_session_prompt(session_name, prompt): + # 备份原始数据 + bak_path = 'logs/{}-{}.bak'.format( + session_name, + time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime()) + ) + f = open(bak_path, 'w+') + f.write(prompt) + f.close() + # 生成新数据 + config = pkg.utils.context.get_config() + prompt = [ + { + 'role': 'system', + 'content': config.default_prompt['default'] + } + ] + # 警告 + logging.warning( + """ +用户[{}]的数据已被重置,有可能是因为数据版本过旧或存储错误 +原始数据将备份在: +{}""".format(session_name, bak_path) + ) # 为保证多行文本格式正确故无缩进 + return prompt + + +# 从数据加载session +def load_sessions(): + """从数据库加载sessions""" + + global sessions + + db_inst = pkg.utils.context.get_database_manager() + + session_data = db_inst.load_valid_sessions() + + for session_name in session_data: + logging.info('加载session: {}'.format(session_name)) + + temp_session = Session(session_name) + temp_session.name = session_name + temp_session.create_timestamp = session_data[session_name]['create_timestamp'] + temp_session.last_interact_timestamp = session_data[session_name]['last_interact_timestamp'] + try: + temp_session.prompt = json.loads(session_data[session_name]['prompt']) + except Exception: + temp_session.prompt = reset_session_prompt(session_name, session_data[session_name]['prompt']) + temp_session.persistence() + + sessions[session_name] = temp_session + + +# 获取指定名称的session,如果不存在则创建一个新的 +def get_session(session_name: str): + global sessions + if session_name not in sessions: + sessions[session_name] = Session(session_name) + return sessions[session_name] + + +def dump_session(session_name: str): + global sessions + if session_name in sessions: + assert isinstance(sessions[session_name], Session) + sessions[session_name].persistence() + del sessions[session_name] + + +# 通用的OpenAI API交互session +# session内部保留了对话的上下文, +# 收到用户消息后,将上下文提交给OpenAI API生成回复 +class Session: + name = '' + + prompt = [] + """使用list来保存会话中的回合""" + + create_timestamp = 0 + """会话创建时间""" + + last_interact_timestamp = 0 + """上次交互(产生回复)时间""" + + just_switched_to_exist_session = False + + response_lock = None + + # 加锁 + def acquire_response_lock(self): + logging.debug('{},lock acquire,{}'.format(self.name, self.response_lock)) + self.response_lock.acquire() + logging.debug('{},lock acquire successfully,{}'.format(self.name, self.response_lock)) + + # 释放锁 + def release_response_lock(self): + if self.response_lock.locked(): + logging.debug('{},lock release,{}'.format(self.name, self.response_lock)) + self.response_lock.release() + logging.debug('{},lock release successfully,{}'.format(self.name, self.response_lock)) + + # 从配置文件获取会话预设信息 + def get_default_prompt(self, use_default: str = None): + config = pkg.utils.context.get_config() + + import pkg.openai.dprompt as dprompt + + if use_default is None: + current_default_prompt = dprompt.get_prompt(dprompt.get_current()) + else: + current_default_prompt = dprompt.get_prompt(use_default) + + return [ + { + 'role': 'user', + 'content': current_default_prompt + }, { + 'role': 'assistant', + 'content': 'ok' + } + ] + + def __init__(self, name: str): + self.name = name + self.create_timestamp = int(time.time()) + self.last_interact_timestamp = int(time.time()) + self.schedule() + + self.response_lock = threading.Lock() + self.prompt = self.get_default_prompt() + + # 设定检查session最后一次对话是否超过过期时间的计时器 + def schedule(self): + threading.Thread(target=self.expire_check_timer_loop, args=(self.create_timestamp,)).start() + + # 检查session是否已经过期 + def expire_check_timer_loop(self, create_timestamp: int): + global sessions + while True: + time.sleep(60) + + # 不是此session已更换,退出 + if self.create_timestamp != create_timestamp or self not in sessions.values(): + return + + config = pkg.utils.context.get_config() + if int(time.time()) - self.last_interact_timestamp > config.session_expire_time: + 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) + + # 删除此session + del sessions[self.name] + return + + # 请求回复 + # 这个函数是阻塞的 + def append(self, text: str) -> str: + """向session中添加一条消息,返回接口回复""" + + 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 + + config = pkg.utils.context.get_config() + max_length = config.prompt_submit_length if hasattr(config, "prompt_submit_length") else 1024 + + # 向API请求补全 + message = pkg.utils.context.get_openai_manager().request_completion( + self.cut_out(text, max_length), + ) + + # 成功获取,处理回复 + res_test = message + res_ans = res_test + + # 去除开头可能的提示 + res_ans_spt = res_test.split("\n\n") + if len(res_ans_spt) > 1: + del (res_ans_spt[0]) + res_ans = '\n\n'.join(res_ans_spt) + + # 将此次对话的双方内容加入到prompt中 + self.prompt.append({'role': 'user', 'content': text}) + self.prompt.append({'role': 'assistant', 'content': res_ans}) + + if self.just_switched_to_exist_session: + self.just_switched_to_exist_session = False + self.set_ongoing() + + return res_ans if res_ans[0] != '\n' else res_ans[1:] + + # 删除上一回合并返回上一回合的问题 + def undo(self) -> str: + self.last_interact_timestamp = int(time.time()) + + # 删除最后两个消息 + if len(self.prompt) < 2: + raise Exception('之前无对话,无法撤销') + + question = self.prompt[-2]['content'] + self.prompt = self.prompt[:-2] + + # 返回上一回合的问题 + return question + + # 构建对话体 + def cut_out(self, msg: str, max_tokens: int) -> list: + """将现有prompt进行切割处理,使得新的prompt长度不超过max_tokens""" + # 如果用户消息长度超过max_tokens,直接返回 + + temp_prompt = [ + { + 'role': 'user', + 'content': msg + } + ] + + token_count = len(msg) + # 倒序遍历prompt + for i in range(len(self.prompt) - 1, -1, -1): + if token_count >= max_tokens: + break + + # 将prompt加到temp_prompt头部 + temp_prompt.insert(0, self.prompt[i]) + token_count += len(self.prompt[i]['content']) + + logging.debug('cut_out: {}'.format(str(temp_prompt))) + + return temp_prompt + + # 持久化session + def persistence(self): + if self.prompt == self.get_default_prompt(): + return + + db_inst = pkg.utils.context.get_database_manager() + + name_spt = self.name.split('_') + + subject_type = name_spt[0] + subject_number = int(name_spt[1]) + + db_inst.persistence_session(subject_type, subject_number, self.create_timestamp, self.last_interact_timestamp, + json.dumps(self.prompt)) + + # 重置session + def reset(self, explicit: bool = False, expired: bool = False, schedule_new: bool = True, use_prompt: str = None): + if self.prompt[-1]['role'] != "system": + self.persistence() + 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) + + if expired: + pkg.utils.context.get_database_manager().set_session_expired(self.name, self.create_timestamp) + self.prompt = self.get_default_prompt(use_prompt) + self.create_timestamp = int(time.time()) + self.last_interact_timestamp = int(time.time()) + self.just_switched_to_exist_session = False + + # self.response_lock = threading.Lock() + + if schedule_new: + self.schedule() + + # 将本session的数据库状态设置为on_going + def set_ongoing(self): + pkg.utils.context.get_database_manager().set_session_ongoing(self.name, self.create_timestamp) + + # 切换到上一个session + def last_session(self): + last_one = pkg.utils.context.get_database_manager().last_session(self.name, self.last_interact_timestamp) + if last_one is None: + return None + else: + self.persistence() + + self.create_timestamp = last_one['create_timestamp'] + self.last_interact_timestamp = last_one['last_interact_timestamp'] + try: + self.prompt = json.loads(last_one['prompt']) + except json.decoder.JSONDecodeError: + self.prompt = reset_session_prompt(self.name, last_one['prompt']) + self.persistence() + + self.just_switched_to_exist_session = True + return self + + # 切换到下一个session + def next_session(self): + next_one = pkg.utils.context.get_database_manager().next_session(self.name, self.last_interact_timestamp) + if next_one is None: + return None + else: + self.persistence() + + self.create_timestamp = next_one['create_timestamp'] + self.last_interact_timestamp = next_one['last_interact_timestamp'] + try: + self.prompt = json.loads(next_one['prompt']) + except json.decoder.JSONDecodeError: + self.prompt = reset_session_prompt(self.name, next_one['prompt']) + self.persistence() + + self.just_switched_to_exist_session = True + return self + + def list_history(self, capacity: int = 10, page: int = 0): + return pkg.utils.context.get_database_manager().list_history(self.name, capacity, page) + + def draw_image(self, prompt: str): + return pkg.utils.context.get_openai_manager().request_image(prompt) diff --git a/pkg/qqbot/filter.py b/pkg/qqbot/filter.py index 6ed83329..f0efeda9 100644 --- a/pkg/qqbot/filter.py +++ b/pkg/qqbot/filter.py @@ -7,6 +7,8 @@ import logging class ReplyFilter: sensitive_words = [] + mask = "*" + mask_word = "" # 默认值( 兼容性考虑 ) baidu_check = False @@ -14,8 +16,10 @@ class ReplyFilter: baidu_secret_key = "" inappropriate_message_tips = "[百度云]请珍惜机器人,当前返回内容不合规" - def __init__(self, sensitive_words: list): + def __init__(self, sensitive_words: list, mask: str = "*", mask_word: str = ""): self.sensitive_words = sensitive_words + self.mask = mask + self.mask_word = mask_word import config if hasattr(config, 'baidu_check') and hasattr(config, 'baidu_api_key') and hasattr(config, 'baidu_secret_key'): self.baidu_check = config.baidu_check @@ -36,7 +40,10 @@ class ReplyFilter: match = re.findall(word, message) if len(match) > 0: for i in range(len(match)): - message = message.replace(match[i], "*" * len(match[i])) + if self.mask_word == "": + message = message.replace(match[i], self.mask * len(match[i])) + else: + message = message.replace(match[i], self.mask_word) # 百度云审核 if self.baidu_check: diff --git a/pkg/qqbot/manager.py b/pkg/qqbot/manager.py index 5d683757..b80b8a66 100644 --- a/pkg/qqbot/manager.py +++ b/pkg/qqbot/manager.py @@ -2,6 +2,7 @@ import asyncio import json import os import threading +from concurrent.futures import ThreadPoolExecutor import mirai.models.bus from mirai import At, GroupMessage, MessageEvent, Mirai, StrangerMessage, WebSocketAdapter, HTTPAdapter, \ @@ -21,12 +22,6 @@ import pkg.plugin.host as plugin_host import pkg.plugin.models as plugin_models -# 并行运行 -def go(func, args=()): - thread = threading.Thread(target=func, args=args, daemon=True) - thread.start() - - # 检查消息是否符合泛响应匹配机制 def check_response_rule(text: str, event): config = pkg.utils.context.get_config() @@ -41,7 +36,6 @@ def check_response_rule(text: str, event): import re if re.search(bot_name, text): return True, text - rules = config.response_rules # 检查前缀匹配 if 'prefix' in rules: @@ -56,14 +50,33 @@ def check_response_rule(text: str, event): match = re.match(rule, text) if match: return True, text - + return False, "" +def response_at(): + config = pkg.utils.context.get_config() + if 'at' not in config.response_rules: + return True + + return config.response_rules['at'] + + +def random_responding(): + config = pkg.utils.context.get_config() + if 'random_rate' in config.response_rules: + import random + return random.random() < config.response_rules['random_rate'] + return False + + # 控制QQ消息输入输出的类 class QQBotManager: retry = 3 + #线程池控制 + pool = None + bot: Mirai = None reply_filter = None @@ -73,11 +86,14 @@ class QQBotManager: ban_person = [] ban_group = [] - def __init__(self, mirai_http_api_config: dict, timeout: int = 60, retry: int = 3, first_time_init=True): - + def __init__(self, mirai_http_api_config: dict, timeout: int = 60, retry: int = 3, pool_num: int = 10, first_time_init=True): self.timeout = timeout self.retry = retry + self.pool_num = pool_num + self.pool = ThreadPoolExecutor(max_workers=self.pool_num) + logging.debug("Registered thread pool Size:{}".format(pool_num)) + # 加载禁用列表 if os.path.exists("banlist.py"): import banlist @@ -91,7 +107,12 @@ class QQBotManager: and config.sensitive_word_filter is not None \ and config.sensitive_word_filter: with open("sensitive.json", "r", encoding="utf-8") as f: - self.reply_filter = pkg.qqbot.filter.ReplyFilter(json.load(f)['words']) + sensitive_json = json.load(f) + self.reply_filter = pkg.qqbot.filter.ReplyFilter( + sensitive_words=sensitive_json['words'], + mask=sensitive_json['mask'] if 'mask' in sensitive_json else '*', + mask_word=sensitive_json['mask_word'] if 'mask_word' in sensitive_json else '' + ) else: self.reply_filter = pkg.qqbot.filter.ReplyFilter([]) @@ -125,7 +146,7 @@ class QQBotManager: self.on_person_message(event) - go(friend_message_handler, (event,)) + self.go(friend_message_handler, event) @self.bot.on(StrangerMessage) async def on_stranger_message(event: StrangerMessage): @@ -145,7 +166,7 @@ class QQBotManager: self.on_person_message(event) - go(stranger_message_handler, (event,)) + self.go(stranger_message_handler, event) @self.bot.on(GroupMessage) async def on_group_message(event: GroupMessage): @@ -165,7 +186,7 @@ class QQBotManager: self.on_group_message(event) - go(group_message_handler, (event,)) + self.go(group_message_handler, event) def unsubscribe_all(): """取消所有订阅 @@ -182,6 +203,9 @@ class QQBotManager: self.unsubscribe_all = unsubscribe_all + def go(self, func, *args, **kwargs): + self.pool.submit(func, *args, **kwargs) + def first_time_init(self, mirai_http_api_config: dict): """热重载后不再运行此函数""" @@ -297,14 +321,19 @@ class QQBotManager: if Image in event.message_chain: pass - elif At(self.bot.qq) not in event.message_chain: - check, result = check_response_rule(str(event.message_chain).strip(), event) - - if check: - reply = process(result.strip()) else: - # 直接调用 - reply = process() + if At(self.bot.qq) in event.message_chain and response_at(): + # 直接调用 + reply = process() + else: + check, result = check_response_rule(str(event.message_chain).strip(), event) + + if check: + reply = process(result.strip()) + # 检查是否随机响应 + elif random_responding(): + logging.info("随机响应group_{}消息".format(event.group.id)) + reply = process() if reply: return self.send(event, reply) diff --git a/pkg/qqbot~/__init__.py b/pkg/qqbot~/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pkg/qqbot~/banlist.py b/pkg/qqbot~/banlist.py new file mode 100644 index 00000000..2c7dcb12 --- /dev/null +++ b/pkg/qqbot~/banlist.py @@ -0,0 +1,50 @@ +import pkg.utils.context + + +def is_banned(launcher_type: str, launcher_id: int, sender_id: int) -> bool: + if not pkg.utils.context.get_qqbot_manager().enable_banlist: + return False + + result = False + + if launcher_type == 'group': + # 检查是否显式声明发起人QQ要被person忽略 + if sender_id in pkg.utils.context.get_qqbot_manager().ban_person: + result = True + else: + for group_rule in pkg.utils.context.get_qqbot_manager().ban_group: + if type(group_rule) == int: + if group_rule == launcher_id: # 此群群号被禁用 + result = True + elif type(group_rule) == str: + if group_rule.startswith('!'): + # 截取!后面的字符串作为表达式,判断是否匹配 + reg_str = group_rule[1:] + import re + if re.match(reg_str, str(launcher_id)): # 被豁免,最高级别 + result = False + break + else: + # 判断是否匹配regexp + import re + if re.match(group_rule, str(launcher_id)): # 此群群号被禁用 + result = True + + else: + # ban_person, 与群规则相同 + for person_rule in pkg.utils.context.get_qqbot_manager().ban_person: + if type(person_rule) == int: + if person_rule == launcher_id: + result = True + elif type(person_rule) == str: + if person_rule.startswith('!'): + reg_str = person_rule[1:] + import re + if re.match(reg_str, str(launcher_id)): + result = False + break + else: + import re + if re.match(person_rule, str(launcher_id)): + result = True + return result diff --git a/pkg/qqbot~/blob.py b/pkg/qqbot~/blob.py new file mode 100644 index 00000000..c6edff2e --- /dev/null +++ b/pkg/qqbot~/blob.py @@ -0,0 +1,105 @@ +# 长消息处理相关 +import logging +import os +import time +import base64 + +import config +from mirai.models.message import MessageComponent, MessageChain, Image +from mirai.models.message import ForwardMessageNode +from mirai.models.base import MiraiBaseModel +from typing import List +import pkg.utils.context as context +import pkg.utils.text2img as text2img + + +class ForwardMessageDiaplay(MiraiBaseModel): + title: str = "群聊的聊天记录" + brief: str = "[聊天记录]" + source: str = "聊天记录" + preview: List[str] = [] + summary: str = "查看x条转发消息" + + +class Forward(MessageComponent): + """合并转发。""" + type: str = "Forward" + """消息组件类型。""" + display: ForwardMessageDiaplay + """显示信息""" + node_list: List[ForwardMessageNode] + """转发消息节点列表。""" + def __init__(self, *args, **kwargs): + if len(args) == 1: + self.node_list = args[0] + super().__init__(**kwargs) + super().__init__(*args, **kwargs) + + def __str__(self): + return '[聊天记录]' + + +def text_to_image(text: str) -> MessageComponent: + """将文本转换成图片""" + # 检查temp文件夹是否存在 + if not os.path.exists('temp'): + os.mkdir('temp') + img_path = text2img.text_to_image(text_str=text, save_as='temp/{}.png'.format(int(time.time()))) + + compressed_path, size = text2img.compress_image(img_path, outfile="temp/{}_compressed.png".format(int(time.time()))) + # 读取图片,转换成base64 + with open(compressed_path, 'rb') as f: + img = f.read() + + b64 = base64.b64encode(img) + + # 删除图片 + os.remove(img_path) + + # 判断compressed_path是否存在 + if os.path.exists(compressed_path): + os.remove(compressed_path) + # 返回图片 + return Image(base64=b64.decode('utf-8')) + + +def check_text(text: str) -> list: + """检查文本是否为长消息,并转换成该使用的消息链组件""" + if not hasattr(config, 'blob_message_threshold'): + return [text] + + if len(text) > config.blob_message_threshold: + if not hasattr(config, 'blob_message_strategy'): + raise AttributeError('未定义长消息处理策略') + + # logging.info("长消息: {}".format(text)) + if config.blob_message_strategy == 'image': + # 转换成图片 + return [text_to_image(text)] + elif config.blob_message_strategy == 'forward': + # 敏感词屏蔽 + text = context.get_qqbot_manager().reply_filter.process(text) + + # 包装转发消息 + display = ForwardMessageDiaplay( + title='群聊的聊天记录', + brief='[聊天记录]', + source='聊天记录', + preview=["bot: "+text], + summary="查看1条转发消息" + ) + + node = ForwardMessageNode( + sender_id=config.mirai_http_api_config['qq'], + sender_name='bot', + message_chain=MessageChain([text]) + ) + + forward = Forward( + display=display, + node_list=[node] + ) + + return [forward] + else: + return [text] \ No newline at end of file diff --git a/pkg/qqbot~/command.py b/pkg/qqbot~/command.py new file mode 100644 index 00000000..b174d453 --- /dev/null +++ b/pkg/qqbot~/command.py @@ -0,0 +1,359 @@ +# 指令处理模块 +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 +import pkg.utils.credit as credit + +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".format(len(plugin_host.__plugins__)) + idx = 0 + for key in plugin_host.iter_plugins_name(): + plugin = plugin_list[key] + reply_str += "\n#{} {} {}\n{}\nv{}\n作者: {}\n"\ + .format((idx+1), plugin['name'], + "[已禁用]" if not plugin['enabled'] else "", + plugin['description'], + plugin['version'], plugin['author']) + + if updater.is_repo("/".join(plugin['path'].split('/')[:-1])): + remote_url = updater.get_remote_url("/".join(plugin['path'].split('/')[:-1])) + if remote_url != "https://github.com/RockChinQ/QChatGPT" and remote_url != "https://gitee.com/RockChin/QChatGPT": + reply_str += "源码: "+remote_url+"\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("已更新插件: {}".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, is_admin: bool) -> 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': + if len(params) == 0: + pkg.openai.session.get_session(session_name).reset(explicit=True) + reply = ["[bot]会话已重置"] + else: + pkg.openai.session.get_session(session_name).reset(explicit=True, use_prompt=params[0]) + reply = ["[bot]会话已重置,使用场景预设:{}".format(params[0])] + 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)] + 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)] + elif cmd == 'prompt': + msgs = "" + session:list = pkg.openai.session.get_session(session_name).prompt + for msg in session: + if len(params) != 0 and params[0] in ['-all', '-a']: + msgs = msgs + "{}: {}\n\n".format(msg['role'], msg['content']) + elif len(msg['content']) > 30: + msgs = msgs + "[{}]: {}...\n\n".format(msg['role'], msg['content'][:30]) + else: + msgs = msgs + "[{}]: {}\n\n".format(msg['role'], msg['content']) + reply = ["[bot]当前对话所有内容:\n{}".format(msgs)] + 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']) + msg = "" + try: + msg = json.loads(results[i]['prompt']) + except json.decoder.JSONDecodeError: + msg = pkg.openai.session.reset_session_prompt(session_name, results[i]['prompt']) + # 持久化 + pkg.openai.session.get_session(session_name).persistence() + if len(msg) >= 2: + reply_str += "#{} 创建:{} {}\n".format(i + page * 10, + datetime_obj.strftime("%Y-%m-%d %H:%M:%S"), + msg[1]['content']) + else: + reply_str += "#{} 创建:{} {}\n".format(i + page * 10, + datetime_obj.strftime("%Y-%m-%d %H:%M:%S"), + "无内容") + 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)) + # 获取此key的额度 + try: + credit_data = credit.fetch_credit_data(api_keys[key_name]) + reply_str += " - 使用额度:{:.2f}/{:.2f}\n".format(credit_data['total_used'],credit_data['total_granted']) + except Exception as e: + logging.warning("获取额度失败:{}".format(e)) + + 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, is_admin) + + elif cmd == 'default': + if len(params) == 0: + # 输出目前所有情景预设 + import pkg.openai.dprompt as dprompt + reply_str = "[bot]当前所有情景预设:\n\n" + for key,value in dprompt.get_prompt_dict().items(): + reply_str += " - {}: {}\n".format(key,value) + + reply_str += "\n当前默认情景预设:{}\n".format(dprompt.get_current()) + reply_str += "请使用!default <情景预设>来设置默认情景预设" + reply = [reply_str] + elif len(params) >0 and is_admin: + # 设置默认情景 + import pkg.openai.dprompt as dprompt + try: + dprompt.set_current(params[0]) + reply = ["[bot]已设置默认情景预设为:{}".format(dprompt.get_current())] + except KeyError: + reply = ["[bot]err: 未找到情景预设:{}".format(params[0])] + else: + reply = ["[bot]err: 仅管理员可设置默认情景预设"] + elif cmd == 'reload' and is_admin: + def reload_task(): + pkg.utils.reloader.reload_all() + + threading.Thread(target=reload_task, daemon=True).start() + elif cmd == 'update' and is_admin: + 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 is_admin: + reply = config_operation(cmd, params) + else: + if cmd.startswith("~") and is_admin: + 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 diff --git a/pkg/qqbot~/filter.py b/pkg/qqbot~/filter.py new file mode 100644 index 00000000..f0efeda9 --- /dev/null +++ b/pkg/qqbot~/filter.py @@ -0,0 +1,84 @@ +# 敏感词过滤模块 +import re +import requests +import json +import logging + + +class ReplyFilter: + sensitive_words = [] + mask = "*" + mask_word = "" + + # 默认值( 兼容性考虑 ) + baidu_check = False + baidu_api_key = "" + baidu_secret_key = "" + inappropriate_message_tips = "[百度云]请珍惜机器人,当前返回内容不合规" + + def __init__(self, sensitive_words: list, mask: str = "*", mask_word: str = ""): + self.sensitive_words = sensitive_words + self.mask = mask + self.mask_word = mask_word + import config + if hasattr(config, 'baidu_check') and hasattr(config, 'baidu_api_key') and hasattr(config, 'baidu_secret_key'): + self.baidu_check = config.baidu_check + self.baidu_api_key = config.baidu_api_key + self.baidu_secret_key = config.baidu_secret_key + self.inappropriate_message_tips = config.inappropriate_message_tips + + def is_illegal(self, message: str) -> bool: + processed = self.process(message) + if processed != message: + return True + return False + + def process(self, message: str) -> str: + + # 本地关键词屏蔽 + for word in self.sensitive_words: + match = re.findall(word, message) + if len(match) > 0: + for i in range(len(match)): + if self.mask_word == "": + message = message.replace(match[i], self.mask * len(match[i])) + else: + message = message.replace(match[i], self.mask_word) + + # 百度云审核 + if self.baidu_check: + + # 百度云审核URL + baidu_url = "https://aip.baidubce.com/rest/2.0/solution/v1/text_censor/v2/user_defined?access_token=" + \ + str(requests.post("https://aip.baidubce.com/oauth/2.0/token", + params={"grant_type": "client_credentials", + "client_id": self.baidu_api_key, + "client_secret": self.baidu_secret_key}).json().get("access_token")) + + # 百度云审核 + payload = "text=" + message + logging.info("向百度云发送:" + payload) + headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json'} + + if isinstance(payload, str): + payload = payload.encode('utf-8') + + response = requests.request("POST", baidu_url, headers=headers, data=payload) + response_dict = json.loads(response.text) + + if "error_code" in response_dict: + error_msg = response_dict.get("error_msg") + logging.warning(f"百度云判定出错,错误信息:{error_msg}") + conclusion = f"百度云判定出错,错误信息:{error_msg}\n以下是原消息:{message}" + else: + conclusion = response_dict["conclusion"] + if conclusion in ("合规"): + logging.info(f"百度云判定结果:{conclusion}") + return message + else: + logging.warning(f"百度云判定结果:{conclusion}") + conclusion = self.inappropriate_message_tips + # 返回百度云审核结果 + return conclusion + + return message diff --git a/pkg/qqbot~/ignore.py b/pkg/qqbot~/ignore.py new file mode 100644 index 00000000..01994b2e --- /dev/null +++ b/pkg/qqbot~/ignore.py @@ -0,0 +1,19 @@ +import re + + +def ignore(msg: str) -> bool: + """检查消息是否应该被忽略""" + import config + + if not hasattr(config, 'ignore_rules'): + return False + + if 'prefix' in config.ignore_rules: + for rule in config.ignore_rules['prefix']: + if msg.startswith(rule): + return True + + if 'regexp' in config.ignore_rules: + for rule in config.ignore_rules['regexp']: + if re.search(rule, msg): + return True diff --git a/pkg/qqbot~/manager.py b/pkg/qqbot~/manager.py new file mode 100644 index 00000000..5d817eee --- /dev/null +++ b/pkg/qqbot~/manager.py @@ -0,0 +1,357 @@ +import asyncio +import json +import os +import threading +from concurrent.futures import ThreadPoolExecutor + +import mirai.models.bus +from mirai import At, GroupMessage, MessageEvent, Mirai, StrangerMessage, WebSocketAdapter, HTTPAdapter, \ + FriendMessage, Image +from func_timeout import func_set_timeout + +import pkg.openai.session +import pkg.openai.manager +from func_timeout import FunctionTimedOut +import logging + +import pkg.qqbot.filter +import pkg.qqbot.process as processor +import pkg.utils.context + +import pkg.plugin.host as plugin_host +import pkg.plugin.models as plugin_models + + +# 检查消息是否符合泛响应匹配机制 +def check_response_rule(text: str): + config = pkg.utils.context.get_config() + if not hasattr(config, 'response_rules'): + return False, '' + + rules = config.response_rules + # 检查前缀匹配 + if 'prefix' in rules: + for rule in rules['prefix']: + if text.startswith(rule): + return True, text.replace(rule, "", 1) + + # 检查正则表达式匹配 + if 'regexp' in rules: + for rule in rules['regexp']: + import re + match = re.match(rule, text) + if match: + return True, text + + return False, "" + + +def response_at(): + config = pkg.utils.context.get_config() + if 'at' not in config.response_rules: + return True + + return config.response_rules['at'] + + +def random_responding(): + config = pkg.utils.context.get_config() + if 'random_rate' in config.response_rules: + import random + return random.random() < config.response_rules['random_rate'] + return False + + +# 控制QQ消息输入输出的类 +class QQBotManager: + retry = 3 + + #线程池控制 + pool = None + + bot: Mirai = None + + reply_filter = None + + enable_banlist = False + + ban_person = [] + ban_group = [] + + def __init__(self, mirai_http_api_config: dict, timeout: int = 60, retry: int = 3, pool_num: int = 10, first_time_init=True): + self.timeout = timeout + self.retry = retry + + self.pool_num = pool_num + self.pool = ThreadPoolExecutor(max_workers=self.pool_num) + logging.debug("Registered thread pool Size:{}".format(pool_num)) + + # 加载禁用列表 + if os.path.exists("banlist.py"): + import banlist + self.enable_banlist = banlist.enable + self.ban_person = banlist.person + self.ban_group = banlist.group + logging.info("加载禁用列表: person: {}, group: {}".format(self.ban_person, self.ban_group)) + + config = pkg.utils.context.get_config() + if os.path.exists("sensitive.json") \ + and config.sensitive_word_filter is not None \ + and config.sensitive_word_filter: + with open("sensitive.json", "r", encoding="utf-8") as f: + sensitive_json = json.load(f) + self.reply_filter = pkg.qqbot.filter.ReplyFilter( + sensitive_words=sensitive_json['words'], + mask=sensitive_json['mask'] if 'mask' in sensitive_json else '*', + mask_word=sensitive_json['mask_word'] if 'mask_word' in sensitive_json else '' + ) + else: + self.reply_filter = pkg.qqbot.filter.ReplyFilter([]) + + # 由于YiriMirai的bot对象是单例的,且shutdown方法暂时无法使用 + # 故只在第一次初始化时创建bot对象,重载之后使用原bot对象 + # 因此,bot的配置不支持热重载 + if first_time_init: + self.first_time_init(mirai_http_api_config) + else: + self.bot = pkg.utils.context.get_qqbot_manager().bot + + pkg.utils.context.set_qqbot_manager(self) + + # Caution: 注册新的事件处理器之后,请务必在unsubscribe_all中编写相应的取消订阅代码 + @self.bot.on(FriendMessage) + async def on_friend_message(event: FriendMessage): + + 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) + + self.go(friend_message_handler, event) + + @self.bot.on(StrangerMessage) + async def on_stranger_message(event: StrangerMessage): + + 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) + + self.go(stranger_message_handler, event) + + @self.bot.on(GroupMessage) + async def on_group_message(event: GroupMessage): + + 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) + + self.go(group_message_handler, event) + + def unsubscribe_all(): + """取消所有订阅 + + 用于在热重载流程中卸载所有事件处理器 + """ + assert isinstance(self.bot, Mirai) + bus = self.bot.bus + assert isinstance(bus, mirai.models.bus.ModelEventBus) + + bus.unsubscribe(FriendMessage, on_friend_message) + bus.unsubscribe(StrangerMessage, on_stranger_message) + bus.unsubscribe(GroupMessage, on_group_message) + + self.unsubscribe_all = unsubscribe_all + + def go(self, func, *args, **kwargs): + self.pool.submit(func, *args, **kwargs) + + def first_time_init(self, mirai_http_api_config: dict): + """热重载后不再运行此函数""" + + if 'adapter' not in mirai_http_api_config or mirai_http_api_config['adapter'] == "WebSocketAdapter": + bot = Mirai( + qq=mirai_http_api_config['qq'], + adapter=WebSocketAdapter( + verify_key=mirai_http_api_config['verifyKey'], + host=mirai_http_api_config['host'], + port=mirai_http_api_config['port'] + ) + ) + elif mirai_http_api_config['adapter'] == "HTTPAdapter": + bot = Mirai( + qq=mirai_http_api_config['qq'], + adapter=HTTPAdapter( + verify_key=mirai_http_api_config['verifyKey'], + host=mirai_http_api_config['host'], + port=mirai_http_api_config['port'] + ) + ) + + else: + raise Exception("未知的适配器类型") + + self.bot = bot + + def send(self, event, msg, check_quote=True): + config = pkg.utils.context.get_config() + asyncio.run( + self.bot.send(event, msg, quote=True if hasattr(config, + "quote_origin") and config.quote_origin and check_quote else False)) + + # 私聊消息处理 + def on_person_message(self, event: MessageEvent): + import config + reply = '' + + if event.sender.id == self.bot.qq: + pass + else: + if Image in event.message_chain: + pass + else: + # 超时则重试,重试超过次数则放弃 + failed = 0 + for i in range(self.retry): + try: + + @func_set_timeout(config.process_message_timeout) + def time_ctrl_wrapper(): + reply = processor.process_message('person', event.sender.id, str(event.message_chain), + event.message_chain, + event.sender.id) + return reply + + reply = time_ctrl_wrapper() + break + except FunctionTimedOut: + logging.warning("person_{}: 超时,重试中({})".format(event.sender.id, i)) + pkg.openai.session.get_session('person_{}'.format(event.sender.id)).release_response_lock() + if "person_{}".format(event.sender.id) in pkg.qqbot.process.processing: + pkg.qqbot.process.processing.remove('person_{}'.format(event.sender.id)) + failed += 1 + continue + + if failed == self.retry: + pkg.openai.session.get_session('person_{}'.format(event.sender.id)).release_response_lock() + self.notify_admin("{} 请求超时".format("person_{}".format(event.sender.id))) + reply = ["[bot]err:请求超时"] + + if reply: + return self.send(event, reply, check_quote=False) + + # 群消息处理 + def on_group_message(self, event: GroupMessage): + import config + reply = '' + + def process(text=None) -> str: + replys = "" + if At(self.bot.qq) in event.message_chain: + event.message_chain.remove(At(self.bot.qq)) + + # 超时则重试,重试超过次数则放弃 + failed = 0 + for i in range(self.retry): + try: + @func_set_timeout(config.process_message_timeout) + def time_ctrl_wrapper(): + replys = processor.process_message('group', event.group.id, + str(event.message_chain).strip() if text is None else text, + event.message_chain, + event.sender.id) + return replys + + replys = time_ctrl_wrapper() + break + except FunctionTimedOut: + logging.warning("group_{}: 超时,重试中({})".format(event.group.id, i)) + pkg.openai.session.get_session('group_{}'.format(event.group.id)).release_response_lock() + if "group_{}".format(event.group.id) in pkg.qqbot.process.processing: + pkg.qqbot.process.processing.remove('group_{}'.format(event.group.id)) + failed += 1 + continue + + if failed == self.retry: + pkg.openai.session.get_session('group_{}'.format(event.group.id)).release_response_lock() + self.notify_admin("{} 请求超时".format("group_{}".format(event.group.id))) + replys = ["[bot]err:请求超时"] + + return replys + + if Image in event.message_chain: + pass + else: + if At(self.bot.qq) in event.message_chain and response_at(): + # 直接调用 + reply = process() + else: + check, result = check_response_rule(str(event.message_chain).strip()) + + if check: + reply = process(result.strip()) + # 检查是否随机响应 + elif random_responding(): + logging.info("随机响应group_{}消息".format(event.group.id)) + reply = process() + + if reply: + return self.send(event, reply) + + # 通知系统管理员 + def notify_admin(self, message: str): + config = pkg.utils.context.get_config() + if hasattr(config, "admin_qq") and config.admin_qq != 0 and config.admin_qq != []: + logging.info("通知管理员:{}".format(message)) + if type(config.admin_qq) == int: + send_task = self.bot.send_friend_message(config.admin_qq, "[bot]{}".format(message)) + threading.Thread(target=asyncio.run, args=(send_task,)).start() + else: + for adm in config.admin_qq: + send_task = self.bot.send_friend_message(adm, "[bot]{}".format(message)) + threading.Thread(target=asyncio.run, args=(send_task,)).start() + + + def notify_admin_message_chain(self, message): + config = pkg.utils.context.get_config() + if hasattr(config, "admin_qq") and config.admin_qq != 0 and config.admin_qq != []: + logging.info("通知管理员:{}".format(message)) + if type(config.admin_qq) == int: + send_task = self.bot.send_friend_message(config.admin_qq, message) + threading.Thread(target=asyncio.run, args=(send_task,)).start() + else: + for adm in config.admin_qq: + send_task = self.bot.send_friend_message(adm, message) + threading.Thread(target=asyncio.run, args=(send_task,)).start() diff --git a/pkg/qqbot~/message.py b/pkg/qqbot~/message.py new file mode 100644 index 00000000..e6106df1 --- /dev/null +++ b/pkg/qqbot~/message.py @@ -0,0 +1,130 @@ +# 普通消息处理模块 +import logging +import time +import openai +import pkg.utils.context +import pkg.openai.session + +import pkg.plugin.host as plugin_host +import pkg.plugin.models as plugin_models +import pkg.qqbot.blob as blob + + +def handle_exception(notify_admin: str = "", set_reply: str = "") -> list: + """处理异常,当notify_admin不为空时,会通知管理员,返回通知用户的消息""" + import config + pkg.utils.context.get_qqbot_manager().notify_admin(notify_admin) + if hasattr(config, 'hide_exce_info_to_user') and config.hide_exce_info_to_user: + if hasattr(config, 'alter_tip_message'): + return [config.alter_tip_message] if config.alter_tip_message else [] + else: + return ["[bot]出错了,请重试或联系管理员"] + else: + return [set_reply] + + +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) + + unexpected_exception_times = 0 + + max_unexpected_exception_times = 3 + + reply = [] + while True: + if unexpected_exception_times >= max_unexpected_exception_times: + reply = handle_exception(notify_admin=f"{session_name},多次尝试失败。", set_reply=f"[bot]多次尝试失败,请重试或联系管理员") + break + 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 = blob.check_text(prefix + text) + except openai.error.APIConnectionError as e: + err_msg = str(e) + if err_msg.__contains__('Error communicating with OpenAI'): + reply = handle_exception("{}会话调用API失败:{}\n请尝试关闭网络代理来解决此问题。".format(session_name, e), + "[bot]err:调用API失败,请重试或联系管理员,或等待修复") + else: + reply = handle_exception("{}会话调用API失败:{}".format(session_name, e), "[bot]err:调用API失败,请重试或联系管理员,或等待修复") + except openai.error.RateLimitError as e: + logging.debug(type(e)) + logging.debug(e.error['message']) + + if 'message' in e.error and e.error['message'].__contains__('You exceeded your current quota'): + # 尝试切换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: + reply = handle_exception( + "api-key调用额度超限({}),无可用api_key,请向OpenAI账户充值或在config.py中更换api_key;如果你认为这是误判,请尝试重启程序。".format( + current_key_name), "[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 + elif 'message' in e.error and e.error['message'].__contains__('You can retry your request'): + # 重试 + unexpected_exception_times += 1 + continue + elif 'message' in e.error and e.error['message']\ + .__contains__('The server had an error while processing your request'): + # 重试 + unexpected_exception_times += 1 + continue + else: + reply = handle_exception("{}会话调用API失败:{}".format(session_name, e), + "[bot]err:RateLimitError,请重试或联系作者,或等待修复") + except openai.error.InvalidRequestError as e: + reply = handle_exception("{}API调用参数错误:{}\n\n这可能是由于config.py中的prompt_submit_length参数或" + "completion_api_params中的max_tokens参数数值过大导致的,请尝试将其降低".format( + session_name, e), "[bot]err:API调用参数错误,请联系管理员,或等待修复") + except openai.error.ServiceUnavailableError as e: + reply = handle_exception("{}API调用服务不可用:{}".format(session_name, e), "[bot]err:API调用服务不可用,请重试或联系管理员,或等待修复") + except Exception as e: + logging.exception(e) + reply = handle_exception("{}会话处理异常:{}".format(session_name, e), "[bot]err:{}".format(e)) + break + + return reply diff --git a/pkg/qqbot~/process.py b/pkg/qqbot~/process.py new file mode 100644 index 00000000..3ca275ac --- /dev/null +++ b/pkg/qqbot~/process.py @@ -0,0 +1,168 @@ +# 此模块提供了消息处理的具体逻辑的接口 +import asyncio +import time + +import mirai +import logging + +from mirai import MessageChain, Plain + +# 这里不使用动态引入config +# 因为在这里动态引入会卡死程序 +# 而此模块静态引用config与动态引入的表现一致 +# 已弃用,由于超时时间现已动态使用 +# import config as config_init_import + +import pkg.openai.session +import pkg.openai.manager +import pkg.utils.reloader +import pkg.utils.updater +import pkg.utils.context +import pkg.qqbot.message +import pkg.qqbot.command +import pkg.qqbot.ratelimit as ratelimit + +import pkg.plugin.host as plugin_host +import pkg.plugin.models as plugin_models +import pkg.qqbot.ignore as ignore +import pkg.qqbot.banlist as banlist + +processing = [] + + +def is_admin(qq: int) -> bool: + """兼容list和int类型的管理员判断""" + import config + if type(config.admin_qq) == list: + return qq in config.admin_qq + else: + return qq == config.admin_qq + + +def process_message(launcher_type: str, launcher_id: int, text_message: str, message_chain: MessageChain, + sender_id: int) -> MessageChain: + global processing + + mgr = pkg.utils.context.get_qqbot_manager() + + reply = [] + session_name = "{}_{}".format(launcher_type, launcher_id) + + # 检查发送方是否被禁用 + if banlist.is_banned(launcher_type, launcher_id, sender_id): + logging.info("根据禁用列表忽略{}_{}的消息".format(launcher_type, launcher_id)) + return [] + + if ignore.ignore(text_message): + logging.info("根据忽略规则忽略消息: {}".format(text_message)) + return [] + + # 检查是否被禁言 + if launcher_type == 'group': + result = mgr.bot.member_info(target=launcher_id, member_id=mgr.bot.qq).get() + result = asyncio.run(result) + if result.mute_time_remaining > 0: + logging.info("机器人被禁言,跳过消息处理(group_{},剩余{}s)".format(launcher_id, + result.mute_time_remaining)) + return reply + + import config + if hasattr(config, 'income_msg_check') and config.income_msg_check: + if mgr.reply_filter.is_illegal(text_message): + return MessageChain(Plain("[bot] 你的提问中有不合适的内容, 请更换措辞~")) + + pkg.openai.session.get_session(session_name).acquire_response_lock() + + text_message = text_message.strip() + + # 处理消息 + try: + if session_name in processing: + pkg.openai.session.get_session(session_name).release_response_lock() + return MessageChain([Plain("[bot]err:正在处理中,请稍后再试")]) + + config = pkg.utils.context.get_config() + + processing.append(session_name) + try: + if text_message.startswith('!') or text_message.startswith("!"): # 指令 + # 触发插件事件 + args = { + 'launcher_type': launcher_type, + 'launcher_id': launcher_id, + 'sender_id': sender_id, + 'command': text_message[1:].strip().split(' ')[0], + 'params': text_message[1:].strip().split(' ')[1:], + 'text_message': text_message, + 'is_admin': is_admin(sender_id), + } + event = plugin_host.emit(plugin_models.PersonCommandSent + if launcher_type == 'person' + else plugin_models.GroupCommandSent, **args) + + if event.get_return_value("alter") is not None: + text_message = event.get_return_value("alter") + + # 取出插件提交的返回值赋值给reply + if event.get_return_value("reply") is not None: + reply = event.get_return_value("reply") + + if not event.is_prevented_default(): + reply = pkg.qqbot.command.process_command(session_name, text_message, + mgr, config, launcher_type, launcher_id, sender_id, is_admin(sender_id)) + + else: # 消息 + # 限速丢弃检查 + # print(ratelimit.__crt_minute_usage__[session_name]) + if hasattr(config, "rate_limitation") and config.rate_limit_strategy == "drop": + if ratelimit.is_reach_limit(session_name): + logging.info("根据限速策略丢弃[{}]消息: {}".format(session_name, text_message)) + return MessageChain(["[bot]"+config.rate_limit_drop_tip]) if hasattr(config, "rate_limit_drop_tip") and config.rate_limit_drop_tip != "" else [] + + before = time.time() + # 触发插件事件 + args = { + "launcher_type": launcher_type, + "launcher_id": launcher_id, + "sender_id": sender_id, + "text_message": text_message, + } + event = plugin_host.emit(plugin_models.PersonNormalMessageReceived + if launcher_type == 'person' + else plugin_models.GroupNormalMessageReceived, **args) + + if event.get_return_value("alter") is not None: + text_message = event.get_return_value("alter") + + # 取出插件提交的返回值赋值给reply + if event.get_return_value("reply") is not None: + reply = event.get_return_value("reply") + + if not event.is_prevented_default(): + reply = pkg.qqbot.message.process_normal_message(text_message, + mgr, config, launcher_type, launcher_id, sender_id) + + # 限速等待时间 + if hasattr(config, "rate_limitation") and config.rate_limit_strategy == "wait": + time.sleep(ratelimit.get_rest_wait_time(session_name, time.time() - before)) + + if hasattr(config, "rate_limitation"): + ratelimit.add_usage(session_name) + + if reply is not None and len(reply) > 0 and (type(reply[0]) == str or type(reply[0]) == mirai.Plain): + if type(reply[0]) == mirai.Plain: + reply[0] = reply[0].text + logging.info( + "回复[{}]文字消息:{}".format(session_name, + reply[0][:min(100, len(reply[0]))] + ( + "..." if len(reply[0]) > 100 else ""))) + reply = [mgr.reply_filter.process(reply[0])] + else: + logging.info("回复[{}]消息".format(session_name)) + + finally: + processing.remove(session_name) + finally: + pkg.openai.session.get_session(session_name).release_response_lock() + + return MessageChain(reply) diff --git a/pkg/qqbot~/ratelimit.py b/pkg/qqbot~/ratelimit.py new file mode 100644 index 00000000..2a759b6e --- /dev/null +++ b/pkg/qqbot~/ratelimit.py @@ -0,0 +1,86 @@ +# 限速相关模块 +import time +import logging +import threading + +__crt_minute_usage__ = {} +"""当前分钟每个会话的对话次数""" + + +__timer_thr__: threading.Thread = None + + +def add_usage(session_name: str): + """增加会话的对话次数""" + global __crt_minute_usage__ + if session_name in __crt_minute_usage__: + __crt_minute_usage__[session_name] += 1 + else: + __crt_minute_usage__[session_name] = 1 + + +def start_timer(): + """启动定时器""" + global __timer_thr__ + __timer_thr__ = threading.Thread(target=run_timer, daemon=True) + __timer_thr__.start() + + +def run_timer(): + """启动定时器,每分钟清空一次对话次数""" + global __crt_minute_usage__ + global __timer_thr__ + + # 等待直到整分钟 + time.sleep(60 - time.time() % 60) + + while True: + if __timer_thr__ != threading.current_thread(): + break + + logging.debug("清空当前分钟的对话次数") + __crt_minute_usage__ = {} + time.sleep(60) + + +def get_usage(session_name: str) -> int: + """获取会话的对话次数""" + global __crt_minute_usage__ + if session_name in __crt_minute_usage__: + return __crt_minute_usage__[session_name] + else: + return 0 + + +def get_rest_wait_time(session_name: str, spent: float) -> float: + """获取会话此回合的剩余等待时间""" + global __crt_minute_usage__ + + import config + + if not hasattr(config, 'rate_limitation'): + return 0 + + min_seconds_per_round = 60.0 / config.rate_limitation + + if session_name in __crt_minute_usage__: + return max(0, min_seconds_per_round - spent) + else: + return 0 + + +def is_reach_limit(session_name: str) -> bool: + """判断会话是否超过限制""" + global __crt_minute_usage__ + + import config + + if not hasattr(config, 'rate_limitation'): + return False + + if session_name in __crt_minute_usage__: + return __crt_minute_usage__[session_name] >= config.rate_limitation + else: + return False + +start_timer() diff --git a/pkg/utils/constants.py b/pkg/utils/constants.py index 4a3f3b53..08712b4d 100644 --- a/pkg/utils/constants.py +++ b/pkg/utils/constants.py @@ -1,5 +1,5 @@ alipay_qr_b64 = """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""" -wechat_qr_b64 = """iVBORw0KGgoAAAANSUhEUgAAASwAAAFSCAYAAABIVe·LEAAAAAXNSR0IArs4c6QAAAARzQklUCAgICHwIZIgAAAAEZ0FNQQAAsY8L/GEFAAAACXBIWXMAAA7EAAAOxAGVKw4bAAB5iUlEQVR4Xu2dB2Ac1dHH5/qdumTJvVdsMJjimI7BQAi99wChl5jQe/sIhN4CgdAChB56gNB7sTE2uIAx2Ma4SrZ61/Vv/rO70up0p2LLts6eH6xv9+3u273T7f9m5s17zxFnSFEUJQ1wmq+Koig9HhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUspduorq6mc889l958802zpGNef/11uuCCC6i8vNwsUZTU6MzPSrdx9NFH00svvSTr0WiUnM6Ofw8dDoe8HnPMMfTCCy/IuqKkQi0sJSXLli2jSy65hIqLi82S9olEIuaacW5HrFq1ylwj6uzvJs656qqraPny5WaJsjmhgqWkZL/99qO77rqLJk+ebJa0T//+/c016pSLV1paaq4RFRUVmWvtM378eLrlllvk3pTNDxUsJSXbbbedvE6aNEleO2K33XYz14jq6urMtdRUVVWZa0Tbb7+9uZaalStXUkVFhax7vV55VTYvNIaltAusoM5aP2DPPfek3r1704svvmiWtM+BBx5IsViM3nrrrQ5jXk8//TSddNJJsn7llVfS3/72N1lXNh9UsDYzlixZQsOGDTO30guIFUQLzJo1q9kC7IilS5fSkCFDzC0lnVGXcDPizjvvpOHDh9PQoUPNkvTC3orYWbEaMGCAvN/77rvPLFHSGRWszQhYVwAWx/oEsabuBq2C4XBY1u2xso6wWiKt966kNypYmxGwsO6//36aN2+eWdL9HHLIITRw4EDJq+pOamtrzTWiE044wVzrGLzXf/zjH3TrrbeaJUo6ozEspVvJzs6WFkKPx0OhUMgs7R4QaK+pqREBUjZPVLCUbgV5Uj/88IOsI5HU5XLJejK+++47mj59Op1zzjnNGe+K0h7qEirdij15dM2aNeZacpB7dd5559Ff//pXs0RR2kcFK80pKysT6wStYVZQemOC/oQgLy+P+vXrJ+upyMjIkNfBgwfL68YG9wyLsLNdkZQNjwpWmvPFF1/IK1rDFi5cKOud5eWXX6apU6e26gO4rpx22mliWVVWVpolqamvr5fE1FNOOcUsWXcQN7vooouaO2F3ll9//VVGm0AS65dffmmWKj0NjWFtAlx88cViYeFB7SwIXufm5sr6qaeeSo8//rispzvHHXdcc75WV7/ayJyHiN58881midLTUMHajAkEAtTU1CTrsC5ycnJkPRW/+93v5IGeOXOmnLshQDrDTjvtJO7jjBkzzNLkoHUSrZQW+tXe9FCXcBMDD/UHH3xgbrXPvffea64RXXHFFeZacr755hv69ttvaf78+fT555+bpck5//zzxeL78ccfzZK154EHHpB6cO0PP/zQLE0OBg+0eOihh8y19nn//fc7FEKlBwELS9k0WLZsGUwKWVi0zNL2sY7HEolEzNLkHHvssfGDDjooHg6HzZK2xGKx5vrOPvtss3TtGT9+fHN9JSUlZmlyJk2aJMc5nU6zpH2uu+665rpXrlxplio9GRWsTYiqqqrmB3Du3Llmaftcc801zefgAe4O2MKKjxo1qsN7wP12hHVvffr0MUtSA9E555xz4rNmzTJLUrNgwYLmurGw62nuUXoyKlibGNXV1R1aIonYH9wNxa233irXO/roo82Strz77rvN9/WXv/zFLO0eIIBW3TfccINZqvR0NOieRqDJ/dFHH5UMcazn5+fTEUcc0ekB9lJx5plnSr0AMSMkc6YCOUoPP/ywnGNPEu0qW221VXOMK9VX8OSTT6Z///vfsv7OO+902yijSOXA+wRjx46VuJwdDF3z/PPPS0sq8rK22WYbYvfW3KtsVCBYSs/nyiuvbLYIEpdhw4bFf/31V/PIFp544on4vvvuG//pp5/MkuRUVlY215WdnW2WJodFSo7bfvvtzZK1o1evXlJPe64ei0nzfTU0NJilbZk8ebIcM3v2bLMkNdOnT2+uE0tZWZm5Jx5fuHBhfMiQIa3225fucpmVtUcFKw2A6FgPjdfrje+6665xtjYkTmR/oL7//nvzDAOrHOd3BALq1vEQulSwRSfHnH766WZJ1wkGg83XGjdunFnaFraw5JjDDjvMLGkLhMyq68YbbzRLU+PxeJqP//vf/26WxuNz5sxpLscyevRo+dx22WWXuM/nay4/4IADzDOUjY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""" +wechat_qr_b64 = """iVBORw0KGgoAAAANSUhEUgAAASwAAAFSCAYAAABIVeLEAAAAAXNSR0IArs4c6QAAAARzQklUCAgICHwIZIgAAAAEZ0FNQQAAsY8L/GEFAAAACXBIWXMAAA7EAAAOxAGVKw4bAAB5iUlEQVR4Xu2dB2Ac1dHH5/qdumTJvVdsMJjimI7BQAi99wChl5jQe/sIhN4CgdAChB56gNB7sTE2uIAx2Ma4SrZ61/Vv/rO70up0p2LLts6eH6xv9+3u273T7f9m5s17zxFnSFEUJQ1wmq+Koig9HhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUsRVHSBhUspduorq6mc889l958802zpGNef/11uuCCC6i8vNwsUZTU6MzPSrdx9NFH00svvSTr0WiUnM6Ofw8dDoe8HnPMMfTCCy/IuqKkQi0sJSXLli2jSy65hIqLi82S9olEIuaacW5HrFq1ylwj6uzvJs656qqraPny5WaJsjmhgqWkZL/99qO77rqLJk+ebJa0T//+/c016pSLV1paaq4RFRUVmWvtM378eLrlllvk3pTNDxUsJSXbbbedvE6aNEleO2K33XYz14jq6urMtdRUVVWZa0Tbb7+9uZaalStXUkVFhax7vV55VTYvNIaltAusoM5aP2DPPfek3r1704svvmiWtM+BBx5IsViM3nrrrQ5jXk8//TSddNJJsn7llVfS3/72N1lXNh9UsDYzlixZQsOGDTO30guIFUQLzJo1q9kC7IilS5fSkCFDzC0lnVGXcDPizjvvpOHDh9PQoUPNkvTC3orYWbEaMGCAvN/77rvPLFHSGRWszQhYVwAWx/oEsabuBq2C4XBY1u2xso6wWiKt966kNypYmxGwsO6//36aN2+eWdL9HHLIITRw4EDJq+pOamtrzTWiE044wVzrGLzXf/zjH3TrrbeaJUo6ozEspVvJzs6WFkKPx0OhUMgs7R4QaK+pqREBUjZPVLCUbgV5Uj/88IOsI5HU5XLJejK+++47mj59Op1zzjnNGe+K0h7qEirdij15dM2aNeZacpB7dd5559Ff//pXs0RR2kcFK80pKysT6wStYVZQemOC/oQgLy+P+vXrJ+upyMjIkNfBgwfL68YG9wyLsLNdkZQNjwpWmvPFF1/IK1rDFi5cKOud5eWXX6apU6e26gO4rpx22mliWVVWVpolqamvr5fE1FNOOcUsWXcQN7vooouaO2F3ll9//VVGm0AS65dffmmWKj0NjWFtAlx88cViYeFB7SwIXufm5sr6qaeeSo8//rispzvHHXdcc75WV7/ayJyHiN58881midLTUMHajAkEAtTU1CTrsC5ycnJkPRW/+93v5IGeOXOmnLshQDrDTjvtJO7jjBkzzNLkoHUSrZQW+tXe9FCXcBMDD/UHH3xgbrXPvffea64RXXHFFeZacr755hv69ttvaf78+fT555+bpck5//zzxeL78ccfzZK154EHHpB6cO0PP/zQLE0OBg+0eOihh8y19nn//fc7FEKlBwELS9k0WLZsGUwKWVi0zNL2sY7HEolEzNLkHHvssfGDDjooHg6HzZK2xGKx5vrOPvtss3TtGT9+fHN9JSUlZmlyJk2aJMc5nU6zpH2uu+665rpXrlxplio9GRWsTYiqqqrmB3Du3Llmaftcc801zefgAe4O2MKKjxo1qsN7wP12hHVvffr0MUtSA9E555xz4rNmzTJLUrNgwYLmurGw62nuUXoyKlibGNXV1R1aIonYH9wNxa233irXO/roo82Strz77rvN9/WXv/zFLO0eIIBW3TfccINZqvR0NOieRqDJ/dFHH5UMcazn5+fTEUcc0ekB9lJx5plnSr0AMSMkc6YCOUoPP/ywnGNPEu0qW221VXOMK9VX8OSTT6Z///vfsv7OO+902yijSOXA+wRjx46VuJwdDF3z/PPPS0sq8rK22WYbYvfW3KtsVCBYSs/nyiuvbLYIEpdhw4bFf/31V/PIFp544on4vvvuG//pp5/MkuRUVlY215WdnW2WJodFSo7bfvvtzZK1o1evXlJPe64ei0nzfTU0NJilbZk8ebIcM3v2bLMkNdOnT2+uE0tZWZm5Jx5fuHBhfMiQIa3225fucpmVtUcFKw2A6FgPjdfrje+6665xtjYkTmR/oL7//nvzDAOrHOd3BALq1vEQulSwRSfHnH766WZJ1wkGg83XGjdunFnaFraw5JjDDjvMLGkLhMyq68YbbzRLU+PxeJqP//vf/26W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""" -semantic_version = "v2.1.0" +semantic_version = "v2.1.4" diff --git a/pkg/utils/updater.py b/pkg/utils/updater.py index 84aa836a..d2d89517 100644 --- a/pkg/utils/updater.py +++ b/pkg/utils/updater.py @@ -54,7 +54,7 @@ def get_current_tag() -> str: return current_tag -def update_all() -> bool: +def update_all(cli: bool = False) -> bool: """检查更新并下载源码""" current_tag = get_current_tag() @@ -69,12 +69,19 @@ def update_all() -> bool: if latest_rls == {}: latest_rls = rls - logging.info("更新日志: {}".format(rls_notes)) + if not cli: + logging.info("更新日志: {}".format(rls_notes)) + else: + print("更新日志: {}".format(rls_notes)) + if latest_rls == {}: # 没有新版本 return False # 下载最新版本的zip到temp目录 - logging.info("开始下载最新版本: {}".format(latest_rls['zipball_url'])) + if not cli: + logging.info("开始下载最新版本: {}".format(latest_rls['zipball_url'])) + else: + print("开始下载最新版本: {}".format(latest_rls['zipball_url'])) zip_url = latest_rls['zipball_url'] zip_resp = requests.get(url=zip_url) zip_data = zip_resp.content @@ -87,7 +94,10 @@ def update_all() -> bool: with open("temp/updater/{}.zip".format(latest_rls['tag_name']), "wb") as f: f.write(zip_data) - logging.info("下载最新版本完成: {}".format("temp/updater/{}.zip".format(latest_rls['tag_name']))) + if not cli: + logging.info("下载最新版本完成: {}".format("temp/updater/{}.zip".format(latest_rls['tag_name']))) + else: + print("下载最新版本完成: {}".format("temp/updater/{}.zip".format(latest_rls['tag_name']))) # 解压zip到temp/updater// import zipfile @@ -124,8 +134,11 @@ def update_all() -> bool: f.write(current_tag) # 通知管理员 - import pkg.utils.context - pkg.utils.context.get_qqbot_manager().notify_admin("已更新到最新版本: {}\n更新日志:\n{}\n新功能通常可以在config-template.py中看到,完整的更新日志请前往 https://github.com/RockChinQ/QChatGPT/releases 查看".format(current_tag, "\n".join(rls_notes))) + if not cli: + import pkg.utils.context + pkg.utils.context.get_qqbot_manager().notify_admin("已更新到最新版本: {}\n更新日志:\n{}\n新功能通常可以在config-template.py中看到,完整的更新日志请前往 https://github.com/RockChinQ/QChatGPT/releases 查看".format(current_tag, "\n".join(rls_notes))) + else: + print("已更新到最新版本: {}\n更新日志:\n{}\n新功能通常可以在config-template.py中看到,完整的更新日志请前往 https://github.com/RockChinQ/QChatGPT/releases 查看".format(current_tag, "\n".join(rls_notes))) return True diff --git a/sensitive-template.json b/sensitive-template.json index 5ee7ee77..61d15ff9 100644 --- a/sensitive-template.json +++ b/sensitive-template.json @@ -1,4 +1,7 @@ { + "说明": "mask将替换敏感词中的每一个字,若mask_word值不为空,则将敏感词整个替换为mask_word的值", + "mask": "*", + "mask_word": "", "words": [ "习近平", "胡锦涛",