mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2025-12-14 21:52:56 +08:00
✨ 插件商店
This commit is contained in:
parent
1f90490230
commit
99774adc87
BIN
requirements.txt
Normal file
BIN
requirements.txt
Normal file
Binary file not shown.
@ -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:
|
||||
@ -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
|
||||
|
||||
89
zhenxun/builtin_plugins/plugin_shop/__init__.py
Normal file
89
zhenxun/builtin_plugins/plugin_shop/__init__.py
Normal file
@ -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()
|
||||
15
zhenxun/builtin_plugins/plugin_shop/config.py
Normal file
15
zhenxun/builtin_plugins/plugin_shop/config.py
Normal file
@ -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"
|
||||
)
|
||||
"""插件下载地址"""
|
||||
195
zhenxun/builtin_plugins/plugin_shop/data_source.py
Normal file
195
zhenxun/builtin_plugins/plugin_shop/data_source.py
Normal file
@ -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} 移除成功!"
|
||||
@ -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")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user