From 99774adc87cc3bc7f9aa9056e21317961729e4d2 Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Tue, 20 Aug 2024 21:29:42 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=E6=8F=92=E4=BB=B6=E5=95=86?= =?UTF-8?q?=E5=BA=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | Bin 0 -> 5828 bytes zhenxun/{plugins => builtin_plugins}/about.py | 0 .../auto_update/__init__.py | 0 .../auto_update/_data_source.py | 2 +- .../auto_update/config.py | 0 zhenxun/builtin_plugins/help/__init__.py | 1 + .../builtin_plugins/plugin_shop/__init__.py | 89 ++++++++ zhenxun/builtin_plugins/plugin_shop/config.py | 15 ++ .../plugin_shop/data_source.py | 195 ++++++++++++++++++ .../statistics/__init__.py | 0 .../statistics/_data_source.py | 0 .../statistics/statistics_handle.py | 0 .../statistics/statistics_hook.py | 0 zhenxun/configs/config.py | 4 +- 14 files changed, 303 insertions(+), 3 deletions(-) create mode 100644 requirements.txt rename zhenxun/{plugins => builtin_plugins}/about.py (100%) rename zhenxun/{plugins => builtin_plugins}/auto_update/__init__.py (100%) rename zhenxun/{plugins => builtin_plugins}/auto_update/_data_source.py (99%) rename zhenxun/{plugins => builtin_plugins}/auto_update/config.py (100%) create mode 100644 zhenxun/builtin_plugins/plugin_shop/__init__.py create mode 100644 zhenxun/builtin_plugins/plugin_shop/config.py create mode 100644 zhenxun/builtin_plugins/plugin_shop/data_source.py rename zhenxun/{plugins => builtin_plugins}/statistics/__init__.py (100%) rename zhenxun/{plugins => builtin_plugins}/statistics/_data_source.py (100%) rename zhenxun/{plugins => builtin_plugins}/statistics/statistics_handle.py (100%) rename zhenxun/{plugins => builtin_plugins}/statistics/statistics_hook.py (100%) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..f2bca1fe1e84e8b0c38a4d491f445abc9b55b07d GIT binary patch literal 5828 zcma)=OHX4*5QTdkDL=&sV<3}77PE+yM2UnnizqAP2Y$@JwXqEz^W&48ug)#^y$qR& z5YMivt~zz<(fsrGxU9;$%=PuY+?8GVNgo$wRL=DEtPIQd<-1~;rM_?Uag-dN6a9X# zkJLE}t*f5KzU-xeO)L(}xxS7%5*ROmqGY5mBl7}OV8fPF4!28|CS%f z##$eV4Ibmuk0;HoP7mQnMx3$igvY9E<;VQwC--J1c8ax;r}$`DZXo$PK;`9~klIFgBMkHhv#k%0Uz?%I^g zux7uIH=YyuLzapBv2&kUwUI6nrtMYaWh*S!(rEP6G2kPXozsadj)lsnuy+wBU}-DU zID;AeY+bcBs?;v{b7&(qySi5TIArZOuBq&Abpt4@T0hQ(6JBD;o=$a|=P;tnZ`eDQ zjahg~Ch)K#o#(qa9|Rg(<(BGPm6y05E&Q@?@E5MHPS}`Xc;vH2_Q$f6dH0PM?J3L# z_i@~kGDLKJl7NG7Ya1N{8RDuvh3Z->Gw^j9Jb}foO{A{%nHeC@o3OWtTQ@@F7`|Ob zL}Wc>j}ci~;$+ylbta#=XQS^hH|5edTbao#*akwA69!=SF5<)EnIy)ivtg$$?u5#^ ze3ULxJK`GmUHL3+Jb)8)7-9k`_sR6cSkH9_p3r%1&k;E$dpnmky4&lRj3Dv5`m@KA zyQ~Rf8U~-t!+CfFpS%5w%w7f3X$g;ERudTEVP+UvOA!~IAw!KJ!F@#F+CX9>U$S=- z`*p{gW%!PsqsF#lUdkSm1r~8jl_6?dPnmu!SVG1=rp)J)1|_%&F+ z(5=5jc9wCDZ$^^}qPNnssC={NR(}D7v24OYTlGZAsOW0cB&;A??z?aYuN^teT#JX- z`h6HTgBqVchj*UMiOFS$|G(+95jV&BTSkShvrjc1^vMnP0uN^#0JTTUGo_`UiQD_q%7%uezrL`N1S+-$==dLfWEgK1h=q zK+1KNo?;W0;Bn?5Z868}@q;{~PuXYgAl^J<#!^{XF-AH&!GkpKWgDiDIkP+~&v-pV z_c|&TYM<`Y2w~U0cc--J3^wXH1KEN%c$?^(v5oZc2mX_v+M~&Vv1A@1H;K3hl7@41 z95e>u>n?c4RN;<{+Wqa@`fKTV-%3q0=v$hfrGP3WO8gu6@|o*WV~}Z*7ycivLv5}iR z$2)h}^ll@J*3n7pc83-3(Ty@omvw*0F6lh3^>@&ns*AaY?8Q2A%Wa-f;LYc|mOEMH z zkP6{N0SaKv2^hGukS#WvMqXsz-7SqT+Z!l%cQa#>s2qhEn<+Oq1H|K(99whRVe{Yy zwjalw8927HwQeOyfQ;|Bk&g@I2L9|@-0)$j7-}Ao;q}9`6k zgH6UxzcT-*3jC|_vkB5i{mNv+SnDs;Y$A%hCxLBZjTdsA74CTz5jsh3aYR`=1s3v@ z4w|pR+}Vs9@-@R*YfKv0e%E6JNck0crg*#WI^X3wMVF~f7M>%r%u@0mzf$&N*f9e5 z!e=+Slw%|4ZyEdrJBi&}QPe5#{M2sL&tCOsC-!^p%!MDe&7E2$c>fY77xL8aK}U8Y zjX&iT`0W;BZf4+U zZoe&grrY}OHllG>ncXXK&KbeaVax(Eg-wsUtUYS~YmlrM@MW3f(Io%+uYg zN&W5ak-R<^M#GjBw&wnME30pB+(r!ajVra-eV3)>8u5#;V3M*!f)toq_et+5vg_{3 z-#v}Y7{4r%Q)v3us{JgC{f@2uc@+3KXPkp2bpboF9~#38#hp20E9Ad`%U~h0b?WNbwAGiVW``|5q`&Q|6i{WS&#q# literal 0 HcmV?d00001 diff --git a/zhenxun/plugins/about.py b/zhenxun/builtin_plugins/about.py similarity index 100% rename from zhenxun/plugins/about.py rename to zhenxun/builtin_plugins/about.py diff --git a/zhenxun/plugins/auto_update/__init__.py b/zhenxun/builtin_plugins/auto_update/__init__.py similarity index 100% rename from zhenxun/plugins/auto_update/__init__.py rename to zhenxun/builtin_plugins/auto_update/__init__.py diff --git a/zhenxun/plugins/auto_update/_data_source.py b/zhenxun/builtin_plugins/auto_update/_data_source.py similarity index 99% rename from zhenxun/plugins/auto_update/_data_source.py rename to zhenxun/builtin_plugins/auto_update/_data_source.py index 2f1c0276..5f497bdf 100644 --- a/zhenxun/plugins/auto_update/_data_source.py +++ b/zhenxun/builtin_plugins/auto_update/_data_source.py @@ -98,7 +98,7 @@ def _file_handle(latest_version: str | None): if latest_version: with open(VERSION_FILE, "w", encoding="utf8") as f: f.write(f"__version__: {latest_version}") - os.system(f"poetry install --directory={Path().absolute()}") + os.system(f"poetry run pip install -r requirements.txt") class UpdateManage: diff --git a/zhenxun/plugins/auto_update/config.py b/zhenxun/builtin_plugins/auto_update/config.py similarity index 100% rename from zhenxun/plugins/auto_update/config.py rename to zhenxun/builtin_plugins/auto_update/config.py diff --git a/zhenxun/builtin_plugins/help/__init__.py b/zhenxun/builtin_plugins/help/__init__.py index 69a981a3..3f89c117 100644 --- a/zhenxun/builtin_plugins/help/__init__.py +++ b/zhenxun/builtin_plugins/help/__init__.py @@ -67,6 +67,7 @@ async def _( session: EventSession, is_superuser: Query[bool] = AlconnaQuery("superuser.value", False), ): + logger.debug("进入help") _is_superuser = False if is_superuser.available: _is_superuser = is_superuser.result diff --git a/zhenxun/builtin_plugins/plugin_shop/__init__.py b/zhenxun/builtin_plugins/plugin_shop/__init__.py new file mode 100644 index 00000000..5e6c12a2 --- /dev/null +++ b/zhenxun/builtin_plugins/plugin_shop/__init__.py @@ -0,0 +1,89 @@ +from nonebot.permission import SUPERUSER +from nonebot.plugin import PluginMetadata +from nonebot_plugin_alconna import Alconna, Args, Subcommand, on_alconna +from nonebot_plugin_session import EventSession + +from zhenxun.configs.utils import PluginExtraData +from zhenxun.services.log import logger +from zhenxun.utils.enum import PluginType +from zhenxun.utils.message import MessageUtils + +from .data_source import ShopManage + +__plugin_meta__ = PluginMetadata( + name="插件商店", + description="插件商店", + usage=""" + 插件商店 : 查看当前的插件商店 + 添加插件 id : 添加插件 + 移除插件 id : 移除插件 + + """.strip(), + extra=PluginExtraData( + author="HibiKier", + version="0.1", + plugin_type=PluginType.SUPERUSER, + ).dict(), +) + +_matcher = on_alconna( + Alconna( + "插件商店", + Subcommand("add", Args["plugin_id", int]), + Subcommand("remove", Args["plugin_id", int]), + ), + permission=SUPERUSER, + priority=1, + block=True, +) + + +_matcher.shortcut( + r"添加插件", + command="插件商店", + arguments=["add", "{%0}"], + prefix=True, +) + +_matcher.shortcut( + r"移除插件", + command="插件商店", + arguments=["remove", "{%0}"], + prefix=True, +) + + +@_matcher.assign("$main") +async def _(session: EventSession): + try: + result = await ShopManage.get_plugins_info() + logger.info("查看插件列表", "插件商店", session=session) + await MessageUtils.build_message(result).finish() + except Exception as e: + logger.error(f"查看插件列表失败 e: {e}", "插件商店", session=session, e=e) + + +@_matcher.assign("add") +async def _(session: EventSession, plugin_id: int): + try: + result = await ShopManage.add_plugin(plugin_id) + except Exception as e: + logger.error(f"添加插件 Id: {plugin_id}失败", "插件商店", session=session, e=e) + await MessageUtils.build_message( + f"添加插件 Id: {plugin_id} 失败 e: {e}" + ).finish() + logger.info(f"添加插件 Id: {plugin_id}", "插件商店", session=session) + await MessageUtils.build_message(result).finish() + + +@_matcher.assign("remove") +async def _(session: EventSession, plugin_id: int): + try: + result = await ShopManage.remove_plugin(plugin_id) + except Exception as e: + logger.error(f"移除插件 Id: {plugin_id}失败", "插件商店", session=session, e=e) + await MessageUtils.build_message( + f"移除插件 Id: {plugin_id} 失败 e: {e}" + ).finish() + logger.info(f"移除插件 Id: {plugin_id}", "插件商店", session=session) + await MessageUtils.build_message(result).finish() diff --git a/zhenxun/builtin_plugins/plugin_shop/config.py b/zhenxun/builtin_plugins/plugin_shop/config.py new file mode 100644 index 00000000..5128128f --- /dev/null +++ b/zhenxun/builtin_plugins/plugin_shop/config.py @@ -0,0 +1,15 @@ +from pathlib import Path + +BASE_PATH = Path() / "zhenxun" +BASE_PATH.mkdir(parents=True, exist_ok=True) + + +CONFIG_URL = ( + "https://raw.githubusercontent.com/HibiKier/zhenxun_bot_plugins/main/plugins.json" +) +"""插件信息文件""" + +DOWNLOAD_URL = ( + "https://api.github.com/repos/HibiKier/zhenxun_bot_plugins/contents/{}?ref=main" +) +"""插件下载地址""" diff --git a/zhenxun/builtin_plugins/plugin_shop/data_source.py b/zhenxun/builtin_plugins/plugin_shop/data_source.py new file mode 100644 index 00000000..324104e5 --- /dev/null +++ b/zhenxun/builtin_plugins/plugin_shop/data_source.py @@ -0,0 +1,195 @@ +import shutil +from pathlib import Path + +import nonebot +import ujson as json + +from zhenxun.services.log import logger +from zhenxun.utils.http_utils import AsyncHttpx +from zhenxun.utils.image_utils import BuildImage, ImageTemplate, RowStyle + +from .config import BASE_PATH, CONFIG_URL, DOWNLOAD_URL + + +def row_style(column: str, text: str) -> RowStyle: + """被动技能文本风格 + + 参数: + column: 表头 + text: 文本内容 + + 返回: + RowStyle: RowStyle + """ + style = RowStyle() + if column in ["-"]: + if text == "已安装": + style.font_color = "#67C23A" + return style + + +async def recurrence_get_url( + url: str, data_list: list[tuple[str, str]], ignore_list: list[str] = [] +): + """递归获取目录下所有文件 + + 参数: + url: 信息url + data_list: 数据列表 + + 异常: + ValueError: 访问错误 + """ + logger.debug(f"访问插件下载信息 URL: {url}", "插件管理") + res = await AsyncHttpx.get(url) + if res.status_code != 200: + raise ValueError(f"访问错误, code: {res.status_code}") + json_data = res.json() + if isinstance(json_data, list): + for v in json_data: + data_list.append((v.get("download_url"), v["path"])) + else: + data_list.append((json_data.get("download_url"), json_data["path"])) + for download_url, path in data_list: + if not download_url: + _url = DOWNLOAD_URL.format(path) + if _url not in ignore_list: + ignore_list.append(_url) + await recurrence_get_url(_url, data_list, ignore_list) + + +async def download_file(url: str): + """下载文件 + + 参数: + url: 插件详情url + + 异常: + ValueError: 访问失败 + ValueError: 下载失败 + """ + data_list = [] + await recurrence_get_url(url, data_list) + for download_url, path in data_list: + if download_url and "." in path: + logger.debug(f"下载文件: {path}", "插件管理") + file = Path(f"zhenxun/{path}") + file.parent.mkdir(parents=True, exist_ok=True) + r = await AsyncHttpx.get(download_url) + if r.status_code != 200: + raise ValueError(f"文件下载错误, code: {r.status_code}") + with open(file, "w", encoding="utf8") as f: + logger.debug(f"写入文件: {file}", "插件管理") + f.write(r.text) + + +class ShopManage: + + type2name = { + "NORMAL": "普通插件", + "ADMIN": "管理员插件", + "SUPERUSER": "超级用户插件", + "ADMIN_SUPERUSER": "管理员/超级用户插件", + "DEPENDANT": "依赖插件", + "HIDDEN": "其他插件", + } + + @classmethod + async def __get_data(cls) -> dict: + """获取插件信息数据 + + 异常: + ValueError: 访问请求失败 + + 返回: + dict: 插件信息数据 + """ + res = await AsyncHttpx.get(CONFIG_URL) + if res.status_code != 200: + raise ValueError(f"下载错误, code: {res.status_code}") + return json.loads(res.text) + + @classmethod + async def get_plugins_info(cls) -> BuildImage | str: + """插件列表 + + 返回: + BuildImage | str: 返回消息 + """ + data: dict = await cls.__get_data() + column_name = ["-", "ID", "名称", "简介", "作者", "版本", "类型"] + for k in data.copy(): + if data[k]["plugin_type"]: + data[k]["plugin_type"] = cls.type2name[data[k]["plugin_type"]] + suc_plugin = [p.name for p in nonebot.get_loaded_plugins()] + data_list = [ + [ + "已安装" if v[1]["module"] in suc_plugin else "", + i, + v[0], + v[1]["description"], + v[1]["author"], + v[1]["version"], + v[1]["plugin_type"], + ] + for i, v in enumerate(data.items()) + ] + return await ImageTemplate.table_page( + "插件列表", + f"通过安装/卸载插件 ID 来管理插件", + column_name, + data_list, + text_style=row_style, + ) + + @classmethod + async def add_plugin(cls, plugin_id: int) -> str: + data: dict = await cls.__get_data() + if plugin_id < 0 or plugin_id >= len(data): + return "插件ID不存在..." + plugin_key = list(data.keys())[plugin_id] + plugin_info = data[plugin_key] + module_path_split = plugin_info["module_path"].split(".") + url_path = None + path = BASE_PATH + if len(module_path_split) == 2: + """单个文件或文件夹""" + if plugin_info["is_dir"]: + url_path = "/".join(module_path_split) + else: + url_path = "/".join(module_path_split) + ".py" + else: + """嵌套文件或文件夹""" + for p in module_path_split[:-1]: + path = path / p + path.mkdir(parents=True, exist_ok=True) + if plugin_info["is_dir"]: + url_path = f"{'/'.join(module_path_split)}" + else: + url_path = f"{'/'.join(module_path_split)}.py" + if not url_path: + return "插件下载地址构建失败..." + logger.debug(f"尝试下载插件 URL: {url_path}", "插件管理") + await download_file(DOWNLOAD_URL.format(url_path)) + return f"插件 {plugin_key} 安装成功!" + + @classmethod + async def remove_plugin(cls, plugin_id: int) -> str: + data: dict = await cls.__get_data() + if plugin_id < 0 or plugin_id >= len(data): + return "插件ID不存在..." + plugin_key = list(data.keys())[plugin_id] + plugin_info = data[plugin_key] + path = BASE_PATH + for p in plugin_info["module_path"].split("."): + path = path / p + if not plugin_info["is_dir"]: + path = Path(f"{path}.py") + if not path.exists(): + return f"插件 {plugin_key} 不存在..." + logger.debug(f"尝试移除插件 {plugin_key} 文件: {path}", "插件管理") + if plugin_info["is_dir"]: + shutil.rmtree(path) + else: + path.unlink() + return f"插件 {plugin_key} 移除成功!" diff --git a/zhenxun/plugins/statistics/__init__.py b/zhenxun/builtin_plugins/statistics/__init__.py similarity index 100% rename from zhenxun/plugins/statistics/__init__.py rename to zhenxun/builtin_plugins/statistics/__init__.py diff --git a/zhenxun/plugins/statistics/_data_source.py b/zhenxun/builtin_plugins/statistics/_data_source.py similarity index 100% rename from zhenxun/plugins/statistics/_data_source.py rename to zhenxun/builtin_plugins/statistics/_data_source.py diff --git a/zhenxun/plugins/statistics/statistics_handle.py b/zhenxun/builtin_plugins/statistics/statistics_handle.py similarity index 100% rename from zhenxun/plugins/statistics/statistics_handle.py rename to zhenxun/builtin_plugins/statistics/statistics_handle.py diff --git a/zhenxun/plugins/statistics/statistics_hook.py b/zhenxun/builtin_plugins/statistics/statistics_hook.py similarity index 100% rename from zhenxun/plugins/statistics/statistics_hook.py rename to zhenxun/builtin_plugins/statistics/statistics_hook.py diff --git a/zhenxun/configs/config.py b/zhenxun/configs/config.py index e1140a88..0206c131 100644 --- a/zhenxun/configs/config.py +++ b/zhenxun/configs/config.py @@ -19,7 +19,7 @@ NICKNAME: str = "小真寻" # 数据库(必要) # 如果填写了bind就不需要再填写后面的字段了#) # 示例:"bind": "postgres://user:password@127.0.0.1:5432/database" -bind: str = "" # 数据库连接链接 +bind: str = "" sql_name: str = "postgres" user: str = "" # 数据用户名 password: str = "" # 数据库密码 @@ -29,7 +29,7 @@ database: str = "" # 数据库名称 # 代理,例如 "http://127.0.0.1:7890" # 如果是WLS 可以 f"http://{hostip}:7890" 使用寄主机的代理 -SYSTEM_PROXY: str | None = None # 全局代理 +SYSTEM_PROXY: str | None = "http://127.0.0.1:7890" # 全局代理 Config = ConfigsManager(Path() / "data" / "configs" / "plugins2config.yaml")