From 081c86aba0df526b7d118ce251e697c928598508 Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Thu, 28 Dec 2023 01:52:06 +0800 Subject: [PATCH 01/20] test --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ad1eaaac..4b74dee2 100644 --- a/README.md +++ b/README.md @@ -338,6 +338,7 @@ PS: **ARM平台** 请使用全量版 同时 **如果你的机器 RAM < 1G 可能 ### 2023/12/28 * 修复B站动态获取失败的时候,会发送空消息 +* test ### 2023/9/6 From 25af1802afb5cc1bc738bb6031b2dac7a00a43d0 Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Sat, 30 Dec 2023 05:27:45 +0800 Subject: [PATCH 02/20] =?UTF-8?q?=E9=87=8D=E6=9E=84webui=E4=B8=BB=E9=A1=B5?= =?UTF-8?q?=E4=B8=8E=E5=A5=BD=E5=8F=8B/=E7=BE=A4=E7=BB=84api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- basic_plugins/chat_history/chat_message.py | 5 +- basic_plugins/invite_manager/__init__.py | 6 +- models/chat_history.py | 8 +- plugins/web_ui/__init__.py | 79 ++-- plugins/web_ui/api/__init__.py | 9 +- plugins/web_ui/api/base_info.py | 130 +++---- plugins/web_ui/api/group.py | 124 +++--- plugins/web_ui/api/logs/log_manager.py | 9 +- plugins/web_ui/api/logs/logs.py | 9 +- plugins/web_ui/api/plugins.py | 210 +++++----- plugins/web_ui/api/request.py | 88 ++--- plugins/web_ui/api/system.py | 410 ++++++++++---------- plugins/web_ui/api/tabs/__init__.py | 2 + plugins/web_ui/api/tabs/main/__init__.py | 149 +++++++ plugins/web_ui/api/tabs/main/data_source.py | 36 ++ plugins/web_ui/api/tabs/main/model.py | 56 +++ plugins/web_ui/api/tabs/manage/__init__.py | 245 ++++++++++++ plugins/web_ui/api/tabs/manage/model.py | 151 +++++++ plugins/web_ui/auth/__init__.py | 2 +- plugins/web_ui/base_model.py | 139 +++++++ plugins/web_ui/config.py | 112 +++--- plugins/web_ui/models/model.py | 223 ----------- plugins/web_ui/models/params.py | 59 --- plugins/web_ui/utils.py | 126 +++++- utils/browser.py | 3 +- utils/manager/data_class.py | 2 +- utils/manager/requests_manager.py | 35 +- 27 files changed, 1530 insertions(+), 897 deletions(-) create mode 100644 plugins/web_ui/api/tabs/__init__.py create mode 100644 plugins/web_ui/api/tabs/main/__init__.py create mode 100644 plugins/web_ui/api/tabs/main/data_source.py create mode 100644 plugins/web_ui/api/tabs/main/model.py create mode 100644 plugins/web_ui/api/tabs/manage/__init__.py create mode 100644 plugins/web_ui/api/tabs/manage/model.py create mode 100644 plugins/web_ui/base_model.py delete mode 100644 plugins/web_ui/models/model.py delete mode 100644 plugins/web_ui/models/params.py diff --git a/basic_plugins/chat_history/chat_message.py b/basic_plugins/chat_history/chat_message.py index bca98e22..2cefb2d7 100644 --- a/basic_plugins/chat_history/chat_message.py +++ b/basic_plugins/chat_history/chat_message.py @@ -1,5 +1,5 @@ from nonebot import on_message -from nonebot.adapters.onebot.v11 import GroupMessageEvent, MessageEvent +from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, MessageEvent from configs.config import Config from models.chat_history import ChatHistory @@ -32,7 +32,7 @@ TEMP_LIST = [] @chat_history.handle() -async def _(event: MessageEvent, msg: str = PlaintText()): +async def _(bot: Bot, event: MessageEvent, msg: str = PlaintText()): group_id = None if isinstance(event, GroupMessageEvent): group_id = str(event.group_id) @@ -42,6 +42,7 @@ async def _(event: MessageEvent, msg: str = PlaintText()): group_id=group_id, text=str(event.get_message()), plain_text=msg, + bot_id=str(bot.self_id), ) ) diff --git a/basic_plugins/invite_manager/__init__.py b/basic_plugins/invite_manager/__init__.py index ba289d00..0317713b 100755 --- a/basic_plugins/invite_manager/__init__.py +++ b/basic_plugins/invite_manager/__init__.py @@ -58,9 +58,12 @@ async def _(bot: Bot, event: FriendRequestEvent): if Config.get_config("invite_manager", "AUTO_ADD_FRIEND"): logger.debug(f"已开启好友请求自动同意,成功通过该请求", "好友请求", target=event.user_id) await bot.set_friend_add_request(flag=event.flag, approve=True) - await FriendUser.create(user_id=str(user["user_id"]), user_name=user["nickname"]) + await FriendUser.create( + user_id=str(user["user_id"]), user_name=user["nickname"] + ) else: requests_manager.add_request( + str(bot.self_id), event.user_id, "private", event.flag, @@ -126,6 +129,7 @@ async def _(bot: Bot, event: GroupRequestEvent): "等待管理员处理吧!", ) requests_manager.add_request( + str(bot.self_id), event.user_id, "group", event.flag, diff --git a/models/chat_history.py b/models/chat_history.py index dcdaf570..60afd130 100644 --- a/models/chat_history.py +++ b/models/chat_history.py @@ -21,6 +21,8 @@ class ChatHistory(Model): """纯文本""" create_time = fields.DatetimeField(auto_now_add=True) """创建时间""" + bot_id = fields.CharField(255, null=True) + """bot记录id""" class Meta: table = "chat_history" @@ -56,7 +58,9 @@ class ChatHistory(Model): ) # type: ignore @classmethod - async def get_group_first_msg_datetime(cls, group_id: Union[int, str]) -> Optional[datetime]: + async def get_group_first_msg_datetime( + cls, group_id: Union[int, str] + ) -> Optional[datetime]: """ 说明: 获取群第一条记录消息时间 @@ -117,4 +121,6 @@ class ChatHistory(Model): "ALTER TABLE chat_history RENAME COLUMN user_qq TO user_id;", # 将user_id改为user_id "ALTER TABLE chat_history ALTER COLUMN user_id TYPE character varying(255);", "ALTER TABLE chat_history ALTER COLUMN group_id TYPE character varying(255);", + "ALTER TABLE chat_history ADD bot_id VARCHAR(255);", # 添加bot_id字段 + "ALTER TABLE chat_history ALTER COLUMN bot_id TYPE character varying(255);", ] diff --git a/plugins/web_ui/__init__.py b/plugins/web_ui/__init__.py index 9b2791cc..c9c8a8ca 100644 --- a/plugins/web_ui/__init__.py +++ b/plugins/web_ui/__init__.py @@ -1,56 +1,67 @@ -# import asyncio +import asyncio -# import nonebot -# from fastapi import APIRouter, FastAPI -# from nonebot.adapters.onebot.v11 import Bot, MessageEvent -# from nonebot.log import default_filter, default_format -# from nonebot.matcher import Matcher -# from nonebot.message import run_preprocessor -# from nonebot.typing import T_State +import nonebot +from fastapi import APIRouter, FastAPI +from nonebot.adapters.onebot.v11 import Bot, MessageEvent +from nonebot.log import default_filter, default_format +from nonebot.matcher import Matcher +from nonebot.message import run_preprocessor +from nonebot.typing import T_State -# from configs.config import Config as gConfig -# from services.log import logger, logger_ -# from utils.manager import plugins2settings_manager +from configs.config import Config as gConfig +from services.log import logger, logger_ +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_routes -# from .api.logs.log_manager import LOG_STORAGE +from .api.logs import router as ws_log_routes +from .api.logs.log_manager import LOG_STORAGE + # from .api.plugins import router as plugin_routes -# from .api.request import router as request_routes +from .api.request import router as request_routes + # from .api.system import router as system_routes +from .api.tabs.main import router as main_router +from .api.tabs.main import ws_router as status_routes +from .api.tabs.manage import router as manage_router -# # from .api.g import * -# from .auth import router as auth_router +# from .api.g import * +from .auth import router as auth_router -# 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_="前端管理用户名") -# gConfig.add_plugin_config("web-ui", "password", None, name="web-ui", help_="前端管理密码") +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(request_routes) # BaseApiRouter.include_router(system_routes) -# BaseApiRouter.include_router(base_info_routes) +BaseApiRouter.include_router(main_router) +BaseApiRouter.include_router(manage_router) -# @driver.on_startup -# def _(): +@driver.on_startup +def _(): + try: + loop = asyncio.get_running_loop() -# loop = asyncio.get_running_loop() + def log_sink(message: str): + loop.create_task(LOG_STORAGE.add(message.rstrip("\n"))) -# def log_sink(message: str): -# loop.create_task(LOG_STORAGE.add(message.rstrip("\n"))) + logger_.add( + log_sink, colorize=True, filter=default_filter, format=default_format + ) -# logger_.add(log_sink, colorize=True, filter=default_filter, format=default_format) - -# app: FastAPI = nonebot.get_app() -# app.include_router(BaseApiRouter) -# app.include_router(ws_routes) -# logger.info("API启动成功", "Web UI") + app: FastAPI = nonebot.get_app() + app.include_router(BaseApiRouter) + app.include_router(ws_log_routes) + app.include_router(status_routes) + logger.info("API启动成功", "Web UI") + except Exception as e: + logger.error("API启动失败", "Web UI", e=e) diff --git a/plugins/web_ui/api/__init__.py b/plugins/web_ui/api/__init__.py index 0b61b3a0..92af2280 100644 --- a/plugins/web_ui/api/__init__.py +++ b/plugins/web_ui/api/__init__.py @@ -1,4 +1,5 @@ -from .group import * -from .plugins import * -from .request import * -from .system import * +# from .group import * +# from .plugins import * +# from .request import * +# from .system import * +from .tabs import * diff --git a/plugins/web_ui/api/base_info.py b/plugins/web_ui/api/base_info.py index 1bfbb2f7..3b9a7db3 100644 --- a/plugins/web_ui/api/base_info.py +++ b/plugins/web_ui/api/base_info.py @@ -1,75 +1,75 @@ -from datetime import datetime, timedelta -from typing import List, Optional +# from datetime import datetime, timedelta +# from typing import List, Optional -import nonebot -from fastapi import APIRouter +# 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 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 ..models.model import BotInfo, Result -from ..models.params import UpdateConfig, UpdatePlugin -from ..utils import authentication +# 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" +# AVA_URL = "http://q1.qlogo.cn/g?b=qq&nk={}&s=160" -router = APIRouter() +# router = APIRouter() -@router.get("/get_bot_info", dependencies=[authentication()]) -async def _(self_id: Optional[str] = None) -> Result: - """ - 获取Bot基础信息 +# @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. +# 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 - ) +# 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连接") +# return Result.ok(bot_list, "已获取操作列表") +# return Result.fail("无Bot连接") diff --git a/plugins/web_ui/api/group.py b/plugins/web_ui/api/group.py index 664d9a0e..76f1a4d0 100644 --- a/plugins/web_ui/api/group.py +++ b/plugins/web_ui/api/group.py @@ -1,69 +1,69 @@ -from fastapi import APIRouter -from pydantic.error_wrappers import ValidationError +# 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 services.log import logger +# from utils.manager import group_manager +# from utils.utils import get_bot -from ..models.model import Group, GroupResult, Result, Task -from ..models.params import UpdateGroup -from ..utils import authentication +# from ..base_model import Group, GroupResult, Result, Task +# from ..models.params import UpdateGroup +# from ..utils import authentication -router = APIRouter() +# 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.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="已完成记录!") +# @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="已完成记录!") diff --git a/plugins/web_ui/api/logs/log_manager.py b/plugins/web_ui/api/logs/log_manager.py index ed773766..0257f9cf 100644 --- a/plugins/web_ui/api/logs/log_manager.py +++ b/plugins/web_ui/api/logs/log_manager.py @@ -1,6 +1,6 @@ import asyncio import re -from typing import Awaitable, Callable, ClassVar, Dict, Generic, List, Set, TypeVar +from typing import Awaitable, Callable, Dict, Generic, List, Set, TypeVar from urllib.parse import urlparse PATTERN = r"\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))" @@ -10,6 +10,11 @@ LogListener = Callable[[_T], Awaitable[None]] class LogStorage(Generic[_T]): + + """ + 日志存储 + """ + def __init__(self, rotation: float = 5 * 60): self.count, self.rotation = 0, rotation self.logs: Dict[int, str] = {} @@ -42,4 +47,4 @@ class LogStorage(Generic[_T]): return -LOG_STORAGE = LogStorage[str]() +LOG_STORAGE: LogStorage[str] = LogStorage[str]() diff --git a/plugins/web_ui/api/logs/logs.py b/plugins/web_ui/api/logs/logs.py index 4b5b1138..e6abebf3 100644 --- a/plugins/web_ui/api/logs/logs.py +++ b/plugins/web_ui/api/logs/logs.py @@ -2,7 +2,7 @@ from typing import List from fastapi import APIRouter, WebSocket from loguru import logger -from nonebot.utils import escape_tag, run_sync +from nonebot.utils import escape_tag from starlette.websockets import WebSocket, WebSocketDisconnect, WebSocketState from .log_manager import LOG_STORAGE @@ -12,7 +12,12 @@ router = APIRouter() @router.get("/logs", response_model=List[str]) async def system_logs_history(reverse: bool = False): - return LOG_STORAGE.list(reverse=reverse) + """历史日志 + + 参数: + reverse: 反转顺序. + """ + return LOG_STORAGE.list(reverse=reverse) # type: ignore @router.websocket("/logs") diff --git a/plugins/web_ui/api/plugins.py b/plugins/web_ui/api/plugins.py index 1140f060..8262a0bd 100644 --- a/plugins/web_ui/api/plugins.py +++ b/plugins/web_ui/api/plugins.py @@ -1,115 +1,115 @@ -from typing import Optional +# from typing import Optional -import cattrs -from fastapi import APIRouter +# 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 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 ..models.model import Plugin, PluginConfig, Result -from ..models.params import UpdateConfig, UpdatePlugin -from ..utils import authentication +# from ..config import * +# from ..base_model import Plugin, PluginConfig, Result +# from ..models.params import UpdateConfig, UpdatePlugin +# from ..utils import authentication -router = APIRouter() +# 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.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_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="写入配置项了哦!") +# @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="写入配置项了哦!") diff --git a/plugins/web_ui/api/request.py b/plugins/web_ui/api/request.py index d68583ed..83a878dd 100644 --- a/plugins/web_ui/api/request.py +++ b/plugins/web_ui/api/request.py @@ -8,7 +8,7 @@ from services.log import logger from utils.manager import requests_manager from utils.utils import get_bot -from ..models.model import RequestResult, Result +from ..base_model import RequestResult, Result from ..models.params import HandleRequest from ..utils import authentication @@ -42,46 +42,46 @@ def _(request_type: Optional[str]) -> Result: 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}") +# @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}") diff --git a/plugins/web_ui/api/system.py b/plugins/web_ui/api/system.py index 6e2d6fb8..dacc1d9f 100644 --- a/plugins/web_ui/api/system.py +++ b/plugins/web_ui/api/system.py @@ -1,228 +1,228 @@ -import asyncio -import os -from datetime import datetime -from pathlib import Path -from typing import Dict, Optional, Union +# import asyncio +# import os +# from datetime import datetime +# from pathlib import Path +# from typing import Dict, Optional, Union -import psutil -import ujson as json -from fastapi import APIRouter +# import psutil +# import ujson as json +# from fastapi import APIRouter -from configs.path_config import ( - DATA_PATH, - FONT_PATH, - IMAGE_PATH, - LOG_PATH, - RECORD_PATH, - TEMP_PATH, - TEXT_PATH, -) -from services.log import logger -from utils.http_utils import AsyncHttpx +# from configs.path_config import ( +# DATA_PATH, +# FONT_PATH, +# IMAGE_PATH, +# LOG_PATH, +# RECORD_PATH, +# TEMP_PATH, +# TEXT_PATH, +# ) +# from services.log import logger +# from utils.http_utils import AsyncHttpx -from ..models.model import ( - Result, - SystemFolderSize, - SystemNetwork, - SystemResult, - SystemStatus, - SystemStatusList, -) -from ..utils import authentication +# from ..base_model import ( +# Result, +# SystemFolderSize, +# SystemNetwork, +# SystemResult, +# SystemStatus, +# SystemStatusList, +# ) +# from ..utils import authentication -CPU_DATA_PATH = DATA_PATH / "system" / "cpu.json" -MEMORY_DATA_PATH = DATA_PATH / "system" / "memory.json" -DISK_DATA_PATH = DATA_PATH / "system" / "disk.json" -CPU_DATA_PATH.parent.mkdir(exist_ok=True, parents=True) -cpu_data = {"data": []} -memory_data = {"data": []} -disk_data = {"data": []} +# CPU_DATA_PATH = DATA_PATH / "system" / "cpu.json" +# MEMORY_DATA_PATH = DATA_PATH / "system" / "memory.json" +# DISK_DATA_PATH = DATA_PATH / "system" / "disk.json" +# CPU_DATA_PATH.parent.mkdir(exist_ok=True, parents=True) +# cpu_data = {"data": []} +# memory_data = {"data": []} +# disk_data = {"data": []} -router = APIRouter() +# router = APIRouter() -@router.get("/system", dependencies=[authentication()]) -async def _() -> Result: - return await get_system_data() +# @router.get("/system", dependencies=[authentication()]) +# async def _() -> Result: +# return await get_system_data() -@router.get("/status", dependencies=[authentication()]) -async def _() -> Result: - return Result.ok( - await asyncio.get_event_loop().run_in_executor(None, _get_system_status), - ) +# @router.get("/status", dependencies=[authentication()]) +# async def _() -> Result: +# return Result.ok( +# await asyncio.get_event_loop().run_in_executor(None, _get_system_status), +# ) -@router.get("/system/disk", dependencies=[authentication()]) -async def _(type_: Optional[str] = None) -> Result: - return Result.ok( - data=await asyncio.get_event_loop().run_in_executor( - None, _get_system_disk, type_ - ), - ) +# @router.get("/system/disk", dependencies=[authentication()]) +# async def _(type_: Optional[str] = None) -> Result: +# return Result.ok( +# data=await asyncio.get_event_loop().run_in_executor( +# None, _get_system_disk, type_ +# ), +# ) -@router.get("/system/statusList", dependencies=[authentication()]) -async def _() -> Result: - global cpu_data, memory_data, disk_data - await asyncio.get_event_loop().run_in_executor(None, _get_system_status) - cpu_rst = cpu_data["data"][-10:] if len(cpu_data["data"]) > 10 else cpu_data["data"] - memory_rst = ( - memory_data["data"][-10:] - if len(memory_data["data"]) > 10 - else memory_data["data"] - ) - disk_rst = ( - disk_data["data"][-10:] if len(disk_data["data"]) > 10 else disk_data["data"] - ) - return Result.ok( - SystemStatusList( - cpu_data=cpu_rst, - memory_data=memory_rst, - disk_data=disk_rst, - ), - ) +# @router.get("/system/statusList", dependencies=[authentication()]) +# async def _() -> Result: +# global cpu_data, memory_data, disk_data +# await asyncio.get_event_loop().run_in_executor(None, _get_system_status) +# cpu_rst = cpu_data["data"][-10:] if len(cpu_data["data"]) > 10 else cpu_data["data"] +# memory_rst = ( +# memory_data["data"][-10:] +# if len(memory_data["data"]) > 10 +# else memory_data["data"] +# ) +# disk_rst = ( +# disk_data["data"][-10:] if len(disk_data["data"]) > 10 else disk_data["data"] +# ) +# return Result.ok( +# SystemStatusList( +# cpu_data=cpu_rst, +# memory_data=memory_rst, +# disk_data=disk_rst, +# ), +# ) -async def get_system_data(): - """ - 说明: - 获取系统信息,资源文件大小,网络状态等 - """ - baidu = 200 - google = 200 - try: - await AsyncHttpx.get("https://www.baidu.com/", timeout=5) - except Exception as e: - logger.warning(f"访问BaiDu失败... {type(e)}: {e}") - baidu = 404 - try: - await AsyncHttpx.get("https://www.google.com/", timeout=5) - except Exception as e: - logger.warning(f"访问Google失败... {type(e)}: {e}") - google = 404 - network = SystemNetwork(baidu=baidu, google=google) - disk = await asyncio.get_event_loop().run_in_executor(None, _get_system_disk, None) - status = await asyncio.get_event_loop().run_in_executor(None, _get_system_status) - return Result.ok( - SystemResult( - status=status, - network=network, - disk=disk, # type: ignore - check_time=datetime.now().replace(microsecond=0), - ), - ) +# async def get_system_data(): +# """ +# 说明: +# 获取系统信息,资源文件大小,网络状态等 +# """ +# baidu = 200 +# google = 200 +# try: +# await AsyncHttpx.get("https://www.baidu.com/", timeout=5) +# except Exception as e: +# logger.warning(f"访问BaiDu失败... {type(e)}: {e}") +# baidu = 404 +# try: +# await AsyncHttpx.get("https://www.google.com/", timeout=5) +# except Exception as e: +# logger.warning(f"访问Google失败... {type(e)}: {e}") +# google = 404 +# network = SystemNetwork(baidu=baidu, google=google) +# disk = await asyncio.get_event_loop().run_in_executor(None, _get_system_disk, None) +# status = await asyncio.get_event_loop().run_in_executor(None, _get_system_status) +# return Result.ok( +# SystemResult( +# status=status, +# network=network, +# disk=disk, # type: ignore +# check_time=datetime.now().replace(microsecond=0), +# ), +# ) -def _get_system_status() -> SystemStatus: - """ - 说明: - 获取系统信息等 - """ - cpu = psutil.cpu_percent() - memory = psutil.virtual_memory().percent - disk = psutil.disk_usage("/").percent - save_system_data(cpu, memory, disk) - return SystemStatus( - cpu=cpu, - memory=memory, - disk=disk, - check_time=datetime.now().replace(microsecond=0), - ) +# def _get_system_status() -> SystemStatus: +# """ +# 说明: +# 获取系统信息等 +# """ +# cpu = psutil.cpu_percent() +# memory = psutil.virtual_memory().percent +# disk = psutil.disk_usage("/").percent +# save_system_data(cpu, memory, disk) +# return SystemStatus( +# cpu=cpu, +# memory=memory, +# disk=disk, +# check_time=datetime.now().replace(microsecond=0), +# ) -def _get_system_disk( - type_: Optional[str], -) -> Union[SystemFolderSize, Dict[str, Union[float, datetime]]]: - """ - 说明: - 获取资源文件大小等 - """ - if not type_: - disk = SystemFolderSize( - font_dir_size=_get_dir_size(FONT_PATH) / 1024 / 1024, - image_dir_size=_get_dir_size(IMAGE_PATH) / 1024 / 1024, - text_dir_size=_get_dir_size(TEXT_PATH) / 1024 / 1024, - record_dir_size=_get_dir_size(RECORD_PATH) / 1024 / 1024, - temp_dir_size=_get_dir_size(TEMP_PATH) / 1024 / 102, - data_dir_size=_get_dir_size(DATA_PATH) / 1024 / 1024, - log_dir_size=_get_dir_size(LOG_PATH) / 1024 / 1024, - check_time=datetime.now().replace(microsecond=0), - ) - return disk - else: - if type_ == "image": - dir_path = IMAGE_PATH - elif type_ == "font": - dir_path = FONT_PATH - elif type_ == "text": - dir_path = TEXT_PATH - elif type_ == "record": - dir_path = RECORD_PATH - elif type_ == "data": - dir_path = DATA_PATH - elif type_ == "temp": - dir_path = TEMP_PATH - else: - dir_path = LOG_PATH - dir_map = {} - other_file_size = 0 - for file in os.listdir(dir_path): - file = Path(dir_path / file) - if file.is_dir(): - dir_map[file.name] = _get_dir_size(file) / 1024 / 1024 - else: - other_file_size += os.path.getsize(file) / 1024 / 1024 - dir_map["其他文件"] = other_file_size - dir_map["check_time"] = datetime.now().replace(microsecond=0) - return dir_map +# def _get_system_disk( +# type_: Optional[str], +# ) -> Union[SystemFolderSize, Dict[str, Union[float, datetime]]]: +# """ +# 说明: +# 获取资源文件大小等 +# """ +# if not type_: +# disk = SystemFolderSize( +# font_dir_size=_get_dir_size(FONT_PATH) / 1024 / 1024, +# image_dir_size=_get_dir_size(IMAGE_PATH) / 1024 / 1024, +# text_dir_size=_get_dir_size(TEXT_PATH) / 1024 / 1024, +# record_dir_size=_get_dir_size(RECORD_PATH) / 1024 / 1024, +# temp_dir_size=_get_dir_size(TEMP_PATH) / 1024 / 102, +# data_dir_size=_get_dir_size(DATA_PATH) / 1024 / 1024, +# log_dir_size=_get_dir_size(LOG_PATH) / 1024 / 1024, +# check_time=datetime.now().replace(microsecond=0), +# ) +# return disk +# else: +# if type_ == "image": +# dir_path = IMAGE_PATH +# elif type_ == "font": +# dir_path = FONT_PATH +# elif type_ == "text": +# dir_path = TEXT_PATH +# elif type_ == "record": +# dir_path = RECORD_PATH +# elif type_ == "data": +# dir_path = DATA_PATH +# elif type_ == "temp": +# dir_path = TEMP_PATH +# else: +# dir_path = LOG_PATH +# dir_map = {} +# other_file_size = 0 +# for file in os.listdir(dir_path): +# file = Path(dir_path / file) +# if file.is_dir(): +# dir_map[file.name] = _get_dir_size(file) / 1024 / 1024 +# else: +# other_file_size += os.path.getsize(file) / 1024 / 1024 +# dir_map["其他文件"] = other_file_size +# dir_map["check_time"] = datetime.now().replace(microsecond=0) +# return dir_map -def _get_dir_size(dir_path: Path) -> float: - """ - 说明: - 获取文件夹大小 - 参数: - :param dir_path: 文件夹路径 - """ - size = 0 - for root, dirs, files in os.walk(dir_path): - size += sum([os.path.getsize(os.path.join(root, name)) for name in files]) - return size +# def _get_dir_size(dir_path: Path) -> float: +# """ +# 说明: +# 获取文件夹大小 +# 参数: +# :param dir_path: 文件夹路径 +# """ +# size = 0 +# for root, dirs, files in os.walk(dir_path): +# size += sum([os.path.getsize(os.path.join(root, name)) for name in files]) +# return size -def save_system_data(cpu: float, memory: float, disk: float): - """ - 说明: - 保存一些系统信息 - 参数: - :param cpu: cpu - :param memory: memory - :param disk: disk - """ - global cpu_data, memory_data, disk_data - if CPU_DATA_PATH.exists() and not cpu_data["data"]: - with open(CPU_DATA_PATH, "r") as f: - cpu_data = json.load(f) - if MEMORY_DATA_PATH.exists() and not memory_data["data"]: - with open(MEMORY_DATA_PATH, "r") as f: - memory_data = json.load(f) - if DISK_DATA_PATH.exists() and not disk_data["data"]: - with open(DISK_DATA_PATH, "r") as f: - disk_data = json.load(f) - now = str(datetime.now().time().replace(microsecond=0)) - cpu_data["data"].append({"time": now, "data": cpu}) - memory_data["data"].append({"time": now, "data": memory}) - disk_data["data"].append({"time": now, "data": disk}) - if len(cpu_data["data"]) > 50: - cpu_data["data"] = cpu_data["data"][-50:] - if len(memory_data["data"]) > 50: - memory_data["data"] = memory_data["data"][-50:] - if len(disk_data["data"]) > 50: - disk_data["data"] = disk_data["data"][-50:] - with open(CPU_DATA_PATH, "w") as f: - json.dump(cpu_data, f, indent=4, ensure_ascii=False) - with open(MEMORY_DATA_PATH, "w") as f: - json.dump(memory_data, f, indent=4, ensure_ascii=False) - with open(DISK_DATA_PATH, "w") as f: - json.dump(disk_data, f, indent=4, ensure_ascii=False) +# def save_system_data(cpu: float, memory: float, disk: float): +# """ +# 说明: +# 保存一些系统信息 +# 参数: +# :param cpu: cpu +# :param memory: memory +# :param disk: disk +# """ +# global cpu_data, memory_data, disk_data +# if CPU_DATA_PATH.exists() and not cpu_data["data"]: +# with open(CPU_DATA_PATH, "r") as f: +# cpu_data = json.load(f) +# if MEMORY_DATA_PATH.exists() and not memory_data["data"]: +# with open(MEMORY_DATA_PATH, "r") as f: +# memory_data = json.load(f) +# if DISK_DATA_PATH.exists() and not disk_data["data"]: +# with open(DISK_DATA_PATH, "r") as f: +# disk_data = json.load(f) +# now = str(datetime.now().time().replace(microsecond=0)) +# cpu_data["data"].append({"time": now, "data": cpu}) +# memory_data["data"].append({"time": now, "data": memory}) +# disk_data["data"].append({"time": now, "data": disk}) +# if len(cpu_data["data"]) > 50: +# cpu_data["data"] = cpu_data["data"][-50:] +# if len(memory_data["data"]) > 50: +# memory_data["data"] = memory_data["data"][-50:] +# if len(disk_data["data"]) > 50: +# disk_data["data"] = disk_data["data"][-50:] +# with open(CPU_DATA_PATH, "w") as f: +# json.dump(cpu_data, f, indent=4, ensure_ascii=False) +# with open(MEMORY_DATA_PATH, "w") as f: +# json.dump(memory_data, f, indent=4, ensure_ascii=False) +# with open(DISK_DATA_PATH, "w") as f: +# json.dump(disk_data, f, indent=4, ensure_ascii=False) diff --git a/plugins/web_ui/api/tabs/__init__.py b/plugins/web_ui/api/tabs/__init__.py new file mode 100644 index 00000000..f750065f --- /dev/null +++ b/plugins/web_ui/api/tabs/__init__.py @@ -0,0 +1,2 @@ +from .main import * +from .manage import * diff --git a/plugins/web_ui/api/tabs/main/__init__.py b/plugins/web_ui/api/tabs/main/__init__.py new file mode 100644 index 00000000..7daa0ddc --- /dev/null +++ b/plugins/web_ui/api/tabs/main/__init__.py @@ -0,0 +1,149 @@ +import asyncio +import time +from datetime import datetime, timedelta +from typing import List, Optional + +import nonebot +from fastapi import APIRouter, WebSocket +from nonebot.utils import escape_tag +from starlette.websockets import WebSocket, WebSocketDisconnect, WebSocketState + +from configs.config import NICKNAME +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 ....config import QueryDateType +from ....base_model import Result +from ....utils import authentication, get_system_status +from .data_source import bot_live +from .model import BaseInfo + +AVA_URL = "http://q1.qlogo.cn/g?b=qq&nk={}&s=160" + +run_time = time.time() + +ws_router = APIRouter() +router = APIRouter() + + +@router.get("/get_base_info", dependencies=[authentication()], description="基础信息") +async def _(bot_id: Optional[str] = None) -> Result: + """ + 获取Bot基础信息 + + Args: + qq (Optional[str], optional): qq号. Defaults to None. + + Returns: + Result: 获取指定bot信息与bot列表 + """ + bot_list: List[BaseInfo] = [] + if bots := nonebot.get_bots(): + select_bot: BaseInfo + for key, bot in bots.items(): + bot_list.append( + BaseInfo( + bot=bot, # type: ignore + self_id=bot.self_id, + nickname=NICKNAME, + ava_url=AVA_URL.format(bot.self_id), + ) + ) + # 获取指定qq号的bot信息,若无指定则获取第一个 + if _bl := [b for b in bot_list if b.self_id == bot_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=select_bot.self_id, + create_time__gte=now - timedelta(hours=now.hour), + ).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 + ) + # 连接时间 + select_bot.connect_time = bot_live.get(select_bot.self_id) or 0 + + return Result.ok(bot_list, "已获取操作列表") + return Result.warning_("无Bot连接...") + + +@router.get("/get_ch_count", dependencies=[authentication()], description="获取接收消息数量") +async def _(bot_id: str, query_type: Optional[QueryDateType] = None) -> Result: + if bots := nonebot.get_bots(): + if not query_type: + return Result.ok(await ChatHistory.filter(bot_id=bot_id).count()) + now = datetime.now() + if query_type == QueryDateType.DAY: + return Result.ok( + await ChatHistory.filter( + bot_id=bot_id, create_time__gte=now - timedelta(hours=now.hour) + ).count() + ) + if query_type == QueryDateType.WEEK: + return Result.ok( + await ChatHistory.filter( + bot_id=bot_id, create_time__gte=now - timedelta(days=7) + ).count() + ) + if query_type == QueryDateType.MONTH: + return Result.ok( + await ChatHistory.filter( + bot_id=bot_id, create_time__gte=now - timedelta(days=30) + ).count() + ) + if query_type == QueryDateType.YEAR: + return Result.ok( + await ChatHistory.filter( + bot_id=bot_id, create_time__gte=now - timedelta(days=365) + ).count() + ) + return Result.warning_("无Bot连接...") + + +@router.get("get_fg_count", dependencies=[authentication()], description="好友/群组数量") +async def _(bot_id: str) -> Result: + if bots := nonebot.get_bots(): + if bot_id not in bots: + return Result.warning_("指定Bot未连接...") + bot = bots[bot_id] + data = { + "friend_count": len(await bot.get_friend_list()), + "group_count": len(await bot.get_group_list()), + } + return Result.ok(data) + return Result.warning_("无Bot连接...") + + +@router.get("/get_run_time", dependencies=[authentication()], description="获取nb运行时间") +async def _() -> Result: + return Result.ok(int(time.time() - run_time)) + + +@ws_router.websocket("/system_status") +async def system_logs_realtime(websocket: WebSocket): + await websocket.accept() + logger.debug("ws system_status is connect") + try: + while websocket.client_state == WebSocketState.CONNECTED: + system_status = await get_system_status() + await websocket.send_text(system_status.json()) + await asyncio.sleep(5) + except WebSocketDisconnect: + pass + return diff --git a/plugins/web_ui/api/tabs/main/data_source.py b/plugins/web_ui/api/tabs/main/data_source.py new file mode 100644 index 00000000..e164383c --- /dev/null +++ b/plugins/web_ui/api/tabs/main/data_source.py @@ -0,0 +1,36 @@ +import time +from typing import Optional + +import nonebot +from nonebot import Driver +from nonebot.adapters.onebot.v12 import Bot + +driver: Driver = nonebot.get_driver() + + +class BotLive: + def __init__(self): + self._data = {} + + def add(self, bot_id: str): + self._data[bot_id] = time.time() + + def get(self, bot_id: str) -> Optional[int]: + return self._data.get(bot_id) + + def remove(self, bot_id: str): + if bot_id in self._data: + del self._data[bot_id] + + +bot_live = BotLive() + + +@driver.on_bot_connect +async def _(bot: Bot): + bot_live.add(bot.self_id) + + +@driver.on_bot_disconnect +async def _(bot: Bot): + bot_live.remove(bot.self_id) diff --git a/plugins/web_ui/api/tabs/main/model.py b/plugins/web_ui/api/tabs/main/model.py new file mode 100644 index 00000000..decb2aea --- /dev/null +++ b/plugins/web_ui/api/tabs/main/model.py @@ -0,0 +1,56 @@ +from nonebot.adapters.onebot.v11 import Bot +from pydantic import BaseModel + + +class SystemStatus(BaseModel): + """ + 系统状态 + """ + + cpu: float + memory: float + disk: float + + +class BaseInfo(BaseModel): + """ + 基础信息 + """ + + bot: Bot + """Bot""" + self_id: str + """SELF ID""" + nickname: str + """昵称""" + ava_url: str + """头像url""" + friend_count: int = 0 + """好友数量""" + group_count: int = 0 + """群聊数量""" + received_messages: int = 0 + """今日 累计接收消息""" + # received_messages_day: int = 0 + # """今日累计接收消息""" + # received_messages_week: int = 0 + # """一周内累计接收消息""" + # received_messages_month: int = 0 + # """一月内累计接收消息""" + # received_messages_year: int = 0 + # """一年内累计接受消息""" + connect_time: int = 0 + """连接时间""" + + plugin_count: int = 0 + """加载插件数量""" + success_plugin_count: int = 0 + """加载成功插件数量""" + fail_plugin_count: int = 0 + """加载失败插件数量""" + + is_select: bool = False + """当前选择""" + + class Config: + arbitrary_types_allowed = True diff --git a/plugins/web_ui/api/tabs/manage/__init__.py b/plugins/web_ui/api/tabs/manage/__init__.py new file mode 100644 index 00000000..a764f1bd --- /dev/null +++ b/plugins/web_ui/api/tabs/manage/__init__.py @@ -0,0 +1,245 @@ +from typing import Literal + +import nonebot +from fastapi import APIRouter +from pydantic.error_wrappers import ValidationError + +from configs.config import NICKNAME +from models.group_info import GroupInfo +from services.log import logger +from utils.manager import group_manager, requests_manager +from utils.utils import get_bot + +from ....base_model import Result +from ....utils import authentication +from .model import ( + DeleteFriend, + Friend, + FriendRequestResult, + Group, + GroupRequestResult, + GroupResult, + HandleRequest, + LeaveGroup, + Task, + UpdateGroup, +) + +router = APIRouter() + + +@router.get("/get_group_list", dependencies=[authentication()], description="获取群组列表") +async def _(bot_id: str) -> Result: + """ + 获取群信息 + """ + if bots := nonebot.get_bots(): + if bot_id not in bots: + return Result.warning_("指定Bot未连接...") + group_list_result = [] + try: + group_info = {} + group_list = await bots[bot_id].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_list", e=e) + return Result.fail(f"{type(e)}: {e}") + return Result.ok(group_list_result, "拿到了新鲜出炉的数据!") + return Result.warning_("无Bot连接...") + + +@router.post("/update_group", dependencies=[authentication()], description="修改群组信息") +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) + if group.task_status: + for task in group.task_status: + if group.task_status[task]: + group_manager.open_group_task(group_id, task) + else: + group_manager.close_group_task(group_id, task) + 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="已完成记录!") + + +@router.get("/get_friend_list", dependencies=[authentication()], description="获取好友列表") +async def _(bot_id: str) -> Result: + """ + 获取群信息 + """ + if bots := nonebot.get_bots(): + if bot_id not in bots: + return Result.warning_("指定Bot未连接...") + try: + friend_list = await bots[bot_id].get_friend_list() + return Result.ok([Friend(**f) for f in friend_list], "拿到了新鲜出炉的数据!") + except Exception as e: + logger.error("调用API错误", "/get_group_list", e=e) + return Result.fail(f"{type(e)}: {e}") + return Result.warning_("无Bot连接...") + + +@router.get("/get_request_count", dependencies=[authentication()], description="获取请求数量") +def _() -> Result: + data = { + "friend_count": len(requests_manager.get_data().get("private") or []), + "group_count": len(requests_manager.get_data().get("group") or []), + } + return Result.ok(data, f"{NICKNAME}带来了最新的数据!") + + +@router.get("/get_request_list", dependencies=[authentication()], description="获取请求列表") +def _(request_type: Literal["private", "group"]) -> Result: + try: + req_data = requests_manager.get_data().get(request_type) or [] + req_list = [] + for x in req_data: + req_data[x]["oid"] = x + if request_type == "private": + req_list.append(FriendRequestResult(**req_data[x])) + else: + req_list.append(GroupRequestResult(**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()], description="清空请求列表") +def _(request_type: Literal["private", "group"]) -> Result: + """ + 清空请求 + :param type_: 类型 + """ + requests_manager.clear(request_type) + return Result.ok(info="成功清除了数据!") + + +@router.post("/refuse_request", dependencies=[authentication()], description="拒绝请求") +async def _(parma: HandleRequest) -> Result: + """ + 操作请求 + :param parma: 参数 + """ + try: + if bots := nonebot.get_bots(): + bot_id = parma.bot_id + if bot_id not in nonebot.get_bots(): + return Result.warning_("指定Bot未连接...") + flag = await requests_manager.refused(bots[bot_id], parma.id, parma.request_type) # type: ignore + if flag == 1: + requests_manager.delete_request(parma.id, parma.request_type) + return Result.warning_("该请求已失效...") + elif flag == 2: + return Result.warning_("未找到此Id请求...") + return Result.ok(info="成功处理了请求!") + return Result.warning_("Bot未连接...") + except Exception as e: + logger.error("调用API错误", "/refuse_request", e=e) + return Result.fail(f"{type(e)}: {e}") + + +@router.post("/delete_request", dependencies=[authentication()], description="忽略请求") +async def _(parma: HandleRequest) -> Result: + """ + 操作请求 + :param parma: 参数 + """ + requests_manager.delete_request(parma.id, parma.request_type) + return Result.ok(info="成功处理了请求!") + + +@router.post("/approve_request", dependencies=[authentication()], description="同意请求") +async def _(parma: HandleRequest) -> Result: + """ + 操作请求 + :param parma: 参数 + """ + try: + if bots := nonebot.get_bots(): + bot_id = parma.bot_id + if bot_id not in nonebot.get_bots(): + return Result.warning_("指定Bot未连接...") + if parma.request_type == "group": + if rid := requests_manager.get_group_id(parma.id): + if group := await GroupInfo.get_or_none(group_id=str(rid)): + await group.update_or_create(group_flag=1) + else: + group_info = await bots[bot_id].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, + }, + ) + await requests_manager.approve(bots[bot_id], parma.id, parma.request_type) # type: ignore + return Result.ok(info="成功处理了请求!") + return Result.warning_("Bot未连接...") + except Exception as e: + logger.error("调用API错误", "/approve_request", e=e) + return Result.fail(f"{type(e)}: {e}") + + +@router.post("/leave_group", dependencies=[authentication()], description="退群") +async def _(param: LeaveGroup) -> Result: + try: + if bots := nonebot.get_bots(): + bot_id = param.bot_id + group_list = await bots[bot_id].get_group_list() + if param.group_id not in [str(g["group_id"]) for g in group_list]: + return Result.warning_("Bot未在该群聊中...") + await bots[bot_id].set_group_leave(group_id=param.group_id) + return Result.ok(info="成功处理了请求!") + return Result.warning_("Bot未连接...") + except Exception as e: + logger.error("调用API错误", "/leave_group", e=e) + return Result.fail(f"{type(e)}: {e}") + + +@router.post("/delete_friend", dependencies=[authentication()], description="删除好友") +async def _(param: DeleteFriend) -> Result: + try: + if bots := nonebot.get_bots(): + bot_id = param.bot_id + friend_list = await bots[bot_id].get_friend_list() + if param.user_id not in [str(g["user_id"]) for g in friend_list]: + return Result.warning_("Bot未有其好友...") + await bots[bot_id].delete_friend(user_id=param.user_id) + return Result.ok(info="成功处理了请求!") + return Result.warning_("Bot未连接...") + except Exception as e: + logger.error("调用API错误", "/delete_friend", e=e) + return Result.fail(f"{type(e)}: {e}") diff --git a/plugins/web_ui/api/tabs/manage/model.py b/plugins/web_ui/api/tabs/manage/model.py new file mode 100644 index 00000000..089da80d --- /dev/null +++ b/plugins/web_ui/api/tabs/manage/model.py @@ -0,0 +1,151 @@ +from typing import Dict, List, Literal, Optional, Union + +from matplotlib.dates import FR +from nonebot.adapters.onebot.v11 import Bot +from pydantic import BaseModel + + +class Group(BaseModel): + """ + 群组信息 + """ + + group_id: Union[str, int] + """群组id""" + group_name: str + """群组名称""" + member_count: int + """成员人数""" + max_member_count: int + """群组最大人数""" + + +class Task(BaseModel): + """ + 被动技能 + """ + + name: str + """被动名称""" + nameZh: str + """被动中文名称""" + status: bool + """状态""" + + +class GroupResult(BaseModel): + """ + 群组返回数据 + """ + + group: Group + """Group""" + level: int + """群等级""" + status: bool + """状态""" + close_plugins: List[str] + """关闭的插件""" + task: List[Task] + """被动列表""" + + +class Friend(BaseModel): + """ + 好友数据 + """ + + user_id: Union[str, int] + """用户id""" + nickname: str = "" + """昵称""" + remark: str = "" + """备注""" + + +class UpdateGroup(BaseModel): + """ + 更新群组信息 + """ + + group_id: str + """群号""" + status: bool + """状态""" + level: int + """群权限""" + task_status: Dict[str, bool] + """被动状态""" + + +class FriendRequestResult(BaseModel): + """ + 好友/群组请求管理 + """ + + bot_id: Union[str, int] + """bot_id""" + oid: str + """排序""" + id: int + """id""" + flag: str + """flag""" + nickname: Optional[str] + """昵称""" + level: Optional[int] + """等级""" + sex: Optional[str] + """性别""" + age: Optional[int] + """年龄""" + from_: Optional[str] + """来自""" + comment: Optional[str] + """备注信息""" + + +class GroupRequestResult(FriendRequestResult): + """ + 群聊邀请请求 + """ + + invite_group: Union[int, str] + """邀请群聊""" + group_name: Optional[str] + """群聊名称""" + + +class HandleRequest(BaseModel): + """ + 操作请求接收数据 + """ + + bot_id: str + """bot_id""" + id: int + """id""" + request_type: Literal["private", "group"] + """类型""" + + +class LeaveGroup(BaseModel): + """ + 退出群聊 + """ + + bot_id: str + """bot_id""" + group_id: str + """群聊id""" + + +class DeleteFriend(BaseModel): + """ + 删除好友 + """ + + bot_id: str + """bot_id""" + user_id: str + """用户id""" diff --git a/plugins/web_ui/auth/__init__.py b/plugins/web_ui/auth/__init__.py index 88a726d3..0040be85 100644 --- a/plugins/web_ui/auth/__init__.py +++ b/plugins/web_ui/auth/__init__.py @@ -5,7 +5,7 @@ import nonebot from fastapi import APIRouter, Depends from fastapi.security import OAuth2PasswordRequestForm -from ..models.model import Result +from ..base_model import Result from ..utils import ( ACCESS_TOKEN_EXPIRE_MINUTES, create_token, diff --git a/plugins/web_ui/base_model.py b/plugins/web_ui/base_model.py new file mode 100644 index 00000000..3f2e562f --- /dev/null +++ b/plugins/web_ui/base_model.py @@ -0,0 +1,139 @@ +from datetime import datetime +from logging import warning +from typing import Any, Dict, List, Optional, TypeVar, Union + +from nonebot.adapters.onebot.v11 import Bot +from pydantic import BaseModel + +from configs.utils import Config +from utils.manager.models import Plugin as PluginManager +from utils.manager.models import PluginBlock, PluginCd, PluginCount, PluginSetting + + +class User(BaseModel): + username: str + password: str + + +class Token(BaseModel): + access_token: str + token_type: str + + +class Result(BaseModel): + """ + 总体返回 + """ + + suc: bool + """调用状态""" + code: int = 200 + """code""" + info: str = "操作成功" + """info""" + warning: Optional[str] = None + """警告信息""" + data: Any = None + """返回数据""" + + @classmethod + def warning_(cls, info: str, code: int = 200) -> "Result": + return cls(suc=True, warning=info, code=code) + + @classmethod + def fail(cls, info: str = "异常错误", code: int = 500) -> "Result": + return cls(suc=False, info=info, code=code) + + @classmethod + def ok(cls, data: Any = None, info: str = "操作成功", code: int = 200) -> "Result": + return cls(suc=True, info=info, code=code, data=data) + + +# class PluginConfig(BaseModel): +# """ +# 插件配置项 +# """ + +# module: str +# key: str +# value: Optional[Any] +# help: Optional[str] +# default_value: Optional[Any] +# has_type: bool + + +# class Plugin(BaseModel): +# """ +# 插件 +# """ + +# model: 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 SystemStatus(BaseModel): + """ + 系统状态 + """ + + cpu: float + memory: float + disk: float + check_time: datetime + + +class SystemNetwork(BaseModel): + """ + 系统网络状态 + """ + + baidu: int + google: int + + +class SystemFolderSize(BaseModel): + """ + 资源文件占比 + """ + + font_dir_size: float + image_dir_size: float + text_dir_size: float + record_dir_size: float + temp_dir_size: float + data_dir_size: float + log_dir_size: float + check_time: datetime + + +class SystemStatusList(BaseModel): + """ + 状态记录 + """ + + cpu_data: List[Dict[str, Union[float, str]]] + memory_data: List[Dict[str, Union[float, str]]] + disk_data: List[Dict[str, Union[float, str]]] + + +class SystemResult(BaseModel): + """ + 系统api返回 + """ + + status: SystemStatus + network: SystemNetwork + disk: SystemFolderSize + check_time: datetime diff --git a/plugins/web_ui/config.py b/plugins/web_ui/config.py index b2cf0145..a85d9126 100644 --- a/plugins/web_ui/config.py +++ b/plugins/web_ui/config.py @@ -5,6 +5,7 @@ import nonebot from fastapi import APIRouter from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel +from strenum import StrEnum app = nonebot.get_app() @@ -19,85 +20,62 @@ app.add_middleware( ) -class RequestResult(BaseModel): +class QueryDateType(StrEnum): + """ - 好友/群组请求管理 + 查询日期类型 """ - oid: str - id: int - flag: str - nickname: Optional[str] - level: Optional[int] - sex: Optional[str] - age: Optional[int] - from_: Optional[str] - comment: Optional[str] - invite_group: Optional[int] - group_name: Optional[str] + DAY = "day" + """日""" + WEEK = "week" + """周""" + MONTH = "month" + """月""" + YEAR = "year" + """年""" -class RequestParma(BaseModel): - """ - 操作请求接收数据 - """ +# class SystemNetwork(BaseModel): +# """ +# 系统网络状态 +# """ - id: int - handle: str - type: str +# baidu: int +# google: int -class SystemStatus(BaseModel): - """ - 系统状态 - """ +# class SystemFolderSize(BaseModel): +# """ +# 资源文件占比 +# """ - cpu: int - memory: int - disk: int - check_time: datetime +# font_dir_size: float +# image_dir_size: float +# text_dir_size: float +# record_dir_size: float +# temp_dir_size: float +# data_dir_size: float +# log_dir_size: float +# check_time: datetime -class SystemNetwork(BaseModel): - """ - 系统网络状态 - """ +# class SystemStatusList(BaseModel): +# """ +# 状态记录 +# """ - baidu: int - google: int +# cpu_data: List[Dict[str, Union[float, str]]] +# memory_data: List[Dict[str, Union[float, str]]] +# disk_data: List[Dict[str, Union[float, str]]] -class SystemFolderSize(BaseModel): - """ - 资源文件占比 - """ +# class SystemResult(BaseModel): +# """ +# 系统api返回 +# """ - font_dir_size: float - image_dir_size: float - text_dir_size: float - record_dir_size: float - temp_dir_size: float - data_dir_size: float - log_dir_size: float - check_time: datetime - - -class SystemStatusList(BaseModel): - """ - 状态记录 - """ - - cpu_data: List[Dict[str, Union[float, str]]] - memory_data: List[Dict[str, Union[float, str]]] - disk_data: List[Dict[str, Union[float, str]]] - - -class SystemResult(BaseModel): - """ - 系统api返回 - """ - - status: SystemStatus - network: SystemNetwork - disk: SystemFolderSize - check_time: datetime +# status: SystemStatus +# network: SystemNetwork +# disk: SystemFolderSize +# check_time: datetime diff --git a/plugins/web_ui/models/model.py b/plugins/web_ui/models/model.py deleted file mode 100644 index 257678b0..00000000 --- a/plugins/web_ui/models/model.py +++ /dev/null @@ -1,223 +0,0 @@ -from datetime import datetime -from typing import Any, Dict, List, Optional, TypeVar, Union - -from nonebot.adapters.onebot.v11 import Bot -from pydantic import BaseModel - -from configs.utils import Config -from utils.manager.models import Plugin as PluginManager -from utils.manager.models import PluginBlock, PluginCd, PluginCount, PluginSetting - - -class User(BaseModel): - username: str - password: str - - -class Token(BaseModel): - access_token: str - token_type: str - - -class Result(BaseModel): - """ - 总体返回 - """ - - suc: bool - """调用状态""" - code: int = 200 - """code""" - info: str = "操作成功" - """info""" - data: Any = None - """返回数据""" - - @classmethod - def fail(cls, info: str = "异常错误", code: int = 500) -> "Result": - return cls(suc=False, info=info, code=code) - - @classmethod - def ok(cls, data: Any = None, info: str = "操作成功", code: int = 200) -> "Result": - return cls(suc=True, info=info, code=code, data=data) - - -class PluginConfig(BaseModel): - """ - 插件配置项 - """ - - module: str - key: str - value: Optional[Any] - help: Optional[str] - default_value: Optional[Any] - has_type: bool - - -class Plugin(BaseModel): - """ - 插件 - """ - - model: 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 Group(BaseModel): - """ - 群组信息 - """ - - group_id: int - group_name: str - member_count: int - max_member_count: int - - -class Task(BaseModel): - """ - 被动技能 - """ - - name: str - nameZh: str - status: bool - - -class GroupResult(BaseModel): - """ - 群组返回数据 - """ - - group: Group - level: int - status: bool - close_plugins: List[str] - task: List[Task] - - -class RequestResult(BaseModel): - """ - 好友/群组请求管理 - """ - - oid: str - id: int - flag: str - nickname: Optional[str] - level: Optional[int] - sex: Optional[str] - age: Optional[int] - from_: Optional[str] - comment: Optional[str] - invite_group: Optional[int] - group_name: Optional[str] - - -class SystemStatus(BaseModel): - """ - 系统状态 - """ - - cpu: float - memory: float - disk: float - check_time: datetime - - -class SystemNetwork(BaseModel): - """ - 系统网络状态 - """ - - baidu: int - google: int - - -class SystemFolderSize(BaseModel): - """ - 资源文件占比 - """ - - font_dir_size: float - image_dir_size: float - text_dir_size: float - record_dir_size: float - temp_dir_size: float - data_dir_size: float - log_dir_size: float - check_time: datetime - - -class SystemStatusList(BaseModel): - """ - 状态记录 - """ - - cpu_data: List[Dict[str, Union[float, str]]] - memory_data: List[Dict[str, Union[float, str]]] - disk_data: List[Dict[str, Union[float, str]]] - - -class SystemResult(BaseModel): - """ - 系统api返回 - """ - - status: SystemStatus - network: SystemNetwork - disk: SystemFolderSize - check_time: datetime - - -class BotInfo(BaseModel): - """ - Bot基础信息 - """ - - bot: Bot - """Bot""" - self_id: str - """SELF ID""" - nickname: str - """昵称""" - ava_url: str - """头像url""" - friend_count: int = 0 - """好友数量""" - group_count: int = 0 - """群聊数量""" - received_messages: int = 0 - """累计接收消息""" - received_messages_day: int = 0 - """今日累计接收消息""" - received_messages_week: int = 0 - """一周内累计接收消息""" - received_messages_month: int = 0 - """一月内累计接收消息""" - - plugin_count: int = 0 - """加载插件数量""" - success_plugin_count: int = 0 - """加载成功插件数量""" - fail_plugin_count: int = 0 - """加载失败插件数量""" - - is_select: bool = False - """当前选择""" - - class Config: - arbitrary_types_allowed = True diff --git a/plugins/web_ui/models/params.py b/plugins/web_ui/models/params.py deleted file mode 100644 index 86a30401..00000000 --- a/plugins/web_ui/models/params.py +++ /dev/null @@ -1,59 +0,0 @@ -from typing import Any, List - -from pydantic import BaseModel - - -class UpdatePlugin(BaseModel): - """ - 插件修改参数 - """ - - module: str - """模块""" - default_status: bool - """默认开关""" - limit_superuser: bool - """限制超级用户""" - cost_gold: int - """金币花费""" - cmd: List[str] - """插件别名""" - menu_type: str - """插件菜单类型""" - group_level: int - """插件所需群权限""" - block_type: str - """禁用类型""" - - -class UpdateConfig(BaseModel): - """ - 配置项修改参数 - """ - - module: str - """模块""" - key: str - """配置项key""" - value: Any - """配置项值""" - - -class UpdateGroup(BaseModel): - - group_id: str - """群号""" - status: bool - """状态""" - level: int - """群权限""" - - -class HandleRequest(BaseModel): - """ - 操作请求接收数据 - """ - - id: int - handle: str - type: str diff --git a/plugins/web_ui/utils.py b/plugins/web_ui/utils.py index 4531f3e7..cce1c692 100644 --- a/plugins/web_ui/utils.py +++ b/plugins/web_ui/utils.py @@ -1,16 +1,35 @@ +import os from datetime import datetime, timedelta -from typing import Any, Optional +from pathlib import Path +from typing import Any, Dict, Optional, Union -import nonebot +import psutil import ujson as json from fastapi import Depends, HTTPException from fastapi.security import OAuth2PasswordBearer from jose import JWTError, jwt +from nonebot.utils import run_sync from configs.config import Config -from configs.path_config import DATA_PATH +from configs.path_config import ( + DATA_PATH, + FONT_PATH, + IMAGE_PATH, + LOG_PATH, + RECORD_PATH, + TEMP_PATH, + TEXT_PATH, +) -from .models.model import Result, User +from .base_model import ( + Result, + SystemFolderSize, + SystemNetwork, + SystemResult, + SystemStatus, + SystemStatusList, + User, +) SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" ALGORITHM = "HS256" @@ -29,6 +48,14 @@ if token_file.exists(): def get_user(uname: str) -> Optional[User]: + """获取账号密码 + + 参数: + uname: uname + + 返回: + Optional[User]: 用户信息 + """ username = Config.get_config("web-ui", "username") password = Config.get_config("web-ui", "password") if username and password and uname == username: @@ -36,6 +63,12 @@ def get_user(uname: str) -> Optional[User]: def create_token(user: User, expires_delta: Optional[timedelta] = None): + """创建token + + 参数: + user: 用户信息 + expires_delta: 过期时间. + """ expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15)) return jwt.encode( claims={"sub": user.username, "exp": expire}, @@ -45,6 +78,13 @@ def create_token(user: User, expires_delta: Optional[timedelta] = None): def authentication(): + """权限验证 + + + 异常: + JWTError: JWTError + HTTPException: HTTPException + """ # if token not in token_data["token"]: def inner(token: str = Depends(oauth2_scheme)): try: @@ -57,3 +97,81 @@ def authentication(): raise HTTPException(status_code=400, detail="登录验证失败或已失效, 踢出房间!") return Depends(inner) + + +def _get_dir_size(dir_path: Path) -> float: + """ + 说明: + 获取文件夹大小 + 参数: + :param dir_path: 文件夹路径 + """ + size = 0 + for root, dirs, files in os.walk(dir_path): + size += sum([os.path.getsize(os.path.join(root, name)) for name in files]) + return size + + +@run_sync +def get_system_status() -> SystemStatus: + """ + 说明: + 获取系统信息等 + """ + cpu = psutil.cpu_percent() + memory = psutil.virtual_memory().percent + disk = psutil.disk_usage("/").percent + return SystemStatus( + cpu=cpu, + memory=memory, + disk=disk, + check_time=datetime.now().replace(microsecond=0), + ) + + +@run_sync +def get_system_disk( + type_: Optional[str], +) -> Union[SystemFolderSize, Dict[str, Union[float, datetime]]]: + """ + 说明: + 获取资源文件大小等 + """ + if not type_: + disk = SystemFolderSize( + font_dir_size=_get_dir_size(FONT_PATH) / 1024 / 1024, + image_dir_size=_get_dir_size(IMAGE_PATH) / 1024 / 1024, + text_dir_size=_get_dir_size(TEXT_PATH) / 1024 / 1024, + record_dir_size=_get_dir_size(RECORD_PATH) / 1024 / 1024, + temp_dir_size=_get_dir_size(TEMP_PATH) / 1024 / 102, + data_dir_size=_get_dir_size(DATA_PATH) / 1024 / 1024, + log_dir_size=_get_dir_size(LOG_PATH) / 1024 / 1024, + check_time=datetime.now().replace(microsecond=0), + ) + return disk + else: + if type_ == "image": + dir_path = IMAGE_PATH + elif type_ == "font": + dir_path = FONT_PATH + elif type_ == "text": + dir_path = TEXT_PATH + elif type_ == "record": + dir_path = RECORD_PATH + elif type_ == "data": + dir_path = DATA_PATH + elif type_ == "temp": + dir_path = TEMP_PATH + else: + dir_path = LOG_PATH + dir_map = {} + other_file_size = 0 + for file in os.listdir(dir_path): + file = Path(dir_path / file) + if file.is_dir(): + dir_map[file.name] = _get_dir_size(file) / 1024 / 1024 + else: + other_file_size += os.path.getsize(file) / 1024 / 1024 + dir_map["其他文件"] = other_file_size + dir_map["check_time"] = datetime.now().replace(microsecond=0) + return dir_map diff --git a/utils/browser.py b/utils/browser.py index c7a15587..66530008 100755 --- a/utils/browser.py +++ b/utils/browser.py @@ -2,7 +2,6 @@ import asyncio from typing import Optional from nonebot import get_driver -from nonebot.log import logger from playwright.async_api import Browser, Playwright, async_playwright from services.log import logger @@ -26,7 +25,7 @@ async def shutdown_browser(): if _browser: await _browser.close() if _playwright: - _playwright.stop() + await _playwright.stop() # type: ignore def get_browser() -> Browser: diff --git a/utils/manager/data_class.py b/utils/manager/data_class.py index cdfcd64d..b93a0ef5 100755 --- a/utils/manager/data_class.py +++ b/utils/manager/data_class.py @@ -30,7 +30,7 @@ class StaticData(Generic[T]): try: self._data: dict = json.load(f) except ValueError: - if f.read().strip(): + if not f.read().strip(): raise ValueError(f"{file} 文件加载错误,请检查文件内容格式.") elif file.name.endswith("yaml"): self._data = _yaml.load(f) diff --git a/utils/manager/requests_manager.py b/utils/manager/requests_manager.py index 41bef067..c97f2000 100644 --- a/utils/manager/requests_manager.py +++ b/utils/manager/requests_manager.py @@ -1,11 +1,13 @@ -from utils.manager.data_class import StaticData -from nonebot.adapters.onebot.v11 import Bot, ActionFailed -from services.log import logger -from typing import Optional -from utils.image_utils import BuildImage -from utils.utils import get_user_avatar -from pathlib import Path from io import BytesIO +from pathlib import Path +from typing import Optional + +from nonebot.adapters.onebot.v11 import ActionFailed, Bot + +from services.log import logger +from utils.image_utils import BuildImage +from utils.manager.data_class import StaticData +from utils.utils import get_user_avatar class RequestManager(StaticData): @@ -21,6 +23,7 @@ class RequestManager(StaticData): def add_request( self, + bot_id: str, id_: int, type_: str, flag: str, @@ -36,6 +39,7 @@ class RequestManager(StaticData): ): """ 添加一个请求 + :param bot_id: bot_id :param id_: id,用户id或群id :param type_: 类型,private 或 group :param flag: event.flag @@ -49,6 +53,7 @@ class RequestManager(StaticData): :param group_name: 群聊名称 """ self._data[type_][str(len(self._data[type_].keys()))] = { + "bot_id": bot_id, "id": id_, "flag": flag, "nickname": nickname, @@ -102,7 +107,9 @@ class RequestManager(StaticData): """ return await self._set_add_request(bot, id_, type_, False) - def clear(self, type_: Optional[str] = None): # type_: Optional[Literal["group", "private"]] = None + def clear( + self, type_: Optional[str] = None + ): # type_: Optional[Literal["group", "private"]] = None """ 清空所有请求信息,无视请求 :param type_: 类型 @@ -113,7 +120,9 @@ class RequestManager(StaticData): self._data = {"private": {}, "group": {}} self.save() - def delete_request(self, id_: int, type_: str): # type_: Literal["group", "private"] + def delete_request( + self, id_: int, type_: str + ): # type_: Literal["group", "private"] """ 删除请求 :param id_: id @@ -230,7 +239,7 @@ class RequestManager(StaticData): return bk.pic2bs4() async def _set_add_request( - self, bot: Bot, id_: int, type_: str, approve: bool + self, bot: Bot, idx: int, type_: str, approve: bool ) -> int: """ 处理请求 @@ -239,7 +248,7 @@ class RequestManager(StaticData): :param type_: 类型,private 或 group :param approve: 是否同意 """ - id_ = str(id_) + id_ = str(idx) if id_ in self._data[type_].keys(): try: if type_ == "private": @@ -259,7 +268,7 @@ class RequestManager(StaticData): f"同意{self._data[type_][id_]['nickname']}({self._data[type_][id_]['id']})" f"的{'好友' if type_ == 'private' else '入群'}请求失败了..." ) - return 1 # flag失效 + return 1 # flag失效 else: logger.info( f"{'同意' if approve else '拒绝'}{self._data[type_][id_]['nickname']}({self._data[type_][id_]['id']})" @@ -268,4 +277,4 @@ class RequestManager(StaticData): del self._data[type_][id_] self.save() return rid - return 2 # 未找到id + return 2 # 未找到id From c146df3d25293d4909aab787d892bcf2cc5f4d5b Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Sun, 31 Dec 2023 01:58:26 +0800 Subject: [PATCH 03/20] =?UTF-8?q?feat=E2=9C=A8:=20webui=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/web_ui/__init__.py | 11 ++- plugins/web_ui/api/tabs/__init__.py | 1 + plugins/web_ui/api/tabs/database/__init__.py | 76 +++++++++++++++++++ .../web_ui/api/tabs/database/models/model.py | 23 ++++++ .../api/tabs/database/models/sql_log.py | 40 ++++++++++ plugins/web_ui/base_model.py | 31 +++++++- 6 files changed, 176 insertions(+), 6 deletions(-) create mode 100644 plugins/web_ui/api/tabs/database/__init__.py create mode 100644 plugins/web_ui/api/tabs/database/models/model.py create mode 100644 plugins/web_ui/api/tabs/database/models/sql_log.py diff --git a/plugins/web_ui/__init__.py b/plugins/web_ui/__init__.py index c9c8a8ca..4e03feda 100644 --- a/plugins/web_ui/__init__.py +++ b/plugins/web_ui/__init__.py @@ -16,9 +16,7 @@ from utils.manager import plugins2settings_manager # from .api.group import router as group_routes from .api.logs import router as ws_log_routes from .api.logs.log_manager import LOG_STORAGE - -# from .api.plugins import router as plugin_routes -from .api.request import router as request_routes +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 @@ -28,6 +26,10 @@ from .api.tabs.manage import router as manage_router # from .api.g import * 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() gConfig.add_plugin_config("web-ui", "username", "admin", name="web-ui", help_="前端管理用户名") @@ -40,10 +42,11 @@ BaseApiRouter = APIRouter(prefix="/zhenxun/api") BaseApiRouter.include_router(auth_router) # BaseApiRouter.include_router(plugin_routes) # BaseApiRouter.include_router(group_routes) -BaseApiRouter.include_router(request_routes) +# BaseApiRouter.include_router(request_routes) # BaseApiRouter.include_router(system_routes) BaseApiRouter.include_router(main_router) BaseApiRouter.include_router(manage_router) +BaseApiRouter.include_router(database_router) @driver.on_startup diff --git a/plugins/web_ui/api/tabs/__init__.py b/plugins/web_ui/api/tabs/__init__.py index f750065f..97abef9c 100644 --- a/plugins/web_ui/api/tabs/__init__.py +++ b/plugins/web_ui/api/tabs/__init__.py @@ -1,2 +1,3 @@ +from .database import * from .main import * from .manage import * diff --git a/plugins/web_ui/api/tabs/database/__init__.py b/plugins/web_ui/api/tabs/database/__init__.py new file mode 100644 index 00000000..3df9d212 --- /dev/null +++ b/plugins/web_ui/api/tabs/database/__init__.py @@ -0,0 +1,76 @@ +from os import name +from typing import Optional + +import nonebot +from fastapi import APIRouter, Request +from nonebot.drivers import Driver +from tortoise import Tortoise +from tortoise.exceptions import OperationalError + +from configs.config import NICKNAME +from services.db_context import TestSQL +from utils.utils import get_matchers + +from ....base_model import QueryModel, Result +from ....config import QueryDateType +from ....utils import authentication +from .models.model import SqlModel, SqlText +from .models.sql_log import SqlLog + +router = APIRouter() + + +driver: Driver = nonebot.get_driver() + + +SQL_DICT = {} + + +@driver.on_startup +async def _(): + for matcher in get_matchers(True): + if _plugin := matcher.plugin: + try: + _module = _plugin.module + except AttributeError: + pass + else: + plugin_name = matcher.plugin_name + if plugin_name in SQL_DICT: + raise ValueError(f"{plugin_name} 常用SQL plugin_name 重复") + SqlModel( + name=getattr(_module, "__plugin_name__", None) or plugin_name or "", + plugin_name=plugin_name or "", + sql_list=getattr(_module, "sql_list", []), + ) + SQL_DICT[plugin_name] = SqlModel + + +@router.post("/exec_sql", dependencies=[authentication()], description="执行sql") +async def _(sql: SqlText, request: Request) -> Result: + ip = request.client.host if request.client else "unknown" + try: + if sql.sql.lower().startswith("select"): + db = Tortoise.get_connection("default") + res = await db.execute_query_dict(sql.sql) + return Result.ok(res, "执行成功啦!") + else: + result = await TestSQL.raw(sql.sql) + await SqlLog.add(ip or "0.0.0.0", sql.sql, str(result)) + return Result.ok(info="执行成功啦!") + except OperationalError as e: + await SqlLog.add(ip or "0.0.0.0", sql.sql, str(e), False) + return Result.warning_(f"sql执行错误: {e}") + + +@router.post("/get_sql_log", dependencies=[authentication()], description="sql日志列表") +async def _(query: QueryModel) -> Result: + data = await SqlLog.all().offset((query.index - 1) * query.size).limit(query.size) + return Result.ok(data) + + +@router.get("/get_sql", dependencies=[authentication()], description="常用sql") +async def _(plugin_name: Optional[str] = None) -> Result: + if plugin_name: + return Result.ok(SQL_DICT.get(plugin_name)) + return Result.ok(SQL_DICT) diff --git a/plugins/web_ui/api/tabs/database/models/model.py b/plugins/web_ui/api/tabs/database/models/model.py new file mode 100644 index 00000000..ed78c405 --- /dev/null +++ b/plugins/web_ui/api/tabs/database/models/model.py @@ -0,0 +1,23 @@ +from typing import List + +from pydantic import BaseModel + +from utils.models import CommonSql + + +class SqlText(BaseModel): + """ + sql语句 + """ + + sql: str + + +class SqlModel(BaseModel): + + name: str + """插件中文名称""" + plugin_name: str + """插件名称""" + sql_list: List[CommonSql] + """插件列表""" diff --git a/plugins/web_ui/api/tabs/database/models/sql_log.py b/plugins/web_ui/api/tabs/database/models/sql_log.py new file mode 100644 index 00000000..d58ddd34 --- /dev/null +++ b/plugins/web_ui/api/tabs/database/models/sql_log.py @@ -0,0 +1,40 @@ +from typing import Optional, Union + +from tortoise import fields + +from services.db_context import Model + + +class SqlLog(Model): + + id = fields.IntField(pk=True, generated=True, auto_increment=True) + """自增id""" + ip = fields.CharField(255) + """ip""" + sql = fields.CharField(255) + """sql""" + result = fields.CharField(255, null=True) + """结果""" + is_suc = fields.BooleanField(default=True) + """是否成功""" + create_time = fields.DatetimeField(auto_now_add=True) + """创建时间""" + + class Meta: + table = "sql_log" + table_description = "sql执行日志" + + @classmethod + async def add( + cls, ip: str, sql: str, result: Optional[str] = None, is_suc: bool = True + ): + """ + 说明: + 获取用户在群内的等级 + 参数: + :param ip: ip + :param sql: sql + :param result: 返回结果 + :param is_suc: 是否成功 + """ + await cls.create(ip=ip, sql=sql, result=result, is_suc=is_suc) diff --git a/plugins/web_ui/base_model.py b/plugins/web_ui/base_model.py index 3f2e562f..d9f76531 100644 --- a/plugins/web_ui/base_model.py +++ b/plugins/web_ui/base_model.py @@ -1,14 +1,16 @@ from datetime import datetime from logging import warning -from typing import Any, Dict, List, Optional, TypeVar, Union +from typing import Any, Dict, Generic, List, Optional, TypeVar, Union from nonebot.adapters.onebot.v11 import Bot -from pydantic import BaseModel +from pydantic import BaseModel, validator from configs.utils import Config from utils.manager.models import Plugin as PluginManager from utils.manager.models import PluginBlock, PluginCd, PluginCount, PluginSetting +T = TypeVar("T") + class User(BaseModel): username: str @@ -49,6 +51,31 @@ class Result(BaseModel): return cls(suc=True, info=info, code=code, data=data) +class QueryModel(BaseModel, Generic[T]): + """ + 基本查询条件 + """ + + index: int + """页数""" + size: int + """每页数量""" + data: T + """携带数据""" + + @validator("index") + def index_validator(cls, index): + if index < 1: + raise ValueError("查询下标小于1...") + return index + + @validator("size") + def size_validator(cls, size): + if size < 1: + raise ValueError("每页数量小于1...") + return size + + # class PluginConfig(BaseModel): # """ # 插件配置项 From a5bb1f768ea4296dfc57a7ce8c0f5c3c5fed99cf Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Sat, 6 Jan 2024 17:36:22 +0800 Subject: [PATCH 04/20] =?UTF-8?q?webui=E4=B8=BB=E9=A1=B5=E5=92=8C=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E7=AE=A1=E7=90=86api=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_model.py => models/statistics.py | 0 plugins/statistics/_config.py | 26 ++ plugins/statistics/statistics_handle.py | 6 +- plugins/statistics/statistics_hook.py | 325 +++++++++--------- plugins/statistics/utils.py | 16 + plugins/web_ui/__init__.py | 16 +- plugins/web_ui/api/__init__.py | 4 - plugins/web_ui/api/base_info.py | 75 ---- plugins/web_ui/api/group.py | 69 ---- plugins/web_ui/api/logs/log_manager.py | 24 +- plugins/web_ui/api/plugins.py | 115 ------- plugins/web_ui/api/request.py | 87 ----- plugins/web_ui/api/tabs/__init__.py | 1 + plugins/web_ui/api/tabs/main/__init__.py | 130 ++++++- plugins/web_ui/api/tabs/main/data_source.py | 2 +- plugins/web_ui/api/tabs/main/model.py | 54 +++ .../web_ui/api/tabs/plugin_manage/__init__.py | 149 ++++++++ .../web_ui/api/tabs/plugin_manage/model.py | 135 ++++++++ plugins/web_ui/base_model.py | 56 +-- 19 files changed, 711 insertions(+), 579 deletions(-) rename plugins/statistics/_model.py => models/statistics.py (100%) create mode 100644 plugins/statistics/_config.py create mode 100644 plugins/statistics/utils.py delete mode 100644 plugins/web_ui/api/base_info.py delete mode 100644 plugins/web_ui/api/group.py delete mode 100644 plugins/web_ui/api/plugins.py delete mode 100644 plugins/web_ui/api/request.py create mode 100644 plugins/web_ui/api/tabs/plugin_manage/__init__.py create mode 100644 plugins/web_ui/api/tabs/plugin_manage/model.py diff --git a/plugins/statistics/_model.py b/models/statistics.py similarity index 100% rename from plugins/statistics/_model.py rename to models/statistics.py diff --git a/plugins/statistics/_config.py b/plugins/statistics/_config.py new file mode 100644 index 00000000..edee1069 --- /dev/null +++ b/plugins/statistics/_config.py @@ -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 + """搜索类型""" diff --git a/plugins/statistics/statistics_handle.py b/plugins/statistics/statistics_handle.py index 706ff10c..f0515a4b 100755 --- a/plugins/statistics/statistics_handle.py +++ b/plugins/statistics/statistics_handle.py @@ -1,13 +1,13 @@ import asyncio import os -from typing import Tuple from nonebot import on_command 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 models.group_info import GroupInfo +from utils.depends import OneCommand from utils.image_utils import BuildMat from utils.manager import plugins2settings_manager from utils.message_builder import image @@ -91,7 +91,7 @@ statistics_user_file = DATA_PATH / "statistics" / "_prefix_user_count.json" @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() if cmd[0][:2] == "全局": if str(event.user_id) in bot.config.superusers: diff --git a/plugins/statistics/statistics_hook.py b/plugins/statistics/statistics_hook.py index 2de3a1f1..9fea8b2c 100755 --- a/plugins/statistics/statistics_hook.py +++ b/plugins/statistics/statistics_hook.py @@ -6,11 +6,10 @@ from nonebot.message import run_postprocessor from nonebot.typing import Optional, T_State from configs.path_config import DATA_PATH +from models.statistics import Statistics from utils.manager import plugins2settings_manager from utils.utils import scheduler -from ._model import Statistics - try: import ujson as json except ModuleNotFoundError: @@ -21,69 +20,69 @@ __zx_plugin_name__ = "功能调用统计 [Hidden]" __plugin_version__ = 0.1 __plugin_author__ = "HibiKier" -statistics_group_file = DATA_PATH / "statistics" / "_prefix_count.json" -statistics_user_file = DATA_PATH / "statistics" / "_prefix_user_count.json" +# statistics_group_file = DATA_PATH / "statistics" / "_prefix_count.json" +# statistics_user_file = DATA_PATH / "statistics" / "_prefix_user_count.json" -try: - with open(statistics_group_file, "r", encoding="utf8") as f: - _prefix_count_dict = json.load(f) -except (FileNotFoundError, ValueError): - _prefix_count_dict = { - "total_statistics": { - "total": {}, - }, - "day_statistics": { - "total": {}, - }, - "week_statistics": { - "total": {}, - }, - "month_statistics": { - "total": {}, - }, - "start_time": str(datetime.now().date()), - "day_index": 0, - } +# try: +# with open(statistics_group_file, "r", encoding="utf8") as f: +# _prefix_count_dict = json.load(f) +# except (FileNotFoundError, ValueError): +# _prefix_count_dict = { +# "total_statistics": { +# "total": {}, +# }, +# "day_statistics": { +# "total": {}, +# }, +# "week_statistics": { +# "total": {}, +# }, +# "month_statistics": { +# "total": {}, +# }, +# "start_time": str(datetime.now().date()), +# "day_index": 0, +# } -try: - with open(statistics_user_file, "r", encoding="utf8") as f: - _prefix_user_count_dict = json.load(f) -except (FileNotFoundError, ValueError): - _prefix_user_count_dict = { - "total_statistics": { - "total": {}, - }, - "day_statistics": { - "total": {}, - }, - "week_statistics": { - "total": {}, - }, - "month_statistics": { - "total": {}, - }, - "start_time": str(datetime.now().date()), - "day_index": 0, - } +# try: +# with open(statistics_user_file, "r", encoding="utf8") as f: +# _prefix_user_count_dict = json.load(f) +# except (FileNotFoundError, ValueError): +# _prefix_user_count_dict = { +# "total_statistics": { +# "total": {}, +# }, +# "day_statistics": { +# "total": {}, +# }, +# "week_statistics": { +# "total": {}, +# }, +# "month_statistics": { +# "total": {}, +# }, +# "start_time": str(datetime.now().date()), +# "day_index": 0, +# } -# 以前版本转换 -if _prefix_count_dict.get("day_index") is None: - tmp = _prefix_count_dict.copy() - _prefix_count_dict = { - "total_statistics": tmp["total_statistics"], - "day_statistics": { - "total": {}, - }, - "week_statistics": { - "total": {}, - }, - "month_statistics": { - "total": {}, - }, - "start_time": tmp["start_time"], - "day_index": 0, - } +# # 以前版本转换 +# if _prefix_count_dict.get("day_index") is None: +# tmp = _prefix_count_dict.copy() +# _prefix_count_dict = { +# "total_statistics": tmp["total_statistics"], +# "day_statistics": { +# "total": {}, +# }, +# "week_statistics": { +# "total": {}, +# }, +# "month_statistics": { +# "total": {}, +# }, +# "start_time": tmp["start_time"], +# "day_index": 0, +# } # 添加命令次数 @@ -95,7 +94,7 @@ async def _( event: MessageEvent, state: T_State, ): - global _prefix_count_dict + # global _prefix_count_dict if ( matcher.type == "message" and matcher.priority not in [1, 999] @@ -107,112 +106,112 @@ async def _( plugin_name=matcher.plugin_name, create_time=datetime.now(), ) - module = matcher.plugin_name - day_index = _prefix_count_dict["day_index"] - try: - group_id = str(event.group_id) - except AttributeError: - group_id = "total" - user_id = str(event.user_id) - plugin_name = plugins2settings_manager.get_plugin_data(module) - if plugin_name and plugin_name.cmd: - plugin_name = plugin_name.cmd[0] - check_exists_key(group_id, user_id, plugin_name) - for data in [_prefix_count_dict, _prefix_user_count_dict]: - data["total_statistics"]["total"][plugin_name] += 1 - data["day_statistics"]["total"][plugin_name] += 1 - data["week_statistics"]["total"][plugin_name] += 1 - data["month_statistics"]["total"][plugin_name] += 1 - # print(_prefix_count_dict) - if group_id != "total": - for data in [_prefix_count_dict, _prefix_user_count_dict]: - if data == _prefix_count_dict: - key = group_id - else: - key = user_id - data["total_statistics"][key][plugin_name] += 1 - data["day_statistics"][key][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 - with open(statistics_group_file, "w", encoding="utf8") as f: - json.dump(_prefix_count_dict, f, indent=4, ensure_ascii=False) - with open(statistics_user_file, "w", encoding="utf8") as f: - json.dump(_prefix_user_count_dict, f, ensure_ascii=False, indent=4) + # module = matcher.plugin_name + # day_index = _prefix_count_dict["day_index"] + # try: + # group_id = str(event.group_id) + # except AttributeError: + # group_id = "total" + # user_id = str(event.user_id) + # plugin_name = plugins2settings_manager.get_plugin_data(module) + # if plugin_name and plugin_name.cmd: + # plugin_name = plugin_name.cmd[0] + # check_exists_key(group_id, user_id, plugin_name) + # for data in [_prefix_count_dict, _prefix_user_count_dict]: + # data["total_statistics"]["total"][plugin_name] += 1 + # data["day_statistics"]["total"][plugin_name] += 1 + # data["week_statistics"]["total"][plugin_name] += 1 + # data["month_statistics"]["total"][plugin_name] += 1 + # # print(_prefix_count_dict) + # if group_id != "total": + # for data in [_prefix_count_dict, _prefix_user_count_dict]: + # if data == _prefix_count_dict: + # key = group_id + # else: + # key = user_id + # data["total_statistics"][key][plugin_name] += 1 + # data["day_statistics"][key][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 + # with open(statistics_group_file, "w", encoding="utf8") as f: + # json.dump(_prefix_count_dict, f, indent=4, ensure_ascii=False) + # with open(statistics_user_file, "w", encoding="utf8") as f: + # 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): - global _prefix_count_dict, _prefix_user_count_dict - for data in [_prefix_count_dict, _prefix_user_count_dict]: - if data == _prefix_count_dict: - key = group_id - else: - key = user_id - if not data["total_statistics"]["total"].get(plugin_name): - data["total_statistics"]["total"][plugin_name] = 0 - if not data["day_statistics"]["total"].get(plugin_name): - data["day_statistics"]["total"][plugin_name] = 0 - if not data["week_statistics"]["total"].get(plugin_name): - data["week_statistics"]["total"][plugin_name] = 0 - if not data["month_statistics"]["total"].get(plugin_name): - data["month_statistics"]["total"][plugin_name] = 0 +# def check_exists_key(group_id: str, user_id: str, plugin_name: str): +# global _prefix_count_dict, _prefix_user_count_dict +# for data in [_prefix_count_dict, _prefix_user_count_dict]: +# if data == _prefix_count_dict: +# key = group_id +# else: +# key = user_id +# if not data["total_statistics"]["total"].get(plugin_name): +# data["total_statistics"]["total"][plugin_name] = 0 +# if not data["day_statistics"]["total"].get(plugin_name): +# data["day_statistics"]["total"][plugin_name] = 0 +# if not data["week_statistics"]["total"].get(plugin_name): +# data["week_statistics"]["total"][plugin_name] = 0 +# if not data["month_statistics"]["total"].get(plugin_name): +# data["month_statistics"]["total"][plugin_name] = 0 - if not data["total_statistics"].get(key): - data["total_statistics"][key] = {} - if not data["total_statistics"][key].get(plugin_name): - data["total_statistics"][key][plugin_name] = 0 - if not data["day_statistics"].get(key): - data["day_statistics"][key] = {} - if not data["day_statistics"][key].get(plugin_name): - data["day_statistics"][key][plugin_name] = 0 +# if not data["total_statistics"].get(key): +# data["total_statistics"][key] = {} +# if not data["total_statistics"][key].get(plugin_name): +# data["total_statistics"][key][plugin_name] = 0 +# if not data["day_statistics"].get(key): +# data["day_statistics"][key] = {} +# if not data["day_statistics"][key].get(plugin_name): +# data["day_statistics"][key][plugin_name] = 0 - if key != "total": - if not data["week_statistics"].get(key): - data["week_statistics"][key] = {} - if data["week_statistics"][key].get("0") is None: - for i in range(7): - data["week_statistics"][key][str(i)] = {} - if data["week_statistics"][key]["0"].get(plugin_name) is None: - for i in range(7): - data["week_statistics"][key][str(i)][plugin_name] = 0 +# if key != "total": +# if not data["week_statistics"].get(key): +# data["week_statistics"][key] = {} +# if data["week_statistics"][key].get("0") is None: +# for i in range(7): +# data["week_statistics"][key][str(i)] = {} +# if data["week_statistics"][key]["0"].get(plugin_name) is None: +# for i in range(7): +# data["week_statistics"][key][str(i)][plugin_name] = 0 - if not data["month_statistics"].get(key): - data["month_statistics"][key] = {} - if data["month_statistics"][key].get("0") is None: - for i in range(30): - data["month_statistics"][key][str(i)] = {} - if data["month_statistics"][key]["0"].get(plugin_name) is None: - for i in range(30): - data["month_statistics"][key][str(i)][plugin_name] = 0 +# if not data["month_statistics"].get(key): +# data["month_statistics"][key] = {} +# if data["month_statistics"][key].get("0") is None: +# for i in range(30): +# data["month_statistics"][key][str(i)] = {} +# if data["month_statistics"][key]["0"].get(plugin_name) is None: +# for i in range(30): +# data["month_statistics"][key][str(i)][plugin_name] = 0 # 天 -@scheduler.scheduled_job( - "cron", - hour=0, - minute=1, -) -async def _(): - for data in [_prefix_count_dict, _prefix_user_count_dict]: - data["day_index"] += 1 - for x in data["day_statistics"].keys(): - for key in data["day_statistics"][x].keys(): - try: - data["day_statistics"][x][key] = 0 - except KeyError: - pass - for type_ in ["week_statistics", "month_statistics"]: - index = str( - data["day_index"] % 7 - if type_ == "week_statistics" - else data["day_index"] % 30 - ) - for x in data[type_].keys(): - try: - for key in data[type_][x][index].keys(): - data[type_][x][index][key] = 0 - except KeyError: - pass - with open(statistics_group_file, "w", encoding="utf8") as f: - json.dump(_prefix_count_dict, f, indent=4, ensure_ascii=False) - with open(statistics_user_file, "w", encoding="utf8") as f: - json.dump(_prefix_user_count_dict, f, indent=4, ensure_ascii=False) +# @scheduler.scheduled_job( +# "cron", +# hour=0, +# minute=1, +# ) +# async def _(): +# for data in [_prefix_count_dict, _prefix_user_count_dict]: +# data["day_index"] += 1 +# for x in data["day_statistics"].keys(): +# for key in data["day_statistics"][x].keys(): +# try: +# data["day_statistics"][x][key] = 0 +# except KeyError: +# pass +# for type_ in ["week_statistics", "month_statistics"]: +# index = str( +# data["day_index"] % 7 +# if type_ == "week_statistics" +# else data["day_index"] % 30 +# ) +# for x in data[type_].keys(): +# try: +# for key in data[type_][x][index].keys(): +# data[type_][x][index][key] = 0 +# except KeyError: +# pass +# with open(statistics_group_file, "w", encoding="utf8") as f: +# json.dump(_prefix_count_dict, f, indent=4, ensure_ascii=False) +# with open(statistics_user_file, "w", encoding="utf8") as f: +# json.dump(_prefix_user_count_dict, f, indent=4, ensure_ascii=False) diff --git a/plugins/statistics/utils.py b/plugins/statistics/utils.py new file mode 100644 index 00000000..c35b5672 --- /dev/null +++ b/plugins/statistics/utils.py @@ -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 + diff --git a/plugins/web_ui/__init__.py b/plugins/web_ui/__init__.py index 4e03feda..fddfe793 100644 --- a/plugins/web_ui/__init__.py +++ b/plugins/web_ui/__init__.py @@ -12,24 +12,15 @@ from configs.config import Config as gConfig from services.log import logger, logger_ 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.log_manager import LOG_STORAGE 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 ws_router as status_routes from .api.tabs.manage import router as manage_router - -# from .api.g import * +from .api.tabs.plugin_manage import router as plugin_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() 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.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(manage_router) BaseApiRouter.include_router(database_router) +BaseApiRouter.include_router(plugin_router) @driver.on_startup diff --git a/plugins/web_ui/api/__init__.py b/plugins/web_ui/api/__init__.py index 92af2280..32d31b27 100644 --- a/plugins/web_ui/api/__init__.py +++ b/plugins/web_ui/api/__init__.py @@ -1,5 +1 @@ -# from .group import * -# from .plugins import * -# from .request import * -# from .system import * from .tabs import * diff --git a/plugins/web_ui/api/base_info.py b/plugins/web_ui/api/base_info.py deleted file mode 100644 index 3b9a7db3..00000000 --- a/plugins/web_ui/api/base_info.py +++ /dev/null @@ -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连接") diff --git a/plugins/web_ui/api/group.py b/plugins/web_ui/api/group.py deleted file mode 100644 index 76f1a4d0..00000000 --- a/plugins/web_ui/api/group.py +++ /dev/null @@ -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="已完成记录!") diff --git a/plugins/web_ui/api/logs/log_manager.py b/plugins/web_ui/api/logs/log_manager.py index 0257f9cf..c7c8140e 100644 --- a/plugins/web_ui/api/logs/log_manager.py +++ b/plugins/web_ui/api/logs/log_manager.py @@ -21,18 +21,18 @@ class LogStorage(Generic[_T]): self.listeners: Set[LogListener[str]] = set() async def add(self, log: str): - log = re.sub(PATTERN, "", log) - log_split = log.split() - time = log_split[0] + " " + log_split[1] - level = log_split[2] - main = log_split[3] - type_ = None - log_ = " ".join(log_split[3:]) - if "Calling API" in log_: - sp = log_.split("|") - type_ = sp[1] - log_ = "|".join(log_[1:]) - data = {"time": time, "level": level, "main": main, "type": type_, "log": log_} + # log = re.sub(PATTERN, "", log) + # log_split = log.split() + # time = log_split[0] + " " + log_split[1] + # level = log_split[2] + # main = log_split[3] + # type_ = None + # log_ = " ".join(log_split[3:]) + # if "Calling API" in log_: + # sp = log_.split("|") + # type_ = sp[1] + # log_ = "|".join(log_[1:]) + # data = {"time": time, "level": level, "main": main, "type": type_, "log": log_} seq = self.count = self.count + 1 self.logs[seq] = log asyncio.get_running_loop().call_later(self.rotation, self.remove, seq) diff --git a/plugins/web_ui/api/plugins.py b/plugins/web_ui/api/plugins.py deleted file mode 100644 index 8262a0bd..00000000 --- a/plugins/web_ui/api/plugins.py +++ /dev/null @@ -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="写入配置项了哦!") diff --git a/plugins/web_ui/api/request.py b/plugins/web_ui/api/request.py deleted file mode 100644 index 83a878dd..00000000 --- a/plugins/web_ui/api/request.py +++ /dev/null @@ -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}") diff --git a/plugins/web_ui/api/tabs/__init__.py b/plugins/web_ui/api/tabs/__init__.py index 97abef9c..c2758a6a 100644 --- a/plugins/web_ui/api/tabs/__init__.py +++ b/plugins/web_ui/api/tabs/__init__.py @@ -1,3 +1,4 @@ from .database import * from .main import * from .manage import * +from .plugin_manage import * diff --git a/plugins/web_ui/api/tabs/main/__init__.py b/plugins/web_ui/api/tabs/main/__init__.py index 7daa0ddc..125cb587 100644 --- a/plugins/web_ui/api/tabs/main/__init__.py +++ b/plugins/web_ui/api/tabs/main/__init__.py @@ -7,21 +7,26 @@ import nonebot from fastapi import APIRouter, WebSocket from nonebot.utils import escape_tag from starlette.websockets import WebSocket, WebSocketDisconnect, WebSocketState +from tortoise.functions import Count +from websockets.exceptions import ConnectionClosedError, ConnectionClosedOK from configs.config import NICKNAME from models.chat_history import ChatHistory +from models.group_info import GroupInfo +from models.statistics import Statistics 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 QueryDateType from ....base_model import Result +from ....config import QueryDateType from ....utils import authentication, get_system_status 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" +GROUP_AVA_URL = "http://p.qlogo.cn/gh/{}/{}/640/" + run_time = time.time() ws_router = APIRouter() @@ -34,7 +39,7 @@ async def _(bot_id: Optional[str] = None) -> Result: 获取Bot基础信息 Args: - qq (Optional[str], optional): qq号. Defaults to None. + bot_id (Optional[str], optional): bot_id. Defaults to None. Returns: Result: 获取指定bot信息与bot列表 @@ -43,20 +48,22 @@ async def _(bot_id: Optional[str] = None) -> Result: if bots := nonebot.get_bots(): select_bot: BaseInfo for key, bot in bots.items(): + login_info = await bot.get_login_info() bot_list.append( BaseInfo( bot=bot, # type: ignore self_id=bot.self_id, - nickname=NICKNAME, + nickname=login_info["nickname"], 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]: select_bot = _bl[0] else: select_bot = bot_list[0] select_bot.is_select = True + select_bot.config = select_bot.bot.config now = datetime.now() # 今日累计接收消息 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 + 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连接...") +@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="获取接收消息数量") async def _(bot_id: str, query_type: Optional[QueryDateType] = None) -> Result: if bots := nonebot.get_bots(): @@ -135,6 +174,81 @@ async def _() -> Result: 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") async def system_logs_realtime(websocket: WebSocket): await websocket.accept() @@ -144,6 +258,6 @@ async def system_logs_realtime(websocket: WebSocket): system_status = await get_system_status() await websocket.send_text(system_status.json()) await asyncio.sleep(5) - except WebSocketDisconnect: + except (WebSocketDisconnect, ConnectionClosedError, ConnectionClosedOK): pass return diff --git a/plugins/web_ui/api/tabs/main/data_source.py b/plugins/web_ui/api/tabs/main/data_source.py index e164383c..42a3df42 100644 --- a/plugins/web_ui/api/tabs/main/data_source.py +++ b/plugins/web_ui/api/tabs/main/data_source.py @@ -3,7 +3,7 @@ from typing import Optional import nonebot from nonebot import Driver -from nonebot.adapters.onebot.v12 import Bot +from nonebot.adapters.onebot.v11 import Bot driver: Driver = nonebot.get_driver() diff --git a/plugins/web_ui/api/tabs/main/model.py b/plugins/web_ui/api/tabs/main/model.py index decb2aea..bced1817 100644 --- a/plugins/web_ui/api/tabs/main/model.py +++ b/plugins/web_ui/api/tabs/main/model.py @@ -1,4 +1,8 @@ +from datetime import datetime +from typing import Optional, Union + from nonebot.adapters.onebot.v11 import Bot +from nonebot.config import Config from pydantic import BaseModel @@ -41,6 +45,8 @@ class BaseInfo(BaseModel): # """一年内累计接受消息""" connect_time: int = 0 """连接时间""" + connect_date: Optional[datetime] = None + """连接日期""" plugin_count: int = 0 """加载插件数量""" @@ -52,5 +58,53 @@ class BaseInfo(BaseModel): is_select: bool = False """当前选择""" + config: Optional[Config] = None + """nb配置""" + class Config: 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 + """调用次数""" diff --git a/plugins/web_ui/api/tabs/plugin_manage/__init__.py b/plugins/web_ui/api/tabs/plugin_manage/__init__.py new file mode 100644 index 00000000..4201a528 --- /dev/null +++ b/plugins/web_ui/api/tabs/plugin_manage/__init__.py @@ -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) \ No newline at end of file diff --git a/plugins/web_ui/api/tabs/plugin_manage/model.py b/plugins/web_ui/api/tabs/plugin_manage/model.py new file mode 100644 index 00000000..869bd594 --- /dev/null +++ b/plugins/web_ui/api/tabs/plugin_manage/model.py @@ -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 + """其他插件""" \ No newline at end of file diff --git a/plugins/web_ui/base_model.py b/plugins/web_ui/base_model.py index d9f76531..2ce1a82f 100644 --- a/plugins/web_ui/base_model.py +++ b/plugins/web_ui/base_model.py @@ -76,38 +76,38 @@ class QueryModel(BaseModel, Generic[T]): return size -# class PluginConfig(BaseModel): -# """ -# 插件配置项 -# """ +class PluginConfig(BaseModel): + """ + 插件配置项 + """ -# module: str -# key: str -# value: Optional[Any] -# help: Optional[str] -# default_value: Optional[Any] -# has_type: bool + module: str + key: str + value: Optional[Any] + help: Optional[str] + default_value: Optional[Any] + has_type: bool -# class Plugin(BaseModel): -# """ -# 插件 -# """ +class Plugin(BaseModel): + """ + 插件 + """ -# model: 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] -# """次数限制""" + model: 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 SystemStatus(BaseModel): From 87a39bf033981378c4f9be723781a66040b9d417 Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Sat, 6 Jan 2024 22:09:44 +0800 Subject: [PATCH 05/20] =?UTF-8?q?refactor=F0=9F=8E=A8:=20=E6=9B=B4?= =?UTF-8?q?=E6=96=B0webui=20model?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/models/__init__.py | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/utils/models/__init__.py b/utils/models/__init__.py index 754d7df7..0b217fcf 100644 --- a/utils/models/__init__.py +++ b/utils/models/__init__.py @@ -1,16 +1,37 @@ +from typing import Any + from nonebot.adapters.onebot.v11 import Message, MessageEvent from pydantic import BaseModel -from typing import Any class ShopParam(BaseModel): + + goods_name: str + """商品名称""" user_id: int + """用户id""" group_id: int + """群聊id""" bot: Any + """bot""" event: MessageEvent - num: int # 道具单次使用数量 + """event""" + num: int + """道具单次使用数量""" message: Message + """message""" text: str - send_success_msg: bool = True # 是否发送使用成功信息 - max_num_limit: int = 1 # 单次使用最大次数 + """text""" + send_success_msg: bool = True + """是否发送使用成功信息""" + max_num_limit: int = 1 + """单次使用最大次数""" + + +class CommonSql(BaseModel): + + sql: str + """sql语句""" + remark: str + """备注""" \ No newline at end of file From 8acbe0bb1cef5a34d48e437e16afb6e25a3732af Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Mon, 8 Jan 2024 05:39:44 +0800 Subject: [PATCH 06/20] =?UTF-8?q?perf=F0=9F=91=8C:=20=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E7=AE=A1=E7=90=86api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/web_ui/__init__.py | 22 +++- .../web_ui/api/tabs/plugin_manage/__init__.py | 113 +++++++++++++----- .../web_ui/api/tabs/plugin_manage/model.py | 41 ++++--- utils/manager/plugin_data_manager.py | 3 + utils/typing.py | 1 + 5 files changed, 129 insertions(+), 51 deletions(-) diff --git a/plugins/web_ui/__init__.py b/plugins/web_ui/__init__.py index fddfe793..cb05b979 100644 --- a/plugins/web_ui/__init__.py +++ b/plugins/web_ui/__init__.py @@ -30,6 +30,7 @@ gConfig.add_plugin_config("web-ui", "password", None, name="web-ui", help_="前 BaseApiRouter = APIRouter(prefix="/zhenxun/api") + BaseApiRouter.include_router(auth_router) BaseApiRouter.include_router(main_router) BaseApiRouter.include_router(manage_router) @@ -37,12 +38,24 @@ BaseApiRouter.include_router(database_router) BaseApiRouter.include_router(plugin_router) +WsApiRouter = APIRouter(prefix="/zhenxun/socket") + +WsApiRouter.include_router(ws_log_routes) +WsApiRouter.include_router(status_routes) + + @driver.on_startup def _(): try: - loop = asyncio.get_running_loop() - - def log_sink(message: str): + async def log_sink(message: str): + loop = None + if not loop: + try: + loop = asyncio.get_running_loop() + except Exception as e: + logger.warning('Web Ui log_sink', e=e) + if not loop: + loop = asyncio.new_event_loop() loop.create_task(LOG_STORAGE.add(message.rstrip("\n"))) logger_.add( @@ -51,8 +64,7 @@ def _(): app: FastAPI = nonebot.get_app() app.include_router(BaseApiRouter) - app.include_router(ws_log_routes) - app.include_router(status_routes) + app.include_router(WsApiRouter) logger.info("API启动成功", "Web UI") except Exception as e: logger.error("API启动失败", "Web UI", e=e) diff --git a/plugins/web_ui/api/tabs/plugin_manage/__init__.py b/plugins/web_ui/api/tabs/plugin_manage/__init__.py index 4201a528..d53cb03d 100644 --- a/plugins/web_ui/api/tabs/plugin_manage/__init__.py +++ b/plugins/web_ui/api/tabs/plugin_manage/__init__.py @@ -1,3 +1,4 @@ +import re from typing import List, Optional import cattrs @@ -10,7 +11,15 @@ 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 +from .model import ( + PluginConfig, + PluginCount, + PluginDetail, + PluginInfo, + PluginSwitch, + UpdateConfig, + UpdatePlugin, +) router = APIRouter() @@ -36,7 +45,7 @@ def _( plugin_info = PluginInfo( module=module, plugin_name=plugin_data.name, - default_switch=getattr(setting, "default_status", False), + default_status=getattr(setting, "default_status", False), limit_superuser=getattr(setting, "limit_superuser", False), cost_gold=getattr(setting, "cost_gold", 0), menu_type=menu_type_, @@ -67,7 +76,7 @@ def _() -> Result: plugin_count.other += 1 return Result.ok(plugin_count) -@router.post("/update_plugins", dependencies=[authentication()], description="更新插件参数") +@router.post("/update_plugin", dependencies=[authentication()], description="更新插件参数") def _(plugin: UpdatePlugin) -> Result: """ 修改插件信息 @@ -79,16 +88,15 @@ def _(plugin: UpdatePlugin) -> Result: 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.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 len(p2s.plugin_type) > 1: + menu_lin = p2s.menu_type[1] if menu_lin is not None: - pd.menu_type = (plugin.menu_type, menu_lin) + p2s.plugin_type = (plugin.menu_type, menu_lin) else: - pd.menu_type = (plugin.menu_type,) + p2s.plugin_type = (plugin.menu_type,) if pm := plugins_manager.get(module): if plugin.block_type: pm.block_type = plugin.block_type @@ -98,34 +106,25 @@ def _(plugin: UpdatePlugin) -> Result: pm.status = True plugins2settings_manager.save() plugins_manager.save() + # 配置项 + if plugin.configs and (configs := Config.get(module)): + for key in plugin.configs: + if c := configs.configs.get(key): + value = plugin.configs[key] + if isinstance(c.value, (list, tuple)) or isinstance( + c.default_value, (list, tuple) + ): + value = value.split(",") + if c.type and value is not None: + value = cattrs.structure(value, c.type) + Config.set_config(module, key, value) + plugin_data_manager.reload() 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): @@ -146,4 +145,54 @@ def _() -> Result: 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) \ No newline at end of file + return Result.ok(menu_type_list) + + +@router.get("/get_plugin", dependencies=[authentication()], description="获取插件详情") +def _(module: str) -> Result: + if plugin_data := plugin_data_manager.get(module): + setting = plugin_data.plugin_setting or PluginSetting() + plugin = plugin_data.plugin_status + config_list = [] + if config := Config.get(module): + for cfg in config.configs: + type_str = "" + type_inner = None + x = str(config.configs[cfg].type) + r = re.search(r"",str(config.configs[cfg].type)) + if r: + type_str = r.group(1) + else: + r = re.search(r"typing\.(.*)\[(.*)\]",str(config.configs[cfg].type)) + if r: + type_str = r.group(1) + if type_str: + type_str = type_str.lower() + type_inner = r.group(2) + if type_inner: + type_inner = [x.strip() for x in type_inner.split(",")] + config_list.append(PluginConfig( + module=module, + key=cfg, + value=config.configs[cfg].value, + help=config.configs[cfg].help, + default_value=config.configs[cfg].default_value, + type=type_str, + type_inner=type_inner + )) + plugin_info = PluginDetail( + module=module, + plugin_name=plugin_data.name, + default_status=getattr(setting, "default_status", False), + limit_superuser=getattr(setting, "limit_superuser", False), + cost_gold=getattr(setting, "cost_gold", 0), + menu_type=getattr(setting, "plugin_type", ["无"])[0], + 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, + config_list=config_list, + block_type=getattr(plugin, "block_type", None) + ) + return Result.ok(plugin_info) + return Result.warning_("未获取到插件详情...") \ No newline at end of file diff --git a/plugins/web_ui/api/tabs/plugin_manage/model.py b/plugins/web_ui/api/tabs/plugin_manage/model.py index 869bd594..31c1ae9a 100644 --- a/plugins/web_ui/api/tabs/plugin_manage/model.py +++ b/plugins/web_ui/api/tabs/plugin_manage/model.py @@ -3,13 +3,7 @@ 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.manager.models import PluginBlock, PluginCd, PluginCount, PluginSetting from utils.typing import BLOCK_TYPE @@ -50,14 +44,14 @@ class UpdatePlugin(BaseModel): """限制超级用户""" cost_gold: int """金币花费""" - cmd: str - """插件别名""" menu_type: str """插件菜单类型""" level: int """插件所需群权限""" - block_type: BLOCK_TYPE + block_type: Optional[BLOCK_TYPE] = None """禁用类型""" + configs: Optional[Dict[str, Any]] = None + """配置项""" class PluginInfo(BaseModel): @@ -69,7 +63,7 @@ class PluginInfo(BaseModel): """插件名称""" plugin_name: str """插件中文名称""" - default_switch: bool + default_status: bool """默认开关""" limit_superuser: bool """限制超级用户""" @@ -85,6 +79,8 @@ class PluginInfo(BaseModel): """当前状态""" author: Optional[str] = None """作者""" + block_type: BLOCK_TYPE = None + """禁用类型""" class PluginConfig(BaseModel): @@ -93,11 +89,20 @@ class PluginConfig(BaseModel): """ module: str + """模块""" key: str + """键""" value: Any - help: Optional[str] + """值""" + help: Optional[str] = None + """帮助""" default_value: Any - has_type: bool + """默认值""" + type: Optional[Any] = None + """值类型""" + type_inner: Optional[List[str]] = None + """List Tuple等内部类型检验""" + class Plugin(BaseModel): @@ -132,4 +137,12 @@ class PluginCount(BaseModel): superuser: int = 0 """超级用户插件""" other: int = 0 - """其他插件""" \ No newline at end of file + """其他插件""" + + +class PluginDetail(PluginInfo): + """ + 插件详情 + """ + + config_list: List[PluginConfig] \ No newline at end of file diff --git a/utils/manager/plugin_data_manager.py b/utils/manager/plugin_data_manager.py index bd9c4aae..f94307dc 100644 --- a/utils/manager/plugin_data_manager.py +++ b/utils/manager/plugin_data_manager.py @@ -29,3 +29,6 @@ class PluginDataManager(StaticData[PluginData]): def __getitem__(self, item) -> Optional[PluginData]: return self._data.get(item) + + def reload(self): + pass diff --git a/utils/typing.py b/utils/typing.py index 5d8fa815..cac8ca09 100644 --- a/utils/typing.py +++ b/utils/typing.py @@ -1,3 +1,4 @@ from typing import Literal BLOCK_TYPE = Literal["all", "private", "group"] +"""禁用类型""" From 3bfb3620f1dcfe18ddebc816f23a272e9f55f345 Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Mon, 8 Jan 2024 05:41:13 +0800 Subject: [PATCH 07/20] modified: basic_plugins/init_plugin_config/init_plugins_settings.py --- .../init_plugins_settings.py | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/basic_plugins/init_plugin_config/init_plugins_settings.py b/basic_plugins/init_plugin_config/init_plugins_settings.py index 52864ef2..cdf7b372 100755 --- a/basic_plugins/init_plugin_config/init_plugins_settings.py +++ b/basic_plugins/init_plugin_config/init_plugins_settings.py @@ -1,8 +1,9 @@ -from utils.manager import plugins2settings_manager, admin_manager, plugin_data_manager +import nonebot + from services.log import logger +from utils.manager import admin_manager, plugin_data_manager, plugins2settings_manager from utils.manager.models import PluginType from utils.utils import get_matchers -import nonebot def init_plugins_settings(): @@ -18,7 +19,10 @@ def init_plugins_settings(): # logger.warning(f"配置文件 模块:{x} 获取 plugin_name 失败...{e}") for matcher in get_matchers(True): try: - if matcher.plugin_name not in plugins2settings_manager.keys(): + if ( + matcher.plugin_name + and matcher.plugin_name not in plugins2settings_manager.keys() + ): if _plugin := matcher.plugin: try: _module = _plugin.module @@ -27,18 +31,26 @@ def init_plugins_settings(): else: if plugin_data := plugin_data_manager.get(matcher.plugin_name): if plugin_settings := plugin_data.plugin_setting: - if (name := _module.__getattribute__("__zx_plugin_name__")) not in plugin_settings.cmd: + if ( + name := _module.__getattribute__( + "__zx_plugin_name__" + ) + ) not in plugin_settings.cmd: plugin_settings.cmd.append(name) # 管理员命令 if plugin_data.plugin_type == PluginType.ADMIN: admin_manager.add_admin_plugin_settings( - matcher.plugin_name, plugin_settings.cmd, plugin_settings.level + matcher.plugin_name, + plugin_settings.cmd, + plugin_settings.level, ) else: plugins2settings_manager.add_plugin_settings( matcher.plugin_name, plugin_settings ) except Exception as e: - logger.error(f'{matcher.plugin_name} 初始化 plugin_settings 发生错误 {type(e)}:{e}') + logger.error( + f"{matcher.plugin_name} 初始化 plugin_settings 发生错误 {type(e)}:{e}" + ) plugins2settings_manager.save() logger.info(f"已成功加载 {len(plugins2settings_manager.get_data())} 个非限制插件.") From 9a1510fe7ef382df3c3a010457a37dfd73b5532a Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Mon, 8 Jan 2024 15:21:07 +0800 Subject: [PATCH 08/20] =?UTF-8?q?perf=F0=9F=91=8C:=20=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E7=AE=A1=E7=90=86api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/web_ui/api/tabs/plugin_manage/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/web_ui/api/tabs/plugin_manage/__init__.py b/plugins/web_ui/api/tabs/plugin_manage/__init__.py index d53cb03d..6179d9c5 100644 --- a/plugins/web_ui/api/tabs/plugin_manage/__init__.py +++ b/plugins/web_ui/api/tabs/plugin_manage/__init__.py @@ -111,10 +111,10 @@ def _(plugin: UpdatePlugin) -> Result: for key in plugin.configs: if c := configs.configs.get(key): value = plugin.configs[key] - if isinstance(c.value, (list, tuple)) or isinstance( - c.default_value, (list, tuple) - ): - value = value.split(",") + # if isinstance(c.value, (list, tuple)) or isinstance( + # c.default_value, (list, tuple) + # ): + # value = value.split(",") if c.type and value is not None: value = cattrs.structure(value, c.type) Config.set_config(module, key, value) From bf55a20241fb23d6076af25b230bfcf93715e508 Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Tue, 9 Jan 2024 13:47:24 +0800 Subject: [PATCH 09/20] =?UTF-8?q?perf=F0=9F=91=8C:=20=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E4=BC=98=E5=8C=96webui=E7=BE=A4=E7=BB=84/=E5=A5=BD=E5=8F=8B?= =?UTF-8?q?=E7=AE=A1=E7=90=86api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/web_ui/__init__.py | 2 + plugins/web_ui/api/logs/log_manager.py | 13 +- plugins/web_ui/api/tabs/main/__init__.py | 8 +- plugins/web_ui/api/tabs/manage/__init__.py | 280 +++++++++++++++++---- plugins/web_ui/api/tabs/manage/model.py | 139 ++++++++-- plugins/web_ui/config.py | 5 + utils/manager/requests_manager.py | 91 ++++++- 7 files changed, 443 insertions(+), 95 deletions(-) diff --git a/plugins/web_ui/__init__.py b/plugins/web_ui/__init__.py index cb05b979..3de51bfe 100644 --- a/plugins/web_ui/__init__.py +++ b/plugins/web_ui/__init__.py @@ -18,6 +18,7 @@ from .api.tabs.database import router as database_router from .api.tabs.main import router as main_router from .api.tabs.main import ws_router as status_routes from .api.tabs.manage import router as manage_router +from .api.tabs.manage import ws_router as chat_routes from .api.tabs.plugin_manage import router as plugin_router from .auth import router as auth_router @@ -42,6 +43,7 @@ WsApiRouter = APIRouter(prefix="/zhenxun/socket") WsApiRouter.include_router(ws_log_routes) WsApiRouter.include_router(status_routes) +WsApiRouter.include_router(chat_routes) @driver.on_startup diff --git a/plugins/web_ui/api/logs/log_manager.py b/plugins/web_ui/api/logs/log_manager.py index c7c8140e..f375313d 100644 --- a/plugins/web_ui/api/logs/log_manager.py +++ b/plugins/web_ui/api/logs/log_manager.py @@ -21,18 +21,6 @@ class LogStorage(Generic[_T]): self.listeners: Set[LogListener[str]] = set() async def add(self, log: str): - # log = re.sub(PATTERN, "", log) - # log_split = log.split() - # time = log_split[0] + " " + log_split[1] - # level = log_split[2] - # main = log_split[3] - # type_ = None - # log_ = " ".join(log_split[3:]) - # if "Calling API" in log_: - # sp = log_.split("|") - # type_ = sp[1] - # log_ = "|".join(log_[1:]) - # data = {"time": time, "level": level, "main": main, "type": type_, "log": log_} seq = self.count = self.count + 1 self.logs[seq] = log asyncio.get_running_loop().call_later(self.rotation, self.remove, seq) @@ -48,3 +36,4 @@ class LogStorage(Generic[_T]): LOG_STORAGE: LogStorage[str] = LogStorage[str]() + diff --git a/plugins/web_ui/api/tabs/main/__init__.py b/plugins/web_ui/api/tabs/main/__init__.py index 125cb587..6fec031c 100644 --- a/plugins/web_ui/api/tabs/main/__init__.py +++ b/plugins/web_ui/api/tabs/main/__init__.py @@ -5,7 +5,6 @@ from typing import List, Optional import nonebot from fastapi import APIRouter, WebSocket -from nonebot.utils import escape_tag from starlette.websockets import WebSocket, WebSocketDisconnect, WebSocketState from tortoise.functions import Count from websockets.exceptions import ConnectionClosedError, ConnectionClosedOK @@ -18,21 +17,18 @@ from services.log import logger from utils.manager import plugin_data_manager, plugins2settings_manager, plugins_manager from ....base_model import Result -from ....config import QueryDateType +from ....config import AVA_URL, GROUP_AVA_URL, QueryDateType from ....utils import authentication, get_system_status from .data_source import bot_live from .model import ActiveGroup, BaseInfo, ChatHistoryCount, HotPlugin -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() ws_router = APIRouter() router = APIRouter() + @router.get("/get_base_info", dependencies=[authentication()], description="基础信息") async def _(bot_id: Optional[str] = None) -> Result: """ diff --git a/plugins/web_ui/api/tabs/manage/__init__.py b/plugins/web_ui/api/tabs/manage/__init__.py index a764f1bd..96e70ccb 100644 --- a/plugins/web_ui/api/tabs/manage/__init__.py +++ b/plugins/web_ui/api/tabs/manage/__init__.py @@ -1,32 +1,54 @@ -from typing import Literal +import re +from typing import Literal, Optional import nonebot from fastapi import APIRouter -from pydantic.error_wrappers import ValidationError +from nonebot.adapters.onebot.v11.exception import ActionFailed +from starlette.websockets import WebSocket, WebSocketDisconnect, WebSocketState +from tortoise.functions import Count +from websockets.exceptions import ConnectionClosedError, ConnectionClosedOK from configs.config import NICKNAME +from models.ban_user import BanUser +from models.chat_history import ChatHistory +from models.friend_user import FriendUser from models.group_info import GroupInfo +from models.group_member_info import GroupInfoUser +from models.statistics import Statistics from services.log import logger -from utils.manager import group_manager, requests_manager +from utils.manager import group_manager, plugin_data_manager, requests_manager from utils.utils import get_bot from ....base_model import Result +from ....config import AVA_URL, GROUP_AVA_URL from ....utils import authentication +from ...logs.log_manager import LOG_STORAGE from .model import ( DeleteFriend, Friend, FriendRequestResult, - Group, + GroupDetail, GroupRequestResult, GroupResult, HandleRequest, LeaveGroup, + Message, + Plugin, + ReqResult, + SendMessage, Task, UpdateGroup, + UserDetail, ) +ws_router = APIRouter() router = APIRouter() +SUB_PATTERN = r"\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))" + +GROUP_PATTERN = r'.*?Message (-?\d*) from (\d*)@\[群:(\d*)] "(.*)"' + +PRIVATE_PATTERN = r'.*?Message (-?\d*) from (\d*) "(.*)"' @router.get("/get_group_list", dependencies=[authentication()], description="获取群组列表") async def _(bot_id: str) -> Result: @@ -41,27 +63,9 @@ async def _(bot_id: str) -> Result: group_info = {} group_list = await bots[bot_id].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)) + gid = g['group_id'] + g['ava_url'] = GROUP_AVA_URL.format(gid, gid) + group_list_result.append(GroupResult(**g)) except Exception as e: logger.error("调用API错误", "/get_group_list", e=e) return Result.fail(f"{type(e)}: {e}") @@ -78,12 +82,14 @@ async def _(group: UpdateGroup) -> Result: group_manager.turn_on_group_bot_status(group_id) else: group_manager.shutdown_group_bot_status(group_id) - if group.task_status: - for task in group.task_status: - if group.task_status[task]: + all_task = group_manager.get_task_data().keys() + if group.task: + for task in all_task: + if task in group.task: group_manager.open_group_task(group_id, task) else: group_manager.close_group_task(group_id, task) + group_manager[group_id].close_plugins = group.close_plugins group_manager.save() except Exception as e: logger.error("调用API错误", "/get_group", e=e) @@ -101,7 +107,9 @@ async def _(bot_id: str) -> Result: return Result.warning_("指定Bot未连接...") try: friend_list = await bots[bot_id].get_friend_list() - return Result.ok([Friend(**f) for f in friend_list], "拿到了新鲜出炉的数据!") + for f in friend_list: + f['ava_url'] = AVA_URL.format(f['user_id']) + return Result.ok([Friend(**f) for f in friend_list if str(f['user_id']) != bot_id], "拿到了新鲜出炉的数据!") except Exception as e: logger.error("调用API错误", "/get_group_list", e=e) return Result.fail(f"{type(e)}: {e}") @@ -118,21 +126,27 @@ def _() -> Result: @router.get("/get_request_list", dependencies=[authentication()], description="获取请求列表") -def _(request_type: Literal["private", "group"]) -> Result: +def _() -> Result: try: - req_data = requests_manager.get_data().get(request_type) or [] - req_list = [] - for x in req_data: - req_data[x]["oid"] = x - if request_type == "private": - req_list.append(FriendRequestResult(**req_data[x])) - else: - req_list.append(GroupRequestResult(**req_data[x])) - req_list.reverse() + req_result = ReqResult() + data = requests_manager.get_data() + for type_ in requests_manager.get_data(): + for x in data[type_]: + data[type_][x]["oid"] = x + data[type_][x]['type'] = type_ + if type_ == "private": + data[type_][x]['ava_url'] = AVA_URL.format(data[type_][x]['id']) + req_result.friend.append(FriendRequestResult(**data[type_][x])) + else: + gid = data[type_][x]['id'] + data[type_][x]['ava_url'] = GROUP_AVA_URL.format(gid, gid) + req_result.group.append(GroupRequestResult(**data[type_][x])) + req_result.friend.reverse() + req_result.group.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}带来了最新的数据!") + return Result.ok(req_result, f"{NICKNAME}带来了最新的数据!") @router.delete("/clear_request", dependencies=[authentication()], description="清空请求列表") @@ -156,14 +170,18 @@ async def _(parma: HandleRequest) -> Result: bot_id = parma.bot_id if bot_id not in nonebot.get_bots(): return Result.warning_("指定Bot未连接...") - flag = await requests_manager.refused(bots[bot_id], parma.id, parma.request_type) # type: ignore + try: + flag = await requests_manager.refused(bots[bot_id], parma.flag, parma.request_type) # type: ignore + except ActionFailed as e: + requests_manager.delete_request(parma.flag, parma.request_type) + return Result.warning_("请求失败,可能该请求已失效或请求数据错误...") if flag == 1: - requests_manager.delete_request(parma.id, parma.request_type) + requests_manager.delete_request(parma.flag, parma.request_type) return Result.warning_("该请求已失效...") elif flag == 2: return Result.warning_("未找到此Id请求...") return Result.ok(info="成功处理了请求!") - return Result.warning_("Bot未连接...") + return Result.warning_("无Bot连接...") except Exception as e: logger.error("调用API错误", "/refuse_request", e=e) return Result.fail(f"{type(e)}: {e}") @@ -175,7 +193,7 @@ async def _(parma: HandleRequest) -> Result: 操作请求 :param parma: 参数 """ - requests_manager.delete_request(parma.id, parma.request_type) + requests_manager.delete_request(parma.flag, parma.request_type) return Result.ok(info="成功处理了请求!") @@ -191,7 +209,7 @@ async def _(parma: HandleRequest) -> Result: if bot_id not in nonebot.get_bots(): return Result.warning_("指定Bot未连接...") if parma.request_type == "group": - if rid := requests_manager.get_group_id(parma.id): + if rid := requests_manager.get_group_id(parma.flag): if group := await GroupInfo.get_or_none(group_id=str(rid)): await group.update_or_create(group_flag=1) else: @@ -205,9 +223,13 @@ async def _(parma: HandleRequest) -> Result: "group_flag": 1, }, ) - await requests_manager.approve(bots[bot_id], parma.id, parma.request_type) # type: ignore - return Result.ok(info="成功处理了请求!") - return Result.warning_("Bot未连接...") + try: + await requests_manager.approve(bots[bot_id], parma.flag, parma.request_type) # type: ignore + return Result.ok(info="成功处理了请求!") + except ActionFailed as e: + requests_manager.delete_request(parma.flag, parma.request_type) + return Result.warning_("请求失败,可能该请求已失效或请求数据错误...") + return Result.warning_("无Bot连接...") except Exception as e: logger.error("调用API错误", "/approve_request", e=e) return Result.fail(f"{type(e)}: {e}") @@ -223,7 +245,7 @@ async def _(param: LeaveGroup) -> Result: return Result.warning_("Bot未在该群聊中...") await bots[bot_id].set_group_leave(group_id=param.group_id) return Result.ok(info="成功处理了请求!") - return Result.warning_("Bot未连接...") + return Result.warning_("无Bot连接...") except Exception as e: logger.error("调用API错误", "/leave_group", e=e) return Result.fail(f"{type(e)}: {e}") @@ -243,3 +265,163 @@ async def _(param: DeleteFriend) -> Result: except Exception as e: logger.error("调用API错误", "/delete_friend", e=e) return Result.fail(f"{type(e)}: {e}") + + + +@router.get("/get_friend_detail", dependencies=[authentication()], description="获取好友详情") +async def _(bot_id: str, user_id: str) -> Result: + if bots := nonebot.get_bots(): + if bot_id in bots: + if fd := [x for x in await bots[bot_id].get_friend_list() if str(x['user_id']) == user_id]: + like_plugin_list = ( + await Statistics.filter(user_id=user_id).annotate(count=Count("id")) + .group_by("plugin_name").order_by("-count").limit(5) + .values_list("plugin_name", "count") + ) + like_plugin = {} + for data in like_plugin_list: + name = data[0] + if plugin_data := plugin_data_manager.get(data[0]): + name = plugin_data.name + like_plugin[name] = data[1] + user = fd[0] + user_detail = UserDetail( + user_id=user_id, + ava_url=AVA_URL.format(user_id), + nickname=user['nickname'], + remark=user['remark'], + is_ban=await BanUser.is_ban(user_id), + chat_count=await ChatHistory.filter(user_id=user_id).count(), + call_count=await Statistics.filter(user_id=user_id).count(), + like_plugin=like_plugin, + ) + return Result.ok(user_detail) + else: + return Result.warning_("未添加指定好友...") + return Result.warning_("无Bot连接...") + + +@router.get("/get_group_detail", dependencies=[authentication()], description="获取群组详情") +async def _(bot_id: str, group_id: str) -> Result: + if bots := nonebot.get_bots(): + if bot_id in bots: + group_info = await bots[bot_id].get_group_info(group_id=int(group_id)) + g = group_manager[group_id] + if not g: + return Result.warning_("指定群组未被收录...") + if group_info: + like_plugin_list = ( + await Statistics.filter(group_id=group_id).annotate(count=Count("id")) + .group_by("plugin_name").order_by("-count").limit(5) + .values_list("plugin_name", "count") + ) + like_plugin = {} + for data in like_plugin_list: + name = data[0] + if plugin_data := plugin_data_manager.get(data[0]): + name = plugin_data.name + like_plugin[name] = data[1] + close_plugins = [] + for module in g.close_plugins: + plugin = Plugin(module=module, plugin_name=module) + if plugin_data := plugin_data_manager.get(module): + plugin.plugin_name = plugin_data.name + close_plugins.append(plugin) + task_list = [] + task_data = group_manager.get_task_data() + for tn, status in g.group_task_status.items(): + task_list.append( + Task( + name=tn, + zh_name=task_data.get(tn) or tn, + status=status + ) + ) + group_detail = GroupDetail( + group_id=group_id, + ava_url=GROUP_AVA_URL.format(group_id, group_id), + name=group_info['group_name'], + member_count=group_info['member_count'], + max_member_count=group_info['max_member_count'], + chat_count=await ChatHistory.filter(group_id=group_id).count(), + call_count=await Statistics.filter(group_id=group_id).count(), + like_plugin=like_plugin, + level=g.level, + status=g.status, + close_plugins=close_plugins, + task=task_list + ) + return Result.ok(group_detail) + else: + return Result.warning_("未添加指定群组...") + return Result.warning_("无Bot连接...") + + +@router.post("/send_message", dependencies=[authentication()], description="获取群组详情") +async def _(param: SendMessage) -> Result: + if bots := nonebot.get_bots(): + if param.bot_id in bots: + try: + if param.user_id: + await bots[param.bot_id].send_private_msg(user_id=str(param.user_id), message=param.message) + else: + await bots[param.bot_id].send_group_msg(group_id=str(param.group_id), message=param.message) + except Exception as e: + return Result.fail(str(e)) + return Result.ok("发送成功!") + return Result.warning_("指定Bot未连接...") + return Result.warning_("无Bot连接...") + +MSG_LIST = [] + +@ws_router.websocket("/chat") +async def _(websocket: WebSocket, group_id: Optional[str] = None, user_id: Optional[str] = None): + global MSG_LIST + await websocket.accept() + + async def log_listener(log: str): + sub_log = re.sub(SUB_PATTERN, "", log) + if "message.private.friend" in log: + if r := re.search(PRIVATE_PATTERN, sub_log): + msg_id = r.group(1) + uid = r.group(2) + msg = r.group(3) + user = await FriendUser.filter(user_id=user_id).first() + name = user.user_name + if uid and uid == user_id and msg_id not in MSG_LIST: + MSG_LIST.append(msg_id) + message = Message( + user_id=uid, + message=msg, + name=name, + ava_url=AVA_URL.format(uid) + ) + await websocket.send_json(message.dict()) + else: + if r := re.search(GROUP_PATTERN, sub_log): + msg_id = r.group(1) + uid = r.group(2) + gid = r.group(3) + msg = r.group(4) + user = await GroupInfoUser.filter(user_id=uid, group_id=gid).first() + name = user.user_name or user.nickname + if gid and gid == group_id and msg_id not in MSG_LIST: + MSG_LIST.append(msg_id) + message = Message( + user_id=uid, + group_id=gid, + message=msg, + name=name, + ava_url=AVA_URL.format(uid) + ) + await websocket.send_json(message.dict()) + + LOG_STORAGE.listeners.add(log_listener) + try: + while websocket.client_state == WebSocketState.CONNECTED: + recv = await websocket.receive() + except WebSocketDisconnect: + pass + finally: + LOG_STORAGE.listeners.remove(log_listener) + return \ No newline at end of file diff --git a/plugins/web_ui/api/tabs/manage/model.py b/plugins/web_ui/api/tabs/manage/model.py index 089da80d..bc8df37f 100644 --- a/plugins/web_ui/api/tabs/manage/model.py +++ b/plugins/web_ui/api/tabs/manage/model.py @@ -27,27 +27,33 @@ class Task(BaseModel): name: str """被动名称""" - nameZh: str + zh_name: str """被动中文名称""" status: bool """状态""" +class Plugin(BaseModel): + """ + 插件 + """ + + module: str + """模块名""" + plugin_name: str + """中文名""" + class GroupResult(BaseModel): """ 群组返回数据 """ - group: Group - """Group""" - level: int - """群等级""" - status: bool - """状态""" - close_plugins: List[str] - """关闭的插件""" - task: List[Task] - """被动列表""" + group_id: Union[str, int] + """群组id""" + group_name: str + """群组名称""" + ava_url: str + """群组头像""" class Friend(BaseModel): @@ -61,7 +67,8 @@ class Friend(BaseModel): """昵称""" remark: str = "" """备注""" - + ava_url: str = "" + """头像url""" class UpdateGroup(BaseModel): """ @@ -74,8 +81,10 @@ class UpdateGroup(BaseModel): """状态""" level: int """群权限""" - task_status: Dict[str, bool] + task: List[str] """被动状态""" + close_plugins: List[str] + """关闭插件""" class FriendRequestResult(BaseModel): @@ -103,6 +112,10 @@ class FriendRequestResult(BaseModel): """来自""" comment: Optional[str] """备注信息""" + ava_url: str + """头像""" + type: str + """类型 private group""" class GroupRequestResult(FriendRequestResult): @@ -121,10 +134,10 @@ class HandleRequest(BaseModel): 操作请求接收数据 """ - bot_id: str + bot_id: Optional[str] = None """bot_id""" - id: int - """id""" + flag: str + """flag""" request_type: Literal["private", "group"] """类型""" @@ -149,3 +162,97 @@ class DeleteFriend(BaseModel): """bot_id""" user_id: str """用户id""" + +class ReqResult(BaseModel): + """ + 好友/群组请求列表 + """ + + friend: List[FriendRequestResult] = [] + """好友请求列表""" + group: List[GroupRequestResult] = [] + """群组请求列表""" + + +class UserDetail(BaseModel): + """ + 用户详情 + """ + + user_id: str + """用户id""" + ava_url: str + """头像url""" + nickname: str + """昵称""" + remark: str + """备注""" + is_ban: bool + """是否被ban""" + chat_count: int + """发言次数""" + call_count: int + """功能调用次数""" + like_plugin: Dict[str, int] + """最喜爱的功能""" + + +class GroupDetail(BaseModel): + """ + 用户详情 + """ + + group_id: str + """群组id""" + ava_url: str + """头像url""" + name: str + """名称""" + member_count: int + """成员数""" + max_member_count: int + """最大成员数""" + chat_count: int + """发言次数""" + call_count: int + """功能调用次数""" + like_plugin: Dict[str, int] + """最喜爱的功能""" + level: int + """群权限""" + status: bool + """状态(睡眠)""" + close_plugins: List[Plugin] + """关闭的插件""" + task: List[Task] + """被动列表""" + + +class Message(BaseModel): + """ + 消息 + """ + + user_id: str + """用户id""" + group_id: Optional[str] = None + """群组id""" + message: str + """消息""" + name: str + """用户名称""" + ava_url: str + """用户头像""" + +class SendMessage(BaseModel): + """ + 发送消息 + """ + bot_id: str + """bot id""" + user_id: Optional[str] = None + """用户id""" + group_id: Optional[str] = None + """群组id""" + message: str + """消息""" diff --git a/plugins/web_ui/config.py b/plugins/web_ui/config.py index a85d9126..b6e6fde0 100644 --- a/plugins/web_ui/config.py +++ b/plugins/web_ui/config.py @@ -20,6 +20,11 @@ app.add_middleware( ) +AVA_URL = "http://q1.qlogo.cn/g?b=qq&nk={}&s=160" + +GROUP_AVA_URL = "http://p.qlogo.cn/gh/{}/{}/640/" + + class QueryDateType(StrEnum): """ diff --git a/utils/manager/requests_manager.py b/utils/manager/requests_manager.py index c97f2000..d9b5175c 100644 --- a/utils/manager/requests_manager.py +++ b/utils/manager/requests_manager.py @@ -1,6 +1,6 @@ from io import BytesIO from pathlib import Path -from typing import Optional +from typing import Optional, Union, overload from nonebot.adapters.onebot.v11 import ActionFailed, Bot @@ -67,14 +67,24 @@ class RequestManager(StaticData): } self.save() + @overload + def remove_request(self, type_: str, flag: str): + ... + + @overload def remove_request(self, type_: str, id_: int): + ... + + def remove_request(self, type_: str, id_: Union[int, str]): """ 删除一个请求数据 :param type_: 类型 :param id_: id,user_id 或 group_id """ for x in self._data[type_].keys(): - if self._data[type_][x].get("id") == id_: + a_id = self._data[type_][x].get("id") + a_flag = self._data[type_][x].get("flag") + if a_id == id_ or a_flag == id_: del self._data[type_][x] break self.save() @@ -88,8 +98,17 @@ class RequestManager(StaticData): if data: return data["invite_group"] return None - + + @overload async def approve(self, bot: Bot, id_: int, type_: str) -> int: + ... + + @overload + async def approve(self, bot: Bot, flag: str, type_: str) -> int: + ... + + + async def approve(self, bot: Bot, id_: Union[int, str], type_: str) -> int: """ 同意请求 :param bot: Bot @@ -97,8 +116,16 @@ class RequestManager(StaticData): :param type_: 类型,private 或 group """ return await self._set_add_request(bot, id_, type_, True) + + @overload + async def refused(self, bot: Bot, id_: int, type_: str) -> int: + ... - async def refused(self, bot: Bot, id_: int, type_: str) -> Optional[int]: + @overload + async def refused(self, bot: Bot, flag: str, type_: str) -> int: + ... + + async def refused(self, bot: Bot, id_: Union[int, str], type_: str) -> Optional[int]: """ 拒绝请求 :param bot: Bot @@ -120,18 +147,32 @@ class RequestManager(StaticData): self._data = {"private": {}, "group": {}} self.save() + @overload + async def delete_request(self, id_: int, type_: str) -> int: + ... + + @overload + async def delete_request(self, flag: str, type_: str) -> int: + ... + def delete_request( - self, id_: int, type_: str + self, id_: Union[str, int], type_: str ): # type_: Literal["group", "private"] """ 删除请求 :param id_: id :param type_: 类型 """ - id_ = str(id_) - if self._data[type_].get(id_): - del self._data[type_][id_] - self.save() + if type(id_) == int: + if self._data[type_].get(id_): + del self._data[type_][id_] + self.save() + else: + for k, item in self._data[type_].items(): + if item['flag'] == id_: + del self._data[type_][k] + self.save() + break def set_group_name(self, group_name: str, group_id: int): """ @@ -239,7 +280,7 @@ class RequestManager(StaticData): return bk.pic2bs4() async def _set_add_request( - self, bot: Bot, idx: int, type_: str, approve: bool + self, bot: Bot, idx: Union[str, int], type_: str, approve: bool ) -> int: """ 处理请求 @@ -248,8 +289,13 @@ class RequestManager(StaticData): :param type_: 类型,private 或 group :param approve: 是否同意 """ - id_ = str(idx) - if id_ in self._data[type_].keys(): + flag = None + id_ = None + if type(idx) == str: + flag = idx + else: + id_ = str(idx) + if id_ and id_ in self._data[type_].keys(): try: if type_ == "private": await bot.set_friend_add_request( @@ -277,4 +323,25 @@ class RequestManager(StaticData): del self._data[type_][id_] self.save() return rid + if flag: + rm_id = None + for k, item in self._data[type_].items(): + if item['flag'] == flag: + rm_id = k + if type_ == 'private': + await bot.set_friend_add_request( + flag=item['flag'], approve=approve + ) + rid = item["id"] + else: + await bot.set_group_add_request( + flag=item['flag'], + sub_type="invite", + approve=approve, + ) + rid = item["invite_group"] + if rm_id is not None: + del self._data[type_][rm_id] + self.save() + return rid return 2 # 未找到id From 59cb2bee8b61691765d39ee07f1b85cc77367473 Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Sun, 14 Jan 2024 05:20:21 +0800 Subject: [PATCH 10/20] =?UTF-8?q?perf=F0=9F=91=8C:=20webui=E5=AE=8C?= =?UTF-8?q?=E5=96=84=E5=A5=BD=E5=8F=8B=E7=BE=A4=E7=BB=84=E7=AE=A1=E7=90=86?= =?UTF-8?q?api=EF=BC=8C=E6=B7=BB=E5=8A=A0=E9=83=A8=E5=88=86=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E7=AE=A1=E7=90=86=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/web_ui/api/tabs/database/__init__.py | 26 ++++- plugins/web_ui/api/tabs/main/__init__.py | 4 +- plugins/web_ui/api/tabs/manage/__init__.py | 100 +++++++++++------- plugins/web_ui/api/tabs/manage/model.py | 14 ++- .../web_ui/api/tabs/plugin_manage/__init__.py | 10 +- 5 files changed, 109 insertions(+), 45 deletions(-) diff --git a/plugins/web_ui/api/tabs/database/__init__.py b/plugins/web_ui/api/tabs/database/__init__.py index 3df9d212..0f3eb985 100644 --- a/plugins/web_ui/api/tabs/database/__init__.py +++ b/plugins/web_ui/api/tabs/database/__init__.py @@ -17,7 +17,7 @@ from ....utils import authentication from .models.model import SqlModel, SqlText from .models.sql_log import SqlLog -router = APIRouter() +router = APIRouter(prefix="/database") driver: Driver = nonebot.get_driver() @@ -26,6 +26,18 @@ driver: Driver = nonebot.get_driver() SQL_DICT = {} +SELECT_TABLE_SQL = """ +select a.tablename as name,d.description as desc from pg_tables a + left join pg_class c on relname=tablename + left join pg_description d on oid=objoid and objsubid=0 where a.schemaname = 'public' +""" + +SELECT_TABLE_COLUMN_SQL = """ +SELECT column_name, data_type, character_maximum_length as max_length, is_nullable +FROM information_schema.columns +WHERE table_name = '{}'; +""" + @driver.on_startup async def _(): for matcher in get_matchers(True): @@ -45,6 +57,18 @@ async def _(): ) SQL_DICT[plugin_name] = SqlModel +@router.get("/get_table_list", dependencies=[authentication()], description="获取数据库表") +async def _() -> Result: + db = Tortoise.get_connection("default") + query = await db.execute_query_dict(SELECT_TABLE_SQL) + return Result.ok(query) + +@router.get("/get_table_column", dependencies=[authentication()], description="获取表字段") +async def _(table_name: str) -> Result: + db = Tortoise.get_connection("default") + print(SELECT_TABLE_COLUMN_SQL.format(table_name)) + query = await db.execute_query_dict(SELECT_TABLE_COLUMN_SQL.format(table_name)) + return Result.ok(query) @router.post("/exec_sql", dependencies=[authentication()], description="执行sql") async def _(sql: SqlText, request: Request) -> Result: diff --git a/plugins/web_ui/api/tabs/main/__init__.py b/plugins/web_ui/api/tabs/main/__init__.py index 6fec031c..b5d6e63d 100644 --- a/plugins/web_ui/api/tabs/main/__init__.py +++ b/plugins/web_ui/api/tabs/main/__init__.py @@ -25,7 +25,7 @@ from .model import ActiveGroup, BaseInfo, ChatHistoryCount, HotPlugin run_time = time.time() ws_router = APIRouter() -router = APIRouter() +router = APIRouter(prefix="/main") @@ -183,7 +183,7 @@ async def _(date_type: Optional[QueryDateType] = None) -> Result: if date_type == QueryDateType.YEAR: query = ChatHistory.filter(create_time__gte=now - timedelta(days=365)) data_list = ( - await query.annotate(count=Count("id")) + await query.annotate(count=Count("id")).filter(group_id__not_isnull=True) .group_by("group_id").order_by("-count").limit(5) .values_list("group_id", "count") ) diff --git a/plugins/web_ui/api/tabs/manage/__init__.py b/plugins/web_ui/api/tabs/manage/__init__.py index 96e70ccb..e6ce8e96 100644 --- a/plugins/web_ui/api/tabs/manage/__init__.py +++ b/plugins/web_ui/api/tabs/manage/__init__.py @@ -33,6 +33,7 @@ from .model import ( HandleRequest, LeaveGroup, Message, + MessageItem, Plugin, ReqResult, SendMessage, @@ -42,7 +43,7 @@ from .model import ( ) ws_router = APIRouter() -router = APIRouter() +router = APIRouter(prefix="/manage") SUB_PATTERN = r"\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))" @@ -50,6 +51,8 @@ GROUP_PATTERN = r'.*?Message (-?\d*) from (\d*)@\[群:(\d*)] "(.*)"' PRIVATE_PATTERN = r'.*?Message (-?\d*) from (\d*) "(.*)"' +IMAGE_PATTERN = r'\[CQ:image,.*,url=(.*);.*?\]' + @router.get("/get_group_list", dependencies=[authentication()], description="获取群组列表") async def _(bot_id: str) -> Result: """ @@ -323,8 +326,10 @@ async def _(bot_id: str, group_id: str) -> Result: like_plugin[name] = data[1] close_plugins = [] for module in g.close_plugins: - plugin = Plugin(module=module, plugin_name=module) - if plugin_data := plugin_data_manager.get(module): + module_ = module.replace(":super", "") + is_super_block = module.endswith(":super") + plugin = Plugin(module=module_, plugin_name=module, is_super_block=is_super_block) + if plugin_data := plugin_data_manager.get(module_): plugin.plugin_name = plugin_data.name close_plugins.append(plugin) task_list = [] @@ -374,48 +379,71 @@ async def _(param: SendMessage) -> Result: MSG_LIST = [] +ID2NAME = {} + + +async def message_handle(sub_log: str, type: Literal["private", "group"]): + global MSG_LIST, ID2NAME + pattern = PRIVATE_PATTERN if type == 'private' else GROUP_PATTERN + msg_id = None + uid = None + gid = None + msg = None + img_list = re.findall(IMAGE_PATTERN, sub_log) + if r := re.search(pattern, sub_log): + if type == 'private': + msg_id = r.group(1) + uid = r.group(2) + msg = r.group(3) + if uid not in ID2NAME: + user = await FriendUser.filter(user_id=uid).first() + ID2NAME[uid] = user.user_name or user.nickname + else: + msg_id = r.group(1) + uid = r.group(2) + gid = r.group(3) + msg = r.group(4) + if gid not in ID2NAME: + user = await GroupInfoUser.filter(user_id=uid, group_id=gid).first() + ID2NAME[gid] = user.user_name or user.nickname + if msg_id in MSG_LIST: + return + MSG_LIST.append(msg_id) + messages = [] + rep = re.split(r'\[CQ:image.*\]', msg) + if img_list: + for i in range(len(rep)): + messages.append(MessageItem(type="text", msg=rep[i])) + if i < len(img_list): + messages.append(MessageItem(type="img", msg=img_list[i])) + else: + messages = [MessageItem(type="text", msg=x) for x in rep] + return Message( + object_id=uid if type == 'private' else gid, + user_id=uid, + group_id=gid, + message=messages, + name=ID2NAME[uid], + ava_url=AVA_URL.format(uid), + ) + @ws_router.websocket("/chat") -async def _(websocket: WebSocket, group_id: Optional[str] = None, user_id: Optional[str] = None): - global MSG_LIST +async def _(websocket: WebSocket): await websocket.accept() async def log_listener(log: str): + global MSG_LIST, ID2NAME sub_log = re.sub(SUB_PATTERN, "", log) + img_list = re.findall(IMAGE_PATTERN, sub_log) if "message.private.friend" in log: - if r := re.search(PRIVATE_PATTERN, sub_log): - msg_id = r.group(1) - uid = r.group(2) - msg = r.group(3) - user = await FriendUser.filter(user_id=user_id).first() - name = user.user_name - if uid and uid == user_id and msg_id not in MSG_LIST: - MSG_LIST.append(msg_id) - message = Message( - user_id=uid, - message=msg, - name=name, - ava_url=AVA_URL.format(uid) - ) - await websocket.send_json(message.dict()) + if message := await message_handle(sub_log, 'private'): + await websocket.send_json(message.dict()) else: if r := re.search(GROUP_PATTERN, sub_log): - msg_id = r.group(1) - uid = r.group(2) - gid = r.group(3) - msg = r.group(4) - user = await GroupInfoUser.filter(user_id=uid, group_id=gid).first() - name = user.user_name or user.nickname - if gid and gid == group_id and msg_id not in MSG_LIST: - MSG_LIST.append(msg_id) - message = Message( - user_id=uid, - group_id=gid, - message=msg, - name=name, - ava_url=AVA_URL.format(uid) - ) + if message := await message_handle(sub_log, 'group'): await websocket.send_json(message.dict()) - + if len(MSG_LIST) > 30: + MSG_LIST = MSG_LIST[-1:] LOG_STORAGE.listeners.add(log_listener) try: while websocket.client_state == WebSocketState.CONNECTED: diff --git a/plugins/web_ui/api/tabs/manage/model.py b/plugins/web_ui/api/tabs/manage/model.py index bc8df37f..ab8b83f1 100644 --- a/plugins/web_ui/api/tabs/manage/model.py +++ b/plugins/web_ui/api/tabs/manage/model.py @@ -41,6 +41,8 @@ class Plugin(BaseModel): """模块名""" plugin_name: str """中文名""" + is_super_block: bool + """是否超级用户禁用""" class GroupResult(BaseModel): @@ -227,23 +229,33 @@ class GroupDetail(BaseModel): task: List[Task] """被动列表""" +class MessageItem(BaseModel): + + type: str + """消息类型""" + msg: str + """内容""" class Message(BaseModel): """ 消息 """ + object_id: str + """主体id user_id 或 group_id""" user_id: str """用户id""" group_id: Optional[str] = None """群组id""" - message: str + message: List[MessageItem] """消息""" name: str """用户名称""" ava_url: str """用户头像""" + + class SendMessage(BaseModel): """ 发送消息 diff --git a/plugins/web_ui/api/tabs/plugin_manage/__init__.py b/plugins/web_ui/api/tabs/plugin_manage/__init__.py index 6179d9c5..20e5fd97 100644 --- a/plugins/web_ui/api/tabs/plugin_manage/__init__.py +++ b/plugins/web_ui/api/tabs/plugin_manage/__init__.py @@ -2,7 +2,7 @@ import re from typing import List, Optional import cattrs -from fastapi import APIRouter +from fastapi import APIRouter, Query from configs.config import Config from services.log import logger @@ -17,26 +17,26 @@ from .model import ( PluginDetail, PluginInfo, PluginSwitch, - UpdateConfig, UpdatePlugin, ) -router = APIRouter() +router = APIRouter(prefix="/plugin") @router.get("/get_plugin_list", dependencies=[authentication()], deprecated="获取插件列表") def _( - plugin_type: PluginType, menu_type: Optional[str] = None + plugin_type: List[PluginType] = Query(None), menu_type: Optional[str] = None ) -> Result: """ 获取插件列表 :param plugin_type: 类型 normal, superuser, hidden, admin + :param menu_type: 菜单类型 """ 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: + if plugin_data and plugin_data.plugin_type in plugin_type: setting = plugin_data.plugin_setting or PluginSetting() plugin = plugin_data.plugin_status menu_type_ = getattr(setting, "plugin_type", ["无"])[0] From 45acf4a0941eaee340d31c95ff971618ffc945bc Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Sun, 14 Jan 2024 17:48:15 +0800 Subject: [PATCH 11/20] =?UTF-8?q?perf=F0=9F=91=8C:=20webui=E5=AE=8C?= =?UTF-8?q?=E5=96=84=E6=95=B0=E6=8D=AE=E5=BA=93=E7=AE=A1=E7=90=86api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/web_ui/api/tabs/database/__init__.py | 12 ++++--- .../web_ui/api/tabs/database/models/model.py | 3 ++ plugins/web_ui/api/tabs/manage/__init__.py | 11 +++++-- plugins/web_ui/base_model.py | 31 +++++++------------ 4 files changed, 31 insertions(+), 26 deletions(-) diff --git a/plugins/web_ui/api/tabs/database/__init__.py b/plugins/web_ui/api/tabs/database/__init__.py index 0f3eb985..da8c74e0 100644 --- a/plugins/web_ui/api/tabs/database/__init__.py +++ b/plugins/web_ui/api/tabs/database/__init__.py @@ -11,7 +11,7 @@ from configs.config import NICKNAME from services.db_context import TestSQL from utils.utils import get_matchers -from ....base_model import QueryModel, Result +from ....base_model import BaseResultModel, QueryModel, Result from ....config import QueryDateType from ....utils import authentication from .models.model import SqlModel, SqlText @@ -77,6 +77,7 @@ async def _(sql: SqlText, request: Request) -> Result: if sql.sql.lower().startswith("select"): db = Tortoise.get_connection("default") res = await db.execute_query_dict(sql.sql) + await SqlLog.add(ip or "0.0.0.0", sql.sql, "") return Result.ok(res, "执行成功啦!") else: result = await TestSQL.raw(sql.sql) @@ -89,11 +90,14 @@ async def _(sql: SqlText, request: Request) -> Result: @router.post("/get_sql_log", dependencies=[authentication()], description="sql日志列表") async def _(query: QueryModel) -> Result: - data = await SqlLog.all().offset((query.index - 1) * query.size).limit(query.size) - return Result.ok(data) + total = await SqlLog.all().count() + if (total % query.size): + total += 1 + data = await SqlLog.all().order_by("-id").offset((query.index - 1) * query.size).limit(query.size) + return Result.ok(BaseResultModel(total=total, data=data)) -@router.get("/get_sql", dependencies=[authentication()], description="常用sql") +@router.get("/get_common_sql", dependencies=[authentication()], description="常用sql") async def _(plugin_name: Optional[str] = None) -> Result: if plugin_name: return Result.ok(SQL_DICT.get(plugin_name)) diff --git a/plugins/web_ui/api/tabs/database/models/model.py b/plugins/web_ui/api/tabs/database/models/model.py index ed78c405..37c682a6 100644 --- a/plugins/web_ui/api/tabs/database/models/model.py +++ b/plugins/web_ui/api/tabs/database/models/model.py @@ -14,6 +14,9 @@ class SqlText(BaseModel): class SqlModel(BaseModel): + """ + 常用sql + """ name: str """插件中文名称""" diff --git a/plugins/web_ui/api/tabs/manage/__init__.py b/plugins/web_ui/api/tabs/manage/__init__.py index e6ce8e96..3bfdf9d5 100644 --- a/plugins/web_ui/api/tabs/manage/__init__.py +++ b/plugins/web_ui/api/tabs/manage/__init__.py @@ -51,6 +51,8 @@ GROUP_PATTERN = r'.*?Message (-?\d*) from (\d*)@\[群:(\d*)] "(.*)"' PRIVATE_PATTERN = r'.*?Message (-?\d*) from (\d*) "(.*)"' +AT_PATTERN = r'\[CQ:at,qq=(.*)\]' + IMAGE_PATTERN = r'\[CQ:image,.*,url=(.*);.*?\]' @router.get("/get_group_list", dependencies=[authentication()], description="获取群组列表") @@ -405,7 +407,12 @@ async def message_handle(sub_log: str, type: Literal["private", "group"]): msg = r.group(4) if gid not in ID2NAME: user = await GroupInfoUser.filter(user_id=uid, group_id=gid).first() - ID2NAME[gid] = user.user_name or user.nickname + ID2NAME[uid] = user.user_name or user.nickname + if at_list := re.findall(AT_PATTERN, msg): + user_list = await GroupInfoUser.filter(user_id__in=at_list, group_id=gid).all() + id2name = {u.user_id: (u.user_name or u.nickname) for u in user_list} + for qq in at_list: + msg = re.sub(rf'\[CQ:at,qq={qq}\]', f"@{id2name[qq] or ''}", msg) if msg_id in MSG_LIST: return MSG_LIST.append(msg_id) @@ -423,7 +430,7 @@ async def message_handle(sub_log: str, type: Literal["private", "group"]): user_id=uid, group_id=gid, message=messages, - name=ID2NAME[uid], + name=ID2NAME.get(uid) or "", ava_url=AVA_URL.format(uid), ) diff --git a/plugins/web_ui/base_model.py b/plugins/web_ui/base_model.py index 2ce1a82f..384d1a7c 100644 --- a/plugins/web_ui/base_model.py +++ b/plugins/web_ui/base_model.py @@ -74,6 +74,17 @@ class QueryModel(BaseModel, Generic[T]): if size < 1: raise ValueError("每页数量小于1...") return size + + +class BaseResultModel(BaseModel): + """ + 基础返回 + """ + + total: int + """总页数""" + data: Any + """数据""" class PluginConfig(BaseModel): @@ -89,26 +100,6 @@ class PluginConfig(BaseModel): has_type: bool -class Plugin(BaseModel): - """ - 插件 - """ - - model: 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 SystemStatus(BaseModel): """ From b656f89c8ea815301a471749819a5724590cadb1 Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Mon, 15 Jan 2024 01:19:37 +0800 Subject: [PATCH 12/20] =?UTF-8?q?perf=F0=9F=91=8C:=20=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E7=AE=A1=E7=90=86api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/web_ui/__init__.py | 2 + plugins/web_ui/api/system.py | 228 ------------------- plugins/web_ui/api/tabs/__init__.py | 1 + plugins/web_ui/api/tabs/database/__init__.py | 2 +- plugins/web_ui/api/tabs/main/__init__.py | 4 +- plugins/web_ui/api/tabs/system/__init__.py | 26 +++ plugins/web_ui/api/tabs/system/model.py | 35 +++ plugins/web_ui/base_model.py | 40 ---- plugins/web_ui/utils.py | 10 +- 9 files changed, 68 insertions(+), 280 deletions(-) delete mode 100644 plugins/web_ui/api/system.py create mode 100644 plugins/web_ui/api/tabs/system/__init__.py create mode 100644 plugins/web_ui/api/tabs/system/model.py diff --git a/plugins/web_ui/__init__.py b/plugins/web_ui/__init__.py index 3de51bfe..00bcd2b5 100644 --- a/plugins/web_ui/__init__.py +++ b/plugins/web_ui/__init__.py @@ -20,6 +20,7 @@ from .api.tabs.main import ws_router as status_routes from .api.tabs.manage import router as manage_router from .api.tabs.manage import ws_router as chat_routes from .api.tabs.plugin_manage import router as plugin_router +from .api.tabs.system import router as system_router from .auth import router as auth_router driver = nonebot.get_driver() @@ -37,6 +38,7 @@ BaseApiRouter.include_router(main_router) BaseApiRouter.include_router(manage_router) BaseApiRouter.include_router(database_router) BaseApiRouter.include_router(plugin_router) +BaseApiRouter.include_router(system_router) WsApiRouter = APIRouter(prefix="/zhenxun/socket") diff --git a/plugins/web_ui/api/system.py b/plugins/web_ui/api/system.py deleted file mode 100644 index dacc1d9f..00000000 --- a/plugins/web_ui/api/system.py +++ /dev/null @@ -1,228 +0,0 @@ -# import asyncio -# import os -# from datetime import datetime -# from pathlib import Path -# from typing import Dict, Optional, Union - -# import psutil -# import ujson as json -# from fastapi import APIRouter - -# from configs.path_config import ( -# DATA_PATH, -# FONT_PATH, -# IMAGE_PATH, -# LOG_PATH, -# RECORD_PATH, -# TEMP_PATH, -# TEXT_PATH, -# ) -# from services.log import logger -# from utils.http_utils import AsyncHttpx - -# from ..base_model import ( -# Result, -# SystemFolderSize, -# SystemNetwork, -# SystemResult, -# SystemStatus, -# SystemStatusList, -# ) -# from ..utils import authentication - -# CPU_DATA_PATH = DATA_PATH / "system" / "cpu.json" -# MEMORY_DATA_PATH = DATA_PATH / "system" / "memory.json" -# DISK_DATA_PATH = DATA_PATH / "system" / "disk.json" -# CPU_DATA_PATH.parent.mkdir(exist_ok=True, parents=True) -# cpu_data = {"data": []} -# memory_data = {"data": []} -# disk_data = {"data": []} - -# router = APIRouter() - - -# @router.get("/system", dependencies=[authentication()]) -# async def _() -> Result: -# return await get_system_data() - - -# @router.get("/status", dependencies=[authentication()]) -# async def _() -> Result: -# return Result.ok( -# await asyncio.get_event_loop().run_in_executor(None, _get_system_status), -# ) - - -# @router.get("/system/disk", dependencies=[authentication()]) -# async def _(type_: Optional[str] = None) -> Result: -# return Result.ok( -# data=await asyncio.get_event_loop().run_in_executor( -# None, _get_system_disk, type_ -# ), -# ) - - -# @router.get("/system/statusList", dependencies=[authentication()]) -# async def _() -> Result: -# global cpu_data, memory_data, disk_data -# await asyncio.get_event_loop().run_in_executor(None, _get_system_status) -# cpu_rst = cpu_data["data"][-10:] if len(cpu_data["data"]) > 10 else cpu_data["data"] -# memory_rst = ( -# memory_data["data"][-10:] -# if len(memory_data["data"]) > 10 -# else memory_data["data"] -# ) -# disk_rst = ( -# disk_data["data"][-10:] if len(disk_data["data"]) > 10 else disk_data["data"] -# ) -# return Result.ok( -# SystemStatusList( -# cpu_data=cpu_rst, -# memory_data=memory_rst, -# disk_data=disk_rst, -# ), -# ) - - -# async def get_system_data(): -# """ -# 说明: -# 获取系统信息,资源文件大小,网络状态等 -# """ -# baidu = 200 -# google = 200 -# try: -# await AsyncHttpx.get("https://www.baidu.com/", timeout=5) -# except Exception as e: -# logger.warning(f"访问BaiDu失败... {type(e)}: {e}") -# baidu = 404 -# try: -# await AsyncHttpx.get("https://www.google.com/", timeout=5) -# except Exception as e: -# logger.warning(f"访问Google失败... {type(e)}: {e}") -# google = 404 -# network = SystemNetwork(baidu=baidu, google=google) -# disk = await asyncio.get_event_loop().run_in_executor(None, _get_system_disk, None) -# status = await asyncio.get_event_loop().run_in_executor(None, _get_system_status) -# return Result.ok( -# SystemResult( -# status=status, -# network=network, -# disk=disk, # type: ignore -# check_time=datetime.now().replace(microsecond=0), -# ), -# ) - - -# def _get_system_status() -> SystemStatus: -# """ -# 说明: -# 获取系统信息等 -# """ -# cpu = psutil.cpu_percent() -# memory = psutil.virtual_memory().percent -# disk = psutil.disk_usage("/").percent -# save_system_data(cpu, memory, disk) -# return SystemStatus( -# cpu=cpu, -# memory=memory, -# disk=disk, -# check_time=datetime.now().replace(microsecond=0), -# ) - - -# def _get_system_disk( -# type_: Optional[str], -# ) -> Union[SystemFolderSize, Dict[str, Union[float, datetime]]]: -# """ -# 说明: -# 获取资源文件大小等 -# """ -# if not type_: -# disk = SystemFolderSize( -# font_dir_size=_get_dir_size(FONT_PATH) / 1024 / 1024, -# image_dir_size=_get_dir_size(IMAGE_PATH) / 1024 / 1024, -# text_dir_size=_get_dir_size(TEXT_PATH) / 1024 / 1024, -# record_dir_size=_get_dir_size(RECORD_PATH) / 1024 / 1024, -# temp_dir_size=_get_dir_size(TEMP_PATH) / 1024 / 102, -# data_dir_size=_get_dir_size(DATA_PATH) / 1024 / 1024, -# log_dir_size=_get_dir_size(LOG_PATH) / 1024 / 1024, -# check_time=datetime.now().replace(microsecond=0), -# ) -# return disk -# else: -# if type_ == "image": -# dir_path = IMAGE_PATH -# elif type_ == "font": -# dir_path = FONT_PATH -# elif type_ == "text": -# dir_path = TEXT_PATH -# elif type_ == "record": -# dir_path = RECORD_PATH -# elif type_ == "data": -# dir_path = DATA_PATH -# elif type_ == "temp": -# dir_path = TEMP_PATH -# else: -# dir_path = LOG_PATH -# dir_map = {} -# other_file_size = 0 -# for file in os.listdir(dir_path): -# file = Path(dir_path / file) -# if file.is_dir(): -# dir_map[file.name] = _get_dir_size(file) / 1024 / 1024 -# else: -# other_file_size += os.path.getsize(file) / 1024 / 1024 -# dir_map["其他文件"] = other_file_size -# dir_map["check_time"] = datetime.now().replace(microsecond=0) -# return dir_map - - -# def _get_dir_size(dir_path: Path) -> float: -# """ -# 说明: -# 获取文件夹大小 -# 参数: -# :param dir_path: 文件夹路径 -# """ -# size = 0 -# for root, dirs, files in os.walk(dir_path): -# size += sum([os.path.getsize(os.path.join(root, name)) for name in files]) -# return size - - -# def save_system_data(cpu: float, memory: float, disk: float): -# """ -# 说明: -# 保存一些系统信息 -# 参数: -# :param cpu: cpu -# :param memory: memory -# :param disk: disk -# """ -# global cpu_data, memory_data, disk_data -# if CPU_DATA_PATH.exists() and not cpu_data["data"]: -# with open(CPU_DATA_PATH, "r") as f: -# cpu_data = json.load(f) -# if MEMORY_DATA_PATH.exists() and not memory_data["data"]: -# with open(MEMORY_DATA_PATH, "r") as f: -# memory_data = json.load(f) -# if DISK_DATA_PATH.exists() and not disk_data["data"]: -# with open(DISK_DATA_PATH, "r") as f: -# disk_data = json.load(f) -# now = str(datetime.now().time().replace(microsecond=0)) -# cpu_data["data"].append({"time": now, "data": cpu}) -# memory_data["data"].append({"time": now, "data": memory}) -# disk_data["data"].append({"time": now, "data": disk}) -# if len(cpu_data["data"]) > 50: -# cpu_data["data"] = cpu_data["data"][-50:] -# if len(memory_data["data"]) > 50: -# memory_data["data"] = memory_data["data"][-50:] -# if len(disk_data["data"]) > 50: -# disk_data["data"] = disk_data["data"][-50:] -# with open(CPU_DATA_PATH, "w") as f: -# json.dump(cpu_data, f, indent=4, ensure_ascii=False) -# with open(MEMORY_DATA_PATH, "w") as f: -# json.dump(memory_data, f, indent=4, ensure_ascii=False) -# with open(DISK_DATA_PATH, "w") as f: -# json.dump(disk_data, f, indent=4, ensure_ascii=False) diff --git a/plugins/web_ui/api/tabs/__init__.py b/plugins/web_ui/api/tabs/__init__.py index c2758a6a..99ed6ea1 100644 --- a/plugins/web_ui/api/tabs/__init__.py +++ b/plugins/web_ui/api/tabs/__init__.py @@ -2,3 +2,4 @@ from .database import * from .main import * from .manage import * from .plugin_manage import * +from .system import * diff --git a/plugins/web_ui/api/tabs/database/__init__.py b/plugins/web_ui/api/tabs/database/__init__.py index da8c74e0..f047d711 100644 --- a/plugins/web_ui/api/tabs/database/__init__.py +++ b/plugins/web_ui/api/tabs/database/__init__.py @@ -101,4 +101,4 @@ async def _(query: QueryModel) -> Result: async def _(plugin_name: Optional[str] = None) -> Result: if plugin_name: return Result.ok(SQL_DICT.get(plugin_name)) - return Result.ok(SQL_DICT) + return Result.ok(str(SQL_DICT)) diff --git a/plugins/web_ui/api/tabs/main/__init__.py b/plugins/web_ui/api/tabs/main/__init__.py index b5d6e63d..fdbe8888 100644 --- a/plugins/web_ui/api/tabs/main/__init__.py +++ b/plugins/web_ui/api/tabs/main/__init__.py @@ -246,14 +246,14 @@ async def _(date_type: Optional[QueryDateType] = None) -> Result: @ws_router.websocket("/system_status") -async def system_logs_realtime(websocket: WebSocket): +async def system_logs_realtime(websocket: WebSocket, sleep: Optional[int] = 5): await websocket.accept() logger.debug("ws system_status is connect") try: while websocket.client_state == WebSocketState.CONNECTED: system_status = await get_system_status() await websocket.send_text(system_status.json()) - await asyncio.sleep(5) + await asyncio.sleep(sleep) except (WebSocketDisconnect, ConnectionClosedError, ConnectionClosedOK): pass return diff --git a/plugins/web_ui/api/tabs/system/__init__.py b/plugins/web_ui/api/tabs/system/__init__.py new file mode 100644 index 00000000..cc696e11 --- /dev/null +++ b/plugins/web_ui/api/tabs/system/__init__.py @@ -0,0 +1,26 @@ +import os +from pathlib import Path +from typing import List, Optional + +from fastapi import APIRouter + +from ....base_model import Result +from ....utils import authentication, get_system_disk +from .model import DirFile + +router = APIRouter(prefix="/system") + + + +@router.get("/get_dir_list", dependencies=[authentication()], description="获取文件列表") +async def _(path: Optional[str] = None) -> Result: + base_path = Path(path) if path else Path() + data_list = [] + for file in os.listdir(base_path): + data_list.append(DirFile(is_file=not (base_path / file).is_dir(), name=file, parent=path)) + return Result.ok(data_list) + + +@router.get("/get_resources_size", dependencies=[authentication()], description="获取文件列表") +async def _(type: Optional[str] = None) -> Result: + return Result.ok(await get_system_disk(type)) \ No newline at end of file diff --git a/plugins/web_ui/api/tabs/system/model.py b/plugins/web_ui/api/tabs/system/model.py new file mode 100644 index 00000000..b00a582c --- /dev/null +++ b/plugins/web_ui/api/tabs/system/model.py @@ -0,0 +1,35 @@ + + + +from datetime import datetime +from typing import Literal, Optional + +from pydantic import BaseModel + + +class DirFile(BaseModel): + + """ + 文件或文件夹 + """ + + is_file: bool + """是否为文件""" + name: str + """文件夹或文件名称""" + parent: Optional[str] = None + """父级""" + +class SystemFolderSize(BaseModel): + """ + 资源文件占比 + """ + + font_dir_size: float + image_dir_size: float + text_dir_size: float + record_dir_size: float + temp_dir_size: float + data_dir_size: float + log_dir_size: float + check_time: datetime \ No newline at end of file diff --git a/plugins/web_ui/base_model.py b/plugins/web_ui/base_model.py index 384d1a7c..f05a685e 100644 --- a/plugins/web_ui/base_model.py +++ b/plugins/web_ui/base_model.py @@ -87,20 +87,6 @@ class BaseResultModel(BaseModel): """数据""" -class PluginConfig(BaseModel): - """ - 插件配置项 - """ - - module: str - key: str - value: Optional[Any] - help: Optional[str] - default_value: Optional[Any] - has_type: bool - - - class SystemStatus(BaseModel): """ 系统状态 @@ -112,13 +98,6 @@ class SystemStatus(BaseModel): check_time: datetime -class SystemNetwork(BaseModel): - """ - 系统网络状态 - """ - - baidu: int - google: int class SystemFolderSize(BaseModel): @@ -136,22 +115,3 @@ class SystemFolderSize(BaseModel): check_time: datetime -class SystemStatusList(BaseModel): - """ - 状态记录 - """ - - cpu_data: List[Dict[str, Union[float, str]]] - memory_data: List[Dict[str, Union[float, str]]] - disk_data: List[Dict[str, Union[float, str]]] - - -class SystemResult(BaseModel): - """ - 系统api返回 - """ - - status: SystemStatus - network: SystemNetwork - disk: SystemFolderSize - check_time: datetime diff --git a/plugins/web_ui/utils.py b/plugins/web_ui/utils.py index cce1c692..a4b7d6d2 100644 --- a/plugins/web_ui/utils.py +++ b/plugins/web_ui/utils.py @@ -21,15 +21,7 @@ from configs.path_config import ( TEXT_PATH, ) -from .base_model import ( - Result, - SystemFolderSize, - SystemNetwork, - SystemResult, - SystemStatus, - SystemStatusList, - User, -) +from .base_model import SystemFolderSize, SystemStatus, User SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" ALGORITHM = "HS256" From 72b0b011ee5e03a47235637c07c775012c78dc93 Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Mon, 15 Jan 2024 13:00:17 +0800 Subject: [PATCH 13/20] =?UTF-8?q?perf=F0=9F=91=8C:=20=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E7=AE=A1=E7=90=86api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/web_ui/api/tabs/system/__init__.py | 4 +- plugins/web_ui/api/tabs/system/model.py | 14 ---- plugins/web_ui/base_model.py | 16 ++--- plugins/web_ui/utils.py | 81 +++++++++++----------- 4 files changed, 51 insertions(+), 64 deletions(-) diff --git a/plugins/web_ui/api/tabs/system/__init__.py b/plugins/web_ui/api/tabs/system/__init__.py index cc696e11..ef80d1bc 100644 --- a/plugins/web_ui/api/tabs/system/__init__.py +++ b/plugins/web_ui/api/tabs/system/__init__.py @@ -22,5 +22,5 @@ async def _(path: Optional[str] = None) -> Result: @router.get("/get_resources_size", dependencies=[authentication()], description="获取文件列表") -async def _(type: Optional[str] = None) -> Result: - return Result.ok(await get_system_disk(type)) \ No newline at end of file +async def _(full_path: Optional[str] = None) -> Result: + return Result.ok(await get_system_disk(full_path)) \ No newline at end of file diff --git a/plugins/web_ui/api/tabs/system/model.py b/plugins/web_ui/api/tabs/system/model.py index b00a582c..01d42218 100644 --- a/plugins/web_ui/api/tabs/system/model.py +++ b/plugins/web_ui/api/tabs/system/model.py @@ -19,17 +19,3 @@ class DirFile(BaseModel): """文件夹或文件名称""" parent: Optional[str] = None """父级""" - -class SystemFolderSize(BaseModel): - """ - 资源文件占比 - """ - - font_dir_size: float - image_dir_size: float - text_dir_size: float - record_dir_size: float - temp_dir_size: float - data_dir_size: float - log_dir_size: float - check_time: datetime \ No newline at end of file diff --git a/plugins/web_ui/base_model.py b/plugins/web_ui/base_model.py index f05a685e..ee7a93e4 100644 --- a/plugins/web_ui/base_model.py +++ b/plugins/web_ui/base_model.py @@ -105,13 +105,13 @@ class SystemFolderSize(BaseModel): 资源文件占比 """ - font_dir_size: float - image_dir_size: float - text_dir_size: float - record_dir_size: float - temp_dir_size: float - data_dir_size: float - log_dir_size: float - check_time: datetime + name: str + """名称""" + size: float + """大小""" + full_path: Optional[str] + """完整路径""" + is_dir: bool + """是否为文件夹""" diff --git a/plugins/web_ui/utils.py b/plugins/web_ui/utils.py index a4b7d6d2..eba64b63 100644 --- a/plugins/web_ui/utils.py +++ b/plugins/web_ui/utils.py @@ -1,7 +1,7 @@ import os from datetime import datetime, timedelta from pathlib import Path -from typing import Any, Dict, Optional, Union +from typing import Any, Dict, List, Optional, Union import psutil import ujson as json @@ -123,47 +123,48 @@ def get_system_status() -> SystemStatus: @run_sync def get_system_disk( - type_: Optional[str], -) -> Union[SystemFolderSize, Dict[str, Union[float, datetime]]]: + full_path: Optional[str], +) -> List[SystemFolderSize]: """ 说明: 获取资源文件大小等 """ - if not type_: - disk = SystemFolderSize( - font_dir_size=_get_dir_size(FONT_PATH) / 1024 / 1024, - image_dir_size=_get_dir_size(IMAGE_PATH) / 1024 / 1024, - text_dir_size=_get_dir_size(TEXT_PATH) / 1024 / 1024, - record_dir_size=_get_dir_size(RECORD_PATH) / 1024 / 1024, - temp_dir_size=_get_dir_size(TEMP_PATH) / 1024 / 102, - data_dir_size=_get_dir_size(DATA_PATH) / 1024 / 1024, - log_dir_size=_get_dir_size(LOG_PATH) / 1024 / 1024, - check_time=datetime.now().replace(microsecond=0), - ) - return disk - else: - if type_ == "image": - dir_path = IMAGE_PATH - elif type_ == "font": - dir_path = FONT_PATH - elif type_ == "text": - dir_path = TEXT_PATH - elif type_ == "record": - dir_path = RECORD_PATH - elif type_ == "data": - dir_path = DATA_PATH - elif type_ == "temp": - dir_path = TEMP_PATH + base_path = Path(full_path) if full_path else Path() + other_size = 0 + data_list = [] + for file in os.listdir(base_path): + f = base_path / file + if f.is_dir(): + size = _get_dir_size(f) / 1024 / 1024 + data_list.append(SystemFolderSize(name=file, size=size, full_path=str(f), is_dir=True)) else: - dir_path = LOG_PATH - dir_map = {} - other_file_size = 0 - for file in os.listdir(dir_path): - file = Path(dir_path / file) - if file.is_dir(): - dir_map[file.name] = _get_dir_size(file) / 1024 / 1024 - else: - other_file_size += os.path.getsize(file) / 1024 / 1024 - dir_map["其他文件"] = other_file_size - dir_map["check_time"] = datetime.now().replace(microsecond=0) - return dir_map + other_size += f.stat().st_size / 1024 / 1024 + if other_size: + data_list.append(SystemFolderSize(name='other_file', size=other_size, full_path=full_path, is_dir=False)) + return data_list + # else: + # if type_ == "image": + # dir_path = IMAGE_PATH + # elif type_ == "font": + # dir_path = FONT_PATH + # elif type_ == "text": + # dir_path = TEXT_PATH + # elif type_ == "record": + # dir_path = RECORD_PATH + # elif type_ == "data": + # dir_path = DATA_PATH + # elif type_ == "temp": + # dir_path = TEMP_PATH + # else: + # dir_path = LOG_PATH + # dir_map = {} + # other_file_size = 0 + # for file in os.listdir(dir_path): + # file = Path(dir_path / file) + # if file.is_dir(): + # dir_map[file.name] = _get_dir_size(file) / 1024 / 1024 + # else: + # other_file_size += os.path.getsize(file) / 1024 / 1024 + # dir_map["其他文件"] = other_file_size + # dir_map["check_time"] = datetime.now().replace(microsecond=0) + # return dir_map From 1a9a19e1cb858c78a8932a044581e8487cb226a0 Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Sun, 21 Jan 2024 06:34:39 +0800 Subject: [PATCH 14/20] =?UTF-8?q?feat=E2=9C=A8:=20webui=E7=BB=93=E6=9E=84?= =?UTF-8?q?=E6=A0=91=E6=B7=BB=E5=8A=A0=E5=88=A0=E9=99=A4/=E6=96=B0?= =?UTF-8?q?=E5=A2=9E/=E9=87=8D=E5=91=BD=E5=90=8D=E6=96=87=E4=BB=B6/?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=A4=B9api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/web_ui/api/tabs/system/__init__.py | 65 +++++++++++++++++++++- plugins/web_ui/api/tabs/system/model.py | 32 +++++++++++ 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/plugins/web_ui/api/tabs/system/__init__.py b/plugins/web_ui/api/tabs/system/__init__.py index ef80d1bc..36755ff4 100644 --- a/plugins/web_ui/api/tabs/system/__init__.py +++ b/plugins/web_ui/api/tabs/system/__init__.py @@ -1,4 +1,5 @@ import os +import shutil from pathlib import Path from typing import List, Optional @@ -6,7 +7,7 @@ from fastapi import APIRouter from ....base_model import Result from ....utils import authentication, get_system_disk -from .model import DirFile +from .model import AddFile, DeleteFile, DirFile, RenameFile router = APIRouter(prefix="/system") @@ -23,4 +24,64 @@ async def _(path: Optional[str] = None) -> Result: @router.get("/get_resources_size", dependencies=[authentication()], description="获取文件列表") async def _(full_path: Optional[str] = None) -> Result: - return Result.ok(await get_system_disk(full_path)) \ No newline at end of file + return Result.ok(await get_system_disk(full_path)) + + +@router.post("/delete_file", dependencies=[authentication()], description="删除文件") +async def _(param: DeleteFile) -> Result: + path = Path(param.full_path) + if not path or not path.exists(): + return Result.warning_("文件不存在...") + try: + path.unlink() + return Result.ok('删除成功!') + except Exception as e: + return Result.warning_('删除失败: ' + str(e)) + +@router.post("/delete_folder", dependencies=[authentication()], description="删除文件夹") +async def _(param: DeleteFile) -> Result: + path = Path(param.full_path) + if not path or not path.exists() or path.is_file(): + return Result.warning_("文件夹不存在...") + try: + shutil.rmtree(path.absolute()) + return Result.ok('删除成功!') + except Exception as e: + return Result.warning_('删除失败: ' + str(e)) + + +@router.post("/rename_file", dependencies=[authentication()], description="重命名文件") +async def _(param: RenameFile) -> Result: + path = (Path(param.parent) / param.old_name) if param.parent else Path(param.old_name) + if not path or not path.exists(): + return Result.warning_("文件不存在...") + try: + path.rename(path.parent / param.name) + return Result.ok('重命名成功!') + except Exception as e: + return Result.warning_('重命名失败: ' + str(e)) + + +@router.post("/rename_folder", dependencies=[authentication()], description="重命名文件夹") +async def _(param: RenameFile) -> Result: + path = (Path(param.parent) / param.old_name) if param.parent else Path(param.old_name) + if not path or not path.exists() or path.is_file(): + return Result.warning_("文件夹不存在...") + try: + new_path = path.parent / param.name + shutil.move(path.absolute(), new_path.absolute()) + return Result.ok('重命名成功!') + except Exception as e: + return Result.warning_('重命名失败: ' + str(e)) + + +@router.post("/add_file", dependencies=[authentication()], description="新建文件") +async def _(param: AddFile) -> Result: + path = (Path(param.parent) / param.name) if param.parent else Path(param.name) + if path.exists(): + return Result.warning_("文件已存在...") + try: + path.open('w') + return Result.ok('新建文件成功!') + except Exception as e: + return Result.warning_('新建文件失败: ' + str(e)) \ No newline at end of file diff --git a/plugins/web_ui/api/tabs/system/model.py b/plugins/web_ui/api/tabs/system/model.py index 01d42218..af4f6209 100644 --- a/plugins/web_ui/api/tabs/system/model.py +++ b/plugins/web_ui/api/tabs/system/model.py @@ -19,3 +19,35 @@ class DirFile(BaseModel): """文件夹或文件名称""" parent: Optional[str] = None """父级""" + +class DeleteFile(BaseModel): + + """ + 删除文件 + """ + + full_path: str + """文件全路径""" + +class RenameFile(BaseModel): + + """ + 删除文件 + """ + parent: Optional[str] + """父路径""" + old_name: str + """旧名称""" + name: str + """新名称""" + + +class AddFile(BaseModel): + + """ + 新建文件 + """ + parent: Optional[str] + """父路径""" + name: str + """新名称""" From 45d19d1cdd96827aa4a5daad1d60f05b16e74be0 Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Sun, 21 Jan 2024 22:27:03 +0800 Subject: [PATCH 15/20] =?UTF-8?q?feat=E2=9C=A8:=20=20=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E7=BB=93=E6=9E=84=E6=A0=91=E7=BC=96=E8=BE=91=E6=9F=A5=E7=9C=8B?= =?UTF-8?q?api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/web_ui/api/tabs/system/__init__.py | 26 +++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/plugins/web_ui/api/tabs/system/__init__.py b/plugins/web_ui/api/tabs/system/__init__.py index 36755ff4..ec41df26 100644 --- a/plugins/web_ui/api/tabs/system/__init__.py +++ b/plugins/web_ui/api/tabs/system/__init__.py @@ -84,4 +84,28 @@ async def _(param: AddFile) -> Result: path.open('w') return Result.ok('新建文件成功!') except Exception as e: - return Result.warning_('新建文件失败: ' + str(e)) \ No newline at end of file + return Result.warning_('新建文件失败: ' + str(e)) + + +@router.post("/add_folder", dependencies=[authentication()], description="新建文件夹") +async def _(param: AddFile) -> Result: + path = (Path(param.parent) / param.name) if param.parent else Path(param.name) + if path.exists(): + return Result.warning_("文件夹已存在...") + try: + path.mkdir() + return Result.ok('新建文件夹成功!') + except Exception as e: + return Result.warning_('新建文件夹失败: ' + str(e)) + + +@router.get("/read_file", dependencies=[authentication()], description="读取文件") +async def _(full_path: str) -> Result: + path = Path(full_path) + if not path.exists(): + return Result.warning_("文件不存在...") + try: + text = path.read_text(encoding='utf-8') + return Result.ok(text) + except Exception as e: + return Result.warning_('新建文件夹失败: ' + str(e)) \ No newline at end of file From 428f374a8c6418279b67757e66312304896f4d01 Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Sun, 21 Jan 2024 22:34:05 +0800 Subject: [PATCH 16/20] =?UTF-8?q?feat=E2=9C=A8:=20=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E7=BB=93=E6=9E=84=E6=A0=91=E7=BC=96=E8=BE=91=E6=96=87=E4=BB=B6?= =?UTF-8?q?api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/web_ui/api/tabs/system/__init__.py | 12 +++++++++++- plugins/web_ui/api/tabs/system/model.py | 11 +++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/plugins/web_ui/api/tabs/system/__init__.py b/plugins/web_ui/api/tabs/system/__init__.py index ec41df26..61430144 100644 --- a/plugins/web_ui/api/tabs/system/__init__.py +++ b/plugins/web_ui/api/tabs/system/__init__.py @@ -7,7 +7,7 @@ from fastapi import APIRouter from ....base_model import Result from ....utils import authentication, get_system_disk -from .model import AddFile, DeleteFile, DirFile, RenameFile +from .model import AddFile, DeleteFile, DirFile, RenameFile, SaveFile router = APIRouter(prefix="/system") @@ -107,5 +107,15 @@ async def _(full_path: str) -> Result: try: text = path.read_text(encoding='utf-8') return Result.ok(text) + except Exception as e: + return Result.warning_('新建文件夹失败: ' + str(e)) + +@router.post("/save_file", dependencies=[authentication()], description="读取文件") +async def _(param: SaveFile) -> Result: + path = Path(param.full_path) + try: + with path.open('w') as f: + f.write(param.content) + return Result.ok("更新成功!") except Exception as e: return Result.warning_('新建文件夹失败: ' + str(e)) \ No newline at end of file diff --git a/plugins/web_ui/api/tabs/system/model.py b/plugins/web_ui/api/tabs/system/model.py index af4f6209..b3b5a45f 100644 --- a/plugins/web_ui/api/tabs/system/model.py +++ b/plugins/web_ui/api/tabs/system/model.py @@ -51,3 +51,14 @@ class AddFile(BaseModel): """父路径""" name: str """新名称""" + + +class SaveFile(BaseModel): + + """ + 保存文件 + """ + full_path: str + """全路径""" + content: str + """内容""" From 66c9b8045da7c75574d7ac5b9a68ca0fea2227ce Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Mon, 22 Jan 2024 05:33:41 +0800 Subject: [PATCH 17/20] =?UTF-8?q?perf=F0=9F=91=8C:=20webui=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E7=BB=86=E8=8A=82=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/web_ui/auth/__init__.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/plugins/web_ui/auth/__init__.py b/plugins/web_ui/auth/__init__.py index 0040be85..d927977f 100644 --- a/plugins/web_ui/auth/__init__.py +++ b/plugins/web_ui/auth/__init__.py @@ -5,6 +5,8 @@ import nonebot from fastapi import APIRouter, Depends from fastapi.security import OAuth2PasswordRequestForm +from configs.config import Config + from ..base_model import Result from ..utils import ( ACCESS_TOKEN_EXPIRE_MINUTES, @@ -22,13 +24,14 @@ router = APIRouter() @router.post("/login") async def login_get_token(form_data: OAuth2PasswordRequestForm = Depends()): - if user := get_user(form_data.username): - if user.password != form_data.password: - return Result.fail("真笨, 密码都能记错!", 999) - else: + username = Config.get_config("web-ui", "username") + password = Config.get_config("web-ui", "password") + if not username or not password: return Result.fail("你滴配置文件里用户名密码配置项为空", 998) + if username != form_data.username or password != form_data.password: + return Result.fail("真笨, 账号密码都能记错!", 999) access_token = create_token( - user=user, expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) + user=get_user(form_data.username), expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) ) token_data["token"].append(access_token) if len(token_data["token"]) > 3: From 38c79a81e3d46de23170e786f02df83b222454e4 Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Thu, 25 Jan 2024 04:08:12 +0800 Subject: [PATCH 18/20] =?UTF-8?q?perf=F0=9F=91=8C:=20=E7=BB=86=E8=8A=82?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/web_ui/api/tabs/main/__init__.py | 9 ++++++++- plugins/web_ui/api/tabs/main/model.py | 13 +++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/plugins/web_ui/api/tabs/main/__init__.py b/plugins/web_ui/api/tabs/main/__init__.py index fdbe8888..ac892e65 100644 --- a/plugins/web_ui/api/tabs/main/__init__.py +++ b/plugins/web_ui/api/tabs/main/__init__.py @@ -1,6 +1,7 @@ import asyncio import time from datetime import datetime, timedelta +from pathlib import Path from typing import List, Optional import nonebot @@ -84,7 +85,13 @@ async def _(bot_id: Optional[str] = None) -> Result: 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") - + version_file = Path() / "__version__" + if version_file.exists(): + if text := version_file.open().read(): + if ver := text.replace("__version__: ", "").strip(): + select_bot.version = ver + day_call = await Statistics.filter(create_time__gte=now - timedelta(hours=now.hour)).count() + select_bot.day_call = day_call return Result.ok(bot_list, "拿到信息啦!") return Result.warning_("无Bot连接...") diff --git a/plugins/web_ui/api/tabs/main/model.py b/plugins/web_ui/api/tabs/main/model.py index bced1817..7dd770c6 100644 --- a/plugins/web_ui/api/tabs/main/model.py +++ b/plugins/web_ui/api/tabs/main/model.py @@ -35,14 +35,6 @@ class BaseInfo(BaseModel): """群聊数量""" received_messages: int = 0 """今日 累计接收消息""" - # received_messages_day: int = 0 - # """今日累计接收消息""" - # received_messages_week: int = 0 - # """一周内累计接收消息""" - # received_messages_month: int = 0 - # """一月内累计接收消息""" - # received_messages_year: int = 0 - # """一年内累计接受消息""" connect_time: int = 0 """连接时间""" connect_date: Optional[datetime] = None @@ -60,6 +52,11 @@ class BaseInfo(BaseModel): config: Optional[Config] = None """nb配置""" + day_call: int = 0 + """今日调用插件次数""" + version: str = "unknown" + """真寻版本""" + class Config: arbitrary_types_allowed = True From 3c9b56f35f80e7402aff4c5d4eb1aca151206bf3 Mon Sep 17 00:00:00 2001 From: HibiKier <45528451+HibiKier@users.noreply.github.com> Date: Thu, 25 Jan 2024 18:32:51 +0800 Subject: [PATCH 19/20] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 4b74dee2..ad1eaaac 100644 --- a/README.md +++ b/README.md @@ -338,7 +338,6 @@ PS: **ARM平台** 请使用全量版 同时 **如果你的机器 RAM < 1G 可能 ### 2023/12/28 * 修复B站动态获取失败的时候,会发送空消息 -* test ### 2023/9/6 From a3004438a9260631ec6deb39f99f96f63e32ac36 Mon Sep 17 00:00:00 2001 From: HibiKier <45528451+HibiKier@users.noreply.github.com> Date: Thu, 25 Jan 2024 18:35:48 +0800 Subject: [PATCH 20/20] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index ad1eaaac..476b3780 100644 --- a/README.md +++ b/README.md @@ -335,6 +335,10 @@ PS: **ARM平台** 请使用全量版 同时 **如果你的机器 RAM < 1G 可能 ## 更新 +### 2024/1/25 + +* 重构webui + ### 2023/12/28 * 修复B站动态获取失败的时候,会发送空消息