webui主页和插件管理api更新

This commit is contained in:
HibiKier 2024-01-06 17:36:22 +08:00
parent c146df3d25
commit a5bb1f768e
19 changed files with 711 additions and 579 deletions

View File

@ -0,0 +1,26 @@
from enum import Enum
from typing import NamedTuple
class SearchType(Enum):
"""
查询类型
"""
DAY = "day_statistics"
""""""
WEEK = "week_statistics"
""""""
MONTH = "month_statistics"
""""""
TOTAL = "total_statistics"
"""总数"""
class ParseData(NamedTuple):
global_search: bool
"""是否全局搜索"""
search_type: SearchType
"""搜索类型"""

View File

@ -1,13 +1,13 @@
import asyncio import asyncio
import os import os
from typing import Tuple
from nonebot import on_command from nonebot import on_command
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message, MessageEvent from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message, MessageEvent
from nonebot.params import Command, CommandArg from nonebot.params import CommandArg
from configs.path_config import DATA_PATH, IMAGE_PATH from configs.path_config import DATA_PATH, IMAGE_PATH
from models.group_info import GroupInfo from models.group_info import GroupInfo
from utils.depends import OneCommand
from utils.image_utils import BuildMat from utils.image_utils import BuildMat
from utils.manager import plugins2settings_manager from utils.manager import plugins2settings_manager
from utils.message_builder import image from utils.message_builder import image
@ -91,7 +91,7 @@ statistics_user_file = DATA_PATH / "statistics" / "_prefix_user_count.json"
@statistics.handle() @statistics.handle()
async def _(bot: Bot, event: MessageEvent, cmd: Tuple[str, ...] = Command(), arg: Message = CommandArg()): async def _(bot: Bot, event: MessageEvent, cmd: str = OneCommand(), arg: Message = CommandArg()):
msg = arg.extract_plain_text().strip() msg = arg.extract_plain_text().strip()
if cmd[0][:2] == "全局": if cmd[0][:2] == "全局":
if str(event.user_id) in bot.config.superusers: if str(event.user_id) in bot.config.superusers:

View File

@ -6,11 +6,10 @@ from nonebot.message import run_postprocessor
from nonebot.typing import Optional, T_State from nonebot.typing import Optional, T_State
from configs.path_config import DATA_PATH from configs.path_config import DATA_PATH
from models.statistics import Statistics
from utils.manager import plugins2settings_manager from utils.manager import plugins2settings_manager
from utils.utils import scheduler from utils.utils import scheduler
from ._model import Statistics
try: try:
import ujson as json import ujson as json
except ModuleNotFoundError: except ModuleNotFoundError:
@ -21,69 +20,69 @@ __zx_plugin_name__ = "功能调用统计 [Hidden]"
__plugin_version__ = 0.1 __plugin_version__ = 0.1
__plugin_author__ = "HibiKier" __plugin_author__ = "HibiKier"
statistics_group_file = DATA_PATH / "statistics" / "_prefix_count.json" # statistics_group_file = DATA_PATH / "statistics" / "_prefix_count.json"
statistics_user_file = DATA_PATH / "statistics" / "_prefix_user_count.json" # statistics_user_file = DATA_PATH / "statistics" / "_prefix_user_count.json"
try: # try:
with open(statistics_group_file, "r", encoding="utf8") as f: # with open(statistics_group_file, "r", encoding="utf8") as f:
_prefix_count_dict = json.load(f) # _prefix_count_dict = json.load(f)
except (FileNotFoundError, ValueError): # except (FileNotFoundError, ValueError):
_prefix_count_dict = { # _prefix_count_dict = {
"total_statistics": { # "total_statistics": {
"total": {}, # "total": {},
}, # },
"day_statistics": { # "day_statistics": {
"total": {}, # "total": {},
}, # },
"week_statistics": { # "week_statistics": {
"total": {}, # "total": {},
}, # },
"month_statistics": { # "month_statistics": {
"total": {}, # "total": {},
}, # },
"start_time": str(datetime.now().date()), # "start_time": str(datetime.now().date()),
"day_index": 0, # "day_index": 0,
} # }
try: # try:
with open(statistics_user_file, "r", encoding="utf8") as f: # with open(statistics_user_file, "r", encoding="utf8") as f:
_prefix_user_count_dict = json.load(f) # _prefix_user_count_dict = json.load(f)
except (FileNotFoundError, ValueError): # except (FileNotFoundError, ValueError):
_prefix_user_count_dict = { # _prefix_user_count_dict = {
"total_statistics": { # "total_statistics": {
"total": {}, # "total": {},
}, # },
"day_statistics": { # "day_statistics": {
"total": {}, # "total": {},
}, # },
"week_statistics": { # "week_statistics": {
"total": {}, # "total": {},
}, # },
"month_statistics": { # "month_statistics": {
"total": {}, # "total": {},
}, # },
"start_time": str(datetime.now().date()), # "start_time": str(datetime.now().date()),
"day_index": 0, # "day_index": 0,
} # }
# 以前版本转换 # # 以前版本转换
if _prefix_count_dict.get("day_index") is None: # if _prefix_count_dict.get("day_index") is None:
tmp = _prefix_count_dict.copy() # tmp = _prefix_count_dict.copy()
_prefix_count_dict = { # _prefix_count_dict = {
"total_statistics": tmp["total_statistics"], # "total_statistics": tmp["total_statistics"],
"day_statistics": { # "day_statistics": {
"total": {}, # "total": {},
}, # },
"week_statistics": { # "week_statistics": {
"total": {}, # "total": {},
}, # },
"month_statistics": { # "month_statistics": {
"total": {}, # "total": {},
}, # },
"start_time": tmp["start_time"], # "start_time": tmp["start_time"],
"day_index": 0, # "day_index": 0,
} # }
# 添加命令次数 # 添加命令次数
@ -95,7 +94,7 @@ async def _(
event: MessageEvent, event: MessageEvent,
state: T_State, state: T_State,
): ):
global _prefix_count_dict # global _prefix_count_dict
if ( if (
matcher.type == "message" matcher.type == "message"
and matcher.priority not in [1, 999] and matcher.priority not in [1, 999]
@ -107,112 +106,112 @@ async def _(
plugin_name=matcher.plugin_name, plugin_name=matcher.plugin_name,
create_time=datetime.now(), create_time=datetime.now(),
) )
module = matcher.plugin_name # module = matcher.plugin_name
day_index = _prefix_count_dict["day_index"] # day_index = _prefix_count_dict["day_index"]
try: # try:
group_id = str(event.group_id) # group_id = str(event.group_id)
except AttributeError: # except AttributeError:
group_id = "total" # group_id = "total"
user_id = str(event.user_id) # user_id = str(event.user_id)
plugin_name = plugins2settings_manager.get_plugin_data(module) # plugin_name = plugins2settings_manager.get_plugin_data(module)
if plugin_name and plugin_name.cmd: # if plugin_name and plugin_name.cmd:
plugin_name = plugin_name.cmd[0] # plugin_name = plugin_name.cmd[0]
check_exists_key(group_id, user_id, plugin_name) # check_exists_key(group_id, user_id, plugin_name)
for data in [_prefix_count_dict, _prefix_user_count_dict]: # for data in [_prefix_count_dict, _prefix_user_count_dict]:
data["total_statistics"]["total"][plugin_name] += 1 # data["total_statistics"]["total"][plugin_name] += 1
data["day_statistics"]["total"][plugin_name] += 1 # data["day_statistics"]["total"][plugin_name] += 1
data["week_statistics"]["total"][plugin_name] += 1 # data["week_statistics"]["total"][plugin_name] += 1
data["month_statistics"]["total"][plugin_name] += 1 # data["month_statistics"]["total"][plugin_name] += 1
# print(_prefix_count_dict) # # print(_prefix_count_dict)
if group_id != "total": # if group_id != "total":
for data in [_prefix_count_dict, _prefix_user_count_dict]: # for data in [_prefix_count_dict, _prefix_user_count_dict]:
if data == _prefix_count_dict: # if data == _prefix_count_dict:
key = group_id # key = group_id
else: # else:
key = user_id # key = user_id
data["total_statistics"][key][plugin_name] += 1 # data["total_statistics"][key][plugin_name] += 1
data["day_statistics"][key][plugin_name] += 1 # data["day_statistics"][key][plugin_name] += 1
data["week_statistics"][key][str(day_index % 7)][plugin_name] += 1 # data["week_statistics"][key][str(day_index % 7)][plugin_name] += 1
data["month_statistics"][key][str(day_index % 30)][plugin_name] += 1 # data["month_statistics"][key][str(day_index % 30)][plugin_name] += 1
with open(statistics_group_file, "w", encoding="utf8") as f: # with open(statistics_group_file, "w", encoding="utf8") as f:
json.dump(_prefix_count_dict, f, indent=4, ensure_ascii=False) # json.dump(_prefix_count_dict, f, indent=4, ensure_ascii=False)
with open(statistics_user_file, "w", encoding="utf8") as f: # with open(statistics_user_file, "w", encoding="utf8") as f:
json.dump(_prefix_user_count_dict, f, ensure_ascii=False, indent=4) # json.dump(_prefix_user_count_dict, f, ensure_ascii=False, indent=4)
def check_exists_key(group_id: str, user_id: str, plugin_name: str): # def check_exists_key(group_id: str, user_id: str, plugin_name: str):
global _prefix_count_dict, _prefix_user_count_dict # global _prefix_count_dict, _prefix_user_count_dict
for data in [_prefix_count_dict, _prefix_user_count_dict]: # for data in [_prefix_count_dict, _prefix_user_count_dict]:
if data == _prefix_count_dict: # if data == _prefix_count_dict:
key = group_id # key = group_id
else: # else:
key = user_id # key = user_id
if not data["total_statistics"]["total"].get(plugin_name): # if not data["total_statistics"]["total"].get(plugin_name):
data["total_statistics"]["total"][plugin_name] = 0 # data["total_statistics"]["total"][plugin_name] = 0
if not data["day_statistics"]["total"].get(plugin_name): # if not data["day_statistics"]["total"].get(plugin_name):
data["day_statistics"]["total"][plugin_name] = 0 # data["day_statistics"]["total"][plugin_name] = 0
if not data["week_statistics"]["total"].get(plugin_name): # if not data["week_statistics"]["total"].get(plugin_name):
data["week_statistics"]["total"][plugin_name] = 0 # data["week_statistics"]["total"][plugin_name] = 0
if not data["month_statistics"]["total"].get(plugin_name): # if not data["month_statistics"]["total"].get(plugin_name):
data["month_statistics"]["total"][plugin_name] = 0 # data["month_statistics"]["total"][plugin_name] = 0
if not data["total_statistics"].get(key): # if not data["total_statistics"].get(key):
data["total_statistics"][key] = {} # data["total_statistics"][key] = {}
if not data["total_statistics"][key].get(plugin_name): # if not data["total_statistics"][key].get(plugin_name):
data["total_statistics"][key][plugin_name] = 0 # data["total_statistics"][key][plugin_name] = 0
if not data["day_statistics"].get(key): # if not data["day_statistics"].get(key):
data["day_statistics"][key] = {} # data["day_statistics"][key] = {}
if not data["day_statistics"][key].get(plugin_name): # if not data["day_statistics"][key].get(plugin_name):
data["day_statistics"][key][plugin_name] = 0 # data["day_statistics"][key][plugin_name] = 0
if key != "total": # if key != "total":
if not data["week_statistics"].get(key): # if not data["week_statistics"].get(key):
data["week_statistics"][key] = {} # data["week_statistics"][key] = {}
if data["week_statistics"][key].get("0") is None: # if data["week_statistics"][key].get("0") is None:
for i in range(7): # for i in range(7):
data["week_statistics"][key][str(i)] = {} # data["week_statistics"][key][str(i)] = {}
if data["week_statistics"][key]["0"].get(plugin_name) is None: # if data["week_statistics"][key]["0"].get(plugin_name) is None:
for i in range(7): # for i in range(7):
data["week_statistics"][key][str(i)][plugin_name] = 0 # data["week_statistics"][key][str(i)][plugin_name] = 0
if not data["month_statistics"].get(key): # if not data["month_statistics"].get(key):
data["month_statistics"][key] = {} # data["month_statistics"][key] = {}
if data["month_statistics"][key].get("0") is None: # if data["month_statistics"][key].get("0") is None:
for i in range(30): # for i in range(30):
data["month_statistics"][key][str(i)] = {} # data["month_statistics"][key][str(i)] = {}
if data["month_statistics"][key]["0"].get(plugin_name) is None: # if data["month_statistics"][key]["0"].get(plugin_name) is None:
for i in range(30): # for i in range(30):
data["month_statistics"][key][str(i)][plugin_name] = 0 # data["month_statistics"][key][str(i)][plugin_name] = 0
# 天 # 天
@scheduler.scheduled_job( # @scheduler.scheduled_job(
"cron", # "cron",
hour=0, # hour=0,
minute=1, # minute=1,
) # )
async def _(): # async def _():
for data in [_prefix_count_dict, _prefix_user_count_dict]: # for data in [_prefix_count_dict, _prefix_user_count_dict]:
data["day_index"] += 1 # data["day_index"] += 1
for x in data["day_statistics"].keys(): # for x in data["day_statistics"].keys():
for key in data["day_statistics"][x].keys(): # for key in data["day_statistics"][x].keys():
try: # try:
data["day_statistics"][x][key] = 0 # data["day_statistics"][x][key] = 0
except KeyError: # except KeyError:
pass # pass
for type_ in ["week_statistics", "month_statistics"]: # for type_ in ["week_statistics", "month_statistics"]:
index = str( # index = str(
data["day_index"] % 7 # data["day_index"] % 7
if type_ == "week_statistics" # if type_ == "week_statistics"
else data["day_index"] % 30 # else data["day_index"] % 30
) # )
for x in data[type_].keys(): # for x in data[type_].keys():
try: # try:
for key in data[type_][x][index].keys(): # for key in data[type_][x][index].keys():
data[type_][x][index][key] = 0 # data[type_][x][index][key] = 0
except KeyError: # except KeyError:
pass # pass
with open(statistics_group_file, "w", encoding="utf8") as f: # with open(statistics_group_file, "w", encoding="utf8") as f:
json.dump(_prefix_count_dict, f, indent=4, ensure_ascii=False) # json.dump(_prefix_count_dict, f, indent=4, ensure_ascii=False)
with open(statistics_user_file, "w", encoding="utf8") as f: # with open(statistics_user_file, "w", encoding="utf8") as f:
json.dump(_prefix_user_count_dict, f, indent=4, ensure_ascii=False) # json.dump(_prefix_user_count_dict, f, indent=4, ensure_ascii=False)

View File

@ -0,0 +1,16 @@
from typing import List
from nonebot.adapters.onebot.v11 import MessageEvent
from ._config import SearchType
def parse_data(cmd: str, event: MessageEvent, superusers: List[str]):
search_type = SearchType.TOTAL
if cmd[:2] == "全局":
if str(event.user_id) in superusers:
if cmd[2] == '':
search_type = SearchType.DAY
elif cmd[2] == '':
_type = SearchType.WEEK
elif cmd[2] == '':
_type = SearchType.MONTH

View File

@ -12,24 +12,15 @@ from configs.config import Config as gConfig
from services.log import logger, logger_ from services.log import logger, logger_
from utils.manager import plugins2settings_manager from utils.manager import plugins2settings_manager
# from .api.base_info import router as base_info_routes
# from .api.group import router as group_routes
from .api.logs import router as ws_log_routes from .api.logs import router as ws_log_routes
from .api.logs.log_manager import LOG_STORAGE from .api.logs.log_manager import LOG_STORAGE
from .api.tabs.database import router as database_router from .api.tabs.database import router as database_router
# from .api.system import router as system_routes
from .api.tabs.main import router as main_router from .api.tabs.main import router as main_router
from .api.tabs.main import ws_router as status_routes from .api.tabs.main import ws_router as status_routes
from .api.tabs.manage import router as manage_router from .api.tabs.manage import router as manage_router
from .api.tabs.plugin_manage import router as plugin_router
# from .api.g import *
from .auth import router as auth_router from .auth import router as auth_router
# from .api.plugins import router as plugin_routes
# from .api.request import router as request_routes
driver = nonebot.get_driver() driver = nonebot.get_driver()
gConfig.add_plugin_config("web-ui", "username", "admin", name="web-ui", help_="前端管理用户名") gConfig.add_plugin_config("web-ui", "username", "admin", name="web-ui", help_="前端管理用户名")
@ -40,13 +31,10 @@ gConfig.add_plugin_config("web-ui", "password", None, name="web-ui", help_="前
BaseApiRouter = APIRouter(prefix="/zhenxun/api") BaseApiRouter = APIRouter(prefix="/zhenxun/api")
BaseApiRouter.include_router(auth_router) BaseApiRouter.include_router(auth_router)
# BaseApiRouter.include_router(plugin_routes)
# BaseApiRouter.include_router(group_routes)
# BaseApiRouter.include_router(request_routes)
# BaseApiRouter.include_router(system_routes)
BaseApiRouter.include_router(main_router) BaseApiRouter.include_router(main_router)
BaseApiRouter.include_router(manage_router) BaseApiRouter.include_router(manage_router)
BaseApiRouter.include_router(database_router) BaseApiRouter.include_router(database_router)
BaseApiRouter.include_router(plugin_router)
@driver.on_startup @driver.on_startup

View File

@ -1,5 +1 @@
# from .group import *
# from .plugins import *
# from .request import *
# from .system import *
from .tabs import * from .tabs import *

View File

@ -1,75 +0,0 @@
# from datetime import datetime, timedelta
# from typing import List, Optional
# import nonebot
# from fastapi import APIRouter
# from configs.config import Config
# from models.chat_history import ChatHistory
# from services.log import logger
# from utils.manager import plugin_data_manager, plugins2settings_manager, plugins_manager
# from utils.manager.models import PluginData, PluginType
# from ..base_model import BotInfo, Result
# from ..models.params import UpdateConfig, UpdatePlugin
# from ..utils import authentication
# AVA_URL = "http://q1.qlogo.cn/g?b=qq&nk={}&s=160"
# router = APIRouter()
# @router.get("/get_bot_info", dependencies=[authentication()])
# async def _(self_id: Optional[str] = None) -> Result:
# """
# 获取Bot基础信息
# Args:
# qq (Optional[str], optional): qq号. Defaults to None.
# Returns:
# Result: 获取指定bot信息与bot列表
# """
# bot_list: List[BotInfo] = []
# if bots := nonebot.get_bots():
# select_bot: BotInfo
# for key, bot in bots.items():
# bot_list.append(
# BotInfo(
# bot=bot, # type: ignore
# self_id=bot.self_id,
# nickname="可爱的小真寻",
# ava_url=AVA_URL.format(bot.self_id),
# )
# )
# if _bl := [b for b in bot_list if b.self_id == self_id]:
# select_bot = _bl[0]
# else:
# select_bot = bot_list[0]
# select_bot.is_select = True
# now = datetime.now()
# select_bot.received_messages = await ChatHistory.filter(
# bot_id=int(select_bot.self_id)
# ).count()
# select_bot.received_messages_day = await ChatHistory.filter(
# bot_id=int(select_bot.self_id),
# create_time__gte=now - timedelta(hours=now.hour),
# ).count()
# select_bot.received_messages_week = await ChatHistory.filter(
# bot_id=int(select_bot.self_id),
# create_time__gte=now - timedelta(days=7),
# ).count()
# select_bot.group_count = len(await select_bot.bot.get_group_list())
# select_bot.friend_count = len(await select_bot.bot.get_friend_list())
# for bot in bot_list:
# bot.bot = None # type: ignore
# # 插件加载数量
# select_bot.plugin_count = len(plugins2settings_manager)
# pm_data = plugins_manager.get_data()
# select_bot.fail_plugin_count = len([pd for pd in pm_data if pm_data[pd].error])
# select_bot.success_plugin_count = (
# select_bot.plugin_count - select_bot.fail_plugin_count
# )
# return Result.ok(bot_list, "已获取操作列表")
# return Result.fail("无Bot连接")

View File

@ -1,69 +0,0 @@
# from fastapi import APIRouter
# from pydantic.error_wrappers import ValidationError
# from services.log import logger
# from utils.manager import group_manager
# from utils.utils import get_bot
# from ..base_model import Group, GroupResult, Result, Task
# from ..models.params import UpdateGroup
# from ..utils import authentication
# router = APIRouter()
# @router.get("/get_group", dependencies=[authentication()])
# async def _() -> Result:
# """
# 获取群信息
# """
# group_list_result = []
# try:
# group_info = {}
# if bot := get_bot():
# group_list = await bot.get_group_list()
# for g in group_list:
# group_info[g["group_id"]] = Group(**g)
# group_data = group_manager.get_data()
# for group_id in group_data.group_manager:
# task_list = []
# data = group_manager[group_id].dict()
# for tn, status in data["group_task_status"].items():
# task_list.append(
# Task(
# **{
# "name": tn,
# "nameZh": group_manager.get_task_data().get(tn) or tn,
# "status": status,
# }
# )
# )
# data["task"] = task_list
# if x := group_info.get(int(group_id)):
# data["group"] = x
# else:
# continue
# group_list_result.append(GroupResult(**data))
# except Exception as e:
# logger.error("调用API错误", "/get_group", e=e)
# return Result.fail(f"{type(e)}: {e}")
# return Result.ok(group_list_result, "拿到了新鲜出炉的数据!")
# @router.post("/update_group", dependencies=[authentication()])
# async def _(group: UpdateGroup) -> Result:
# """
# 修改群信息
# """
# try:
# group_id = group.group_id
# group_manager.set_group_level(group_id, group.level)
# if group.status:
# group_manager.turn_on_group_bot_status(group_id)
# else:
# group_manager.shutdown_group_bot_status(group_id)
# group_manager.save()
# except Exception as e:
# logger.error("调用API错误", "/get_group", e=e)
# return Result.fail(f"{type(e)}: {e}")
# return Result.ok(info="已完成记录!")

View File

@ -21,18 +21,18 @@ class LogStorage(Generic[_T]):
self.listeners: Set[LogListener[str]] = set() self.listeners: Set[LogListener[str]] = set()
async def add(self, log: str): async def add(self, log: str):
log = re.sub(PATTERN, "", log) # log = re.sub(PATTERN, "", log)
log_split = log.split() # log_split = log.split()
time = log_split[0] + " " + log_split[1] # time = log_split[0] + " " + log_split[1]
level = log_split[2] # level = log_split[2]
main = log_split[3] # main = log_split[3]
type_ = None # type_ = None
log_ = " ".join(log_split[3:]) # log_ = " ".join(log_split[3:])
if "Calling API" in log_: # if "Calling API" in log_:
sp = log_.split("|") # sp = log_.split("|")
type_ = sp[1] # type_ = sp[1]
log_ = "|".join(log_[1:]) # log_ = "|".join(log_[1:])
data = {"time": time, "level": level, "main": main, "type": type_, "log": log_} # data = {"time": time, "level": level, "main": main, "type": type_, "log": log_}
seq = self.count = self.count + 1 seq = self.count = self.count + 1
self.logs[seq] = log self.logs[seq] = log
asyncio.get_running_loop().call_later(self.rotation, self.remove, seq) asyncio.get_running_loop().call_later(self.rotation, self.remove, seq)

View File

@ -1,115 +0,0 @@
# from typing import Optional
# import cattrs
# from fastapi import APIRouter
# from configs.config import Config
# from services.log import logger
# from utils.manager import plugin_data_manager, plugins2settings_manager, plugins_manager
# from utils.manager.models import PluginData, PluginType
# from ..config import *
# from ..base_model import Plugin, PluginConfig, Result
# from ..models.params import UpdateConfig, UpdatePlugin
# from ..utils import authentication
# router = APIRouter()
# @router.get("/get_plugins", dependencies=[authentication()])
# def _(
# plugin_type: PluginType,
# ) -> Result:
# """
# 获取插件列表
# :param plugin_type: 类型 normal, superuser, hidden, admin
# """
# try:
# plugin_list = []
# for module in plugin_data_manager.keys():
# plugin_data: Optional[PluginData] = plugin_data_manager[module]
# if plugin_data and plugin_data.plugin_type == plugin_type:
# plugin_config = None
# if plugin_data.plugin_configs:
# plugin_config = {}
# for key in plugin_data.plugin_configs:
# plugin_config[key] = PluginConfig(
# key=key,
# module=module,
# has_type=bool(plugin_data.plugin_configs[key].type),
# **plugin_data.plugin_configs[key].dict(),
# )
# plugin_list.append(
# Plugin(
# model=module,
# plugin_settings=plugin_data.plugin_setting,
# plugin_manager=plugin_data.plugin_status,
# plugin_config=plugin_config,
# cd_limit=plugin_data.plugin_cd,
# block_limit=plugin_data.plugin_block,
# count_limit=plugin_data.plugin_count,
# )
# )
# except Exception as e:
# logger.error("调用API错误", "/get_plugins", e=e)
# return Result.fail(f"{type(e)}: {e}")
# return Result.ok(plugin_list, "拿到了新鲜出炉的数据!")
# @router.post("/update_plugins", dependencies=[authentication()])
# def _(plugin: UpdatePlugin) -> Result:
# """
# 修改插件信息
# :param plugin: 插件内容
# """
# try:
# module = plugin.module
# if p2s := plugins2settings_manager.get(module):
# p2s.default_status = plugin.default_status
# p2s.limit_superuser = plugin.limit_superuser
# p2s.cost_gold = plugin.cost_gold
# p2s.cmd = plugin.cmd
# p2s.level = plugin.group_level
# if pd := plugin_data_manager.get(module):
# menu_lin = None
# if len(pd.menu_type) > 1:
# menu_lin = pd.menu_type[1]
# if menu_lin is not None:
# pd.menu_type = (plugin.menu_type, menu_lin)
# else:
# pd.menu_type = (plugin.menu_type,)
# if pm := plugins_manager.get(module):
# if plugin.block_type:
# pm.block_type = plugin.block_type
# pm.status = False
# else:
# pm.block_type = None
# pm.status = True
# plugins2settings_manager.save()
# plugins_manager.save()
# except Exception as e:
# logger.error("调用API错误", "/update_plugins", e=e)
# return Result.fail(f"{type(e)}: {e}")
# return Result.ok(info="已经帮你写好啦!")
# @router.post("/update_config", dependencies=[authentication()])
# def _(config_list: List[UpdateConfig]) -> Result:
# try:
# for config in config_list:
# if cg := Config.get(config.module):
# if c := cg.configs.get(config.key):
# if isinstance(c.value, (list, tuple)) or isinstance(
# c.default_value, (list, tuple)
# ):
# value = config.value.split(",")
# else:
# value = config.value
# if c.type and value is not None:
# value = cattrs.structure(value, c.type)
# Config.set_config(config.module, config.key, value)
# except Exception as e:
# logger.error("调用API错误", "/update_config", e=e)
# return Result.fail(f"{type(e)}: {e}")
# Config.save(save_simple_data=True)
# return Result.ok(info="写入配置项了哦!")

View File

@ -1,87 +0,0 @@
from typing import Optional
from fastapi import APIRouter
from configs.config import NICKNAME
from models.group_info import GroupInfo
from services.log import logger
from utils.manager import requests_manager
from utils.utils import get_bot
from ..base_model import RequestResult, Result
from ..models.params import HandleRequest
from ..utils import authentication
router = APIRouter()
@router.get("/get_request", dependencies=[authentication()])
def _(request_type: Optional[str]) -> Result:
try:
req_data = requests_manager.get_data()
req_list = []
if request_type in ["group", "private"]:
req_data = req_data[request_type]
for x in req_data:
req_data[x]["oid"] = x
req_list.append(RequestResult(**req_data[x]))
req_list.reverse()
except Exception as e:
logger.error("调用API错误", "/get_request", e=e)
return Result.fail(f"{type(e)}: {e}")
return Result.ok(req_list, f"{NICKNAME}带来了最新的数据!")
@router.delete("/clear_request", dependencies=[authentication()])
def _(request_type: Optional[str]) -> Result:
"""
清空请求
:param type_: 类型
"""
requests_manager.clear(request_type)
return Result.ok(info="成功清除了数据")
# @router.post("/handle_request", dependencies=[authentication()])
# async def _(parma: HandleRequest) -> Result:
# """
# 操作请求
# :param parma: 参数
# """
# try:
# result = "操作成功!"
# flag = 3
# if bot := get_bot():
# if parma.handle == "approve":
# if parma.type == "group":
# if rid := requests_manager.get_group_id(parma.id):
# # await GroupInfo.update_or_create(defaults={"group_flag": 1}, )
# if group := await GroupInfo.get_or_none(group_id=str(rid)):
# await group.update_or_create(group_flag=1)
# else:
# group_info = await bot.get_group_info(group_id=rid)
# await GroupInfo.update_or_create(
# group_id=str(group_info["group_id"]),
# defaults={
# "group_name": group_info["group_name"],
# "max_member_count": group_info["max_member_count"],
# "member_count": group_info["member_count"],
# "group_flag": 1,
# },
# )
# flag = await requests_manager.approve(bot, parma.id, parma.type)
# elif parma.handle == "refuse":
# flag = await requests_manager.refused(bot, parma.id, parma.type)
# elif parma.handle == "delete":
# requests_manager.delete_request(parma.id, parma.type)
# if parma.handle != "delete":
# if flag == 1:
# result = "该请求已失效"
# requests_manager.delete_request(parma.id, parma.type)
# elif flag == 2:
# result = "未找到此Id"
# return Result.ok(result, "成功处理了请求!")
# return Result.fail("Bot未连接")
# except Exception as e:
# logger.error("调用API错误", "/get_group", e=e)
# return Result.fail(f"{type(e)}: {e}")

View File

@ -1,3 +1,4 @@
from .database import * from .database import *
from .main import * from .main import *
from .manage import * from .manage import *
from .plugin_manage import *

View File

@ -7,21 +7,26 @@ import nonebot
from fastapi import APIRouter, WebSocket from fastapi import APIRouter, WebSocket
from nonebot.utils import escape_tag from nonebot.utils import escape_tag
from starlette.websockets import WebSocket, WebSocketDisconnect, WebSocketState from starlette.websockets import WebSocket, WebSocketDisconnect, WebSocketState
from tortoise.functions import Count
from websockets.exceptions import ConnectionClosedError, ConnectionClosedOK
from configs.config import NICKNAME from configs.config import NICKNAME
from models.chat_history import ChatHistory from models.chat_history import ChatHistory
from models.group_info import GroupInfo
from models.statistics import Statistics
from services.log import logger from services.log import logger
from utils.manager import plugin_data_manager, plugins2settings_manager, plugins_manager from utils.manager import plugin_data_manager, plugins2settings_manager, plugins_manager
from utils.manager.models import PluginData, PluginType
from ....config import QueryDateType
from ....base_model import Result from ....base_model import Result
from ....config import QueryDateType
from ....utils import authentication, get_system_status from ....utils import authentication, get_system_status
from .data_source import bot_live from .data_source import bot_live
from .model import BaseInfo from .model import ActiveGroup, BaseInfo, ChatHistoryCount, HotPlugin
AVA_URL = "http://q1.qlogo.cn/g?b=qq&nk={}&s=160" AVA_URL = "http://q1.qlogo.cn/g?b=qq&nk={}&s=160"
GROUP_AVA_URL = "http://p.qlogo.cn/gh/{}/{}/640/"
run_time = time.time() run_time = time.time()
ws_router = APIRouter() ws_router = APIRouter()
@ -34,7 +39,7 @@ async def _(bot_id: Optional[str] = None) -> Result:
获取Bot基础信息 获取Bot基础信息
Args: Args:
qq (Optional[str], optional): qq号. Defaults to None. bot_id (Optional[str], optional): bot_id. Defaults to None.
Returns: Returns:
Result: 获取指定bot信息与bot列表 Result: 获取指定bot信息与bot列表
@ -43,20 +48,22 @@ async def _(bot_id: Optional[str] = None) -> Result:
if bots := nonebot.get_bots(): if bots := nonebot.get_bots():
select_bot: BaseInfo select_bot: BaseInfo
for key, bot in bots.items(): for key, bot in bots.items():
login_info = await bot.get_login_info()
bot_list.append( bot_list.append(
BaseInfo( BaseInfo(
bot=bot, # type: ignore bot=bot, # type: ignore
self_id=bot.self_id, self_id=bot.self_id,
nickname=NICKNAME, nickname=login_info["nickname"],
ava_url=AVA_URL.format(bot.self_id), ava_url=AVA_URL.format(bot.self_id),
) )
) )
# 获取指定qq号的bot信息若无指定则获取第一个 # 获取指定qq号的bot信息若无指定 则获取第一个
if _bl := [b for b in bot_list if b.self_id == bot_id]: if _bl := [b for b in bot_list if b.self_id == bot_id]:
select_bot = _bl[0] select_bot = _bl[0]
else: else:
select_bot = bot_list[0] select_bot = bot_list[0]
select_bot.is_select = True select_bot.is_select = True
select_bot.config = select_bot.bot.config
now = datetime.now() now = datetime.now()
# 今日累计接收消息 # 今日累计接收消息
select_bot.received_messages = await ChatHistory.filter( select_bot.received_messages = await ChatHistory.filter(
@ -78,11 +85,43 @@ async def _(bot_id: Optional[str] = None) -> Result:
) )
# 连接时间 # 连接时间
select_bot.connect_time = bot_live.get(select_bot.self_id) or 0 select_bot.connect_time = bot_live.get(select_bot.self_id) or 0
if select_bot.connect_time:
connect_date = datetime.fromtimestamp(select_bot.connect_time)
select_bot.connect_date = connect_date.strftime("%Y-%m-%d %H:%M:%S")
return Result.ok(bot_list, "已获取操作列表") return Result.ok(bot_list, "拿到信息啦!")
return Result.warning_("无Bot连接...") return Result.warning_("无Bot连接...")
@router.get(
"/get_all_ch_count", dependencies=[authentication()], description="获取接收消息数量"
)
async def _(bot_id: str) -> Result:
now = datetime.now()
all_count = await ChatHistory.filter(bot_id=bot_id).count()
day_count = await ChatHistory.filter(
bot_id=bot_id, create_time__gte=now - timedelta(hours=now.hour)
).count()
week_count = await ChatHistory.filter(
bot_id=bot_id, create_time__gte=now - timedelta(days=7)
).count()
month_count = await ChatHistory.filter(
bot_id=bot_id, create_time__gte=now - timedelta(days=30)
).count()
year_count = await ChatHistory.filter(
bot_id=bot_id, create_time__gte=now - timedelta(days=365)
).count()
return Result.ok(
ChatHistoryCount(
num=all_count,
day=day_count,
week=week_count,
month=month_count,
year=year_count,
)
)
@router.get("/get_ch_count", dependencies=[authentication()], description="获取接收消息数量") @router.get("/get_ch_count", dependencies=[authentication()], description="获取接收消息数量")
async def _(bot_id: str, query_type: Optional[QueryDateType] = None) -> Result: async def _(bot_id: str, query_type: Optional[QueryDateType] = None) -> Result:
if bots := nonebot.get_bots(): if bots := nonebot.get_bots():
@ -135,6 +174,81 @@ async def _() -> Result:
return Result.ok(int(time.time() - run_time)) return Result.ok(int(time.time() - run_time))
@router.get("/get_active_group", dependencies=[authentication()], description="获取活跃群聊")
async def _(date_type: Optional[QueryDateType] = None) -> Result:
query = ChatHistory
now = datetime.now()
if date_type == QueryDateType.DAY:
query = ChatHistory.filter(create_time__gte=now - timedelta(hours=now.hour))
if date_type == QueryDateType.WEEK:
query = ChatHistory.filter(create_time__gte=now - timedelta(days=7))
if date_type == QueryDateType.MONTH:
query = ChatHistory.filter(create_time__gte=now - timedelta(days=30))
if date_type == QueryDateType.YEAR:
query = ChatHistory.filter(create_time__gte=now - timedelta(days=365))
data_list = (
await query.annotate(count=Count("id"))
.group_by("group_id").order_by("-count").limit(5)
.values_list("group_id", "count")
)
active_group_list = []
id2name = {}
if data_list:
if info_list := await GroupInfo.filter(group_id__in=[x[0] for x in data_list]).all():
for group_info in info_list:
id2name[group_info.group_id] = group_info.group_name
for data in data_list:
active_group_list.append(
ActiveGroup(
group_id=data[0],
name=id2name.get(data[0]) or data[0],
chat_num=data[1],
ava_img=GROUP_AVA_URL.format(data[0], data[0]),
)
)
active_group_list = sorted(
active_group_list, key=lambda x: x.chat_num, reverse=True
)
if len(active_group_list) > 5:
active_group_list = active_group_list[:5]
return Result.ok(active_group_list)
@router.get("/get_hot_plugin", dependencies=[authentication()], description="获取热门插件")
async def _(date_type: Optional[QueryDateType] = None) -> Result:
query = Statistics
now = datetime.now()
if date_type == QueryDateType.DAY:
query = Statistics.filter(create_time__gte=now - timedelta(hours=now.hour))
if date_type == QueryDateType.WEEK:
query = Statistics.filter(create_time__gte=now - timedelta(days=7))
if date_type == QueryDateType.MONTH:
query = Statistics.filter(create_time__gte=now - timedelta(days=30))
if date_type == QueryDateType.YEAR:
query = Statistics.filter(create_time__gte=now - timedelta(days=365))
data_list = (
await query.annotate(count=Count("id"))
.group_by("plugin_name").order_by("-count").limit(5)
.values_list("plugin_name", "count")
)
hot_plugin_list = []
for data in data_list:
name = data[0]
if plugin_data := plugin_data_manager.get(data[0]):
name = plugin_data.name
hot_plugin_list.append(
HotPlugin(
module=data[0],
name=name,
count=data[1],
)
)
hot_plugin_list = sorted(hot_plugin_list, key=lambda x: x.count, reverse=True)
if len(hot_plugin_list) > 5:
hot_plugin_list = hot_plugin_list[:5]
return Result.ok(hot_plugin_list)
@ws_router.websocket("/system_status") @ws_router.websocket("/system_status")
async def system_logs_realtime(websocket: WebSocket): async def system_logs_realtime(websocket: WebSocket):
await websocket.accept() await websocket.accept()
@ -144,6 +258,6 @@ async def system_logs_realtime(websocket: WebSocket):
system_status = await get_system_status() system_status = await get_system_status()
await websocket.send_text(system_status.json()) await websocket.send_text(system_status.json())
await asyncio.sleep(5) await asyncio.sleep(5)
except WebSocketDisconnect: except (WebSocketDisconnect, ConnectionClosedError, ConnectionClosedOK):
pass pass
return return

View File

@ -3,7 +3,7 @@ from typing import Optional
import nonebot import nonebot
from nonebot import Driver from nonebot import Driver
from nonebot.adapters.onebot.v12 import Bot from nonebot.adapters.onebot.v11 import Bot
driver: Driver = nonebot.get_driver() driver: Driver = nonebot.get_driver()

View File

@ -1,4 +1,8 @@
from datetime import datetime
from typing import Optional, Union
from nonebot.adapters.onebot.v11 import Bot from nonebot.adapters.onebot.v11 import Bot
from nonebot.config import Config
from pydantic import BaseModel from pydantic import BaseModel
@ -41,6 +45,8 @@ class BaseInfo(BaseModel):
# """一年内累计接受消息""" # """一年内累计接受消息"""
connect_time: int = 0 connect_time: int = 0
"""连接时间""" """连接时间"""
connect_date: Optional[datetime] = None
"""连接日期"""
plugin_count: int = 0 plugin_count: int = 0
"""加载插件数量""" """加载插件数量"""
@ -52,5 +58,53 @@ class BaseInfo(BaseModel):
is_select: bool = False is_select: bool = False
"""当前选择""" """当前选择"""
config: Optional[Config] = None
"""nb配置"""
class Config: class Config:
arbitrary_types_allowed = True arbitrary_types_allowed = True
class ChatHistoryCount(BaseModel):
"""
聊天记录数量
"""
num: int
"""总数"""
day: int
"""一天内"""
week: int
"""一周内"""
month: int
"""一月内"""
year: int
"""一年内"""
class ActiveGroup(BaseModel):
"""
活跃群聊数据
"""
group_id: Union[str, int]
"""群组id"""
name: str
"""群组名称"""
chat_num: int
"""发言数量"""
ava_img: str
"""群组头像"""
class HotPlugin(BaseModel):
"""
热门插件
"""
module: str
"""模块名"""
name: str
"""插件名称"""
count: int
"""调用次数"""

View File

@ -0,0 +1,149 @@
from typing import List, Optional
import cattrs
from fastapi import APIRouter
from configs.config import Config
from services.log import logger
from utils.manager import plugin_data_manager, plugins2settings_manager, plugins_manager
from utils.manager.models import PluginData, PluginSetting, PluginType
from ....base_model import Result
from ....utils import authentication
from .model import PluginCount, PluginInfo, PluginSwitch, UpdateConfig, UpdatePlugin
router = APIRouter()
@router.get("/get_plugin_list", dependencies=[authentication()], deprecated="获取插件列表")
def _(
plugin_type: PluginType, menu_type: Optional[str] = None
) -> Result:
"""
获取插件列表
:param plugin_type: 类型 normal, superuser, hidden, admin
"""
try:
plugin_list: List[PluginInfo] = []
for module in plugin_data_manager.keys():
plugin_data: Optional[PluginData] = plugin_data_manager[module]
if plugin_data and plugin_data.plugin_type == plugin_type:
setting = plugin_data.plugin_setting or PluginSetting()
plugin = plugin_data.plugin_status
menu_type_ = getattr(setting, "plugin_type", [""])[0]
if menu_type and menu_type != menu_type_:
continue
plugin_info = PluginInfo(
module=module,
plugin_name=plugin_data.name,
default_switch=getattr(setting, "default_status", False),
limit_superuser=getattr(setting, "limit_superuser", False),
cost_gold=getattr(setting, "cost_gold", 0),
menu_type=menu_type_,
version=(plugin.version or 0) if plugin else 0,
level=getattr(setting, "level", 5),
status=plugin.status if plugin else False,
author=plugin.author if plugin else None
)
plugin_info.version = (plugin.version or 0) if plugin else 0
plugin_list.append(plugin_info)
except Exception as e:
logger.error("调用API错误", "/get_plugins", e=e)
return Result.fail(f"{type(e)}: {e}")
return Result.ok(plugin_list, "拿到了新鲜出炉的数据!")
@router.get("/get_plugin_count", dependencies=[authentication()], deprecated="获取插件数量")
def _() -> Result:
plugin_count = PluginCount()
for module in plugin_data_manager.keys():
plugin_data: Optional[PluginData] = plugin_data_manager[module]
if plugin_data and plugin_data.plugin_type == PluginType.NORMAL:
plugin_count.normal += 1
elif plugin_data and plugin_data.plugin_type == PluginType.ADMIN:
plugin_count.admin += 1
elif plugin_data and plugin_data.plugin_type == PluginType.SUPERUSER:
plugin_count.superuser += 1
else:
plugin_count.other += 1
return Result.ok(plugin_count)
@router.post("/update_plugins", dependencies=[authentication()], description="更新插件参数")
def _(plugin: UpdatePlugin) -> Result:
"""
修改插件信息
:param plugin: 插件内容
"""
try:
module = plugin.module
if p2s := plugins2settings_manager.get(module):
p2s.default_status = plugin.default_status
p2s.limit_superuser = plugin.limit_superuser
p2s.cost_gold = plugin.cost_gold
p2s.cmd = plugin.cmd.split(",") if plugin.cmd else []
p2s.level = plugin.level
if pd := plugin_data_manager.get(module):
menu_lin = None
if len(pd.menu_type) > 1:
menu_lin = pd.menu_type[1]
if menu_lin is not None:
pd.menu_type = (plugin.menu_type, menu_lin)
else:
pd.menu_type = (plugin.menu_type,)
if pm := plugins_manager.get(module):
if plugin.block_type:
pm.block_type = plugin.block_type
pm.status = False
else:
pm.block_type = None
pm.status = True
plugins2settings_manager.save()
plugins_manager.save()
except Exception as e:
logger.error("调用API错误", "/update_plugins", e=e)
return Result.fail(f"{type(e)}: {e}")
return Result.ok(info="已经帮你写好啦!")
@router.post("/update_config", dependencies=[authentication()], description="更新配置")
def _(config_list: List[UpdateConfig]) -> Result:
try:
for config in config_list:
if cg := Config.get(config.module):
if c := cg.configs.get(config.key):
if isinstance(c.value, (list, tuple)) or isinstance(
c.default_value, (list, tuple)
):
value = config.value.split(",")
else:
value = config.value
if c.type and value is not None:
value = cattrs.structure(value, c.type)
Config.set_config(config.module, config.key, value)
except Exception as e:
logger.error("调用API错误", "/update_config", e=e)
return Result.fail(f"{type(e)}: {e}")
Config.save(save_simple_data=True)
return Result.ok(info="写入配置项了哦!")
@router.post("/change_switch", dependencies=[authentication()], description="开关插件")
def _(param: PluginSwitch) -> Result:
if pm := plugins_manager.get(param.module):
pm.block_type = None if param.status else 'all'
pm.status = param.status
plugins_manager.save()
return Result.ok(info="成功改变了开关状态!")
return Result.warning_("未获取该插件的配置!")
@router.get("/get_plugin_menu_type", dependencies=[authentication()], description="获取插件类型")
def _() -> Result:
menu_type_list = []
for module in plugin_data_manager.keys():
plugin_data: Optional[PluginData] = plugin_data_manager[module]
if plugin_data:
setting = plugin_data.plugin_setting or PluginSetting()
menu_type = getattr(setting, "plugin_type", [""])[0]
if menu_type not in menu_type_list:
menu_type_list.append(menu_type)
return Result.ok(menu_type_list)

View File

@ -0,0 +1,135 @@
from typing import Any, Dict, List, Optional, Union
from pydantic import BaseModel
from utils.manager.models import Plugin as PluginManager
from utils.manager.models import (
PluginBlock,
PluginCd,
PluginCount,
PluginSetting,
PluginType,
)
from utils.typing import BLOCK_TYPE
class PluginSwitch(BaseModel):
"""
插件开关
"""
module: str
"""模块"""
status: bool
"""开关状态"""
class UpdateConfig(BaseModel):
"""
配置项修改参数
"""
module: str
"""模块"""
key: str
"""配置项key"""
value: Any
"""配置项值"""
class UpdatePlugin(BaseModel):
"""
插件修改参数
"""
module: str
"""模块"""
default_status: bool
"""默认开关"""
limit_superuser: bool
"""限制超级用户"""
cost_gold: int
"""金币花费"""
cmd: str
"""插件别名"""
menu_type: str
"""插件菜单类型"""
level: int
"""插件所需群权限"""
block_type: BLOCK_TYPE
"""禁用类型"""
class PluginInfo(BaseModel):
"""
基本插件信息
"""
module: str
"""插件名称"""
plugin_name: str
"""插件中文名称"""
default_switch: bool
"""默认开关"""
limit_superuser: bool
"""限制超级用户"""
cost_gold: int
"""花费金币"""
menu_type: str
"""插件菜单类型"""
version: Union[int, str, float]
"""插件版本"""
level: int
"""群权限"""
status: bool
"""当前状态"""
author: Optional[str] = None
"""作者"""
class PluginConfig(BaseModel):
"""
插件配置项
"""
module: str
key: str
value: Any
help: Optional[str]
default_value: Any
has_type: bool
class Plugin(BaseModel):
"""
插件
"""
module: str
"""模块名称"""
plugin_settings: Optional[PluginSetting]
"""settings"""
plugin_manager: Optional[PluginManager]
"""manager"""
plugin_config: Optional[Dict[str, PluginConfig]]
"""配置项"""
cd_limit: Optional[PluginCd]
"""cd限制"""
block_limit: Optional[PluginBlock]
"""阻断限制"""
count_limit: Optional[PluginCount]
"""次数限制"""
class PluginCount(BaseModel):
"""
插件数量
"""
normal: int = 0
"""普通插件"""
admin: int = 0
"""管理员插件"""
superuser: int = 0
"""超级用户插件"""
other: int = 0
"""其他插件"""

View File

@ -76,38 +76,38 @@ class QueryModel(BaseModel, Generic[T]):
return size return size
# class PluginConfig(BaseModel): class PluginConfig(BaseModel):
# """ """
# 插件配置项 插件配置项
# """ """
# module: str module: str
# key: str key: str
# value: Optional[Any] value: Optional[Any]
# help: Optional[str] help: Optional[str]
# default_value: Optional[Any] default_value: Optional[Any]
# has_type: bool has_type: bool
# class Plugin(BaseModel): class Plugin(BaseModel):
# """ """
# 插件 插件
# """ """
# model: str model: str
# """模块名称""" """模块名称"""
# plugin_settings: Optional[PluginSetting] plugin_settings: Optional[PluginSetting]
# """settings""" """settings"""
# plugin_manager: Optional[PluginManager] plugin_manager: Optional[PluginManager]
# """manager""" """manager"""
# plugin_config: Optional[Dict[str, PluginConfig]] plugin_config: Optional[Dict[str, PluginConfig]]
# """配置项""" """配置项"""
# cd_limit: Optional[PluginCd] cd_limit: Optional[PluginCd]
# """cd限制""" """cd限制"""
# block_limit: Optional[PluginBlock] block_limit: Optional[PluginBlock]
# """阻断限制""" """阻断限制"""
# count_limit: Optional[PluginCount] count_limit: Optional[PluginCount]
# """次数限制""" """次数限制"""
class SystemStatus(BaseModel): class SystemStatus(BaseModel):