✨ 重构自检ui

This commit is contained in:
HibiKier
2024-09-07 01:25:42 +08:00
parent 51371ba954
commit 3b4c206a24
12 changed files with 596 additions and 93 deletions
Generated
+17 -1
View File
@@ -3000,6 +3000,22 @@ type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple" url = "https://mirrors.aliyun.com/pypi/simple"
reference = "ali" reference = "ali"
[[package]]
name = "py-cpuinfo"
version = "9.0.0"
description = "Get CPU info with pure Python"
optional = false
python-versions = "*"
files = [
{file = "py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690"},
{file = "py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5"},
]
[package.source]
type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
reference = "ali"
[[package]] [[package]]
name = "pyasn1" name = "pyasn1"
version = "0.6.0" version = "0.6.0"
@@ -4909,4 +4925,4 @@ reference = "ali"
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.10" python-versions = "^3.10"
content-hash = "706cc73807663c91c3eed8d689e7f04f49fd6f0b6d2b3a2e7e4e078a2c3f89c0" content-hash = "e66133515a2b17715ddf5f887ec31f128751acda1ea67736e9d27b29a9b16b65"
+1
View File
@@ -50,6 +50,7 @@ python-multipart = "^0.0.9"
nonebot-plugin-alconna = "0.51.1" nonebot-plugin-alconna = "0.51.1"
arclet-alconna = "1.8.23" arclet-alconna = "1.8.23"
aiocache = "^0.12.2" aiocache = "^0.12.2"
py-cpuinfo = "^9.0.0"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
nonebug = "^0.3.2" nonebug = "^0.3.2"
+179
View File
@@ -0,0 +1,179 @@
@font-face {
font-family: fzrzFont;
/* 导入的字体文件 */
src: url("./res/font/fzrzExtraBold.ttf");
}
body {
position: absolute;
left: -8px;
top: -8px;
}
.wrapper{
height: 750px;
width: 395px;
background-color: antiquewhite;
position: relative;
}
.top-image {
height: 215px;
width: 395px;
}
.abs-image {
height: 426px;
width: 93px;
position: absolute;
right: 39px;
top: 240px;
}
.bot-text {
height: 55px;
width: 335px;
position: relative;
padding: 0 30px;
margin-top: 10px;
}
.main {
height: 448px;
width: 335px;
padding: 0 30px;
position: relative;
}
.title {
background-color: #EB869D;
height: 32px;
font-family: 'fzrzFont';
color: white;
width: max-content;
border-radius: 20px;
padding: 0 10px;
float: left;
margin-top: 10px;
}
.title-n {
height: 23px;
width: 23px;
border-radius: 50%;
background-color: white;
position: absolute;
left: 36px;
top: 15px;
display: flex;
justify-content: center;
align-items: center;
}
.title-a {
background-color: #ED859E;
height: 19px;
width: 19px;
border-radius: 50%;
}
.network {
display: flex;
margin-left: 10px;
}
.network-item {
font-family: 'fzrzFont';
color: #EC859F;
display: flex;
justify-items: center;
align-items: center;
margin-left: 20px;
}
.network-status {
height: 10px;
width: 10px;
border-radius: 50%;
margin-left: 5px;
}
.network-text {
margin-left: 5px;
}
.network-item {
margin-top: 10px;
}
.data-status-item {
margin-top: 10px;
}
.data-status {
width: 100%;
height: 240px;
/* background-color:blueviolet; */
margin-top: 22px;
}
.data-status-item {
display: flex;
font-family: 'fzrzFont';
font-size: 15px;
line-height: 18.5px;
height: 50px;
}
.process {
height: 28px;
width: 105px;
border: #ED859E 4px solid;
border-radius: 30px;
overflow: hidden;
}
.process-bar {
height: 28px;
width: 30px;
background-color: #EB869D;
border-top-right-radius: 30px;
border-bottom-right-radius: 30px;
}
.data-status-item-text {
margin-left: 10px;
color: #EC839D;
}
.line {
background-color: #EC859F;
height: 2px;
width: 100%;
border-radius: 20px;
}
.status-text {
font-family: 'fzrzFont';
color: #EC859F;
height: 90px;
display: flex;
}
.status-text-title {
height: 25%;
}
.tip {
font-family: 'fzrzFont';
color: #ecbac7;
font-size: 10px;
position: absolute;
right: 5px;
bottom: 1px;
}
+90
View File
@@ -0,0 +1,90 @@
</html>
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>test</title>
<!-- <link rel="stylesheet" href="./res/font-awesome/css/font-awesome.min.css"> -->
<link rel="stylesheet" href="main.css">
</head>
<body>
<div class="wrapper">
<img class="top-image" src="res/img/top.jpg"></img>
<img class="abs-image" src="res/img/bk.png"></img>
<div class="bot-text">
<div class="title">
<div class="title-n">
<div class="title-a"></div>
</div>
<span style="margin-left: 22px;">{{data.nickname}}自检</span>
</div>
<div class="network">
<div class="network-item">BaiDu<span class="network-status" style="background-color: {{data.baidu}}; border: 1px solid {{data.baidu}}"></span></div>
<div class="network-item">Google<span class="network-status" style="background-color: {{data.google}}; border: 1px solid {{data.google}}"></span></div>
</div>
</div>
<div class="main">
<div class="data-status">
<div class="data-status-item">
<div class="process">
<div class="process-bar" style="width: {{data.cpu_process}}%;"></div>
</div>
<div class="data-status-item-text">
<div>CPU</div>
<!-- <div style="font-size: 13px;">0.8% - 3.5Ghz [14core]</div> -->
<div style="font-size: 13px;">{{data.cpu_info}}</div>
</div>
</div>
<div class="data-status-item">
<div class="process">
<div class="process-bar" style="width: {{data.ram_process}}%;"></div>
</div>
<div class="data-status-item-text">
<div>RAM</div>
<div>{{data.ram_info}}</div>
</div>
</div>
<div class="data-status-item">
<div class="process">
<div class="process-bar" style="width: {{data.swap_process}}%;"></div>
</div>
<div class="data-status-item-text">
<div>SWAP</div>
<div>{{data.swap_info}}</div>
</div>
</div>
<div class="data-status-item">
<div class="process">
<div class="process-bar" style="width: {{data.disk_process}}%;"></div>
</div>
<div class="data-status-item-text">
<div>DISK</div>
<div>{{data.disk_info}}</div>
</div>
</div>
</div>
<div class="line"></div>
<div class="status-text">
<div>
<p class="status-text-title">CPU</p>
<p class="status-text-title">SYSTEM</p>
<p class="status-text-title">VERSION</p>
<p class="status-text-title">PLUGINS</p>
</div>
<div style="margin-left: 37px;">
<p class="status-text-title" style="font-size: 14px;line-height: 18.5px;">{{data.brand_raw}}</p>
<p class="status-text-title">{{data.system}}</p>
<p class="status-text-title">{{data.version}}</p>
<p class="status-text-title">{{data.plugin_count}} loaded</p>
</div>
</div>
</div>
<div class="tip">Create By Zhenxun</div>
</div>
</body>
<script type="text/javascript" src="main.js">
</script>
</html>
View File
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

+53
View File
@@ -0,0 +1,53 @@
from nonebot.rule import to_me
from nonebot.permission import SUPERUSER
from nonebot.plugin import PluginMetadata
from nonebot_plugin_session import EventSession
from nonebot_plugin_htmlrender import template_to_pic
from nonebot_plugin_alconna import Alconna, Arparma, on_alconna
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
from zhenxun.utils.message import MessageUtils
from zhenxun.configs.utils import PluginExtraData
from zhenxun.configs.path_config import TEMPLATE_PATH
from .data_source import get_status_info
__plugin_meta__ = PluginMetadata(
name="服务器自我检查",
description="查看服务器当前状态",
usage="""
查看服务器当前状态
指令:
自检
""".strip(),
extra=PluginExtraData(
author="HibiKier", version="0.1", plugin_type=PluginType.SUPERUSER
).dict(),
)
_matcher = on_alconna(
Alconna("自检"), rule=to_me(), permission=SUPERUSER, block=True, priority=1
)
@_matcher.handle()
async def _(session: EventSession, arparma: Arparma):
try:
data = await get_status_info()
image = await template_to_pic(
template_path=str((TEMPLATE_PATH / "check").absolute()),
template_name="main.html",
templates={"data": data},
pages={
"viewport": {"width": 195, "height": 750},
"base_url": f"file://{TEMPLATE_PATH}",
},
wait=2,
)
await MessageUtils.build_message(image).send()
logger.info("自检", arparma.header_result, session=session)
except Exception as e:
await MessageUtils.build_message(f"自检失败: {e}").send()
logger.error("自检失败", arparma.header_result, session=session, e=e)
@@ -0,0 +1,163 @@
import platform
from pathlib import Path
from dataclasses import dataclass
import psutil
import cpuinfo
import nonebot
from pydantic import BaseModel
from nonebot.utils import run_sync
from httpx import NetworkError, ConnectTimeout
from zhenxun.services.log import logger
from zhenxun.configs.config import BotConfig
from zhenxun.utils.http_utils import AsyncHttpx
BAIDU_URL = "https://www.baidu.com/"
GOOGLE_URL = "https://www.google.com/"
VERSION_FILE = Path() / "__version__"
@dataclass
class CPUInfo:
core: int
"""CPU 物理核心数"""
usage: float
"""CPU 占用百分比,取值范围(0,100]"""
freq: float
"""CPU 的时钟速度(单位:GHz)"""
@classmethod
def get_cpu_info(cls):
cpu_core = psutil.cpu_count(logical=False)
cpu_usage = psutil.cpu_percent(interval=1)
cpu_freq = round(psutil.cpu_freq().current / 1000, 2)
return CPUInfo(core=cpu_core, usage=cpu_usage, freq=cpu_freq)
@dataclass
class RAMInfo:
"""RAM 信息(单位:GB)"""
total: float
"""RAM 总量"""
usage: float
"""当前 RAM 占用量/GB"""
@classmethod
def get_ram_info(cls):
ram_total = round(psutil.virtual_memory().total / (1024**3), 2)
ram_usage = round(psutil.virtual_memory().used / (1024**3), 2)
return RAMInfo(total=ram_total, usage=ram_usage)
@dataclass
class SwapMemory:
"""Swap 信息(单位:GB)"""
total: float
"""Swap 总量"""
usage: float
"""当前 Swap 占用量/GB"""
@classmethod
def get_swap_info(cls):
swap_total = round(psutil.swap_memory().total / (1024**3), 2)
swap_usage = round(psutil.swap_memory().used / (1024**3), 2)
return SwapMemory(total=swap_total, usage=swap_usage)
@dataclass
class DiskInfo:
"""硬盘信息"""
total: float
"""硬盘总量"""
usage: float
"""当前硬盘占用量/GB"""
@classmethod
def get_disk_info(cls):
disk_total = round(psutil.disk_usage("/").total / (1024**3), 2)
disk_usage = round(psutil.disk_usage("/").used / (1024**3), 2)
return DiskInfo(total=disk_total, usage=disk_usage)
class SystemInfo(BaseModel):
"""系统信息"""
cpu: CPUInfo
"""CPU信息"""
ram: RAMInfo
"""RAM信息"""
swap: SwapMemory
"""SWAP信息"""
disk: DiskInfo
"""DISK信息"""
def get_system_info(self):
return {
"cpu_info": f"{self.cpu.usage}% - {self.cpu.freq}Ghz [{self.cpu.core} core]",
"cpu_process": psutil.cpu_percent(),
"ram_info": f"{self.ram.usage} / {self.ram.total} GB",
"ram_process": self.ram.usage / self.ram.total * 100,
"swap_info": f"{self.swap.usage} / {self.swap.total} GB",
"swap_process": self.swap.usage / self.swap.total * 100,
"disk_info": f"{self.disk.usage} / {self.disk.total} GB",
"disk_process": self.disk.usage / self.disk.total * 100,
}
@run_sync
def __build_status() -> dict:
"""获取 `CPU` `RAM` `SWAP` `DISK` 信息"""
cpu = CPUInfo.get_cpu_info()
ram = RAMInfo.get_ram_info()
swap = SwapMemory.get_swap_info()
disk = DiskInfo.get_disk_info()
return SystemInfo(cpu=cpu, ram=ram, swap=swap, disk=disk).get_system_info()
async def __get_network_info():
"""网络请求"""
baidu, google = True, True
try:
await AsyncHttpx.get(BAIDU_URL, timeout=5)
except Exception as e:
logger.warning("自检:百度无法访问...", e=e)
baidu = False
try:
await AsyncHttpx.get(GOOGLE_URL, timeout=5)
except Exception as e:
logger.warning("自检:谷歌无法访问...", e=e)
google = False
return baidu, google
def __get_version() -> str | None:
"""获取版本信息"""
with open(VERSION_FILE, encoding="utf-8") as f:
if text := f.read():
text.split(":")[-1]
return None
async def get_status_info() -> dict:
"""获取信息"""
data = await __build_status()
baidu, google = await __get_network_info()
data["baidu"] = "#8CC265" if baidu else "red"
data["google"] = "#8CC265" if google else "red"
system = platform.uname()
data["system"] = f"{system.system} {system.release}"
data["version"] = __get_version()
data["brand_raw"] = cpuinfo.get_cpu_info()["brand_raw"]
data["plugin_count"] = len(nonebot.get_loaded_plugins())
data["nickname"] = BotConfig.self_nickname
return data
+91 -91
View File
@@ -1,27 +1,26 @@
import time import time
from datetime import datetime from datetime import datetime
from nonebot.plugin import PluginMetadata
from nonebot import on_message, on_request from nonebot import on_message, on_request
from nonebot.adapters.onebot.v11 import ( from nonebot_plugin_session import EventSession
ActionFailed, from nonebot_plugin_apscheduler import scheduler
FriendRequestEvent,
GroupRequestEvent,
)
from nonebot.adapters.onebot.v11 import Bot as v11Bot from nonebot.adapters.onebot.v11 import Bot as v11Bot
from nonebot.adapters.onebot.v12 import Bot as v12Bot from nonebot.adapters.onebot.v12 import Bot as v12Bot
from nonebot.plugin import PluginMetadata from nonebot.adapters.onebot.v11 import (
from nonebot_plugin_apscheduler import scheduler ActionFailed,
from nonebot_plugin_session import EventSession GroupRequestEvent,
FriendRequestEvent,
)
from zhenxun.configs.config import BotConfig, Config
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
from zhenxun.models.fg_request import FgRequest
from zhenxun.models.friend_user import FriendUser
from zhenxun.models.group_console import GroupConsole
from zhenxun.services.log import logger from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType, RequestHandleType, RequestType from zhenxun.models.fg_request import FgRequest
from zhenxun.utils.message import MessageUtils
from zhenxun.utils.platform import PlatformUtils from zhenxun.utils.platform import PlatformUtils
from zhenxun.models.friend_user import FriendUser
from zhenxun.configs.config import Config, BotConfig
from zhenxun.models.group_console import GroupConsole
from zhenxun.configs.utils import RegisterConfig, PluginExtraData
from zhenxun.utils.enum import PluginType, RequestType, RequestHandleType
base_config = Config.get("invite_manager") base_config = Config.get("invite_manager")
@@ -119,91 +118,92 @@ async def _(bot: v12Bot | v11Bot, event: FriendRequestEvent, session: EventSessi
@group_req.handle() @group_req.handle()
async def _(bot: v12Bot | v11Bot, event: GroupRequestEvent, session: EventSession): async def _(bot: v12Bot | v11Bot, event: GroupRequestEvent, session: EventSession):
if event.sub_type == "invite": if event.sub_type != "invite":
if str(event.user_id) in bot.config.superusers: return
try: if str(event.user_id) in bot.config.superusers:
logger.debug( try:
"超级用户自动同意加入群聊",
"群聊请求",
session=event.user_id,
target=event.group_id,
)
group, _ = await GroupConsole.update_or_create(
group_id=str(event.group_id),
defaults={
"group_name": "",
"max_member_count": 0,
"member_count": 0,
"group_flag": 1,
},
)
await bot.set_group_add_request(
flag=event.flag, sub_type="invite", approve=True
)
if isinstance(bot, v11Bot):
group_info = await bot.get_group_info(group_id=event.group_id)
max_member_count = group_info["max_member_count"]
member_count = group_info["member_count"]
else:
group_info = await bot.get_group_info(group_id=str(event.group_id))
max_member_count = 0
member_count = 0
group.max_member_count = max_member_count
group.member_count = member_count
group.group_name = group_info["group_name"]
await group.save(
update_fields=["group_name", "max_member_count", "member_count"]
)
except ActionFailed as e:
logger.error(
"超级用户自动同意加入群聊发生错误",
"群聊请求",
session=event.user_id,
target=event.group_id,
e=e,
)
elif Timer.check(f"{event.user_id}:{event.group_id}"):
logger.debug( logger.debug(
f"收录 用户[{event.user_id}] 群聊[{event.group_id}] 群聊请求", "超级用户自动同意加入群聊",
"群聊请求", "群聊请求",
session=event.user_id,
target=event.group_id, target=event.group_id,
) )
nickname = await FriendUser.get_user_name(str(event.user_id)) group, _ = await GroupConsole.update_or_create(
await bot.send_private_msg(
user_id=event.user_id,
message=f"想要邀请我偷偷入群嘛~已经提醒{BotConfig.self_nickname}的管理员大人了\n"
"请确保已经群主或群管理沟通过!\n"
"等待管理员处理吧!",
)
# 旧请求全部设置为过期
await FgRequest.filter(
request_type=RequestType.GROUP,
user_id=str(event.user_id),
group_id=str(event.group_id),
handle_type__isnull=True,
).update(handle_type=RequestHandleType.EXPIRE)
f = await FgRequest.create(
request_type=RequestType.GROUP,
platform=session.platform,
bot_id=bot.self_id,
flag=event.flag,
user_id=str(event.user_id),
nickname=nickname,
group_id=str(event.group_id), group_id=str(event.group_id),
defaults={
"group_name": "",
"max_member_count": 0,
"member_count": 0,
"group_flag": 1,
},
) )
await PlatformUtils.send_superuser( await bot.set_group_add_request(
bot, flag=event.flag, sub_type="invite", approve=True
f"*****一份入群申请*****\n"
"ID:{f.id}\n"
"申请人:{nickname}({event.user_id})\n群聊:"
f"{event.group_id}\n邀请日期:{datetime.now().replace(microsecond=0)}",
) )
else: if isinstance(bot, v11Bot):
logger.debug( group_info = await bot.get_group_info(group_id=event.group_id)
"群聊请求五分钟内重复, 已忽略", max_member_count = group_info["max_member_count"]
member_count = group_info["member_count"]
else:
group_info = await bot.get_group_info(group_id=str(event.group_id))
max_member_count = 0
member_count = 0
group.max_member_count = max_member_count
group.member_count = member_count
group.group_name = group_info["group_name"]
await group.save(
update_fields=["group_name", "max_member_count", "member_count"]
)
except ActionFailed as e:
logger.error(
"超级用户自动同意加入群聊发生错误",
"群聊请求", "群聊请求",
target=f"{event.user_id}:{event.group_id}", session=event.user_id,
target=event.group_id,
e=e,
) )
elif Timer.check(f"{event.user_id}:{event.group_id}"):
logger.debug(
f"收录 用户[{event.user_id}] 群聊[{event.group_id}] 群聊请求",
"群聊请求",
target=event.group_id,
)
nickname = await FriendUser.get_user_name(str(event.user_id))
await bot.send_private_msg(
user_id=event.user_id,
message=f"想要邀请我偷偷入群嘛~已经提醒{BotConfig.self_nickname}的管理员大人了\n"
"请确保已经群主或群管理沟通过!\n"
"等待管理员处理吧!",
)
# 旧请求全部设置为过期
await FgRequest.filter(
request_type=RequestType.GROUP,
user_id=str(event.user_id),
group_id=str(event.group_id),
handle_type__isnull=True,
).update(handle_type=RequestHandleType.EXPIRE)
f = await FgRequest.create(
request_type=RequestType.GROUP,
platform=session.platform,
bot_id=bot.self_id,
flag=event.flag,
user_id=str(event.user_id),
nickname=nickname,
group_id=str(event.group_id),
)
await PlatformUtils.send_superuser(
bot,
f"*****一份入群申请*****\n"
f"ID:{f.id}\n"
f"申请人:{nickname}({event.user_id})\n群聊:"
f"{event.group_id}\n邀请日期:{datetime.now().replace(microsecond=0)}",
)
else:
logger.debug(
"群聊请求五分钟内重复, 已忽略",
"群聊请求",
target=f"{event.user_id}:{event.group_id}",
)
@scheduler.scheduled_job( @scheduler.scheduled_job(
+2 -1
View File
@@ -173,6 +173,7 @@ class AsyncHttpx:
cookies: dict[str, str] | None = None, cookies: dict[str, str] | None = None,
timeout: int = 30, timeout: int = 30,
stream: bool = False, stream: bool = False,
follow_redirects: bool = True,
**kwargs, **kwargs,
) -> bool: ) -> bool:
"""下载文件 """下载文件
@@ -204,7 +205,7 @@ class AsyncHttpx:
use_proxy=use_proxy, use_proxy=use_proxy,
proxy=proxy, proxy=proxy,
timeout=timeout, timeout=timeout,
follow_redirects=True, follow_redirects=follow_redirects,
**kwargs, **kwargs,
) )
response.raise_for_status() response.raise_for_status()