✨ 插件商店

This commit is contained in:
HibiKier
2024-08-20 21:29:42 +08:00
parent 1f90490230
commit 99774adc87
14 changed files with 303 additions and 3 deletions
+41
View File
@@ -0,0 +1,41 @@
from pathlib import Path
from nonebot.plugin import PluginMetadata
from nonebot.rule import to_me
from nonebot_plugin_alconna import Alconna, Arparma, on_alconna
from nonebot_plugin_session import EventSession
from zhenxun.configs.utils import PluginExtraData
from zhenxun.services.log import logger
from zhenxun.utils.message import MessageUtils
__plugin_meta__ = PluginMetadata(
name="关于",
description="想要更加了解真寻吗",
usage="""
指令:
关于
""".strip(),
extra=PluginExtraData(author="HibiKier", version="0.1", menu_type="其他").dict(),
)
_matcher = on_alconna(Alconna("关于"), priority=5, block=True, rule=to_me())
@_matcher.handle()
async def _(session: EventSession, arparma: Arparma):
ver_file = Path() / "__version__"
version = None
if ver_file.exists():
with open(ver_file, "r", encoding="utf8") as f:
version = f.read().split(":")[-1].strip()
info = f"""
『绪山真寻Bot』
版本:{version}
简介:基于Nonebot2开发,支持多平台,是一个非常可爱的Bot呀,希望与大家要好好相处
项目地址:https://github.com/HibiKier/zhenxun_bot
文档地址:https://hibikier.github.io/zhenxun_bot/
""".strip()
await MessageUtils.build_message(info).send()
logger.info("查看关于", arparma.header_result, session=session)
@@ -0,0 +1,87 @@
import nonebot
from nonebot.adapters import Bot
from nonebot.permission import SUPERUSER
from nonebot.plugin import PluginMetadata
from nonebot_plugin_alconna import Alconna, Args, Match, on_alconna
from nonebot_plugin_session import EventSession
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
from zhenxun.utils.message import MessageUtils
from ._data_source import UpdateManage
__plugin_meta__ = PluginMetadata(
name="自动更新",
description="就算是真寻也会成长的",
usage="""
usage:
检查更新真寻最新版本,包括了自动更新
指令:
检查更新真寻
""".strip(),
extra=PluginExtraData(
author="HibiKier",
version="0.1",
plugin_type=PluginType.SUPERUSER,
configs=[
RegisterConfig(
key="UPDATE_REMIND",
value=True,
help="是否检测更新版本",
default_value=True,
),
RegisterConfig(
key="UPDATE_REMIND",
value=True,
help="是否检测更新版本",
default_value=True,
),
],
).dict(),
)
_matcher = on_alconna(
Alconna("检查更新", Args["ver_type?", ["main", "dev", "release"]]),
priority=1,
block=True,
permission=SUPERUSER,
)
@_matcher.handle()
async def _(bot: Bot, session: EventSession, ver_type: Match[str]):
if not session.id1:
await MessageUtils.build_message("用户id为空...").finish()
if not ver_type.available:
result = await UpdateManage.check_version()
logger.info("查看当前版本...", "检查更新", session=session)
await MessageUtils.build_message(result).finish()
try:
result = await UpdateManage.update(bot, session.id1, ver_type.result)
except Exception as e:
logger.error("版本更新失败...", "检查更新", session=session, e=e)
await MessageUtils.build_message(f"更新版本失败...e: {e}").finish()
if result:
await MessageUtils.build_message(result).finish()
await MessageUtils.build_message("更新版本失败...").finish()
# driver = nonebot.get_driver()
# @driver.on_startup
# async def _():
# result = await UpdateManage.check_version()
# print("-----------------------")
# print("-----------------------")
# print(result)
# print("-----------------------")
# print("-----------------------")
# result = await UpdateManage.update(None, "", "dev")
# print("-----------------------")
# print("-----------------------")
# print(result)
# print("-----------------------")
# print("-----------------------")
@@ -0,0 +1,202 @@
import os
import shutil
import tarfile
import zipfile
from pathlib import Path
from nonebot.adapters import Bot
from nonebot.utils import run_sync
from zhenxun.services.log import logger
from zhenxun.utils.http_utils import AsyncHttpx
from zhenxun.utils.platform import PlatformUtils
from .config import (
BACKUP_PATH,
BASE_PATH,
DEV_URL,
DOWNLOAD_GZ_FILE,
DOWNLOAD_ZIP_FILE,
MAIN_URL,
PYPROJECT_FILE,
PYPROJECT_LOCK_FILE,
RELEASE_URL,
REPLACE_FOLDERS,
TMP_PATH,
VERSION_FILE,
)
@run_sync
def _file_handle(latest_version: str | None):
"""文件移动操作
参数:
latest_version: 版本号
"""
BACKUP_PATH.mkdir(exist_ok=True, parents=True)
logger.debug("开始解压文件压缩包...", "检查更新")
download_file = DOWNLOAD_GZ_FILE
if DOWNLOAD_GZ_FILE.exists():
tf = tarfile.open(DOWNLOAD_GZ_FILE)
else:
download_file = DOWNLOAD_ZIP_FILE
tf = zipfile.ZipFile(DOWNLOAD_ZIP_FILE)
tf.extractall(TMP_PATH)
logger.debug("解压文件压缩包完成...", "检查更新")
download_file_path = (
TMP_PATH / [x for x in os.listdir(TMP_PATH) if (TMP_PATH / x).is_dir()][0]
)
_pyproject = download_file_path / "pyproject.toml"
_lock_file = download_file_path / "poetry.lock"
extract_path = download_file_path / "zhenxun"
target_path = BASE_PATH
if PYPROJECT_FILE.exists():
logger.debug(f"移除备份文件: {PYPROJECT_FILE}", "检查更新")
shutil.move(PYPROJECT_FILE, BACKUP_PATH / "pyproject.toml")
if PYPROJECT_LOCK_FILE.exists():
logger.debug(f"移除备份文件: {PYPROJECT_FILE}", "检查更新")
shutil.move(PYPROJECT_LOCK_FILE, BACKUP_PATH / "poetry.lock")
if _pyproject.exists():
logger.debug("移动文件: pyproject.toml", "检查更新")
shutil.move(_pyproject, Path() / "pyproject.toml")
if _lock_file.exists():
logger.debug("移动文件: pyproject.toml", "检查更新")
shutil.move(_lock_file, Path() / "poetry.lock")
for folder in REPLACE_FOLDERS:
"""移动指定文件夹"""
_dir = BASE_PATH / folder
_backup_dir = BACKUP_PATH / folder
if _backup_dir.exists():
logger.debug(f"删除备份文件夹 {_backup_dir}", "检查更新")
shutil.rmtree(_backup_dir)
if _dir.exists():
logger.debug(f"移动旧文件夹 {_dir}", "检查更新")
shutil.move(_dir, _backup_dir)
else:
logger.warning(f"文件夹 {_dir} 不存在,跳过删除", "检查更新")
for folder in REPLACE_FOLDERS:
src_folder_path = extract_path / folder
dest_folder_path = target_path / folder
if src_folder_path.exists():
logger.debug(
f"移动文件夹: {src_folder_path} -> {dest_folder_path}", "检查更新"
)
shutil.move(src_folder_path, dest_folder_path)
else:
logger.debug(f"源文件夹不存在: {src_folder_path}", "检查更新")
if download_file.exists():
logger.debug(f"删除下载文件: {download_file}", "检查更新")
download_file.unlink()
if extract_path.exists():
logger.debug(f"删除解压文件夹: {extract_path}", "检查更新")
shutil.rmtree(extract_path)
if tf:
tf.close()
if TMP_PATH.exists():
shutil.rmtree(TMP_PATH)
if latest_version:
with open(VERSION_FILE, "w", encoding="utf8") as f:
f.write(f"__version__: {latest_version}")
os.system(f"poetry run pip install -r requirements.txt")
class UpdateManage:
@classmethod
async def check_version(cls) -> str:
"""检查更新版本
返回:
str: 更新信息
"""
cur_version = cls.__get_version()
data = await cls.__get_latest_data()
if not data:
return "检查更新获取版本失败..."
return f"检测到当前版本更新\n当前版本:{cur_version}\n最新版本:{data.get('name')}\n创建日期:{data.get('created_at')}\n更新内容:\n{data.get('body')}"
@classmethod
async def update(cls, bot: Bot, user_id: str, version_type: str) -> str | None:
"""更新操作
参数:
bot: Bot
user_id: 用户id
version_type: 更新版本类型
返回:
str | None: 返回消息
"""
logger.info(f"开始下载真寻最新版文件....", "检查更新")
cur_version = cls.__get_version()
new_version = "main"
url = MAIN_URL
if version_type == "dev":
url = DEV_URL
new_version = "dev"
if version_type == "release":
data = await cls.__get_latest_data()
if not data:
return "获取更新版本失败..."
url = data.get("tarball_url")
new_version = data.get("name")
url = (await AsyncHttpx.get(url)).headers.get("Location") # type: ignore
if not url:
return "获取版本下载链接失败..."
if TMP_PATH.exists():
logger.debug(f"删除临时文件夹 {TMP_PATH}", "检查更新")
shutil.rmtree(TMP_PATH)
logger.debug(
f"开始更新版本:{cur_version} -> {new_version} | 下载链接:{url}",
"检查更新",
)
await PlatformUtils.send_superuser(
bot,
f"检测真寻已更新,版本更新:{cur_version} -> {new_version}\n开始更新...",
user_id,
)
download_file = (
DOWNLOAD_GZ_FILE if version_type == "release" else DOWNLOAD_ZIP_FILE
)
if await AsyncHttpx.download_file(url, download_file):
logger.debug("下载真寻最新版文件完成...", "检查更新")
if version_type != "release":
new_version = None
await _file_handle(new_version)
return f"版本更新完成\n版本: {cur_version} -> {new_version}\n请重新启动真寻以完成更新!"
else:
logger.debug("下载真寻最新版文件失败...", "检查更新")
return None
@classmethod
def __get_version(cls) -> str:
"""获取当前版本
返回:
str: 当前版本号
"""
_version = "v0.0.0"
if VERSION_FILE.exists():
text = VERSION_FILE.open(encoding="utf8").readline()
if text:
_version = text.split(":")[-1].strip()
return _version
@classmethod
async def __get_latest_data(cls) -> dict:
"""获取最新版本信息
返回:
dict: 最新版本数据
"""
for _ in range(3):
try:
res = await AsyncHttpx.get(RELEASE_URL)
if res.status_code == 200:
return res.json()
except TimeoutError:
pass
except Exception as e:
logger.error(f"检查更新真寻获取版本失败", e=e)
return {}
@@ -0,0 +1,24 @@
from pathlib import Path
from zhenxun.configs.path_config import TEMP_PATH
DEV_URL = "https://ghproxy.cc/https://github.com/HibiKier/zhenxun_bot/archive/refs/heads/dev.zip"
MAIN_URL = "https://ghproxy.cc/https://github.com/HibiKier/zhenxun_bot/archive/refs/heads/main.zip"
RELEASE_URL = "https://api.github.com/repos/HibiKier/zhenxun_bot/releases/latest"
VERSION_FILE = Path() / "__version__"
PYPROJECT_FILE = Path() / "pyproject.toml"
PYPROJECT_LOCK_FILE = Path() / "poetry.lock"
BASE_PATH = Path() / "zhenxun"
TMP_PATH = TEMP_PATH / "auto_update"
BACKUP_PATH = Path() / "backup"
DOWNLOAD_GZ_FILE = TMP_PATH / "download_latest_file.tar.gz"
DOWNLOAD_ZIP_FILE = TMP_PATH / "download_latest_file.zip"
REPLACE_FOLDERS = ["builtin_plugins", "plugins", "services", "utils", "models"]
+1
View File
@@ -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
@@ -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()
@@ -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"
)
"""插件下载地址"""
@@ -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} 移除成功!"
@@ -0,0 +1,132 @@
import os
from pathlib import Path
import nonebot
import ujson as json
from zhenxun.configs.path_config import DATA_PATH
nonebot.load_plugins(str(Path(__file__).parent.resolve()))
old_file1 = DATA_PATH / "_prefix_count.json"
old_file2 = DATA_PATH / "_prefix_user_count.json"
new_path = DATA_PATH / "statistics"
new_path.mkdir(parents=True, exist_ok=True)
if old_file1.exists():
os.rename(old_file1, new_path / "_prefix_count.json")
if old_file2.exists():
os.rename(old_file2, new_path / "_prefix_user_count.json")
# 修改旧数据
statistics_group_file = DATA_PATH / "statistics" / "_prefix_count.json"
statistics_user_file = DATA_PATH / "statistics" / "_prefix_user_count.json"
for file in [statistics_group_file, statistics_user_file]:
if file.exists():
with open(file, "r", encoding="utf8") as f:
data = json.load(f)
if not (statistics_group_file.parent / f"{file}.bak").exists():
with open(f"{file}.bak", "w", encoding="utf8") as wf:
json.dump(data, wf, ensure_ascii=False, indent=4)
for x in ["total_statistics", "day_statistics"]:
for key in data[x].keys():
num = 0
if data[x][key].get("ai") is not None:
if data[x][key].get("Ai") is not None:
data[x][key]["Ai"] += data[x][key]["ai"]
else:
data[x][key]["Ai"] = data[x][key]["ai"]
del data[x][key]["ai"]
if data[x][key].get("抽卡") is not None:
if data[x][key].get("游戏抽卡") is not None:
data[x][key]["游戏抽卡"] += data[x][key]["抽卡"]
else:
data[x][key]["游戏抽卡"] = data[x][key]["抽卡"]
del data[x][key]["抽卡"]
if data[x][key].get("我的道具") is not None:
num += data[x][key]["我的道具"]
del data[x][key]["我的道具"]
if data[x][key].get("使用道具") is not None:
num += data[x][key]["使用道具"]
del data[x][key]["使用道具"]
if data[x][key].get("我的金币") is not None:
num += data[x][key]["我的金币"]
del data[x][key]["我的金币"]
if data[x][key].get("购买") is not None:
num += data[x][key]["购买"]
del data[x][key]["购买"]
if data[x][key].get("商店") is not None:
data[x][key]["商店"] += num
else:
data[x][key]["商店"] = num
for x in ["week_statistics", "month_statistics"]:
for key in data[x].keys():
num = 0
if key == "total":
if data[x][key].get("ai") is not None:
if data[x][key].get("Ai") is not None:
data[x][key]["Ai"] += data[x][key]["ai"]
else:
data[x][key]["Ai"] = data[x][key]["ai"]
del data[x][key]["ai"]
if data[x][key].get("抽卡") is not None:
if data[x][key].get("游戏抽卡") is not None:
data[x][key]["游戏抽卡"] += data[x][key]["抽卡"]
else:
data[x][key]["游戏抽卡"] = data[x][key]["抽卡"]
del data[x][key]["抽卡"]
if data[x][key].get("我的道具") is not None:
num += data[x][key]["我的道具"]
del data[x][key]["我的道具"]
if data[x][key].get("使用道具") is not None:
num += data[x][key]["使用道具"]
del data[x][key]["使用道具"]
if data[x][key].get("我的金币") is not None:
num += data[x][key]["我的金币"]
del data[x][key]["我的金币"]
if data[x][key].get("购买") is not None:
num += data[x][key]["购买"]
del data[x][key]["购买"]
if data[x][key].get("商店") is not None:
data[x][key]["商店"] += num
else:
data[x][key]["商店"] = num
else:
for day in data[x][key].keys():
num = 0
if data[x][key][day].get("ai") is not None:
if data[x][key][day].get("Ai") is not None:
data[x][key][day]["Ai"] += data[x][key][day]["ai"]
else:
data[x][key][day]["Ai"] = data[x][key][day]["ai"]
del data[x][key][day]["ai"]
if data[x][key][day].get("抽卡") is not None:
if data[x][key][day].get("游戏抽卡") is not None:
data[x][key][day]["游戏抽卡"] += data[x][key][day][
"抽卡"
]
else:
data[x][key][day]["游戏抽卡"] = data[x][key][day][
"抽卡"
]
del data[x][key][day]["抽卡"]
if data[x][key][day].get("我的道具") is not None:
num += data[x][key][day]["我的道具"]
del data[x][key][day]["我的道具"]
if data[x][key][day].get("使用道具") is not None:
num += data[x][key][day]["使用道具"]
del data[x][key][day]["使用道具"]
if data[x][key][day].get("我的金币") is not None:
num += data[x][key][day]["我的金币"]
del data[x][key][day]["我的金币"]
if data[x][key][day].get("购买") is not None:
num += data[x][key][day]["购买"]
del data[x][key][day]["购买"]
if data[x][key][day].get("商店") is not None:
data[x][key][day]["商店"] += num
else:
data[x][key][day]["商店"] = num
with open(file, "w", encoding="utf8") as f:
json.dump(data, f, ensure_ascii=False, indent=4)
@@ -0,0 +1,130 @@
from datetime import datetime, timedelta
from tortoise.functions import Count
from zhenxun.models.group_console import GroupConsole
from zhenxun.models.group_member_info import GroupInfoUser
from zhenxun.models.plugin_info import PluginInfo
from zhenxun.models.statistics import Statistics
from zhenxun.utils.enum import PluginType
from zhenxun.utils.image_utils import BuildImage, BuildMat, MatType
class StatisticsManage:
@classmethod
async def get_statistics(
cls,
plugin_name: str | None,
is_global: bool,
search_type: str | None,
user_id: str | None = None,
group_id: str | None = None,
):
day = None
day_type = ""
if search_type == "day":
day = 1
day_type = "日"
if search_type == "week":
day = 7
day_type = "周"
if search_type == "month":
day = 30
day_type = "月"
if day_type:
day_type += f"({day}天)"
title = ""
if user_id:
"""查用户"""
query = GroupInfoUser.filter(user_id=user_id)
if group_id:
query = query.filter(group_id=group_id)
user = await query.first()
title = f"{user.user_name if user else user_id} {day_type}功能调用统计"
elif group_id:
"""查群组"""
group = await GroupConsole.get_or_none(
group_id=group_id, channel_id__isnull=True
)
title = f"{group.group_name if group else group_id} {day_type}功能调用统计"
else:
title = "功能调用统计"
if is_global and not user_id:
title = "全局 " + title
return await cls.get_global_statistics(plugin_name, day, title)
if user_id:
return await cls.get_my_statistics(user_id, group_id, day, title)
if group_id:
return await cls.get_group_statistics(group_id, day, title)
return None
@classmethod
async def get_global_statistics(
cls, plugin_name: str | None, day: int | None, title: str
) -> BuildImage | str:
query = Statistics
if plugin_name:
query = query.filter(plugin_name=plugin_name)
if day:
time = datetime.now() - timedelta(days=day)
query = query.filter(create_time__gte=time)
data_list = (
await query.annotate(count=Count("id"))
.group_by("plugin_name")
.values_list("plugin_name", "count")
)
if not data_list:
return "统计数据为空..."
return await cls.__build_image(data_list, title)
@classmethod
async def get_my_statistics(
cls, user_id: str, group_id: str | None, day: int | None, title: str
):
query = Statistics.filter(user_id=user_id)
if group_id:
query = query.filter(group_id=group_id)
if day:
time = datetime.now() - timedelta(days=day)
query = query.filter(create_time__gte=time)
data_list = (
await query.annotate(count=Count("id"))
.group_by("plugin_name")
.values_list("plugin_name", "count")
)
if not data_list:
return "统计数据为空..."
return await cls.__build_image(data_list, title)
@classmethod
async def get_group_statistics(cls, group_id: str, day: int | None, title: str):
query = Statistics.filter(group_id=group_id)
if day:
time = datetime.now() - timedelta(days=day)
query = query.filter(create_time__gte=time)
data_list = (
await query.annotate(count=Count("id"))
.group_by("plugin_name")
.values_list("plugin_name", "count")
)
if not data_list:
return "统计数据为空..."
return await cls.__build_image(data_list, title)
@classmethod
async def __build_image(cls, data_list: list[tuple[str, int]], title: str):
mat = BuildMat(MatType.BARH)
module2count = {x[0]: x[1] for x in data_list}
plugin_info = await PluginInfo.filter(
module__in=module2count.keys(), plugin_type=PluginType.NORMAL
).all()
x_index = []
data = []
for plugin in plugin_info:
x_index.append(plugin.name)
data.append(module2count.get(plugin.module, 0))
mat.x_index = x_index
mat.data = data
mat.title = title
return await mat.build()
@@ -0,0 +1,162 @@
from nonebot.plugin import PluginMetadata
from nonebot_plugin_alconna import (
Alconna,
Args,
Arparma,
Match,
Option,
on_alconna,
store_true,
)
from nonebot_plugin_session import EventSession
from zhenxun.configs.utils import PluginExtraData
from zhenxun.utils.enum import PluginType
from zhenxun.utils.message import MessageUtils
from ._data_source import StatisticsManage
__plugin_meta__ = PluginMetadata(
name="功能调用统计",
description="功能调用统计可视化",
usage="""
usage:
功能调用统计可视化
指令:
功能调用统计
日功能调用统计
周功能调用统计
月功能调用统计
我的功能调用统计 : 当前群我的统计
我的功能调用统计 -g: 我的全局统计
我的日功能调用统计
我的周功能调用统计
我的月功能调用统计
""".strip(),
extra=PluginExtraData(
author="HibiKier",
version="0.1",
plugin_type=PluginType.NORMAL,
menu_type="数据统计",
aliases={"功能调用统计"},
superuser_help="""
"全局功能调用统计",
"全局日功能调用统计",
"全局周功能调用统计",
"全局月功能调用统计",
""".strip(),
).dict(),
)
_matcher = on_alconna(
Alconna(
"功能调用统计",
Args["name?", str],
Option("-g|--global", action=store_true, help_text="全局统计"),
Option("-my", action=store_true, help_text="我的"),
Option("-t|--type", Args["search_type", ["day", "week", "month"]]),
),
priority=5,
block=True,
)
_matcher.shortcut(
"日功能调用统计(?P<name>.*)",
command="功能调用统计",
arguments=["{name}", "-t", "day"],
prefix=True,
)
_matcher.shortcut(
"周功能调用统计(?P<name>.*)",
command="功能调用统计",
arguments=["{name}", "-t", "week"],
prefix=True,
)
_matcher.shortcut(
"月功能调用统计(?P<name>.*)",
command="功能调用统计",
arguments=["{name}", "-t", "month"],
prefix=True,
)
_matcher.shortcut(
"全局功能调用统计(?P<name>.*)",
command="功能调用统计",
arguments=["{name}", "-g"],
prefix=True,
)
_matcher.shortcut(
"全局日功能调用统计(?P<name>.*)",
command="功能调用统计",
arguments=["{name}", "-t", "day", "-g"],
prefix=True,
)
_matcher.shortcut(
"全局周功能调用统计(?P<name>.*)",
command="功能调用统计",
arguments=["{name}", "-t", "week", "-g"],
prefix=True,
)
_matcher.shortcut(
"全局月功能调用统计(?P<name>.*)",
command="功能调用统计",
arguments=["{name}", "-t", "month", "-g"],
prefix=True,
)
_matcher.shortcut(
"我的功能调用统计(?P<name>.*)",
command="功能调用统计",
arguments=["{name}", "-my"],
prefix=True,
)
_matcher.shortcut(
"我的日功能调用统计(?P<name>.*)",
command="功能调用统计",
arguments=["{name}", "-t", "day", "-my"],
prefix=True,
)
_matcher.shortcut(
"我的周功能调用统计(?P<name>.*)",
command="功能调用统计",
arguments=["{name}", "-t", "week", "-my"],
prefix=True,
)
_matcher.shortcut(
"我的月功能调用统计(?P<name>.*)",
command="功能调用统计",
arguments=["{name}", "-t", "month", "-my"],
prefix=True,
)
@_matcher.handle()
async def _(
session: EventSession, arparma: Arparma, name: Match[str], search_type: Match[str]
):
plugin_name = name.result if name.available else None
st = search_type.result if search_type.available else None
gid = session.id3 or session.id2
uid = session.id1 if (arparma.find("my") or not gid) else None
is_global = arparma.find("global")
if uid and is_global:
"""个人全局"""
gid = None
if result := await StatisticsManage.get_statistics(
plugin_name, arparma.find("global"), st, uid, gid
):
if isinstance(result, str):
await MessageUtils.build_message(result).finish(reply_to=True)
else:
await MessageUtils.build_message(result).send()
else:
await MessageUtils.build_message("获取数据失败...").send()
@@ -0,0 +1,48 @@
from datetime import datetime
from nonebot.adapters import Bot, Event
from nonebot.adapters.onebot.v11 import PokeNotifyEvent
from nonebot.matcher import Matcher
from nonebot.message import run_postprocessor
from nonebot.plugin import PluginMetadata
from nonebot_plugin_session import EventSession
from zhenxun.configs.utils import PluginExtraData
from zhenxun.models.plugin_info import PluginInfo
from zhenxun.models.statistics import Statistics
from zhenxun.utils.enum import PluginType
__plugin_meta__ = PluginMetadata(
name="功能调用统计",
description="功能调用统计",
usage="""""".strip(),
extra=PluginExtraData(
author="HibiKier", version="0.1", plugin_type=PluginType.HIDDEN
).dict(),
)
@run_postprocessor
async def _(
matcher: Matcher,
exception: Exception | None,
bot: Bot,
session: EventSession,
event: Event,
):
if matcher.type == "notice" and not isinstance(event, PokeNotifyEvent):
"""过滤除poke外的notice"""
return
if session.id1:
plugin = await PluginInfo.get_or_none(module=matcher.module_name)
plugin_type = plugin.plugin_type if plugin else None
if plugin_type == PluginType.NORMAL and matcher.plugin_name not in [
"update_info",
"statistics_handle",
]:
await Statistics.create(
user_id=session.id1,
group_id=session.id3 or session.id2,
plugin_name=matcher.plugin_name,
create_time=datetime.now(),
)