From 62e29f389b91d1f2b34c4755331ecc2eefe531be Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Sun, 2 Apr 2023 22:57:36 +0800 Subject: [PATCH] UPDATE WEB UI --- plugins/web_ui/__init__.py | 46 +++-- plugins/web_ui/api/group.py | 61 ++++--- plugins/web_ui/api/plugins.py | 249 ++++++++++----------------- plugins/web_ui/api/request.py | 118 +++++++------ plugins/web_ui/api/system.py | 44 +++-- plugins/web_ui/auth/__init__.py | 107 +++--------- plugins/web_ui/config.py | 131 -------------- plugins/web_ui/models/model.py | 182 ++++++++++++++++++++ plugins/web_ui/models/params.py | 61 +++++++ plugins/web_ui/utils.py | 59 +++++++ utils/manager/data_class.py | 14 +- utils/manager/models.py | 2 +- utils/manager/plugin_data_manager.py | 2 +- 13 files changed, 583 insertions(+), 493 deletions(-) create mode 100644 plugins/web_ui/models/model.py create mode 100644 plugins/web_ui/models/params.py create mode 100644 plugins/web_ui/utils.py diff --git a/plugins/web_ui/__init__.py b/plugins/web_ui/__init__.py index 866a76ca..5af537d7 100644 --- a/plugins/web_ui/__init__.py +++ b/plugins/web_ui/__init__.py @@ -1,32 +1,40 @@ +import nonebot +from fastapi import APIRouter, FastAPI from nonebot.adapters.onebot.v11 import Bot, MessageEvent from nonebot.matcher import Matcher -from nonebot.message import IgnoredException, run_preprocessor +from nonebot.message import run_preprocessor from nonebot.typing import T_State from configs.config import Config as gConfig +from services.log import logger from utils.manager import plugins2settings_manager -from .api import * -from .auth import * +from .api.group import router as group_routes +from .api.plugins import router as plugin_routes +from .api.request import router as request_routes +from .api.system import router as system_routes + +# from .api.g import * +from .auth import router as auth_router + +driver = nonebot.get_driver() gConfig.add_plugin_config("web-ui", "username", "admin", name="web-ui", help_="前端管理用户名") gConfig.add_plugin_config("web-ui", "password", None, name="web-ui", help_="前端管理密码") -# 使用webui访问api后plugins2settings中的cmd字段将从list变为str -# 暂时找不到原因 -# 先使用hook修复 -@run_preprocessor -async def _(matcher: Matcher, bot: Bot, event: MessageEvent, state: T_State): - flag = False - for module in plugins2settings_manager.keys(): - if plugins2settings_manager.get_plugin_data(module).cmd and isinstance( - plugins2settings_manager.get_plugin_data(module).cmd, str - ): - plugins2settings_manager[module].cmd = plugins2settings_manager[ - module - ].cmd.split(",") - flag = True - if flag: - plugins2settings_manager.save() +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) + + +@driver.on_startup +def _(): + app: FastAPI = nonebot.get_app() + app.include_router(BaseApiRouter) + logger.info("API启动成功", "Web UI") diff --git a/plugins/web_ui/api/group.py b/plugins/web_ui/api/group.py index e7e73561..664d9a0e 100644 --- a/plugins/web_ui/api/group.py +++ b/plugins/web_ui/api/group.py @@ -1,27 +1,31 @@ +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 ..auth import Depends, User, token_to_user -from ..config import * +from ..models.model import Group, GroupResult, Result, Task +from ..models.params import UpdateGroup +from ..utils import authentication + +router = APIRouter() -@router.get("/group", dependencies=[token_to_user()]) +@router.get("/get_group", dependencies=[authentication()]) async def _() -> Result: """ 获取群信息 """ group_list_result = [] - 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: - try: + 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(): @@ -39,24 +43,27 @@ async def _() -> Result: data["group"] = x else: continue - try: - group_list_result.append(GroupResult(**data)) - except ValidationError: - pass - except Exception as e: - logger.error(f"WEB_UI /webui/group 发生错误 {type(e)}:{e}") - return Result(code=200, data=group_list_result) + 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("/group", dependencies=[token_to_user()]) -async def _(group: GroupResult) -> Result: +@router.post("/update_group", dependencies=[authentication()]) +async def _(group: UpdateGroup) -> Result: """ 修改群信息 """ - group_id = group.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) - return Result(data="修改成功!") + 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/plugins.py b/plugins/web_ui/api/plugins.py index 43fc744d..1140f060 100644 --- a/plugins/web_ui/api/plugins.py +++ b/plugins/web_ui/api/plugins.py @@ -1,176 +1,115 @@ -from pydantic import ValidationError +from typing import Optional + +import cattrs +from fastapi import APIRouter from configs.config import Config from services.log import logger -from utils.manager import ( - plugins2block_manager, - plugins2cd_manager, - plugins2count_manager, - plugins2settings_manager, - plugins_manager, -) -from utils.utils import get_matchers +from utils.manager import plugin_data_manager, plugins2settings_manager, plugins_manager +from utils.manager.models import PluginData, PluginType -from ..auth import Depends, User, token_to_user from ..config import * +from ..models.model import Plugin, PluginConfig, Result +from ..models.params import UpdateConfig, UpdatePlugin +from ..utils import authentication -plugin_name_list = None +router = APIRouter() -@router.get("/plugins", dependencies=[token_to_user()]) -def _(type_: Optional[str]) -> Result: +@router.get("/get_plugins", dependencies=[authentication()]) +def _( + plugin_type: PluginType, +) -> Result: """ 获取插件列表 - :param type_: 类型 normal, superuser, hidden, admin + :param plugin_type: 类型 normal, superuser, hidden, admin """ - global plugin_name_list - if not plugin_name_list: - plugin_name_list = [x.plugin_name for x in get_matchers(True)] - plugin_list = [] - plugin_data = plugins_manager.get_data() - for model in plugin_data: - if model in plugin_name_list: - try: - data = plugin_data.get(model) - # data.model = model - plugin_name = data.plugin_name - if ( - (type_ == "hidden" and "[hidden]" not in plugin_name.lower()) - or (type_ == "admin" and "[admin]" not in plugin_name.lower()) - or ( - type_ == "superuser" - and "[superuser]" not in plugin_name.lower() + 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, ) - ): - continue - if type_ == "normal" and ( - "[hidden]" in plugin_name.lower() - or "[admin]" in plugin_name.lower() - or "[superuser]" in plugin_name.lower() - ): - continue - data = {"model": model} - if x := plugin_data.get(model): - if not x.status and x.block_type in [ - "group", - "private", - "all", - ]: - x.block_type = ( - "群聊" - if x.block_type == "group" - else "私聊" - if x.block_type == "private" - else "全部" - ) - data["plugin_manager"] = PluginManager(**x.dict()) - if x := plugins2settings_manager.get(model): - if x.cmd and isinstance(x.cmd, list): - x.cmd = ",".join(x.cmd) - data["plugin_settings"] = PluginSettings(**x.dict()) - if x := plugins2cd_manager.get(model): - data["cd_limit"] = CdLimit(**x.dict()) - if x := plugins2block_manager.get(model): - data["block_limit"] = BlockLimit(**x.dict()) - if x := plugins2count_manager.get(model): - data["count_limit"] = CountLimit(**x.dict()) - if x := Config.get(model): - id_ = 0 - tmp = [] - for key in x.keys(): - tmp.append( - PluginConfig( - **{ - "key": key, - "help_": x[key].get("help"), - "id": id_, - **x[key], - } - ) - ) - id_ += 1 - data["plugin_config"] = tmp - plugin_list.append(Plugin(**data)) - except (AttributeError, ValidationError) as e: - logger.error( - f"WEB_UI GET /webui/plugins model:{model} 发生错误 {type(e)}:{e}" ) - except Exception as e: - logger.error( - f"WEB_UI GET /webui/plugins model:{model} 发生错误 {type(e)}:{e}" - ) - return Result( - code=500, - data=f"WEB_UI GET /webui/plugins model:{model} 发生错误 {type(e)}:{e}", - ) - return Result(code=200, data=plugin_list) + 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("/plugins", dependencies=[token_to_user()]) -def _(plugin: Plugin, user: User = Depends(token_to_user)) -> Result: +@router.post("/update_plugins", dependencies=[authentication()]) +def _(plugin: UpdatePlugin) -> Result: """ 修改插件信息 :param plugin: 插件内容 """ try: - if plugin.plugin_config: - for c in plugin.plugin_config: - if not c.value: - Config.set_config(plugin.model, c.key, None) - continue - if str(c.value).lower() in ["true", "false"] and ( - c.default_value is None or isinstance(c.default_value, bool) - ): - c.value = str(c.value).lower() == "true" - elif isinstance( - Config.get_config(plugin.model, c.key, c.value), int - ) or isinstance(c.default_value, int): - c.value = int(c.value) - elif isinstance( - Config.get_config(plugin.model, c.key, c.value), float - ) or isinstance(c.default_value, float): - c.value = float(c.value) - elif isinstance(c.value, str) and ( - isinstance( - Config.get_config(plugin.model, c.key, c.value), (list, tuple) - ) - or isinstance(c.default_value, (list, tuple)) - ): - default_value = Config.get_config(plugin.model, c.key, c.value) - c.value = c.value.split(",") - if default_value and isinstance(default_value[0], int): - c.value = [int(x) for x in c.value] - elif default_value and isinstance(default_value[0], float): - c.value = [float(x) for x in c.value] - elif default_value and isinstance(default_value[0], bool): - temp = [] - for x in c.value: - temp.append(x.lower() == "true") - c.value = temp - Config.set_config(plugin.model, c.key, c.value) - Config.save(None, True) - else: - if plugin.plugin_settings: - for key, value in plugin.plugin_settings: - if key == "cmd": - value = value.split(",") - setattr(plugins2settings_manager[plugin.model], key, value) - if plugin.plugin_manager: - for key, value in plugin.plugin_manager: - setattr(plugins_manager[plugin.model], key, value) + 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( - f"WEB_UI POST /webui/plugins model:{plugin.model} 发生错误 {type(e)}:{e}" - ) - return Result( - code=500, - data=f"WEB_UI POST /webui/plugins model:{plugin.model} 发生错误 {type(e)}:{e}", - ) - for key in plugins2settings_manager.keys(): - if isinstance(plugins2settings_manager[key].cmd, str): - plugins2settings_manager[key].cmd = plugins2settings_manager[key].cmd.split( - "," - ) - plugins2settings_manager.save() - plugins_manager.save() - return Result(code=200, data="修改成功!") + 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 index d69957c8..809a11cf 100644 --- a/plugins/web_ui/api/request.py +++ b/plugins/web_ui/api/request.py @@ -1,69 +1,87 @@ +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 ..auth import Depends, User, token_to_user -from ..config import * +from ..models.model import RequestResult, Result +from ..models.params import HandleRequest +from ..utils import authentication + +router = APIRouter() -@router.get("/webui/request", dependencies=[token_to_user()]) -def _(type_: Optional[str]) -> Result: - req_data = requests_manager.get_data() - req_list = [] - if type_ in ["group", "private"]: - req_data = req_data[type_] - for x in req_data: - req_data[x]["oid"] = x - req_list.append(RequestResult(**req_data[x])) - req_list.reverse() - return Result(code=200, data=req_list) +@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("/webui/request", dependencies=[token_to_user()]) -def _(type_: Optional[str]) -> Result: +@router.delete("/clear_request", dependencies=[authentication()]) +def _(request_type: Optional[str]) -> Result: """ 清空请求 :param type_: 类型 """ - requests_manager.clear(type_) - return Result(code=200) + requests_manager.clear(request_type) + return Result.ok(info="成功清除了数据") -@router.post("/webui/request", dependencies=[token_to_user()]) -async def _(parma: RequestParma) -> Result: +@router.post("/handle_request", dependencies=[authentication()]) +async def _(parma: HandleRequest) -> Result: """ 操作请求 :param parma: 参数 """ - 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.filter(group_id=rid).first(): - 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=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 = "该请求已失效" + 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=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=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) - elif flag == 2: - result = "未找到此Id" - return Result(code=200, data=result) + 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 6faca37a..6e2d6fb8 100644 --- a/plugins/web_ui/api/system.py +++ b/plugins/web_ui/api/system.py @@ -1,9 +1,12 @@ 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, @@ -17,8 +20,15 @@ from configs.path_config import ( from services.log import logger from utils.http_utils import AsyncHttpx -from ..auth import Depends, User, token_to_user -from ..config import * +from ..models.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" @@ -28,31 +38,31 @@ cpu_data = {"data": []} memory_data = {"data": []} disk_data = {"data": []} +router = APIRouter() -@router.get("/system", dependencies=[token_to_user()]) + +@router.get("/system", dependencies=[authentication()]) async def _() -> Result: return await get_system_data() -@router.get("/webui/system/status", dependencies=[token_to_user()]) +@router.get("/status", dependencies=[authentication()]) async def _() -> Result: - return Result( - code=200, - data=await asyncio.get_event_loop().run_in_executor(None, _get_system_status), + return Result.ok( + await asyncio.get_event_loop().run_in_executor(None, _get_system_status), ) -@router.get("/webui/system/disk", dependencies=[token_to_user()]) +@router.get("/system/disk", dependencies=[authentication()]) async def _(type_: Optional[str] = None) -> Result: - return Result( - code=200, + return Result.ok( data=await asyncio.get_event_loop().run_in_executor( None, _get_system_disk, type_ ), ) -@router.get("/webui/system/statusList", dependencies=[token_to_user()]) +@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) @@ -65,9 +75,8 @@ async def _() -> Result: disk_rst = ( disk_data["data"][-10:] if len(disk_data["data"]) > 10 else disk_data["data"] ) - return Result( - code=200, - data=SystemStatusList( + return Result.ok( + SystemStatusList( cpu_data=cpu_rst, memory_data=memory_rst, disk_data=disk_rst, @@ -95,12 +104,11 @@ async def get_system_data(): 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( - code=200, - data=SystemResult( + return Result.ok( + SystemResult( status=status, network=network, - disk=disk, + disk=disk, # type: ignore check_time=datetime.now().replace(microsecond=0), ), ) diff --git a/plugins/web_ui/auth/__init__.py b/plugins/web_ui/auth/__init__.py index 3983cf84..88a726d3 100644 --- a/plugins/web_ui/auth/__init__.py +++ b/plugins/web_ui/auth/__init__.py @@ -1,74 +1,32 @@ import json -from datetime import datetime, timedelta -from typing import Optional +from datetime import timedelta import nonebot -from fastapi import Depends, HTTPException -from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm -from jose import JWTError, jwt -from pydantic import BaseModel -from starlette import status +from fastapi import APIRouter, Depends +from fastapi.security import OAuth2PasswordRequestForm -from configs.config import Config -from configs.path_config import DATA_PATH - -from ..config import Result, router +from ..models.model import Result +from ..utils import ( + ACCESS_TOKEN_EXPIRE_MINUTES, + create_token, + get_user, + token_data, + token_file, +) app = nonebot.get_app() -SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" -ALGORITHM = "HS256" -ACCESS_TOKEN_EXPIRE_MINUTES = 30 - -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/login") - - -token_file = DATA_PATH / "web_ui" / "token.json" -token_file.parent.mkdir(parents=True, exist_ok=True) -token_data = {"token": []} -if token_file.exists(): - token_data = json.load(open(token_file, "r", encoding="utf8")) - - -class User(BaseModel): - username: str - password: str - - -class Token(BaseModel): - access_token: str - token_type: str - - -def get_user(uname: str) -> Optional[User]: - username = Config.get_config("web-ui", "username") - password = Config.get_config("web-ui", "password") - if username and password and uname == username: - return User(username=username, password=password) - - -form_exception = HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Could not validate credentials", - headers={"WWW-Authenticate": "Bearer"}, -) - - -def create_token(user: User, expires_delta: Optional[timedelta] = None): - expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15)) - return jwt.encode( - claims={"sub": user.username, "exp": expire}, - key=SECRET_KEY, - algorithm=ALGORITHM, - ) +router = APIRouter() @router.post("/login") async def login_get_token(form_data: OAuth2PasswordRequestForm = Depends()): - user = get_user(form_data.username) - if not user or user.password != form_data.password: - raise form_exception + if user := get_user(form_data.username): + if user.password != form_data.password: + return Result.fail("真笨, 密码都能记错!", 999) + else: + return Result.fail("你滴配置文件里用户名密码配置项为空", 998) access_token = create_token( user=user, expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) ) @@ -77,31 +35,6 @@ async def login_get_token(form_data: OAuth2PasswordRequestForm = Depends()): token_data["token"] = token_data["token"][1:] with open(token_file, "w", encoding="utf8") as f: json.dump(token_data, f, ensure_ascii=False, indent=4) - return {"access_token": access_token, "token_type": "bearer"} - - -credentials_exception = HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Could not validate credentials", - headers={"WWW-Authenticate": "Bearer"}, -) - - -@app.post("/auth") -def token_to_user(token: str = Depends(oauth2_scheme)): - if token not in token_data["token"]: - try: - payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username, expire = payload.get("sub"), payload.get("exp") - user = get_user(username) # type: ignore - if user is None: - raise JWTError - except JWTError: - return Result(code=401) - return Result(code=200, info="登录成功") - - -if __name__ == "__main__": - import uvicorn - - uvicorn.run(app, host="127.0.0.1", port=8080) + return Result.ok( + {"access_token": access_token, "token_type": "bearer"}, "欢迎回家, 欧尼酱!" + ) diff --git a/plugins/web_ui/config.py b/plugins/web_ui/config.py index 5b5b9c07..b2cf0145 100644 --- a/plugins/web_ui/config.py +++ b/plugins/web_ui/config.py @@ -18,127 +18,6 @@ app.add_middleware( allow_headers=["*"], ) -router = APIRouter(tags=["api"]) - - -class CdLimit(BaseModel): - """ - Cd 限制 - """ - - cd: int - status: bool - check_type: str - limit_type: str - rst: Optional[str] - - -class BlockLimit(BaseModel): - """ - Block限制 - """ - - status: bool - check_type: str - limit_type: str - rst: Optional[str] - - -class CountLimit(BaseModel): - """ - Count限制 - """ - - max_count: int - status: bool - limit_type: str - rst: Optional[str] - - -class PluginManager(BaseModel): - """ - 插件信息 - """ - - plugin_name: str # 插件名称 - status: Optional[bool] # 插件状态 - error: Optional[bool] # 加载状态 - version: Optional[float] # 版本 - author: Optional[str] # 作者 - block_type: Optional[str] # 禁用类型 - - -class PluginSettings(BaseModel): - """ - 插件基本设置 - """ - - level: Optional[int] # 群权限等级 - default_status: Optional[bool] # 默认开关 - limit_superuser: Optional[bool] # 是否限制超级用户 - cmd: Optional[str] # cmd别名 - cost_gold: Optional[int] # 花费金币限制 - plugin_type: Optional[List[Union[str, int]]] # 帮助类型 - - -class PluginConfig(BaseModel): - """ - 插件配置项 - """ - - id: int - key: str - value: Optional[Any] - help_: Optional[str] - default_value: Optional[Any] - - -class Plugin(BaseModel): - """ - 插件 - """ - - model: str # 模块 - plugin_settings: Optional[PluginSettings] - plugin_manager: Optional[PluginManager] - plugin_config: Optional[List[PluginConfig]] - cd_limit: Optional[CdLimit] - block_limit: Optional[BlockLimit] - count_limit: Optional[CountLimit] - - -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): """ @@ -222,13 +101,3 @@ class SystemResult(BaseModel): network: SystemNetwork disk: SystemFolderSize check_time: datetime - - -class Result(BaseModel): - """ - 总体返回 - """ - - code: int = 200 - info: str = "操作成功" - data: Any = None diff --git a/plugins/web_ui/models/model.py b/plugins/web_ui/models/model.py new file mode 100644 index 00000000..dc56656d --- /dev/null +++ b/plugins/web_ui/models/model.py @@ -0,0 +1,182 @@ +from datetime import datetime +from typing import Any, Dict, List, Optional, TypeVar, Union + +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 diff --git a/plugins/web_ui/models/params.py b/plugins/web_ui/models/params.py new file mode 100644 index 00000000..53cf0138 --- /dev/null +++ b/plugins/web_ui/models/params.py @@ -0,0 +1,61 @@ +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: int + """群号""" + 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 new file mode 100644 index 00000000..4531f3e7 --- /dev/null +++ b/plugins/web_ui/utils.py @@ -0,0 +1,59 @@ +from datetime import datetime, timedelta +from typing import Any, Optional + +import nonebot +import ujson as json +from fastapi import Depends, HTTPException +from fastapi.security import OAuth2PasswordBearer +from jose import JWTError, jwt + +from configs.config import Config +from configs.path_config import DATA_PATH + +from .models.model import Result, User + +SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" +ALGORITHM = "HS256" +ACCESS_TOKEN_EXPIRE_MINUTES = 30 + +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/login") + +token_file = DATA_PATH / "web_ui" / "token.json" +token_file.parent.mkdir(parents=True, exist_ok=True) +token_data = {"token": []} +if token_file.exists(): + try: + token_data = json.load(open(token_file, "r", encoding="utf8")) + except json.JSONDecodeError: + pass + + +def get_user(uname: str) -> Optional[User]: + username = Config.get_config("web-ui", "username") + password = Config.get_config("web-ui", "password") + if username and password and uname == username: + return User(username=username, password=password) + + +def create_token(user: User, expires_delta: Optional[timedelta] = None): + expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15)) + return jwt.encode( + claims={"sub": user.username, "exp": expire}, + key=SECRET_KEY, + algorithm=ALGORITHM, + ) + + +def authentication(): + # if token not in token_data["token"]: + def inner(token: str = Depends(oauth2_scheme)): + try: + payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) + username, expire = payload.get("sub"), payload.get("exp") + user = get_user(username) # type: ignore + if user is None: + raise JWTError + except JWTError: + raise HTTPException(status_code=400, detail="登录验证失败或已失效, 踢出房间!") + + return Depends(inner) diff --git a/utils/manager/data_class.py b/utils/manager/data_class.py index d18c6ded..bbfbaab8 100755 --- a/utils/manager/data_class.py +++ b/utils/manager/data_class.py @@ -67,18 +67,24 @@ class StaticData(Generic[T]): temp[k] = copy.deepcopy(v) return temp - def save(self, path: Union[str, Path] = None) -> NoReturn: + def save(self, path: Optional[Union[str, Path]] = None): path = path or self.file if isinstance(path, str): path = Path(path) if path: with open(path, "w", encoding="utf8") as f: if path.name.endswith("yaml"): - yaml.dump(self._data, f, indent=2, Dumper=yaml.RoundTripDumper, allow_unicode=True) + yaml.dump( + self._data, + f, + indent=2, + Dumper=yaml.RoundTripDumper, + allow_unicode=True, + ) else: json.dump(self.dict(), f, ensure_ascii=False, indent=4) - def reload(self) -> NoReturn: + def reload(self): if self.file.exists(): if self.file.name.endswith("json"): self._data: dict = json.load(open(self.file, "r", encoding="utf8")) @@ -94,7 +100,7 @@ class StaticData(Generic[T]): def __str__(self) -> str: return str(self._data) - def __setitem__(self, key, value) -> NoReturn: + def __setitem__(self, key, value): self._data[key] = value def __getitem__(self, key) -> T: diff --git a/utils/manager/models.py b/utils/manager/models.py index 5ea67b9e..82f1dd0c 100644 --- a/utils/manager/models.py +++ b/utils/manager/models.py @@ -140,4 +140,4 @@ class PluginData(BaseModel): ) def __hash__(self): - return hash(self.name + self.menu_type[0]) + return hash(self.name + str(self.menu_type[0])) diff --git a/utils/manager/plugin_data_manager.py b/utils/manager/plugin_data_manager.py index 093dd0fe..bd9c4aae 100644 --- a/utils/manager/plugin_data_manager.py +++ b/utils/manager/plugin_data_manager.py @@ -24,7 +24,7 @@ class PluginDataManager(StaticData[PluginData]): raise ValueError(f"PluginInfoManager {info.model}:{info.name} 插件名称及类型已存在") self._data[info.model] = info - def get(self, item: str, default: Any = None) -> PluginData: + def get(self, item: str, default: Any = None) -> Optional[PluginData]: return self._data.get(item, default) def __getitem__(self, item) -> Optional[PluginData]: