From 3f29464dbdfa6563ad8e2dae950abc9961738bf6 Mon Sep 17 00:00:00 2001 From: RockChinQ <1010553892@qq.com> Date: Wed, 13 Dec 2023 14:53:39 +0800 Subject: [PATCH 01/15] =?UTF-8?q?feat:=20=E6=A0=87=E8=AF=86=E7=AC=A6?= =?UTF-8?q?=E7=94=9F=E6=88=90=E5=99=A8=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- main.py | 5 ++ pkg/audit/identifier.py | 83 ++++++++++++++++++++++++ pkg/database/manager.py | 2 +- tests/identifier_test/host_identifier.py | 43 ++++++++++++ 5 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 pkg/audit/identifier.py create mode 100644 tests/identifier_test/host_identifier.py diff --git a/.gitignore b/.gitignore index 9d4595de..d671df3a 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,5 @@ qcapi claude.json bard.json /*yaml -!/docker-compose.yaml \ No newline at end of file +!/docker-compose.yaml +res/instance_id.json \ No newline at end of file diff --git a/main.py b/main.py index 043500ef..155620a5 100644 --- a/main.py +++ b/main.py @@ -145,6 +145,11 @@ async def start_process(first_time_init=False): global known_exception_caught import pkg.utils.context + # 计算host和instance标识符 + import pkg.audit.identifier + pkg.audit.identifier.init() + pkg.audit.identifier.print_out() + # 加载配置 cfg_inst: pymodule_cfg.PythonModuleConfigFile = pymodule_cfg.PythonModuleConfigFile( 'config.py', diff --git a/pkg/audit/identifier.py b/pkg/audit/identifier.py new file mode 100644 index 00000000..334b7e3b --- /dev/null +++ b/pkg/audit/identifier.py @@ -0,0 +1,83 @@ +import os +import uuid +import json +import time + + +identifier = { + 'host_id': '', + 'instance_id': '', + 'host_create_ts': 0, + 'instance_create_ts': 0, +} + +HOST_ID_FILE = os.path.expanduser('~/.qchatgpt/host_id.json') +INSTANCE_ID_FILE = 'res/instance_id.json' + +def init(): + global identifier + + if not os.path.exists(os.path.expanduser('~/.qchatgpt')): + os.mkdir(os.path.expanduser('~/.qchatgpt')) + + if not os.path.exists(HOST_ID_FILE): + new_host_id = 'host_'+str(uuid.uuid4()) + new_host_create_ts = int(time.time()) + + with open(HOST_ID_FILE, 'w') as f: + json.dump({ + 'host_id': new_host_id, + 'host_create_ts': new_host_create_ts + }, f) + + identifier['host_id'] = new_host_id + identifier['host_create_ts'] = new_host_create_ts + else: + loaded_host_id = '' + loaded_host_create_ts = 0 + + with open(HOST_ID_FILE, 'r') as f: + file_content = json.load(f) + loaded_host_id = file_content['host_id'] + loaded_host_create_ts = file_content['host_create_ts'] + + identifier['host_id'] = loaded_host_id + identifier['host_create_ts'] = loaded_host_create_ts + + # 检查实例 id + if os.path.exists(INSTANCE_ID_FILE): + instance_id = {} + with open(INSTANCE_ID_FILE, 'r') as f: + instance_id = json.load(f) + + if instance_id['host_id'] != identifier['host_id']: # 如果实例 id 不是当前主机的,删除 + os.remove(INSTANCE_ID_FILE) + + if not os.path.exists(INSTANCE_ID_FILE): + new_instance_id = 'instance_'+str(uuid.uuid4()) + new_instance_create_ts = int(time.time()) + + with open(INSTANCE_ID_FILE, 'w') as f: + json.dump({ + 'host_id': identifier['host_id'], + 'instance_id': new_instance_id, + 'instance_create_ts': new_instance_create_ts + }, f) + + identifier['instance_id'] = new_instance_id + identifier['instance_create_ts'] = new_instance_create_ts + else: + loaded_instance_id = '' + loaded_instance_create_ts = 0 + + with open(INSTANCE_ID_FILE, 'r') as f: + file_content = json.load(f) + loaded_instance_id = file_content['instance_id'] + loaded_instance_create_ts = file_content['instance_create_ts'] + + identifier['instance_id'] = loaded_instance_id + identifier['instance_create_ts'] = loaded_instance_create_ts + +def print_out(): + global identifier + print(identifier) diff --git a/pkg/database/manager.py b/pkg/database/manager.py index f410b418..ad44d512 100644 --- a/pkg/database/manager.py +++ b/pkg/database/manager.py @@ -91,7 +91,7 @@ class DatabaseManager: `json` text not null ) """) - print('Database initialized.') + # print('Database initialized.') # session持久化 def persistence_session(self, subject_type: str, subject_number: int, create_timestamp: int, diff --git a/tests/identifier_test/host_identifier.py b/tests/identifier_test/host_identifier.py new file mode 100644 index 00000000..64834931 --- /dev/null +++ b/tests/identifier_test/host_identifier.py @@ -0,0 +1,43 @@ +import os +import uuid +import json + +# 向 ~/.qchatgpt 写入一个 标识符 + +if not os.path.exists(os.path.expanduser('~/.qchatgpt')): + os.mkdir(os.path.expanduser('~/.qchatgpt')) + +identifier = { + "host_id": "host_"+str(uuid.uuid4()), +} + +if not os.path.exists(os.path.expanduser('~/.qchatgpt/host.json')): + print('create ~/.qchatgpt/host.json') + with open(os.path.expanduser('~/.qchatgpt/host.json'), 'w') as f: + json.dump(identifier, f) +else: + print('load ~/.qchatgpt/host.json') + with open(os.path.expanduser('~/.qchatgpt/host.json'), 'r') as f: + identifier = json.load(f) + +print(identifier) + +instance_id = { + "host_id": identifier['host_id'], + "instance_id": "instance_"+str(uuid.uuid4()), +} + +# 实例 id +if os.path.exists("res/instance_id.json"): + with open("res/instance_id.json", 'r') as f: + instance_id = json.load(f) + + if instance_id['host_id'] != identifier['host_id']: + os.remove("res/instance_id.json") + +if not os.path.exists("res/instance_id.json"): + print('create res/instance_id.json') + with open("res/instance_id.json", 'w') as f: + json.dump(instance_id, f) + +print(instance_id) \ No newline at end of file From d1c24533102453032dc76bdf65ed45e5c9cf851f Mon Sep 17 00:00:00 2001 From: RockChinQ <1010553892@qq.com> Date: Thu, 21 Dec 2023 16:21:24 +0800 Subject: [PATCH 02/15] =?UTF-8?q?feat:=20=E5=90=AF=E5=8A=A8=E6=97=B6?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E4=B8=AD=E5=A4=AE=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=99=A8=20API=20=E4=BA=A4=E4=BA=92=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 -- main.py | 19 +++++++ pkg/utils/center/__init__.py | 0 pkg/utils/center/apigroup.py | 60 +++++++++++++++++++++ pkg/utils/center/groups/__init__.py | 0 pkg/utils/center/groups/main.py | 48 +++++++++++++++++ pkg/utils/center/groups/plugin.py | 58 +++++++++++++++++++++ pkg/utils/center/groups/usage.py | 81 +++++++++++++++++++++++++++++ pkg/utils/center/v2.py | 33 ++++++++++++ pkg/utils/context.py | 14 +++++ pkg/utils/platform.py | 7 +++ requirements.txt | 1 + 12 files changed, 321 insertions(+), 3 deletions(-) create mode 100644 pkg/utils/center/__init__.py create mode 100644 pkg/utils/center/apigroup.py create mode 100644 pkg/utils/center/groups/__init__.py create mode 100644 pkg/utils/center/groups/main.py create mode 100644 pkg/utils/center/groups/plugin.py create mode 100644 pkg/utils/center/groups/usage.py create mode 100644 pkg/utils/center/v2.py create mode 100644 pkg/utils/platform.py diff --git a/README.md b/README.md index 5c84f766..df8b7991 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,6 @@ Static Badge - 项目主页部署文档功能介绍 | @@ -37,6 +36,4 @@ 插件介绍 回复效果(带有联网插件) - - diff --git a/main.py b/main.py index 155620a5..00af25fb 100644 --- a/main.py +++ b/main.py @@ -163,6 +163,7 @@ async def start_process(first_time_init=False): complete_tips() cfg = pkg.utils.context.get_config_manager().data + # 更新openai库到最新版本 if 'upgrade_dependencies' not in cfg or cfg['upgrade_dependencies']: print("正在更新依赖库,请等待...") @@ -209,6 +210,24 @@ async def start_process(first_time_init=False): break except ValueError: print("请输入数字") + + # 初始化中央服务器 API 交互实例 + from pkg.utils.center import apigroup + from pkg.utils.center import v2 as center_v2 + + center_v2_api = center_v2.V2CenterAPI( + basic_info={ + "host_id": pkg.audit.identifier.identifier['host_id'], + "instance_id": pkg.audit.identifier.identifier['instance_id'], + "semantic_version": pkg.utils.updater.get_current_tag(), + "platform": sys.platform, + }, + runtime_info={ + "admin_qq": cfg['admin_qq'], + "msg_source": cfg['msg_source_adapter'], + } + ) + pkg.utils.context.set_center_v2_api(center_v2_api) import pkg.openai.manager import pkg.database.manager diff --git a/pkg/utils/center/__init__.py b/pkg/utils/center/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pkg/utils/center/apigroup.py b/pkg/utils/center/apigroup.py new file mode 100644 index 00000000..5938fd60 --- /dev/null +++ b/pkg/utils/center/apigroup.py @@ -0,0 +1,60 @@ +import abc +import uuid +import json + +import aiohttp + + +class APIGroup(metaclass=abc.ABCMeta): + """API 组抽象类""" + _basic_info: dict = None + _runtime_info: dict = None + + prefix = None + + def __init__(self, prefix: str): + self.prefix = prefix + + async def do( + self, + method: str, + path: str, + data: dict = None, + params: dict = None, + headers: dict = None, + **kwargs + ): + """执行一个请求""" + url = self.prefix + path + data = json.dumps(data) + headers['Content-Type'] = 'application/json' + async with aiohttp.ClientSession() as session: + async with session.request( + method, + url, + data=data, + params=params, + headers=headers, + **kwargs + ) as resp: + return await resp.json() + + def gen_rid( + self + ): + """生成一个请求 ID""" + return str(uuid.uuid4()) + + def basic_info( + self + ): + """获取基本信息""" + basic_info = APIGroup._basic_info.copy() + basic_info['rid'] = self.gen_rid() + return APIGroup._basic_info + + def runtime_info( + self + ): + """获取运行时信息""" + return APIGroup._runtime_info diff --git a/pkg/utils/center/groups/__init__.py b/pkg/utils/center/groups/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pkg/utils/center/groups/main.py b/pkg/utils/center/groups/main.py new file mode 100644 index 00000000..71bef9b2 --- /dev/null +++ b/pkg/utils/center/groups/main.py @@ -0,0 +1,48 @@ +from __future__ import annotations + +from .. import apigroup + + +class V2MainDataAPI(apigroup.APIGroup): + """主程序相关 数据API""" + + def __init__(self, prefix: str): + super().__init__(prefix+"/main") + + async def post_update_record( + self, + spent_seconds: int, + infer_reason: str, + old_version: str, + new_version: str, + ): + """提交更新记录""" + return await self.do( + "POST", + "/update", + data={ + "basic": self.basic_info(), + "update_info": { + "spent_seconds": spent_seconds, + "infer_reason": infer_reason, + "old_version": old_version, + "new_version": new_version, + } + } + ) + + async def post_announcement_showed( + self, + ids: list[int], + ): + """提交公告已阅""" + return await self.do( + "POST", + "/announcement", + data={ + "basic": self.basic_info(), + "announcement_info": { + "ids": ids, + } + } + ) diff --git a/pkg/utils/center/groups/plugin.py b/pkg/utils/center/groups/plugin.py new file mode 100644 index 00000000..beff8714 --- /dev/null +++ b/pkg/utils/center/groups/plugin.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +from .. import apigroup + + +class V2PluginDataAPI(apigroup.APIGroup): + """插件数据相关 API""" + + def __init__(self, prefix: str): + super().__init__(prefix+"/plugin") + + async def post_install_record( + self, + plugin: dict + ): + """提交插件安装记录""" + return await self.do( + "POST", + "/install", + data={ + "basic": self.basic_info(), + "plugin": plugin, + } + ) + + async def post_remove_record( + self, + plugin: dict + ): + """提交插件卸载记录""" + return await self.do( + "POST", + "/remove", + data={ + "basic": self.basic_info(), + "plugin": plugin, + } + ) + + async def post_update_record( + self, + plugin: dict, + old_version: str, + new_version: str, + ): + """提交插件更新记录""" + return await self.do( + "POST", + "/update", + data={ + "basic": self.basic_info(), + "plugin": plugin, + "update_info": { + "old_version": old_version, + "new_version": new_version, + } + } + ) diff --git a/pkg/utils/center/groups/usage.py b/pkg/utils/center/groups/usage.py new file mode 100644 index 00000000..338b869a --- /dev/null +++ b/pkg/utils/center/groups/usage.py @@ -0,0 +1,81 @@ +from __future__ import annotations + +from .. import apigroup + + +class V2UsageDataAPI(apigroup.APIGroup): + """使用量数据相关 API""" + + def __init__(self, prefix: str): + super().__init__(prefix+"/usage") + + async def post_query_record( + self, + session_type: str, + session_id: str, + query_ability_provider: str, + usage: int, + model_name: str, + response_seconds: int, + retry_times: int, + ): + """提交请求记录""" + return await self.do( + "POST", + "/query", + data={ + "basic": self.basic_info(), + "runtime": self.runtime_info(), + "session_info": { + "type": session_type, + "id": session_id, + }, + "query_info": { + "ability_provider": query_ability_provider, + "usage": usage, + "model_name": model_name, + "response_seconds": response_seconds, + "retry_times": retry_times, + } + } + ) + + async def post_event_record( + self, + plugins: list[dict], + event_name: str, + ): + """提交事件触发记录""" + return await self.do( + "POST", + "/event", + data={ + "basic": self.basic_info(), + "runtime": self.runtime_info(), + "plugins": plugins, + "event_info": { + "name": event_name, + } + } + ) + + async def post_function_record( + self, + plugin: dict, + function_name: str, + function_description: str, + ): + """提交内容函数使用记录""" + return await self.do( + "POST", + "/function", + data={ + "basic": self.basic_info(), + "plugin": plugin, + "function_info": { + "name": function_name, + "description": function_description, + } + } + ) + diff --git a/pkg/utils/center/v2.py b/pkg/utils/center/v2.py new file mode 100644 index 00000000..6cda4093 --- /dev/null +++ b/pkg/utils/center/v2.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +from . import apigroup +from .groups import main +from .groups import usage +from .groups import plugin + + +BACKEND_URL = "https://api.qchatgpt.rockchin.top/api/v2" + +class V2CenterAPI: + """中央服务器 v2 API 交互类""" + + main: main.V2MainDataAPI = None + """主 API 组""" + + usage: usage.V2UsageDataAPI = None + """使用量 API 组""" + + plugin: plugin.V2PluginDataAPI = None + """插件 API 组""" + + def __init__(self, basic_info: dict = None, runtime_info: dict = None): + """初始化""" + + print("basic_info:", basic_info) + print("runtime_info:", runtime_info) + apigroup.APIGroup._basic_info = basic_info + apigroup.APIGroup._runtime_info = runtime_info + + self.main = main.V2MainDataAPI(BACKEND_URL) + self.usage = usage.V2UsageDataAPI(BACKEND_URL) + self.plugin = plugin.V2PluginDataAPI(BACKEND_URL) diff --git a/pkg/utils/context.py b/pkg/utils/context.py index e26c702b..e6a2734a 100644 --- a/pkg/utils/context.py +++ b/pkg/utils/context.py @@ -8,6 +8,7 @@ from ..openai import manager as openai_mgr from ..qqbot import manager as qqbot_mgr from ..config import manager as config_mgr from ..plugin import host as plugin_host +from .center import v2 as center_v2 context = { @@ -114,3 +115,16 @@ def get_thread_ctl() -> threadctl.ThreadCtl: t: threadctl.ThreadCtl = context['pool_ctl'] context_lock.release() return t + + +def set_center_v2_api(inst: center_v2.V2CenterAPI): + context_lock.acquire() + context['center_v2_api'] = inst + context_lock.release() + + +def get_center_v2_api() -> center_v2.V2CenterAPI: + context_lock.acquire() + t: center_v2.V2CenterAPI = context['center_v2_api'] + context_lock.release() + return t \ No newline at end of file diff --git a/pkg/utils/platform.py b/pkg/utils/platform.py new file mode 100644 index 00000000..b280b071 --- /dev/null +++ b/pkg/utils/platform.py @@ -0,0 +1,7 @@ +import os +import sys + + +def get_platform() -> str: + """获取当前平台""" + return sys.platform diff --git a/requirements.txt b/requirements.txt index 7047d580..c3e29401 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,4 @@ nakuru-project-idk CallingGPT tiktoken PyYaml +aiohttp \ No newline at end of file From 38357dd68db39fa6f38d59c8f7240723c841ef19 Mon Sep 17 00:00:00 2001 From: RockChinQ <1010553892@qq.com> Date: Thu, 21 Dec 2023 16:28:45 +0800 Subject: [PATCH 03/15] =?UTF-8?q?perf:=20=E7=AE=80=E5=8C=96=E5=90=AF?= =?UTF-8?q?=E5=8A=A8=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 3 +-- pkg/openai/keymgr.py | 2 +- pkg/plugin/host.py | 2 +- pkg/utils/center/v2.py | 6 ++++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/main.py b/main.py index 00af25fb..5fe42013 100644 --- a/main.py +++ b/main.py @@ -122,7 +122,7 @@ def complete_tips(): non_exist_keys = [] is_integrity = True - logging.info("检查tips模块完整性.") + logging.debug("检查tips模块完整性.") tips_template = importlib.import_module('tips-custom-template') tips = importlib.import_module('tips') for key in dir(tips_template): @@ -148,7 +148,6 @@ async def start_process(first_time_init=False): # 计算host和instance标识符 import pkg.audit.identifier pkg.audit.identifier.init() - pkg.audit.identifier.print_out() # 加载配置 cfg_inst: pymodule_cfg.PythonModuleConfigFile = pymodule_cfg.PythonModuleConfigFile( diff --git a/pkg/openai/keymgr.py b/pkg/openai/keymgr.py index ea9c292b..be6d728f 100644 --- a/pkg/openai/keymgr.py +++ b/pkg/openai/keymgr.py @@ -75,7 +75,7 @@ class KeysManager: if self.api_key[key_name] not in self.exceeded: self.using_key = self.api_key[key_name] - logging.info("使用api-key:" + key_name) + logging.debug("使用api-key:" + key_name) # 触发插件事件 args = { diff --git a/pkg/plugin/host.py b/pkg/plugin/host.py index 27806845..889e57f6 100644 --- a/pkg/plugin/host.py +++ b/pkg/plugin/host.py @@ -132,7 +132,7 @@ def load_plugins(): def initialize_plugins(): """初始化插件""" - logging.info("初始化插件") + logging.debug("初始化插件") import pkg.plugin.models as models successfully_initialized_plugins = [] diff --git a/pkg/utils/center/v2.py b/pkg/utils/center/v2.py index 6cda4093..b1c0a3e6 100644 --- a/pkg/utils/center/v2.py +++ b/pkg/utils/center/v2.py @@ -1,5 +1,7 @@ from __future__ import annotations +import logging + from . import apigroup from .groups import main from .groups import usage @@ -23,8 +25,8 @@ class V2CenterAPI: def __init__(self, basic_info: dict = None, runtime_info: dict = None): """初始化""" - print("basic_info:", basic_info) - print("runtime_info:", runtime_info) + logging.debug("basic_info: %s, runtime_info: %s", basic_info, runtime_info) + apigroup.APIGroup._basic_info = basic_info apigroup.APIGroup._runtime_info = runtime_info From b8776fba6579064bcc5a424a358b878b7c812a5f Mon Sep 17 00:00:00 2001 From: RockChinQ <1010553892@qq.com> Date: Thu, 21 Dec 2023 16:44:21 +0800 Subject: [PATCH 04/15] chore: stash --- main.py | 6 +++--- pkg/qqbot/cmds/system/update.py | 10 +++++++--- pkg/utils/updater.py | 16 +++++++++++++++- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/main.py b/main.py index 5fe42013..ebb98e92 100644 --- a/main.py +++ b/main.py @@ -422,7 +422,7 @@ def stop(): raise e -def main(): +async def main(): global use_override # 检查是否携带了 --override 或 -r 参数 if '--override' in sys.argv or '-r' in sys.argv: @@ -450,7 +450,7 @@ def main(): elif len(sys.argv) > 1 and sys.argv[1] == 'update': print("正在进行程序更新...") import pkg.utils.updater as updater - updater.update_all(cli=True) + await updater.update_all(cli=True) sys.exit(0) # 关闭urllib的http警告 @@ -486,5 +486,5 @@ def main(): if __name__ == '__main__': - main() + asyncio.run(main()) diff --git a/pkg/qqbot/cmds/system/update.py b/pkg/qqbot/cmds/system/update.py index d4cca3f3..6d97cd9e 100644 --- a/pkg/qqbot/cmds/system/update.py +++ b/pkg/qqbot/cmds/system/update.py @@ -1,4 +1,5 @@ import threading +import asyncio import traceback from .. import aamgr @@ -20,9 +21,9 @@ class UpdateCommand(aamgr.AbstractCommandNode): import pkg.utils.reloader import pkg.utils.context - def update_task(): + async def update_task(): try: - if pkg.utils.updater.update_all(): + if await pkg.utils.updater.update_all(): pkg.utils.context.get_qqbot_manager().notify_admin("更新完成, 请手动重启程序。") else: pkg.utils.context.get_qqbot_manager().notify_admin("无新版本") @@ -31,7 +32,10 @@ class UpdateCommand(aamgr.AbstractCommandNode): pkg.utils.context.get_qqbot_manager().notify_admin("更新失败:{}".format(e0)) return - threading.Thread(target=update_task, daemon=True).start() + def wrapper(): + asyncio.run(update_task()) + + threading.Thread(target=wrapper).start() reply = ["[bot]正在更新,请耐心等待,请勿重复发起更新..."] diff --git a/pkg/utils/updater.py b/pkg/utils/updater.py index 767f3621..abbc696a 100644 --- a/pkg/utils/updater.py +++ b/pkg/utils/updater.py @@ -1,11 +1,15 @@ +from __future__ import annotations + import datetime import logging import os.path +import time import requests from . import constants from . import network +from . import context def check_dulwich_closure(): @@ -105,9 +109,12 @@ def compare_version_str(v0: str, v1: str) -> int: return 0 -def update_all(cli: bool = False) -> bool: +async def update_all(cli: bool = False) -> bool: """检查更新并下载源码""" + start_time = time.time() + current_tag = get_current_tag() + old_tag = current_tag rls_list = get_release_list() @@ -200,6 +207,13 @@ def update_all(cli: bool = False) -> bool: with open("current_tag", "w") as f: f.write(current_tag) + await context.get_center_v2_api().main.post_update_record( + spent_seconds=int(time.time()-start_time), + infer_reason="update", + old_version=old_tag, + new_version=current_tag, + ) + # 通知管理员 if not cli: import pkg.utils.context From 7c6526d1ea6904c65a3135570f8c8626faea8d2b Mon Sep 17 00:00:00 2001 From: RockChinQ <1010553892@qq.com> Date: Thu, 21 Dec 2023 16:48:50 +0800 Subject: [PATCH 05/15] =?UTF-8?q?feat:=20=E6=94=B9=E4=B8=BA=E5=90=8C?= =?UTF-8?q?=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 6 +++--- pkg/qqbot/cmds/system/update.py | 10 +++------- pkg/utils/center/apigroup.py | 21 ++++++++++----------- pkg/utils/center/groups/main.py | 8 ++++---- pkg/utils/center/groups/plugin.py | 12 ++++++------ pkg/utils/center/groups/usage.py | 12 ++++++------ pkg/utils/updater.py | 4 ++-- 7 files changed, 34 insertions(+), 39 deletions(-) diff --git a/main.py b/main.py index ebb98e92..5fe42013 100644 --- a/main.py +++ b/main.py @@ -422,7 +422,7 @@ def stop(): raise e -async def main(): +def main(): global use_override # 检查是否携带了 --override 或 -r 参数 if '--override' in sys.argv or '-r' in sys.argv: @@ -450,7 +450,7 @@ async def main(): elif len(sys.argv) > 1 and sys.argv[1] == 'update': print("正在进行程序更新...") import pkg.utils.updater as updater - await updater.update_all(cli=True) + updater.update_all(cli=True) sys.exit(0) # 关闭urllib的http警告 @@ -486,5 +486,5 @@ async def main(): if __name__ == '__main__': - asyncio.run(main()) + main() diff --git a/pkg/qqbot/cmds/system/update.py b/pkg/qqbot/cmds/system/update.py index 6d97cd9e..d4cca3f3 100644 --- a/pkg/qqbot/cmds/system/update.py +++ b/pkg/qqbot/cmds/system/update.py @@ -1,5 +1,4 @@ import threading -import asyncio import traceback from .. import aamgr @@ -21,9 +20,9 @@ class UpdateCommand(aamgr.AbstractCommandNode): import pkg.utils.reloader import pkg.utils.context - async def update_task(): + def update_task(): try: - if await pkg.utils.updater.update_all(): + if pkg.utils.updater.update_all(): pkg.utils.context.get_qqbot_manager().notify_admin("更新完成, 请手动重启程序。") else: pkg.utils.context.get_qqbot_manager().notify_admin("无新版本") @@ -32,10 +31,7 @@ class UpdateCommand(aamgr.AbstractCommandNode): pkg.utils.context.get_qqbot_manager().notify_admin("更新失败:{}".format(e0)) return - def wrapper(): - asyncio.run(update_task()) - - threading.Thread(target=wrapper).start() + threading.Thread(target=update_task, daemon=True).start() reply = ["[bot]正在更新,请耐心等待,请勿重复发起更新..."] diff --git a/pkg/utils/center/apigroup.py b/pkg/utils/center/apigroup.py index 5938fd60..1324ab76 100644 --- a/pkg/utils/center/apigroup.py +++ b/pkg/utils/center/apigroup.py @@ -2,7 +2,7 @@ import abc import uuid import json -import aiohttp +import requests class APIGroup(metaclass=abc.ABCMeta): @@ -28,16 +28,15 @@ class APIGroup(metaclass=abc.ABCMeta): url = self.prefix + path data = json.dumps(data) headers['Content-Type'] = 'application/json' - async with aiohttp.ClientSession() as session: - async with session.request( - method, - url, - data=data, - params=params, - headers=headers, - **kwargs - ) as resp: - return await resp.json() + + return requests.request( + method, + url, + data=data, + params=params, + headers=headers, + **kwargs + ) def gen_rid( self diff --git a/pkg/utils/center/groups/main.py b/pkg/utils/center/groups/main.py index 71bef9b2..f158cd79 100644 --- a/pkg/utils/center/groups/main.py +++ b/pkg/utils/center/groups/main.py @@ -9,7 +9,7 @@ class V2MainDataAPI(apigroup.APIGroup): def __init__(self, prefix: str): super().__init__(prefix+"/main") - async def post_update_record( + def post_update_record( self, spent_seconds: int, infer_reason: str, @@ -17,7 +17,7 @@ class V2MainDataAPI(apigroup.APIGroup): new_version: str, ): """提交更新记录""" - return await self.do( + return self.do( "POST", "/update", data={ @@ -31,12 +31,12 @@ class V2MainDataAPI(apigroup.APIGroup): } ) - async def post_announcement_showed( + def post_announcement_showed( self, ids: list[int], ): """提交公告已阅""" - return await self.do( + return self.do( "POST", "/announcement", data={ diff --git a/pkg/utils/center/groups/plugin.py b/pkg/utils/center/groups/plugin.py index beff8714..b3ac423c 100644 --- a/pkg/utils/center/groups/plugin.py +++ b/pkg/utils/center/groups/plugin.py @@ -9,12 +9,12 @@ class V2PluginDataAPI(apigroup.APIGroup): def __init__(self, prefix: str): super().__init__(prefix+"/plugin") - async def post_install_record( + def post_install_record( self, plugin: dict ): """提交插件安装记录""" - return await self.do( + return self.do( "POST", "/install", data={ @@ -23,12 +23,12 @@ class V2PluginDataAPI(apigroup.APIGroup): } ) - async def post_remove_record( + def post_remove_record( self, plugin: dict ): """提交插件卸载记录""" - return await self.do( + return self.do( "POST", "/remove", data={ @@ -37,14 +37,14 @@ class V2PluginDataAPI(apigroup.APIGroup): } ) - async def post_update_record( + def post_update_record( self, plugin: dict, old_version: str, new_version: str, ): """提交插件更新记录""" - return await self.do( + return self.do( "POST", "/update", data={ diff --git a/pkg/utils/center/groups/usage.py b/pkg/utils/center/groups/usage.py index 338b869a..6e383a35 100644 --- a/pkg/utils/center/groups/usage.py +++ b/pkg/utils/center/groups/usage.py @@ -9,7 +9,7 @@ class V2UsageDataAPI(apigroup.APIGroup): def __init__(self, prefix: str): super().__init__(prefix+"/usage") - async def post_query_record( + def post_query_record( self, session_type: str, session_id: str, @@ -20,7 +20,7 @@ class V2UsageDataAPI(apigroup.APIGroup): retry_times: int, ): """提交请求记录""" - return await self.do( + return self.do( "POST", "/query", data={ @@ -40,13 +40,13 @@ class V2UsageDataAPI(apigroup.APIGroup): } ) - async def post_event_record( + def post_event_record( self, plugins: list[dict], event_name: str, ): """提交事件触发记录""" - return await self.do( + return self.do( "POST", "/event", data={ @@ -59,14 +59,14 @@ class V2UsageDataAPI(apigroup.APIGroup): } ) - async def post_function_record( + def post_function_record( self, plugin: dict, function_name: str, function_description: str, ): """提交内容函数使用记录""" - return await self.do( + return self.do( "POST", "/function", data={ diff --git a/pkg/utils/updater.py b/pkg/utils/updater.py index abbc696a..3881298c 100644 --- a/pkg/utils/updater.py +++ b/pkg/utils/updater.py @@ -109,7 +109,7 @@ def compare_version_str(v0: str, v1: str) -> int: return 0 -async def update_all(cli: bool = False) -> bool: +def update_all(cli: bool = False) -> bool: """检查更新并下载源码""" start_time = time.time() @@ -207,7 +207,7 @@ async def update_all(cli: bool = False) -> bool: with open("current_tag", "w") as f: f.write(current_tag) - await context.get_center_v2_api().main.post_update_record( + context.get_center_v2_api().main.post_update_record( spent_seconds=int(time.time()-start_time), infer_reason="update", old_version=old_tag, From b69f193a3ef51d6fda85fb37aaec91bb376e882d Mon Sep 17 00:00:00 2001 From: RockChinQ <1010553892@qq.com> Date: Thu, 21 Dec 2023 17:03:58 +0800 Subject: [PATCH 06/15] =?UTF-8?q?feat:=20main.update=20=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/utils/center/apigroup.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pkg/utils/center/apigroup.py b/pkg/utils/center/apigroup.py index 1324ab76..ccce0988 100644 --- a/pkg/utils/center/apigroup.py +++ b/pkg/utils/center/apigroup.py @@ -1,6 +1,7 @@ import abc import uuid import json +import logging import requests @@ -15,13 +16,13 @@ class APIGroup(metaclass=abc.ABCMeta): def __init__(self, prefix: str): self.prefix = prefix - async def do( + def do( self, method: str, path: str, data: dict = None, params: dict = None, - headers: dict = None, + headers: dict = {}, **kwargs ): """执行一个请求""" @@ -29,7 +30,7 @@ class APIGroup(metaclass=abc.ABCMeta): data = json.dumps(data) headers['Content-Type'] = 'application/json' - return requests.request( + ret = requests.request( method, url, data=data, @@ -37,6 +38,11 @@ class APIGroup(metaclass=abc.ABCMeta): headers=headers, **kwargs ) + + logging.debug("data: %s", data) + + logging.debug("ret: %s", ret.json()) + return ret def gen_rid( self @@ -50,7 +56,7 @@ class APIGroup(metaclass=abc.ABCMeta): """获取基本信息""" basic_info = APIGroup._basic_info.copy() basic_info['rid'] = self.gen_rid() - return APIGroup._basic_info + return basic_info def runtime_info( self From e02765bf9503b757cb978c58521a7ac732776b5a Mon Sep 17 00:00:00 2001 From: RockChinQ <1010553892@qq.com> Date: Thu, 21 Dec 2023 17:11:45 +0800 Subject: [PATCH 07/15] =?UTF-8?q?feat:=20main.announcement=20=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 6 +++++ pkg/utils/center/apigroup.py | 51 ++++++++++++++++++++++++++---------- 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/main.py b/main.py index 5fe42013..48971941 100644 --- a/main.py +++ b/main.py @@ -398,6 +398,12 @@ async def start_process(first_time_init=False): if len(new_announcement) > 0: for announcement in new_announcement: logging.critical("[公告]<{}> {}".format(announcement['time'], announcement['content'])) + + # 发送统计数据 + pkg.utils.context.get_center_v2_api().main.post_announcement_showed( + [announcement['id'] for announcement in new_announcement] + ) + except Exception as e: logging.warning("获取公告失败:{}".format(e)) diff --git a/pkg/utils/center/apigroup.py b/pkg/utils/center/apigroup.py index ccce0988..94812d59 100644 --- a/pkg/utils/center/apigroup.py +++ b/pkg/utils/center/apigroup.py @@ -2,6 +2,7 @@ import abc import uuid import json import logging +import threading import requests @@ -26,23 +27,45 @@ class APIGroup(metaclass=abc.ABCMeta): **kwargs ): """执行一个请求""" - url = self.prefix + path - data = json.dumps(data) - headers['Content-Type'] = 'application/json' - - ret = requests.request( - method, - url, - data=data, - params=params, - headers=headers, + def thr_wrapper( + self, + method: str, + path: str, + data: dict = None, + params: dict = None, + headers: dict = {}, **kwargs - ) + ): + try: + url = self.prefix + path + data = json.dumps(data) + headers['Content-Type'] = 'application/json' + + ret = requests.request( + method, + url, + data=data, + params=params, + headers=headers, + **kwargs + ) - logging.debug("data: %s", data) + logging.debug("data: %s", data) + + logging.debug("ret: %s", ret.json()) + except Exception as e: + logging.debug("上报数据失败: %s", e) + + thr = threading.Thread(target=thr_wrapper, args=( + self, + method, + path, + data, + params, + headers, + ), kwargs=kwargs) + thr.start() - logging.debug("ret: %s", ret.json()) - return ret def gen_rid( self From 61a47808c8198010c548e4e25e8c075f5f503d0f Mon Sep 17 00:00:00 2001 From: RockChinQ <1010553892@qq.com> Date: Thu, 21 Dec 2023 17:35:20 +0800 Subject: [PATCH 08/15] chore: typo --- pkg/qqbot/cmds/aamgr.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/qqbot/cmds/aamgr.py b/pkg/qqbot/cmds/aamgr.py index 27596c4c..f761063c 100644 --- a/pkg/qqbot/cmds/aamgr.py +++ b/pkg/qqbot/cmds/aamgr.py @@ -4,11 +4,10 @@ import pkgutil import traceback import json - -__command_list__ = {} - import tips as tips_custom + +__command_list__ = {} """命令树 结构: From 6f6c3af302719256899fa15ca60e16a043f63cf6 Mon Sep 17 00:00:00 2001 From: RockChinQ <1010553892@qq.com> Date: Thu, 21 Dec 2023 18:04:16 +0800 Subject: [PATCH 09/15] =?UTF-8?q?feat:=20=E6=8F=92=E4=BB=B6=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6=E8=A7=A6=E5=8F=91=E6=8A=A5=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/plugin/host.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/pkg/plugin/host.py b/pkg/plugin/host.py index 889e57f6..d65a0916 100644 --- a/pkg/plugin/host.py +++ b/pkg/plugin/host.py @@ -443,6 +443,8 @@ class PluginHost: event_context = EventContext(event_name) logging.debug("触发事件: {} ({})".format(event_name, event_context.eid)) + + emitted_plugins = [] for plugin in iter_plugins(): if not plugin['enabled']: @@ -460,6 +462,8 @@ class PluginHost: if 'hooks' not in plugin or event_name not in plugin['hooks']: continue + emitted_plugins.append(plugin) + hooks = [] if event_name in plugin["hooks"]: hooks = plugin["hooks"][event_name] @@ -487,6 +491,31 @@ class PluginHost: logging.debug("事件 {} ({}) 处理完毕,返回值: {}".format(event_name, event_context.eid, event_context.__return_value__)) + if len(emitted_plugins) > 0: + + plugins_info = [] + + for plugin in emitted_plugins: + name = plugin['name'] + meta = metadata.get_plugin_metadata(get_plugin_path_name_by_plugin_name(name)) + remote = meta['source'] if meta != {} else "" + author = plugin['author'] + version = plugin['version'] + + plugins_info.append( + { + "name": name, + "remote": remote, + "author": author, + "version": version, + } + ) + + context.get_center_v2_api().usage.post_event_record( + plugins=plugins_info, + event_name=event_name, + ) + return event_context if __name__ == "__main__": From af8c21f3d4ee42be074b08ca791f8fb34618ebfe Mon Sep 17 00:00:00 2001 From: RockChinQ <1010553892@qq.com> Date: Thu, 21 Dec 2023 18:19:04 +0800 Subject: [PATCH 10/15] =?UTF-8?q?feat:=20=E5=AE=8C=E5=96=84=20=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E4=BA=8B=E4=BB=B6=E8=B0=83=E7=94=A8=E6=8A=A5=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/plugin/host.py | 191 +++++++++++++++++++++++++++------------------ 1 file changed, 113 insertions(+), 78 deletions(-) diff --git a/pkg/plugin/host.py b/pkg/plugin/host.py index d65a0916..631012a2 100644 --- a/pkg/plugin/host.py +++ b/pkg/plugin/host.py @@ -84,23 +84,34 @@ def iter_plugins_name(): __current_module_path__ = "" -def walk_plugin_path(module, prefix='', path_prefix=''): +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 + '/') + walk_plugin_path( + __import__(module.__name__ + "." + item.name, fromlist=[""]), + prefix + item.name + ".", + path_prefix + item.name + "/", + ) else: try: - logging.debug("扫描插件模块: plugins/{}".format(path_prefix + item.name + '.py')) - __current_module_path__ = "plugins/"+path_prefix + item.name + '.py' + logging.debug( + "扫描插件模块: plugins/{}".format(path_prefix + item.name + ".py") + ) + __current_module_path__ = "plugins/" + path_prefix + item.name + ".py" - importlib.import_module(module.__name__ + '.' + item.name) - logging.debug('加载模块: plugins/{} 成功'.format(path_prefix + item.name + '.py')) + importlib.import_module(module.__name__ + "." + item.name) + logging.debug( + "加载模块: plugins/{} 成功".format(path_prefix + item.name + ".py") + ) except: - logging.error('加载模块: plugins/{} 失败: {}'.format(path_prefix + item.name + '.py', sys.exc_info())) + logging.error( + "加载模块: plugins/{} 失败: {}".format( + path_prefix + item.name + ".py", sys.exc_info() + ) + ) traceback.print_exc() @@ -108,7 +119,7 @@ def load_plugins(): """加载插件""" logging.debug("加载插件") PluginHost() - walk_plugin_path(__import__('plugins')) + walk_plugin_path(__import__("plugins")) logging.debug(__plugins__) @@ -141,14 +152,14 @@ def initialize_plugins(): # if not plugin['enabled']: # continue try: - models.__current_registering_plugin__ = plugin['name'] - plugin['instance'] = plugin["class"](plugin_host=context.get_plugin_host()) + models.__current_registering_plugin__ = plugin["name"] + plugin["instance"] = plugin["class"](plugin_host=context.get_plugin_host()) # logging.info("插件 {} 已初始化".format(plugin['name'])) - successfully_initialized_plugins.append(plugin['name']) + successfully_initialized_plugins.append(plugin["name"]) except: - logging.error("插件{}初始化时发生错误: {}".format(plugin['name'], sys.exc_info())) + logging.error("插件{}初始化时发生错误: {}".format(plugin["name"], sys.exc_info())) logging.debug(traceback.format_exc()) - + logging.info("以下插件已初始化: {}".format(", ".join(successfully_initialized_plugins))) @@ -172,9 +183,12 @@ def get_github_plugin_repo_label(repo_url: str) -> list[str]: """获取username, repo""" # 提取 username/repo , 正则表达式 - repo = re.findall(r'(?:https?://github\.com/|git@github\.com:)([^/]+/[^/]+?)(?:\.git|/|$)', repo_url) + repo = re.findall( + r"(?:https?://github\.com/|git@github\.com:)([^/]+/[^/]+?)(?:\.git|/|$)", + repo_url, + ) - if len(repo) > 0: # github + if len(repo) > 0: # github return repo[0].split("/") else: return None @@ -183,53 +197,52 @@ def get_github_plugin_repo_label(repo_url: str) -> list[str]: def download_plugin_source_code(repo_url: str, target_path: str) -> str: """下载插件源码""" # 检查源类型 - + # 提取 username/repo , 正则表达式 - repo = get_github_plugin_repo_label(repo_url) + repo = get_github_plugin_repo_label(repo_url) target_path += repo[1] - if repo is not None: # github + if repo is not None: # github logging.info("从 GitHub 下载插件源码...") zipball_url = f"https://api.github.com/repos/{'/'.join(repo)}/zipball/HEAD" zip_resp = requests.get( - url=zipball_url, - proxies=network.wrapper_proxies(), - stream=True + url=zipball_url, proxies=network.wrapper_proxies(), stream=True ) if zip_resp.status_code != 200: raise Exception("下载源码失败: {}".format(zip_resp.text)) - - if os.path.exists("temp/"+target_path): - shutil.rmtree("temp/"+target_path) + + if os.path.exists("temp/" + target_path): + shutil.rmtree("temp/" + target_path) if os.path.exists(target_path): shutil.rmtree(target_path) - os.makedirs("temp/"+target_path) + os.makedirs("temp/" + target_path) - with open("temp/"+target_path+"/source.zip", "wb") as f: + with open("temp/" + target_path + "/source.zip", "wb") as f: for chunk in zip_resp.iter_content(chunk_size=1024): if chunk: f.write(chunk) logging.info("下载完成, 解压...") import zipfile - with zipfile.ZipFile("temp/"+target_path+"/source.zip", 'r') as zip_ref: - zip_ref.extractall("temp/"+target_path) - os.remove("temp/"+target_path+"/source.zip") + + with zipfile.ZipFile("temp/" + target_path + "/source.zip", "r") as zip_ref: + zip_ref.extractall("temp/" + target_path) + os.remove("temp/" + target_path + "/source.zip") # 目标是 username-repo-hash , 用正则表达式提取完整的文件夹名,复制到 plugins/repo import glob # 获取解压后的文件夹名 - unzip_dir = glob.glob("temp/"+target_path+"/*")[0] + unzip_dir = glob.glob("temp/" + target_path + "/*")[0] # 复制到 plugins/repo - shutil.copytree(unzip_dir, target_path+"/") + shutil.copytree(unzip_dir, target_path + "/") # 删除解压后的文件夹 shutil.rmtree(unzip_dir) @@ -237,18 +250,20 @@ def download_plugin_source_code(repo_url: str, target_path: str) -> str: logging.info("解压完成") else: raise Exception("暂不支持的源类型,请使用 GitHub 仓库发行插件。") - + return repo[1] def check_requirements(path: str): # 检查此目录是否包含requirements.txt - if os.path.exists(path+"/requirements.txt"): + if os.path.exists(path + "/requirements.txt"): logging.info("检测到requirements.txt,正在安装依赖") import pkg.utils.pkgmgr - pkg.utils.pkgmgr.install_requirements(path+"/requirements.txt") + + pkg.utils.pkgmgr.install_requirements(path + "/requirements.txt") import pkg.utils.log as log + log.reset_logging() @@ -257,7 +272,7 @@ def install_plugin(repo_url: str): repo_label = download_plugin_source_code(repo_url, "plugins/") - check_requirements("plugins/"+repo_label) + check_requirements("plugins/" + repo_label) metadata.set_plugin_metadata(repo_label, repo_url, int(time.time()), "HEAD") @@ -266,16 +281,16 @@ def uninstall_plugin(plugin_name: str) -> str: """卸载插件""" if plugin_name not in __plugins__: raise Exception("插件不存在") - + # 获取文件夹路径 - plugin_path = __plugins__[plugin_name]['path'].replace("\\", "/") + plugin_path = __plugins__[plugin_name]["path"].replace("\\", "/") # 剪切路径为plugins/插件名 plugin_path = plugin_path.split("plugins/")[1].split("/")[0] # 删除文件夹 - shutil.rmtree("plugins/"+plugin_path) - return "plugins/"+plugin_path + shutil.rmtree("plugins/" + plugin_path) + return "plugins/" + plugin_path def update_plugin(plugin_name: str): @@ -288,11 +303,17 @@ def update_plugin(plugin_name: str): if meta == {}: raise Exception("没有此插件元数据信息,无法更新") - remote_url = meta['source'] - if remote_url == "https://github.com/RockChinQ/QChatGPT" or remote_url == "https://gitee.com/RockChin/QChatGPT" \ - or remote_url == "" or remote_url is None or remote_url == "http://github.com/RockChinQ/QChatGPT" or remote_url == "http://gitee.com/RockChin/QChatGPT": + remote_url = meta["source"] + if ( + remote_url == "https://github.com/RockChinQ/QChatGPT" + or remote_url == "https://gitee.com/RockChin/QChatGPT" + or remote_url == "" + or remote_url is None + or remote_url == "http://github.com/RockChinQ/QChatGPT" + or remote_url == "http://gitee.com/RockChin/QChatGPT" + ): raise Exception("插件没有远程地址记录,无法更新") - + # 重新安装插件 logging.info("正在重新安装插件以进行更新...") @@ -301,7 +322,7 @@ def update_plugin(plugin_name: str): def get_plugin_name_by_path_name(plugin_path_name: str) -> str: for k, v in __plugins__.items(): - if v['path'] == "plugins/"+plugin_path_name+"/main.py": + if v["path"] == "plugins/" + plugin_path_name + "/main.py": return k return None @@ -309,8 +330,8 @@ def get_plugin_name_by_path_name(plugin_path_name: str) -> str: def get_plugin_path_name_by_plugin_name(plugin_name: str) -> str: if plugin_name not in __plugins__: return None - - plugin_main_module_path = __plugins__[plugin_name]['path'] + + plugin_main_module_path = __plugins__[plugin_name]["path"] plugin_main_module_path = plugin_main_module_path.replace("\\", "/") @@ -319,8 +340,29 @@ def get_plugin_path_name_by_plugin_name(plugin_name: str) -> str: return spt[1] +def get_plugin_info_for_audit(plugin_name: str) -> dict: + """获取插件信息""" + if plugin_name not in __plugins__: + return {} + plugin = __plugins__[plugin_name] + + name = plugin["name"] + meta = metadata.get_plugin_metadata(get_plugin_path_name_by_plugin_name(name)) + remote = meta["source"] if meta != {} else "" + author = plugin["author"] + version = plugin["version"] + + return { + "name": name, + "remote": remote, + "author": author, + "version": version, + } + + class EventContext: """事件上下文""" + eid = 0 """事件编号""" @@ -395,6 +437,7 @@ class EventContext: 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) @@ -446,8 +489,7 @@ class PluginHost: emitted_plugins = [] for plugin in iter_plugins(): - - if not plugin['enabled']: + if not plugin["enabled"]: continue # if plugin['instance'] is None: @@ -459,10 +501,10 @@ class PluginHost: # logging.error("插件 {} 初始化时发生错误: {}".format(plugin['name'], sys.exc_info())) # continue - if 'hooks' not in plugin or event_name not in plugin['hooks']: + if "hooks" not in plugin or event_name not in plugin["hooks"]: continue - emitted_plugins.append(plugin) + emitted_plugins.append(plugin['name']) hooks = [] if event_name in plugin["hooks"]: @@ -471,45 +513,37 @@ class PluginHost: try: already_prevented_default = event_context.is_prevented_default() - kwargs['host'] = context.get_plugin_host() - kwargs['event'] = event_context + kwargs["host"] = context.get_plugin_host() + kwargs["event"] = event_context - hook(plugin['instance'], **kwargs) + 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_default() + and not already_prevented_default + ): + logging.debug( + "插件 {} 已要求阻止事件 {} 的默认行为".format(plugin["name"], event_name) + ) except Exception as e: - logging.error("插件{}响应事件{}时发生错误".format(plugin['name'], event_name)) + logging.error("插件{}响应事件{}时发生错误".format(plugin["name"], event_name)) logging.error(traceback.format_exc()) # print("done:{}".format(plugin['name'])) if event_context.is_prevented_postorder(): - logging.debug("插件 {} 阻止了后序插件的执行".format(plugin['name'])) + logging.debug("插件 {} 阻止了后序插件的执行".format(plugin["name"])) break - logging.debug("事件 {} ({}) 处理完毕,返回值: {}".format(event_name, event_context.eid, - event_context.__return_value__)) + logging.debug( + "事件 {} ({}) 处理完毕,返回值: {}".format( + event_name, event_context.eid, event_context.__return_value__ + ) + ) + print(emitted_plugins) if len(emitted_plugins) > 0: - - plugins_info = [] - - for plugin in emitted_plugins: - name = plugin['name'] - meta = metadata.get_plugin_metadata(get_plugin_path_name_by_plugin_name(name)) - remote = meta['source'] if meta != {} else "" - author = plugin['author'] - version = plugin['version'] - - plugins_info.append( - { - "name": name, - "remote": remote, - "author": author, - "version": version, - } - ) + plugins_info = [get_plugin_info_for_audit(p) for p in emitted_plugins] context.get_center_v2_api().usage.post_event_record( plugins=plugins_info, @@ -518,5 +552,6 @@ class PluginHost: return event_context + if __name__ == "__main__": pass From c10f72cf4c35e3dcec4b46f93ad274db9fd9bb8a Mon Sep 17 00:00:00 2001 From: RockChinQ <1010553892@qq.com> Date: Thu, 21 Dec 2023 18:36:02 +0800 Subject: [PATCH 11/15] =?UTF-8?q?feat:=20=E5=86=85=E5=AE=B9=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E8=B0=83=E7=94=A8=E6=8A=A5=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/openai/api/chat_completion.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pkg/openai/api/chat_completion.py b/pkg/openai/api/chat_completion.py index e308f17d..1e0e1bc5 100644 --- a/pkg/openai/api/chat_completion.py +++ b/pkg/openai/api/chat_completion.py @@ -6,6 +6,8 @@ from openai.types.chat import chat_completion_message from .model import RequestBase from .. import funcmgr +from ...plugin import host +from ...utils import context class ChatCompletionRequest(RequestBase): @@ -189,6 +191,16 @@ class ChatCompletionRequest(RequestBase): ret = "error: execute function failed: {}".format(str(e)) logging.error("函数执行失败: {}".format(str(e))) + # 上报数据 + plugin_info = host.get_plugin_info_for_audit(func_name.split('-')[0]) + audit_func_name = func_name.split('-')[1] + audit_func_desc = funcmgr.get_func_schema(func_name)['description'] + context.get_center_v2_api().usage.post_function_record( + plugin=plugin_info, + function_name=audit_func_name, + function_description=audit_func_desc, + ) + self.append_message( role="function", content=json.dumps(ret, ensure_ascii=False), From 565066bbcdd39651c4baf8c165fcce70f43a5efe Mon Sep 17 00:00:00 2001 From: RockChinQ <1010553892@qq.com> Date: Thu, 21 Dec 2023 18:46:48 +0800 Subject: [PATCH 12/15] =?UTF-8?q?feat:=20=E6=8F=92=E4=BB=B6=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E4=B8=8A=E6=8A=A5=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/plugin/host.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/pkg/plugin/host.py b/pkg/plugin/host.py index 631012a2..9128aa76 100644 --- a/pkg/plugin/host.py +++ b/pkg/plugin/host.py @@ -276,11 +276,23 @@ def install_plugin(repo_url: str): metadata.set_plugin_metadata(repo_label, repo_url, int(time.time()), "HEAD") + # 上报安装记录 + context.get_center_v2_api().plugin.post_install_record( + plugin={ + "name": "unknown", + "remote": repo_url, + "author": "unknown", + "version": "HEAD", + } + ) + def uninstall_plugin(plugin_name: str) -> str: """卸载插件""" if plugin_name not in __plugins__: raise Exception("插件不存在") + + plugin_info = get_plugin_info_for_audit(plugin_name) # 获取文件夹路径 plugin_path = __plugins__[plugin_name]["path"].replace("\\", "/") @@ -290,6 +302,12 @@ def uninstall_plugin(plugin_name: str) -> str: # 删除文件夹 shutil.rmtree("plugins/" + plugin_path) + + # 上报卸载记录 + context.get_center_v2_api().plugin.post_remove_record( + plugin=plugin_info + ) + return "plugins/" + plugin_path @@ -302,6 +320,14 @@ def update_plugin(plugin_name: str): if meta == {}: raise Exception("没有此插件元数据信息,无法更新") + + old_plugin_info = get_plugin_info_for_audit(plugin_name) + + context.get_center_v2_api().plugin.post_update_record( + plugin=old_plugin_info, + old_version=old_plugin_info['version'], + new_version='HEAD', + ) remote_url = meta["source"] if ( From 3a63630068114d2e931565d64ad9065cd329c29c Mon Sep 17 00:00:00 2001 From: RockChinQ <1010553892@qq.com> Date: Thu, 21 Dec 2023 18:51:10 +0800 Subject: [PATCH 13/15] =?UTF-8?q?feat:=20account=5Fid=20=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/qqbot/manager.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/qqbot/manager.py b/pkg/qqbot/manager.py index 588d6621..b8ba8a9c 100644 --- a/pkg/qqbot/manager.py +++ b/pkg/qqbot/manager.py @@ -125,6 +125,10 @@ class QQBotManager: else: self.adapter = context.get_qqbot_manager().adapter self.bot_account_id = context.get_qqbot_manager().bot_account_id + + # 保存 account_id 到审计模块 + from ..utils.center import apigroup + apigroup.APIGroup._runtime_info['account_id'] = self.bot_account_id context.set_qqbot_manager(self) From bb12b488871051ab7d03a0134b12b6ab9d959e5a Mon Sep 17 00:00:00 2001 From: RockChinQ <1010553892@qq.com> Date: Fri, 22 Dec 2023 12:38:27 +0800 Subject: [PATCH 14/15] =?UTF-8?q?feat:=20usage.query=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 2 +- pkg/openai/session.py | 22 ++++++++++++++++++++++ pkg/qqbot/manager.py | 2 +- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index 48971941..5ceb67f6 100644 --- a/main.py +++ b/main.py @@ -222,7 +222,7 @@ async def start_process(first_time_init=False): "platform": sys.platform, }, runtime_info={ - "admin_qq": cfg['admin_qq'], + "admin_id": "{}".format(cfg['admin_qq']), "msg_source": cfg['msg_source_adapter'], } ) diff --git a/pkg/openai/session.py b/pkg/openai/session.py index 197ccfd0..1f9c76de 100644 --- a/pkg/openai/session.py +++ b/pkg/openai/session.py @@ -261,6 +261,8 @@ class Session: pending_res_text = "" + start_time = time.time() + # TODO 对不起,我知道这样非常非常屎山,但我之后会重构的 for resp in context.get_openai_manager().request_completion(prompts): @@ -349,6 +351,26 @@ class Session: self.just_switched_to_exist_session = False self.set_ongoing() + # 上报使用量数据 + session_type = session_name_spt[0] + session_id = session_name_spt[1] + + ability_provider = "QChatGPT.Text" + usage = total_tokens + model_name = context.get_config_manager().data['completion_api_params']['model'] + response_seconds = int(time.time() - start_time) + retry_times = -1 # 暂不记录 + + context.get_center_v2_api().usage.post_query_record( + session_type=session_type, + session_id=session_id, + query_ability_provider=ability_provider, + usage=usage, + model_name=model_name, + response_seconds=response_seconds, + retry_times=retry_times + ) + return res_ans if res_ans[0] != '\n' else res_ans[1:], finish_reason, funcs # 删除上一回合并返回上一回合的问题 diff --git a/pkg/qqbot/manager.py b/pkg/qqbot/manager.py index b8ba8a9c..8cd663ff 100644 --- a/pkg/qqbot/manager.py +++ b/pkg/qqbot/manager.py @@ -128,7 +128,7 @@ class QQBotManager: # 保存 account_id 到审计模块 from ..utils.center import apigroup - apigroup.APIGroup._runtime_info['account_id'] = self.bot_account_id + apigroup.APIGroup._runtime_info['account_id'] = "{}".format(self.bot_account_id) context.set_qqbot_manager(self) From d2bd6e23b6cc5f5e27a9e556920461f1d243002e Mon Sep 17 00:00:00 2001 From: RockChinQ <1010553892@qq.com> Date: Fri, 22 Dec 2023 14:36:52 +0800 Subject: [PATCH 15/15] =?UTF-8?q?chore:=20=E5=88=A0=E9=99=A4=E8=B0=83?= =?UTF-8?q?=E8=AF=95=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/plugin/host.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/plugin/host.py b/pkg/plugin/host.py index 9128aa76..50af1ec0 100644 --- a/pkg/plugin/host.py +++ b/pkg/plugin/host.py @@ -566,7 +566,6 @@ class PluginHost: event_name, event_context.eid, event_context.__return_value__ ) ) - print(emitted_plugins) if len(emitted_plugins) > 0: plugins_info = [get_plugin_info_for_audit(p) for p in emitted_plugins]