From 0c7c7f3987b119090210e5923f28e3c923ca2cfc Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Sat, 1 Apr 2023 01:50:34 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BC=98=E5=8C=96=E5=BC=80?= =?UTF-8?q?=E7=AE=B1=E6=98=BE=E7=A4=BA=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 ++ plugins/open_cases/__init__.py | 6 ++- plugins/open_cases/build_image.py | 61 +++++++++++++++++++++-- plugins/open_cases/models/buff_skin.py | 2 +- plugins/open_cases/open_cases_c.py | 58 +++++++++------------ plugins/open_cases/utils.py | 14 +++++- plugins/web_ui/api/group.py | 11 ++-- plugins/web_ui/api/plugins.py | 25 +++++++--- plugins/web_ui/api/request.py | 12 ++--- plugins/web_ui/api/system.py | 21 ++++---- plugins/web_ui/auth/__init__.py | 46 ++++++++--------- plugins/web_ui/config.py | 34 ++++++++++--- resources/image/_icon/abrasion_white.png | Bin 0 -> 5516 bytes resources/image/_icon/name_white.png | Bin 0 -> 5993 bytes resources/image/_icon/tone_white.png | Bin 0 -> 5827 bytes utils/image_utils.py | 13 +++-- 16 files changed, 202 insertions(+), 106 deletions(-) create mode 100644 resources/image/_icon/abrasion_white.png create mode 100644 resources/image/_icon/name_white.png create mode 100644 resources/image/_icon/tone_white.png diff --git a/README.md b/README.md index 4574e51b..d53c10d0 100644 --- a/README.md +++ b/README.md @@ -331,6 +331,11 @@ PS: **ARM平台** 请使用全量版 同时 **如果你的机器 RAM < 1G 可能 ## 更新 +### 2023/4/1 + +* 修复开箱偶尔出现`未抽取到任何皮肤` +* 修改优化开箱显示图片 + ### 2023/3/28 * 补全注释`SCRIPT`中的sql语句 diff --git a/plugins/open_cases/__init__.py b/plugins/open_cases/__init__.py index 941916a5..f1226f97 100755 --- a/plugins/open_cases/__init__.py +++ b/plugins/open_cases/__init__.py @@ -235,7 +235,7 @@ async def _(event: MessageEvent, arg: Message = CommandArg(), cmd: str = OneComm await update_case.finish(f"未登录, 已停止更新...") rand = random.randint(300, 500) result = f"更新全部{type_}完成" - if i < len(case_list): + if i < len(case_list) - 1: next_case = case_list[i + 1] result = f"将在 {rand} 秒后更新下一{type_}: {next_case}" await update_case.send(f"{info}, {result}") @@ -248,7 +248,9 @@ async def _(event: MessageEvent, arg: Message = CommandArg(), cmd: str = OneComm else: await update_case.send(f"开始{cmd}: {msg}, 请稍等") try: - await update_case.send(await update_skin_data(msg, is_update_case_name), at_sender=True) + await update_case.send( + await update_skin_data(msg, is_update_case_name), at_sender=True + ) except Exception as e: logger.error(f"{cmd}: {msg}", e=e) await update_case.send(f"成功{cmd}: {msg} 发生错误: {type(e)}: {e}") diff --git a/plugins/open_cases/build_image.py b/plugins/open_cases/build_image.py index 4413b6ad..972eb45f 100644 --- a/plugins/open_cases/build_image.py +++ b/plugins/open_cases/build_image.py @@ -14,6 +14,56 @@ BASE_PATH = IMAGE_PATH / "csgo_cases" ICON_PATH = IMAGE_PATH / "_icon" +async def draw_card(skin: BuffSkin, rand: str) -> BuildImage: + """构造抽取图片 + + Args: + skin (BuffSkin): BuffSkin + rand (str): 磨损 + + Returns: + BuildImage: BuildImage + """ + name = skin.name + "-" + skin.skin_name + "-" + skin.abrasion + file_path = BASE_PATH / cn2py(skin.case_name.split(",")[0]) / f"{cn2py(name)}.jpg" + if not file_path.exists(): + logger.warning(f"皮肤图片: {name} 不存在", "开箱") + skin_bk = BuildImage( + 460, 200, color=(25, 25, 25, 100), font_size=25, font="CJGaoDeGuo.otf" + ) + if file_path.exists(): + skin_image = BuildImage(205, 153, background=file_path) + await skin_bk.apaste(skin_image, (10, 30), alpha=True) + await skin_bk.aline((220, 10, 220, 180)) + await skin_bk.atext((10, 10), skin.name, (255, 255, 255)) + name_icon = BuildImage(20, 20, background=ICON_PATH / "name_white.png") + await skin_bk.apaste(name_icon, (240, 13), True) + await skin_bk.atext((265, 15), f"名称:", (255, 255, 255), font_size=20) + await skin_bk.atext( + (300, 9), + f"{skin.skin_name + ('(St)' if skin.is_stattrak else '')}", + (255, 255, 255), + ) + tone_icon = BuildImage(20, 20, background=ICON_PATH / "tone_white.png") + await skin_bk.apaste(tone_icon, (240, 45), True) + await skin_bk.atext((265, 45), "品质:", (255, 255, 255), font_size=20) + await skin_bk.atext((300, 40), COLOR2NAME[skin.color][:2], COLOR2COLOR[skin.color]) + type_icon = BuildImage(20, 20, background=ICON_PATH / "type_white.png") + await skin_bk.apaste(type_icon, (240, 73), True) + await skin_bk.atext((265, 75), "类型:", (255, 255, 255), font_size=20) + await skin_bk.atext((300, 70), skin.weapon_type, (255, 255, 255)) + price_icon = BuildImage(20, 20, background=ICON_PATH / "price_white.png") + await skin_bk.apaste(price_icon, (240, 103), True) + await skin_bk.atext((265, 105), "价格:", (255, 255, 255), font_size=20) + await skin_bk.atext((300, 102), str(skin.sell_min_price), (0, 255, 98)) + abrasion_icon = BuildImage(20, 20, background=ICON_PATH / "abrasion_white.png") + await skin_bk.apaste(abrasion_icon, (240, 133), True) + await skin_bk.atext((265, 135), "磨损:", (255, 255, 255), font_size=20) + await skin_bk.atext((300, 130), skin.abrasion, (255, 255, 255)) + await skin_bk.atext((228, 165), f"({rand})", (255, 255, 255)) + return skin_bk + + async def generate_skin(skin: BuffSkin, update_count: int) -> Optional[BuildImage]: """构造皮肤图片 @@ -27,13 +77,13 @@ async def generate_skin(skin: BuffSkin, update_count: int) -> Optional[BuildImag file_path = BASE_PATH / cn2py(skin.case_name.split(",")[0]) / f"{cn2py(name)}.jpg" if not file_path.exists(): logger.warning(f"皮肤图片: {name} 不存在", "查看武器箱") - return None if skin.color == "CASE": - skin_img = BuildImage(200, 200, background=file_path) case_bk = BuildImage( 700, 200, color=(25, 25, 25, 100), font_size=25, font="CJGaoDeGuo.otf" ) - await case_bk.apaste(skin_img, (10, 10), True) + if file_path.exists(): + skin_img = BuildImage(200, 200, background=file_path) + await case_bk.apaste(skin_img, (10, 10), True) await case_bk.aline((250, 10, 250, 190)) await case_bk.aline((280, 160, 660, 160)) name_icon = BuildImage(30, 30, background=ICON_PATH / "box_white.png") @@ -86,11 +136,12 @@ async def generate_skin(skin: BuffSkin, update_count: int) -> Optional[BuildImag skin_bk = BuildImage( 235, 250, color=(25, 25, 25, 100), font_size=25, font="CJGaoDeGuo.otf" ) - skin_image = BuildImage(205, 153, background=file_path) + if file_path.exists(): + skin_image = BuildImage(205, 153, background=file_path) + await skin_bk.apaste(skin_image, (10, 30), alpha=True) update_count_icon = BuildImage( 35, 35, background=ICON_PATH / "reload_white.png" ) - await skin_bk.apaste(skin_image, (10, 30), alpha=True) await skin_bk.aline((10, 180, 220, 180)) await skin_bk.atext((10, 10), skin.name, (255, 255, 255)) await skin_bk.apaste(update_count_icon, (140, 10), True) diff --git a/plugins/open_cases/models/buff_skin.py b/plugins/open_cases/models/buff_skin.py index 93dd0494..279f082e 100644 --- a/plugins/open_cases/models/buff_skin.py +++ b/plugins/open_cases/models/buff_skin.py @@ -72,7 +72,7 @@ class BuffSkin(Model): ) -> List["BuffSkin"]: # type: ignore query = cls if case_name: - query = query.filter(case_name=case_name) + query = query.filter(case_name__contains=case_name) query = query.filter(abrasion=abrasion, is_stattrak=is_stattrak, color=color) skin_list = await query.annotate(rand=Random()).limit(num) # type:ignore num_ = num diff --git a/plugins/open_cases/open_cases_c.py b/plugins/open_cases/open_cases_c.py index f0e56354..4ccfbb26 100755 --- a/plugins/open_cases/open_cases_c.py +++ b/plugins/open_cases/open_cases_c.py @@ -14,6 +14,7 @@ from utils.image_utils import BuildImage from utils.message_builder import image from utils.utils import cn2py +from .build_image import draw_card from .config import * from .models.open_cases_log import OpenCasesLog from .models.open_cases_user import OpenCasesUser @@ -147,14 +148,11 @@ async def open_case(user_qq: int, group_id: int, case_name: str) -> Union[str, M ) logger.debug(f"添加 1 条开箱日志", "开箱", user_qq, group_id) over_count = max_count - user.today_open_total + img = await draw_card(skin, rand) return ( f"开启{case_name}武器箱.\n剩余开箱次数:{over_count}.\n" - + image(img_path) - + "\n" - + f"皮肤:[{COLOR2NAME[skin.color]}]{skin.name}{'(StatTrak™)' if skin.is_stattrak else ''} | {skin.skin_name} ({skin.abrasion})\n" - f"磨损:{rand}\n" - f"价格:{price_result}\n箱子单价:{case_price}\n花费:{17 + case_price:.2f}\n" - f":{ridicule_result}" + + image(img) + + f"\n箱子单价:{case_price}\n花费:{17 + case_price:.2f}\n:{ridicule_result}" ) @@ -191,12 +189,7 @@ async def open_multiple_case( f"今天开箱次数不足{num}次噢,请单抽试试看(也许单抽运气更好?)" f"\n剩余开箱次数:{max_count - user.today_open_total}" ) - if num < 5: - h = 270 - elif num % 5 == 0: - h = 270 * int(num / 5) - else: - h = 270 * int(num / 5) + 270 + logger.debug(f"尝试开启武器箱: {case_name}", "开箱", user_qq, group_id) case = cn2py(case_name) skin_count = {} img_list = [] @@ -212,32 +205,19 @@ async def open_multiple_case( case_price = 0 if case_skin := await BuffSkin.get_or_none(case_name=case_name, color="CASE"): case_price = case_skin.sell_min_price - cnt = 0 + img_w, img_h = 0, 0 for skin, rand in skin_list: + img = await draw_card(skin, str(rand)[:11]) + img_w, img_h = img.size total_price += skin.sell_min_price - rand = str(rand)[:11] - add_count(user, skin, case_price) color_name = COLOR2CN[skin.color] - if skin.is_stattrak: - color_name += "(暗金)" if not skin_count.get(color_name): skin_count[color_name] = 0 skin_count[color_name] += 1 - name = skin.name + "-" + skin.skin_name + "-" + skin.abrasion - img_path = IMAGE_PATH / "csgo_cases" / case / f"{cn2py(name)}.jpg" - wImg = BuildImage(200, 270, 200, 200) - img = BuildImage(200, 200, background=img_path) - await wImg.apaste(img, (0, 0), True) - await wImg.atext( - (5, 200), - f"{skin.name}{'(StatTrak™)' if skin.is_stattrak else ''} | {skin.skin_name} ({skin.abrasion})", - ) - cnt += 1 - await wImg.atext((5, 220), f"磨损:{rand}") - await wImg.atext((5, 240), f"价格:{skin.sell_min_price}") - img_list.append(wImg) + add_count(user, skin, case_price) + img_list.append(img) logger.info( - f"开启{case_name}武器箱获得 {skin.name}{'(StatTrak™)' if skin.is_stattrak else ''} | {skin.skin_name} ({skin.abrasion}) 磨损: [{rand}] 价格: {skin.sell_min_price}", + f"开启{case_name}武器箱获得 {skin.name}{'(StatTrak™)' if skin.is_stattrak else ''} | {skin.skin_name} ({skin.abrasion}) 磨损: [{rand:.11f}] 价格: {skin.sell_min_price}", "开箱", user_qq, group_id, @@ -261,16 +241,26 @@ async def open_multiple_case( if log_list: await OpenCasesLog.bulk_create(log_list, 10) logger.debug(f"添加 {len(log_list)} 条开箱日志", "开箱", user_qq, group_id) - markImg = BuildImage(1000, h, 200, 270) + img_w += 10 + img_h += 10 + w = img_w * 5 + if num < 5: + h = img_h - 10 + w = img_w * num + elif not num % 5: + h = img_h * int(num / 5) + else: + h = img_h * int(num / 5) + img_h + markImg = BuildImage(w, h, img_w - 10, img_h - 10, 10) for img in img_list: - markImg.paste(img) + markImg.paste(img, alpha=True) over_count = max_count - user.today_open_total result = "" for color_name in skin_count: result += f"[{color_name}:{skin_count[color_name]}] " return ( f"开启{case_name}武器箱\n剩余开箱次数:{over_count}\n" - + image(markImg.pic2bs4()) + + image(markImg) + "\n" + result[:-1] + f"\n箱子单价:{case_price}\n总获取金额:{total_price:.2f}\n总花费:{(17 + case_price) * num:.2f}" diff --git a/plugins/open_cases/utils.py b/plugins/open_cases/utils.py index 6c46456b..c91607f3 100755 --- a/plugins/open_cases/utils.py +++ b/plugins/open_cases/utils.py @@ -45,9 +45,19 @@ class CaseManager: @classmethod async def reload(cls): - cls.CURRENT_CASES = ( - await BuffSkin.filter(case_name__not="未知武器箱").annotate().distinct().values_list("case_name", flat=True) # type: ignore + cls.CURRENT_CASES = [] + case_list = await BuffSkin.filter(color="CASE").values_list( + "case_name", flat=True ) + for case_name in ( + await BuffSkin.filter(case_name__not="未知武器箱") + .annotate() + .distinct() + .values_list("case_name", flat=True) + ): + for name in case_name.split(","): # type: ignore + if name not in cls.CURRENT_CASES and name in case_list: + cls.CURRENT_CASES.append(name) async def update_skin_data(name: str, is_update_case_name: bool = False) -> str: diff --git a/plugins/web_ui/api/group.py b/plugins/web_ui/api/group.py index 1e2f89c6..e7e73561 100644 --- a/plugins/web_ui/api/group.py +++ b/plugins/web_ui/api/group.py @@ -1,4 +1,5 @@ from pydantic.error_wrappers import ValidationError + from services.log import logger from utils.manager import group_manager from utils.utils import get_bot @@ -7,8 +8,8 @@ from ..auth import Depends, User, token_to_user from ..config import * -@app.get("/webui/group") -async def _(user: User = Depends(token_to_user)) -> Result: +@router.get("/group", dependencies=[token_to_user()]) +async def _() -> Result: """ 获取群信息 """ @@ -47,8 +48,8 @@ async def _(user: User = Depends(token_to_user)) -> Result: return Result(code=200, data=group_list_result) -@app.post("/webui/group") -async def _(group: GroupResult, user: User = Depends(token_to_user)) -> Result: +@router.post("/group", dependencies=[token_to_user()]) +async def _(group: GroupResult) -> Result: """ 修改群信息 """ @@ -58,4 +59,4 @@ async def _(group: GroupResult, user: User = Depends(token_to_user)) -> Result: group_manager.turn_on_group_bot_status(group_id) else: group_manager.shutdown_group_bot_status(group_id) - return Result(code=200, data="修改成功!") + return Result(data="修改成功!") diff --git a/plugins/web_ui/api/plugins.py b/plugins/web_ui/api/plugins.py index 23a94ba7..43fc744d 100644 --- a/plugins/web_ui/api/plugins.py +++ b/plugins/web_ui/api/plugins.py @@ -1,9 +1,14 @@ from pydantic import ValidationError + 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.manager import ( + plugins2block_manager, + plugins2cd_manager, + plugins2count_manager, + plugins2settings_manager, + plugins_manager, +) from utils.utils import get_matchers from ..auth import Depends, User, token_to_user @@ -12,8 +17,8 @@ from ..config import * plugin_name_list = None -@app.get("/webui/plugins") -def _(type_: Optional[str], user: User = Depends(token_to_user)) -> Result: +@router.get("/plugins", dependencies=[token_to_user()]) +def _(type_: Optional[str]) -> Result: """ 获取插件列表 :param type_: 类型 normal, superuser, hidden, admin @@ -101,7 +106,7 @@ def _(type_: Optional[str], user: User = Depends(token_to_user)) -> Result: return Result(code=200, data=plugin_list) -@app.post("/webui/plugins") +@router.post("/plugins", dependencies=[token_to_user()]) def _(plugin: Plugin, user: User = Depends(token_to_user)) -> Result: """ 修改插件信息 @@ -126,7 +131,9 @@ def _(plugin: Plugin, user: User = Depends(token_to_user)) -> Result: ) 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)) + 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) @@ -161,7 +168,9 @@ def _(plugin: Plugin, user: User = Depends(token_to_user)) -> Result: ) for key in plugins2settings_manager.keys(): if isinstance(plugins2settings_manager[key].cmd, str): - plugins2settings_manager[key].cmd = plugins2settings_manager[key].cmd.split(',') + plugins2settings_manager[key].cmd = plugins2settings_manager[key].cmd.split( + "," + ) plugins2settings_manager.save() plugins_manager.save() return Result(code=200, data="修改成功!") diff --git a/plugins/web_ui/api/request.py b/plugins/web_ui/api/request.py index 02615304..d69957c8 100644 --- a/plugins/web_ui/api/request.py +++ b/plugins/web_ui/api/request.py @@ -6,8 +6,8 @@ from ..auth import Depends, User, token_to_user from ..config import * -@app.get("/webui/request") -def _(type_: Optional[str], user: User = Depends(token_to_user)) -> Result: +@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"]: @@ -19,8 +19,8 @@ def _(type_: Optional[str], user: User = Depends(token_to_user)) -> Result: return Result(code=200, data=req_list) -@app.delete("/webui/request") -def _(type_: Optional[str], user: User = Depends(token_to_user)) -> Result: +@router.delete("/webui/request", dependencies=[token_to_user()]) +def _(type_: Optional[str]) -> Result: """ 清空请求 :param type_: 类型 @@ -29,8 +29,8 @@ def _(type_: Optional[str], user: User = Depends(token_to_user)) -> Result: return Result(code=200) -@app.post("/webui/request") -async def _(parma: RequestParma, user: User = Depends(token_to_user)) -> Result: +@router.post("/webui/request", dependencies=[token_to_user()]) +async def _(parma: RequestParma) -> Result: """ 操作请求 :param parma: 参数 diff --git a/plugins/web_ui/api/system.py b/plugins/web_ui/api/system.py index e83364b8..6faca37a 100644 --- a/plugins/web_ui/api/system.py +++ b/plugins/web_ui/api/system.py @@ -4,6 +4,7 @@ from pathlib import Path import psutil import ujson as json + from configs.path_config import ( DATA_PATH, FONT_PATH, @@ -28,21 +29,21 @@ memory_data = {"data": []} disk_data = {"data": []} -@app.get("/webui/system") +@router.get("/system", dependencies=[token_to_user()]) async def _() -> Result: return await get_system_data() -@app.get("/webui/system/status") -async def _(user: User = Depends(token_to_user)) -> Result: +@router.get("/webui/system/status", dependencies=[token_to_user()]) +async def _() -> Result: return Result( code=200, data=await asyncio.get_event_loop().run_in_executor(None, _get_system_status), ) -@app.get("/webui/system/disk") -async def _(type_: Optional[str] = None, user: User = Depends(token_to_user)) -> Result: +@router.get("/webui/system/disk", dependencies=[token_to_user()]) +async def _(type_: Optional[str] = None) -> Result: return Result( code=200, data=await asyncio.get_event_loop().run_in_executor( @@ -51,8 +52,8 @@ async def _(type_: Optional[str] = None, user: User = Depends(token_to_user)) -> ) -@app.get("/webui/system/statusList") -async def _(user: User = Depends(token_to_user)) -> Result: +@router.get("/webui/system/statusList", dependencies=[token_to_user()]) +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"] @@ -74,7 +75,7 @@ async def _(user: User = Depends(token_to_user)) -> Result: ) -async def get_system_data(user: User = Depends(token_to_user)): +async def get_system_data(): """ 说明: 获取系统信息,资源文件大小,网络状态等 @@ -105,7 +106,7 @@ async def get_system_data(user: User = Depends(token_to_user)): ) -def _get_system_status(user: User = Depends(token_to_user)) -> SystemStatus: +def _get_system_status() -> SystemStatus: """ 说明: 获取系统信息等 @@ -123,7 +124,7 @@ def _get_system_status(user: User = Depends(token_to_user)) -> SystemStatus: def _get_system_disk( - type_: Optional[str], user: User = Depends(token_to_user) + type_: Optional[str], ) -> Union[SystemFolderSize, Dict[str, Union[float, datetime]]]: """ 说明: diff --git a/plugins/web_ui/auth/__init__.py b/plugins/web_ui/auth/__init__.py index 4fce1d36..3983cf84 100644 --- a/plugins/web_ui/auth/__init__.py +++ b/plugins/web_ui/auth/__init__.py @@ -1,16 +1,18 @@ import json from datetime import datetime, timedelta -from configs.path_config import DATA_PATH from typing import Optional -from starlette import status + +import nonebot from fastapi import Depends, HTTPException from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm -from pydantic import BaseModel -from configs.config import Config from jose import JWTError, jwt -import nonebot +from pydantic import BaseModel +from starlette import status -from ..config import Result +from configs.config import Config +from configs.path_config import DATA_PATH + +from ..config import Result, router app = nonebot.get_app() @@ -19,14 +21,14 @@ SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES = 30 -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="webui/login") +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')) + token_data = json.load(open(token_file, "r", encoding="utf8")) class User(BaseModel): @@ -39,11 +41,6 @@ class Token(BaseModel): token_type: str -# USER_LIST = [ -# User(username="admin", password="123") -# ] - - def get_user(uname: str) -> Optional[User]: username = Config.get_config("web-ui", "username") password = Config.get_config("web-ui", "password") @@ -59,24 +56,26 @@ form_exception = HTTPException( def create_token(user: User, expires_delta: Optional[timedelta] = None): - expire = datetime.utcnow() + expires_delta or timedelta(minutes=15) + expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15)) return jwt.encode( claims={"sub": user.username, "exp": expire}, key=SECRET_KEY, - algorithm=ALGORITHM + algorithm=ALGORITHM, ) -@app.post("/webui/login") +@router.post("/login") async def login_get_token(form_data: OAuth2PasswordRequestForm = Depends()): - user: User = get_user(form_data.username) + user = get_user(form_data.username) if not user or user.password != form_data.password: raise form_exception - access_token = create_token(user=user, expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)) + access_token = create_token( + user=user, expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) + ) token_data["token"].append(access_token) if len(token_data["token"]) > 3: token_data["token"] = token_data["token"][1:] - with open(token_file, 'w', encoding="utf8") as f: + 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"} @@ -88,20 +87,21 @@ credentials_exception = HTTPException( ) -@app.post("/webui/auth") +@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) + user = get_user(username) # type: ignore if user is None: raise JWTError except JWTError: return Result(code=401) - return Result(code=200, data="ok") + return Result(code=200, info="登录成功") -if __name__ == '__main__': +if __name__ == "__main__": import uvicorn + uvicorn.run(app, host="127.0.0.1", port=8080) diff --git a/plugins/web_ui/config.py b/plugins/web_ui/config.py index ffda27b9..5b5b9c07 100644 --- a/plugins/web_ui/config.py +++ b/plugins/web_ui/config.py @@ -1,9 +1,10 @@ -from typing import Optional, List, Any, Union, Dict -from pydantic import BaseModel -from fastapi.middleware.cors import CORSMiddleware from datetime import datetime -import nonebot +from typing import Any, Dict, List, Optional, Union +import nonebot +from fastapi import APIRouter +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel app = nonebot.get_app() @@ -17,11 +18,14 @@ app.add_middleware( allow_headers=["*"], ) +router = APIRouter(tags=["api"]) + class CdLimit(BaseModel): """ Cd 限制 """ + cd: int status: bool check_type: str @@ -33,6 +37,7 @@ class BlockLimit(BaseModel): """ Block限制 """ + status: bool check_type: str limit_type: str @@ -43,6 +48,7 @@ class CountLimit(BaseModel): """ Count限制 """ + max_count: int status: bool limit_type: str @@ -53,6 +59,7 @@ class PluginManager(BaseModel): """ 插件信息 """ + plugin_name: str # 插件名称 status: Optional[bool] # 插件状态 error: Optional[bool] # 加载状态 @@ -65,6 +72,7 @@ class PluginSettings(BaseModel): """ 插件基本设置 """ + level: Optional[int] # 群权限等级 default_status: Optional[bool] # 默认开关 limit_superuser: Optional[bool] # 是否限制超级用户 @@ -77,6 +85,7 @@ class PluginConfig(BaseModel): """ 插件配置项 """ + id: int key: str value: Optional[Any] @@ -88,6 +97,7 @@ class Plugin(BaseModel): """ 插件 """ + model: str # 模块 plugin_settings: Optional[PluginSettings] plugin_manager: Optional[PluginManager] @@ -101,6 +111,7 @@ class Group(BaseModel): """ 群组信息 """ + group_id: int group_name: str member_count: int @@ -111,6 +122,7 @@ class Task(BaseModel): """ 被动技能 """ + name: str nameZh: str status: bool @@ -120,6 +132,7 @@ class GroupResult(BaseModel): """ 群组返回数据 """ + group: Group level: int status: bool @@ -131,6 +144,7 @@ class RequestResult(BaseModel): """ 好友/群组请求管理 """ + oid: str id: int flag: str @@ -148,6 +162,7 @@ class RequestParma(BaseModel): """ 操作请求接收数据 """ + id: int handle: str type: str @@ -157,6 +172,7 @@ class SystemStatus(BaseModel): """ 系统状态 """ + cpu: int memory: int disk: int @@ -167,6 +183,7 @@ class SystemNetwork(BaseModel): """ 系统网络状态 """ + baidu: int google: int @@ -175,6 +192,7 @@ class SystemFolderSize(BaseModel): """ 资源文件占比 """ + font_dir_size: float image_dir_size: float text_dir_size: float @@ -189,6 +207,7 @@ 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]]] @@ -198,6 +217,7 @@ class SystemResult(BaseModel): """ 系统api返回 """ + status: SystemStatus network: SystemNetwork disk: SystemFolderSize @@ -208,5 +228,7 @@ class Result(BaseModel): """ 总体返回 """ - code: int - data: Any + + code: int = 200 + info: str = "操作成功" + data: Any = None diff --git a/resources/image/_icon/abrasion_white.png b/resources/image/_icon/abrasion_white.png new file mode 100644 index 0000000000000000000000000000000000000000..ec08c67dbaa5195314be472d4ec177311e46fe7a GIT binary patch literal 5516 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 X=38=W9XC6100000NkvXXu0mjfICJ7f literal 0 HcmV?d00001 diff --git a/resources/image/_icon/tone_white.png b/resources/image/_icon/tone_white.png new file mode 100644 index 0000000000000000000000000000000000000000..8e8434ba1bdda8f256c8a746642f042b1a43f6f9 GIT binary patch literal 5827 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 N002ovPDHLkV1nce(QW_$ literal 0 HcmV?d00001 diff --git a/utils/image_utils.py b/utils/image_utils.py index eb265ba5..7686ce9e 100755 --- a/utils/image_utils.py +++ b/utils/image_utils.py @@ -160,6 +160,7 @@ class BuildImage: h: int, paste_image_width: int = 0, paste_image_height: int = 0, + paste_space: int = 0, color: Union[str, Tuple[int, int, int], Tuple[int, int, int, int]] = None, image_mode: ModeType = "RGBA", font_size: int = 10, @@ -177,6 +178,7 @@ class BuildImage: :param h: 自定义图片的高度,h=0时为图片原本高度 :param paste_image_width: 当图片做为背景图时,设置贴图的宽度,用于贴图自动换行 :param paste_image_height: 当图片做为背景图时,设置贴图的高度,用于贴图自动换行 + :param paste_space: 自动贴图间隔 :param color: 生成图片的颜色 :param image_mode: 图片的类型 :param font_size: 文字大小 @@ -190,6 +192,7 @@ class BuildImage: self.h = int(h) self.paste_image_width = int(paste_image_width) self.paste_image_height = int(paste_image_height) + self.paste_space = int(paste_space) self._current_w = 0 self._current_h = 0 self.uid = uuid.uuid1() @@ -277,7 +280,9 @@ class BuildImage: :param center_type: 居中类型,可能的值 center: 完全居中,by_width: 水平居中,by_height: 垂直居中 :param allow_negative: 允许使用负数作为坐标且不超出图片范围,从右侧开始计算 """ - await self.loop.run_in_executor(None, self.paste, img, pos, alpha, center_type, allow_negative) + await self.loop.run_in_executor( + None, self.paste, img, pos, alpha, center_type, allow_negative + ) def paste( self, @@ -324,7 +329,7 @@ class BuildImage: img = img.markImg if self._current_w >= self.w: self._current_w = 0 - self._current_h += self.paste_image_height + self._current_h += self.paste_image_height + self.paste_space if not pos: pos = (self._current_w, self._current_h) if alpha: @@ -335,7 +340,7 @@ class BuildImage: self.markImg.paste(img, pos, img) else: self.markImg.paste(img, pos) - self._current_w += self.paste_image_width + self._current_w += self.paste_image_width + self.paste_space @classmethod def get_text_size(cls, msg: str, font: str, font_size: int) -> Tuple[int, int]: @@ -469,7 +474,7 @@ class BuildImage: "center_type must be 'center', 'by_width' or 'by_height'" ) w, h = self.w, self.h - longgest_text = '' + longgest_text = "" sentence = text.split("\n") for x in sentence: longgest_text = x if len(x) > len(longgest_text) else longgest_text