feat: 更多的字体
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import json
|
||||
import platform
|
||||
|
||||
import nonebot
|
||||
@@ -7,15 +8,17 @@ from nonebot import on_command
|
||||
from nonebot.adapters.onebot.v11 import MessageSegment
|
||||
from nonebot.permission import SUPERUSER
|
||||
|
||||
from liteyuki.utils import __NAME__, __VERSION__
|
||||
from liteyuki.utils import __NAME__, __VERSION__, load_from_yaml
|
||||
from liteyuki.utils.htmlrender import template2image
|
||||
from liteyuki.utils.language import get_user_lang
|
||||
from liteyuki.utils.language import Language, get_default_lang, get_user_lang
|
||||
from liteyuki.utils.ly_typing import T_Bot, T_MessageEvent
|
||||
from liteyuki.utils.resource import get_path
|
||||
from liteyuki.utils.tools import convert_size
|
||||
|
||||
stats = on_command("stats", aliases={"状态"}, priority=5, permission=SUPERUSER)
|
||||
|
||||
config = load_from_yaml("config.yml")
|
||||
|
||||
protocol_names = {
|
||||
0: "iPad",
|
||||
1: "Android Phone",
|
||||
@@ -29,19 +32,108 @@ protocol_names = {
|
||||
@stats.handle()
|
||||
async def _(bot: T_Bot, event: T_MessageEvent):
|
||||
ulang = get_user_lang(str(event.user_id))
|
||||
fake_device_info: dict = bot.config.dict().get("fake_device_info", {})
|
||||
mem_total = fake_device_info.get('mem', {}).get('total', psutil.virtual_memory().total)
|
||||
image = await template2image(
|
||||
get_path("templates/stats.html", abs_path=True),
|
||||
{
|
||||
"data": await get_stats_data(bot.self_id, ulang.lang_code)
|
||||
},
|
||||
debug=True
|
||||
)
|
||||
await stats.finish(MessageSegment.image(image))
|
||||
|
||||
|
||||
async def get_bots_data(ulang: Language, self_id: "") -> list:
|
||||
bots_data = []
|
||||
for bot_id, bot in nonebot.get_bots().items():
|
||||
groups = 0
|
||||
friends = 0
|
||||
status = {}
|
||||
bot_name = bot_id
|
||||
version_info = {}
|
||||
try:
|
||||
bot_name = (await bot.get_login_info())["nickname"]
|
||||
groups = len(await bot.get_group_list())
|
||||
friends = len(await bot.get_friend_list())
|
||||
status = await bot.get_status()
|
||||
version_info = await bot.get_version_info()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
statistics = status.get("stat", {})
|
||||
app_name = version_info.get("app_name", "UnknownImplementation")
|
||||
if app_name in ["Lagrange.OneBot", "LLOneBot", "Shamrock"]:
|
||||
icon = f"https://q.qlogo.cn/g?b=qq&nk={bot_id}&s=640"
|
||||
else:
|
||||
icon = "./img/liteyuki.png"
|
||||
bot_data = {
|
||||
"name": bot_name,
|
||||
"icon": icon,
|
||||
"id" : bot_id,
|
||||
"tags": [
|
||||
protocol_names.get(version_info.get("protocol_name"), "Online"),
|
||||
f"{ulang.get('liteyuki.stats.groups')} {groups}",
|
||||
f"{ulang.get('liteyuki.stats.friends')} {friends}",
|
||||
f"{ulang.get('liteyuki.stats.sent')} {statistics.get('message_sent', 0)}",
|
||||
f"{ulang.get('liteyuki.stats.received')} {statistics.get('message_received', 0)}",
|
||||
app_name,
|
||||
|
||||
],
|
||||
"self": bot_id == self_id, # 判断是否是自己
|
||||
}
|
||||
bots_data.append(bot_data)
|
||||
bots_data.append(
|
||||
{
|
||||
"name": "Liteyuki",
|
||||
"icon": "./img/liteyuki.png",
|
||||
"id" : "liteyuki",
|
||||
"tags": [
|
||||
f"{__NAME__} {__VERSION__}",
|
||||
f"{ulang.get('liteyuki.stats.plugins')} {len(nonebot.get_loaded_plugins())}",
|
||||
f"Nonebot {nonebot.__version__}",
|
||||
f"{platform.python_implementation()} {platform.python_version()}",
|
||||
]
|
||||
}
|
||||
)
|
||||
return bots_data
|
||||
|
||||
|
||||
async def get_stats_data(self_id: str = None, lang: str = None):
|
||||
if self_id is None:
|
||||
self_id = list(nonebot.get_bots().keys())[0] if len(nonebot.get_bots()) > 0 else "liteyuki"
|
||||
if lang is None:
|
||||
ulang = get_default_lang()
|
||||
else:
|
||||
ulang = Language(lang)
|
||||
|
||||
fake_device_info: dict = config.get("fake_device_info", {})
|
||||
|
||||
mem_info = psutil.virtual_memory()
|
||||
mem_total = fake_device_info.get('mem', {}).get('total', mem_info.total)
|
||||
|
||||
convert_size(mem_total, 1, False)
|
||||
mem_total_show = convert_size(mem_total, 1) # 格式化带单位
|
||||
|
||||
mem_used_bot = psutil.Process().memory_info().rss
|
||||
mem_used_other = psutil.virtual_memory().used - mem_used_bot
|
||||
mem_free = mem_total - mem_used_other - mem_used_bot
|
||||
mem_used_bot_show = convert_size(mem_used_bot, 1)
|
||||
mem_used_other = mem_info.used - mem_used_bot
|
||||
mem_free = mem_total - mem_info.used
|
||||
mem_used_show = convert_size(mem_total - mem_used_bot - mem_used_other, 1) # 计算已用格式化带单位
|
||||
|
||||
groups = len(await bot.get_group_list())
|
||||
friends = len(await bot.get_friend_list())
|
||||
swap_info = psutil.swap_memory()
|
||||
|
||||
status = await bot.get_status()
|
||||
statistics = status.get("stat", {})
|
||||
version_info = await bot.get_version_info()
|
||||
disk_data = []
|
||||
for disk in psutil.disk_partitions(all=True):
|
||||
disk_usage = psutil.disk_usage(disk.mountpoint)
|
||||
disk_total_show = convert_size(disk_usage.total, 1)
|
||||
disk_free_show = convert_size(disk_usage.free, 1)
|
||||
disk_data.append(
|
||||
{
|
||||
"name" : disk.device,
|
||||
"total" : disk_total_show,
|
||||
"free" : disk_free_show,
|
||||
"percent": disk_usage.percent,
|
||||
}
|
||||
)
|
||||
|
||||
cpu_info = get_cpu_info()
|
||||
if "AMD" in cpu_info.get("brand_raw", ""):
|
||||
@@ -56,7 +148,7 @@ async def _(bot: T_Bot, event: T_MessageEvent):
|
||||
|
||||
cpu_info = get_cpu_info()
|
||||
templ = {
|
||||
"CPUDATA" : [
|
||||
"cpu" : [
|
||||
{
|
||||
"name" : "USED",
|
||||
"value": psutil.cpu_percent(interval=1)
|
||||
@@ -66,7 +158,7 @@ async def _(bot: T_Bot, event: T_MessageEvent):
|
||||
"value": 100 - psutil.cpu_percent(interval=1)
|
||||
}
|
||||
],
|
||||
"MEMDATA" : [
|
||||
"mem" : [
|
||||
|
||||
{
|
||||
"name" : "OTHER",
|
||||
@@ -81,7 +173,7 @@ async def _(bot: T_Bot, event: T_MessageEvent):
|
||||
"value": mem_used_bot
|
||||
},
|
||||
],
|
||||
"SWAPDATA" : [
|
||||
"swap" : [
|
||||
{
|
||||
"name" : "USED",
|
||||
"value": psutil.swap_memory().used
|
||||
@@ -91,44 +183,30 @@ async def _(bot: T_Bot, event: T_MessageEvent):
|
||||
"value": psutil.swap_memory().free
|
||||
}
|
||||
],
|
||||
"BOT_ID" : bot.self_id,
|
||||
"BOT_NAME" : (await bot.get_login_info())["nickname"],
|
||||
"BOT_TAGS" : [
|
||||
protocol_names.get(version_info.get("protocol_name"), "Online"),
|
||||
f"{ulang.get('liteyuki.stats.plugins')} {len(nonebot.get_loaded_plugins())}",
|
||||
f"{ulang.get('liteyuki.stats.groups')} {groups}",
|
||||
f"{ulang.get('liteyuki.stats.friends')} {friends}",
|
||||
f"{ulang.get('liteyuki.stats.sent')} {statistics.get('message_sent', 0)}",
|
||||
f"{ulang.get('liteyuki.stats.received')} {statistics.get('message_received', 0)}",
|
||||
f"{__NAME__} {__VERSION__}",
|
||||
f"Nonebot {nonebot.__version__}",
|
||||
f"{platform.python_implementation()} {platform.python_version()}",
|
||||
version_info.get("app_name"),
|
||||
],
|
||||
"CPU_TAGS" : [
|
||||
"disk" : disk_data, # list[{"name":"C", "total":100, "used":50, "free":50}]
|
||||
"bot" : await get_bots_data(ulang, self_id),
|
||||
"cpuTags" : [
|
||||
f"{brand} {cpu_info.get('arch', 'Unknown')}",
|
||||
f"{fake_device_info.get('cpu', {}).get('cores', psutil.cpu_count(logical=False))}C "
|
||||
f"{fake_device_info.get('cpu', {}).get('logical_cores', psutil.cpu_count(logical=True))}T",
|
||||
f"{fake_device_info.get('cpu', {}).get('frequency', psutil.cpu_freq().current) / 1000}GHz"
|
||||
],
|
||||
"MEM_TAGS" : [
|
||||
f"Bot {convert_size(mem_used_bot, 1)}",
|
||||
f"{ulang.get('main.monitor.used')} {convert_size(mem_used_other + mem_used_bot, 1)}",
|
||||
f"{ulang.get('main.monitor.total')} {convert_size(mem_total, 1)}",
|
||||
"memTags" : [
|
||||
f"Bot {mem_used_bot_show}",
|
||||
f"{ulang.get('main.monitor.used')} {mem_used_show}",
|
||||
f"{ulang.get('main.monitor.total')} {mem_total_show}",
|
||||
f"{ulang.get('main.monitor.free')} {convert_size(mem_free, 1)}",
|
||||
],
|
||||
"SWAP_TAGS": [
|
||||
f"{ulang.get('main.monitor.used')} {convert_size(psutil.swap_memory().used, 1)}",
|
||||
f"{ulang.get('main.monitor.total')} {convert_size(psutil.swap_memory().total, 1)}",
|
||||
"swapTags" : [
|
||||
f"{ulang.get('main.monitor.used')} {convert_size(swap_info.used, 1)}",
|
||||
f"{ulang.get('main.monitor.total')} {convert_size(swap_info.total, 1)}",
|
||||
f"{ulang.get('main.monitor.free')} {convert_size(swap_info.free, 1)}",
|
||||
],
|
||||
"CPU" : ulang.get("main.monitor.cpu"),
|
||||
"MEM" : ulang.get("main.monitor.memory"),
|
||||
"SWAP" : ulang.get("main.monitor.swap"),
|
||||
"cpu_trans" : ulang.get("main.monitor.cpu"),
|
||||
"mem_trans" : ulang.get("main.monitor.memory"),
|
||||
"swap_trans" : ulang.get("main.monitor.swap"),
|
||||
"used_trans" : ulang.get("main.monitor.used"),
|
||||
"free_trans" : ulang.get("main.monitor.free"),
|
||||
"total_trans": ulang.get("main.monitor.total"),
|
||||
}
|
||||
image_bytes = await template2image(
|
||||
template=get_path("templates/stats.html", abs_path=True),
|
||||
templates=templ,
|
||||
scale_factor=1,
|
||||
debug=True
|
||||
)
|
||||
# await md.send_image(image_bytes, bot, event=event)
|
||||
await stats.finish(MessageSegment.image(image_bytes))
|
||||
return templ
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
from nonebot import on_command
|
||||
from liteyuki.utils.data import LiteModel
|
||||
|
||||
|
||||
@@ -11,3 +11,30 @@ class WeatherNow(LiteModel):
|
||||
time: str = ""
|
||||
city: str = ""
|
||||
|
||||
|
||||
weather = on_command("weather", aliases={"天气", "查天气", "天气预报", "查天气预报"})
|
||||
weather_now = on_command("weather_now", aliases={"实时天气", "查实时天气", "实时天气预报", "查实时天气预报"})
|
||||
weather_forecast = on_command("weather_forecast", aliases={"天气预报", "查天气预报", "未来天气", "查未来天气"})
|
||||
weather_warning = on_command("weather_warning", aliases={"天气预警", "查天气预警", "天气警告", "查天气警告"})
|
||||
weather_life = on_command("weather_life", aliases={"生活指数", "查生活指数", "生活指数预报", "查生活指数预报"})
|
||||
weather_air = on_command("weather_air", aliases={"空气质量", "查空气质量", "空气质量预报", "查空气质量预报"})
|
||||
weather_rain = on_command("weather_rain", aliases={"降雨预报", "查降雨预报", "降雨量", "查降雨量"})
|
||||
weather_snow = on_command("weather_snow", aliases={"降雪预报", "查降雪预报", "降雪量", "查降雪量"})
|
||||
|
||||
|
||||
@weather.handle()
|
||||
async def handle_weather(bot, event):
|
||||
args = str(event.get_message()).strip()
|
||||
if not args:
|
||||
await weather.finish("请输入要查询的城市")
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
@weather_now.handle()
|
||||
async def handle_weather_now(bot, event):
|
||||
pass
|
||||
|
||||
@weather_forecast.handle()
|
||||
async def handle_weather_forecast(bot, event):
|
||||
pass
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -97,4 +97,5 @@ user.profile.set_failed={ATTR} festlegen fehlgeschlagen. Bitte überprüfen Sie,
|
||||
liteyuki.image_mode_on=Markdown-Bild
|
||||
liteyuki.image_mode_off=Markdown-Link
|
||||
|
||||
npm.page=Page {PAGE}/{TOTAL}
|
||||
npm.page=Page {PAGE}/{TOTAL}
|
||||
main.monitor.free=Free
|
||||
@@ -1,10 +1,10 @@
|
||||
language.name=English
|
||||
|
||||
log.debug=Debug
|
||||
log.info=Info
|
||||
log.warning=Warning
|
||||
log.error=Error
|
||||
log.success=Success
|
||||
log.debug=DEBUG
|
||||
log.info=INFO
|
||||
log.warning=WARNING
|
||||
log.error=ERROR
|
||||
log.success=SUCCESS
|
||||
|
||||
liteyuki.restart=Restart
|
||||
liteyuki.restart_now=Restart Now
|
||||
@@ -97,4 +97,5 @@ user.profile.set_failed=Setting {ATTR} failed. Please check if the input is vali
|
||||
liteyuki.image_mode_on=Enable markdown image
|
||||
liteyuki.image_mode_off=Closed markdown image
|
||||
|
||||
npm.page=Page {PAGE}/{TOTAL}
|
||||
npm.page=Page {PAGE}/{TOTAL}
|
||||
main.monitor.free=Free
|
||||
@@ -92,4 +92,10 @@ user.profile.nickname.desc=Establecer el apodo del Bot para el usuario
|
||||
|
||||
user.profile.input_value=Por favor ingresa {ATTR}
|
||||
user.profile.set_success={ATTR} establecido correctamente a {VALUE}
|
||||
user.profile.set_failed=Fallo al establecer {ATTR}. Por favor, verifica si la entrada es válida.
|
||||
user.profile.set_failed=Fallo al establecer {ATTR}. Por favor, verifica si la entrada es válida.
|
||||
|
||||
liteyuki.image_mode_on=Markdown de imagen abierta
|
||||
liteyuki.image_mode_off=Markdown de imagen cerrada
|
||||
|
||||
npm.page=Página {PAGE}/{TOTAL}
|
||||
main.monitor.free=Libre
|
||||
@@ -92,4 +92,7 @@ user.profile.nickname.desc=Définir le pseudo du Bot pour l'utilisateur
|
||||
|
||||
user.profile.input_value=Veuillez entrer {ATTR}
|
||||
user.profile.set_success={ATTR} défini avec succès sur {VALUE}
|
||||
user.profile.set_failed=Échec de la définition de {ATTR}. Veuillez vérifier si l'entrée est valide.
|
||||
user.profile.set_failed=Échec de la définition de {ATTR}. Veuillez vérifier si l'entrée est valide.
|
||||
|
||||
liteyuki.image_mode_on=Ouvert markdown image
|
||||
liteyuki.image_mode_off=Fermé markdown image
|
||||
@@ -97,4 +97,5 @@ user.profile.set_failed=设置 {ATTR} 失败,请检查输入是否合法
|
||||
liteyuki.image_mode_on=开启Markdown图片模式
|
||||
liteyuki.image_mode_off=关闭Markdown图片模式
|
||||
|
||||
npm.page=第{PAGE}/{TOTAL}页
|
||||
npm.page=第{PAGE}/{TOTAL}页
|
||||
main.monitor.free=空闲
|
||||
@@ -1,13 +1,54 @@
|
||||
/*MiSans*/
|
||||
|
||||
@font-face {
|
||||
font-family: 'MiSans';
|
||||
src: url('../../fonts/normal.ttf') format('truetype');
|
||||
src: url('../fonts/MiSans/MiSans-Normal.woff2') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'MiSans';
|
||||
src: url('../../fonts/bold.ttf') format('truetype');
|
||||
src: url('../fonts/MiSans/MiSans-Bold.woff2') format('truetype');
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/*MapleMono*/
|
||||
@font-face {
|
||||
font-family: 'MapleMono';
|
||||
src: url('../fonts/MapleMono/MapleMono-Light.woff2') format('truetype');
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'MapleMono';
|
||||
src: url('../fonts/MapleMono/MapleMono-LightItalic.woff2') format('truetype');
|
||||
font-weight: 200;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
|
||||
@font-face {
|
||||
font-family: 'MapleMono';
|
||||
src: url('../fonts/MapleMono/MapleMono-Regular.woff2') format('truetype');
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'MapleMono';
|
||||
src: url('../fonts/MapleMono/MapleMono-Italic.woff2') format('truetype');
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'MapleMono';
|
||||
src: url('../fonts/MapleMono/MapleMono-Bold.woff2') format('truetype');
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'MapleMono';
|
||||
src: url('../fonts/MapleMono/MapleMono-BoldItalic.woff2') format('truetype');
|
||||
font-weight: 700;
|
||||
font-style: italic;
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
@@ -1,13 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
<meta name="viewport" content="width=1080, initial-scale=1.0">
|
||||
<title>Liteyuki Stats</title>
|
||||
<link rel="stylesheet" href="css/fonts.css">
|
||||
<style>
|
||||
|
||||
@font-face {
|
||||
font-family: 'MiSans';
|
||||
src: url('fonts/normal.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'MiSans';
|
||||
src: url('fonts/bold.ttf') format('truetype');
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'MiSans', serif;
|
||||
background-repeat: repeat-y;
|
||||
@@ -16,7 +30,8 @@
|
||||
background-image: url('img/bg1.jpg');
|
||||
color: white;
|
||||
// 上10px,左右10px,下0px
|
||||
margin: 24px;
|
||||
//margin: 24px;
|
||||
margin: 20px;
|
||||
|
||||
|
||||
}
|
||||
@@ -26,47 +41,54 @@
|
||||
padding: 30px;
|
||||
backdrop-filter: blur(30px);
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
margin: 0 24px 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#cpu-chart, #mem-chart, #swap-chart {
|
||||
.pie-chart {
|
||||
height: 240px;
|
||||
width: 240px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#cpu-info, #mem-info, #swap-info {
|
||||
.pie-info {
|
||||
margin: 0 40px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#bot-info {
|
||||
margin-top: 24px;
|
||||
.bot-info {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#hardware-info {
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#bot-icon {
|
||||
#disks-info {
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.bot-icon {
|
||||
border-radius: 50%;
|
||||
height: 200px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
#bot-name, #bot-tag {
|
||||
.bot-name, .bot-tag {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
#bot-name {
|
||||
.bot-name {
|
||||
font-size: 42px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
#bot-tag {
|
||||
.bot-tag {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
@@ -76,87 +98,67 @@
|
||||
}
|
||||
|
||||
.tag {
|
||||
font-size: 27px;
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tag::after {
|
||||
content: "|";
|
||||
.tag[suffix="1"]::after {
|
||||
content: " | ";
|
||||
display: inline-block;
|
||||
margin: 0 5px;
|
||||
height: 50%;
|
||||
line-height: 50%;
|
||||
color: #aaa;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
</style>
|
||||
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts@4.3.0/dist/echarts.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="info-box" id="bot-info">
|
||||
<span>
|
||||
<img id="bot-icon" src="https://q.qlogo.cn/g?b=qq&nk={{BOT_ID}}}&s=640" alt="BotIcon">
|
||||
</span>
|
||||
<span>
|
||||
<span id="bot-name">
|
||||
{{ BOT_NAME }}
|
||||
</span>
|
||||
<div id="bot-tag"></div>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="info-box" id="hardware-info">
|
||||
<div id="cpu-info">
|
||||
<div id="cpu-chart"></div>
|
||||
<div class="pie-info" id="cpu-info">
|
||||
<div class="pie-chart" id="cpu-chart"></div>
|
||||
</div>
|
||||
<div id="mem-info">
|
||||
<div id="mem-chart"></div>
|
||||
<div class="pie-info" id="mem-info">
|
||||
<div class="pie-chart" id="mem-chart"></div>
|
||||
</div>
|
||||
<div id="swap-info">
|
||||
<div id="swap-chart"></div>
|
||||
<div class="pie-info" id="swap-info">
|
||||
<div class="pie-chart" id="swap-chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="cpuData" style="display: none;">{{ CPUDATA | tojson }}</div>
|
||||
<div id="memData" style="display: none;">{{ MEMDATA | tojson }}</div>
|
||||
<div id="swapData" style="display: none;">{{ SWAPDATA | tojson }}</div>
|
||||
<div id="botTag" style="display: none;">{{ BOT_TAGS | tojson }}</div>
|
||||
<div id="cpuTag" style="display: none;">{{ CPU_TAGS | tojson }}</div>
|
||||
<div id="memTag" style="display: none;">{{ MEM_TAGS | tojson }}</div>
|
||||
<div id="swapTag" style="display: none;">{{ SWAP_TAGS | tojson }}</div>
|
||||
<div class="info-box" id="disks-info">
|
||||
</div>
|
||||
|
||||
<!--储存数据div,不显示-->
|
||||
<div id="data" style="display: none">{{ data | tojson }}</div>
|
||||
<script>
|
||||
// 环形图
|
||||
{
|
||||
let bgs = ["bg1.jpg"]
|
||||
// 环形图
|
||||
|
||||
let bgs = ["bg1.jpg", "bg2.jpg", "bg3.jpg", "bg4.jpg"]
|
||||
// 随机选择背景图片
|
||||
document.body.style.backgroundImage = `url(./img/${bgs[Math.floor(Math.random() * bgs.length)]})`;
|
||||
|
||||
let botTags = JSON.parse(document.getElementById('botTag').innerText);
|
||||
|
||||
botTags.forEach(tag => {
|
||||
let tagSpan = document.createElement('span');
|
||||
tagSpan.innerText = tag;
|
||||
tagSpan.className = 'tag';
|
||||
document.getElementById('bot-tag').appendChild(tagSpan);
|
||||
});
|
||||
|
||||
|
||||
|
||||
let cpuInfo = echarts.init(document.getElementById('cpu-chart'));
|
||||
let memInfo = echarts.init(document.getElementById('mem-chart'));
|
||||
let swapInfo = echarts.init(document.getElementById('swap-chart'));
|
||||
let cpuData = JSON.parse(document.getElementById('cpuData').innerText);
|
||||
let memData = JSON.parse(document.getElementById('memData').innerText);
|
||||
let swapData = JSON.parse(document.getElementById('swapData').innerText);
|
||||
|
||||
sub_tag_data = {
|
||||
cpu: JSON.parse(document.getElementById('cpuTag').innerText),
|
||||
mem: JSON.parse(document.getElementById('memTag').innerText),
|
||||
swap: JSON.parse(document.getElementById('swapTag').innerText)
|
||||
let data = JSON.parse(document.getElementById('data').innerText);
|
||||
|
||||
let cpuData = data.cpu;
|
||||
let memData = data.mem;
|
||||
let swapData = data.swap;
|
||||
let diskData = data.disk;
|
||||
let sub_tag_data = {
|
||||
cpu: data.cpuTags,
|
||||
mem: data.memTags,
|
||||
swap: data.swapTags
|
||||
}
|
||||
// 遍历key和value,key是cpu,mem,swap,value是对应的tag数组,添加div标签class为chart-label
|
||||
for (let key in sub_tag_data) {
|
||||
console.log(key, sub_tag_data[key])
|
||||
let infoDiv = document.getElementById(key + '-info');
|
||||
sub_tag_data[key].forEach(tag => {
|
||||
let tagSpan = document.createElement('div');
|
||||
@@ -165,8 +167,84 @@
|
||||
infoDiv.appendChild(tagSpan);
|
||||
});
|
||||
}
|
||||
|
||||
function getOption(title, data) {
|
||||
cpuInfo.setOption(getPieOption(data.cpu_trans, cpuData));
|
||||
memInfo.setOption(getPieOption(data.mem_trans, memData));
|
||||
swapInfo.setOption(getPieOption(data.swap_trans, swapData));
|
||||
|
||||
|
||||
// 在disks-info中插入每个disk的div,用横向柱状图表示用量,每一行div显示一个disk,不加info-box
|
||||
diskData.forEach(disk => {
|
||||
let diskDiv = document.createElement('div');
|
||||
document.getElementById('disks-info').appendChild(diskDiv);
|
||||
let diskChart = document.createElement('div');
|
||||
diskChart.style.width = '100%';
|
||||
diskChart.style.height = '100px';
|
||||
diskDiv.appendChild(diskChart);
|
||||
let diskInfo = echarts.init(diskChart);
|
||||
// let diskTitle = disk.name + ' {{ FREE }} ' + disk.free + ' {{ TOTAL }} ' + disk.total;
|
||||
let diskTitle = `${disk.name} ${data.free_trans} ${disk.free} ${data.total_trans} ${disk.total}`;
|
||||
diskInfo.setOption(getBarOption(diskTitle, disk.percent));
|
||||
});
|
||||
|
||||
let botData = data.bot;
|
||||
// 清空bot-info
|
||||
let botInfos = document.getElementsByClassName('bot-info');
|
||||
while (botInfos.length > 0) {
|
||||
botInfos[0].remove();
|
||||
}
|
||||
botData.forEach(bot => {
|
||||
// 在hardware-info前面插入一个div
|
||||
let botDiv = document.createElement('div');
|
||||
botDiv.className = 'info-box bot-info';
|
||||
// 在body内的hardware-info前面插入botDiv
|
||||
document.body.insertBefore(botDiv, document.getElementById('hardware-info'));
|
||||
|
||||
let botIconBlock = document.createElement('div');
|
||||
let botIcon = document.createElement('img');
|
||||
botIcon.src = bot.icon;
|
||||
botIcon.className = 'bot-icon';
|
||||
botIconBlock.appendChild(botIcon);
|
||||
botDiv.appendChild(botIconBlock);
|
||||
|
||||
let botDetail = document.createElement('div');
|
||||
let botName = document.createElement('div');
|
||||
botName.className = 'bot-name';
|
||||
botName.innerText = bot.name;
|
||||
if (bot.self) {
|
||||
// 添加颜色
|
||||
botName.style.color = '#d0e9ff';
|
||||
}
|
||||
botDetail.appendChild(botName);
|
||||
|
||||
let botTags = document.createElement('div');
|
||||
botTags.className = 'bot-tag';
|
||||
botDetail.appendChild(botTags)
|
||||
|
||||
bot.tags.forEach((tag, index) => {
|
||||
if (!tag) {
|
||||
return;
|
||||
}
|
||||
let tagSpan = document.createElement('span');
|
||||
|
||||
tagSpan.innerText = tag;
|
||||
tagSpan.className = 'tag';
|
||||
if (bot.self) {
|
||||
// 添加颜色
|
||||
tagSpan.style.color = '#a2d8f4';
|
||||
}
|
||||
botTags.appendChild(tagSpan);
|
||||
if (index === bot.tags.length - 1) {
|
||||
tagSpan.setAttribute("suffix", "0")
|
||||
} else {
|
||||
tagSpan.setAttribute("suffix", "1")
|
||||
}
|
||||
});
|
||||
|
||||
botDiv.appendChild(botDetail);
|
||||
}
|
||||
)
|
||||
|
||||
function getPieOption(title, data) {
|
||||
return {
|
||||
animation: false,
|
||||
title: {
|
||||
@@ -216,10 +294,69 @@
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
cpuInfo.setOption(getOption("{{ CPU }}", cpuData));
|
||||
memInfo.setOption(getOption('{{ MEM }}', memData));
|
||||
swapInfo.setOption(getOption('{{ SWAP }}', swapData));
|
||||
|
||||
function getBarOption(title, percent) {
|
||||
// data为百分比,最大值为100
|
||||
return {
|
||||
background: '#d0e9ff',
|
||||
title: {
|
||||
text: title,
|
||||
left: '5%',
|
||||
top: 'center',
|
||||
textStyle: {
|
||||
color: '#fff',
|
||||
fontSize: 30
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: "item",
|
||||
backgroundColor: "#ffffff",
|
||||
},
|
||||
grid: {
|
||||
left: '0',
|
||||
right: '0',
|
||||
top: '10%',
|
||||
bottom: '10%'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
show: false
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
data: [''],
|
||||
show: false
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: 'Used',
|
||||
type: 'bar',
|
||||
stack: 'total',
|
||||
data: [percent],
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: '#a2d8f4',
|
||||
barBorderRadius: [50, 0, 0, 50]
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Free',
|
||||
type: 'bar',
|
||||
stack: 'total',
|
||||
data: [100 - percent],
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: '#d0e9ff',
|
||||
barBorderRadius: [0, 50, 50, 0]
|
||||
}
|
||||
},
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -31,6 +31,21 @@ from nonebot_plugin_htmlrender import *
|
||||
# """
|
||||
# return await html_to_pic(html, wait=wait, template_path=template_path, scale_factor=scale_factor)
|
||||
|
||||
async def template2html(
|
||||
template: str,
|
||||
templates: dict,
|
||||
) -> str:
|
||||
"""
|
||||
Args:
|
||||
template: str: 模板文件
|
||||
**templates: dict: 模板参数
|
||||
Returns:
|
||||
HTML 正文
|
||||
"""
|
||||
template_path = os.path.dirname(template)
|
||||
template_name = os.path.basename(template)
|
||||
return await template_to_html(template_path, template_name, **templates)
|
||||
|
||||
|
||||
async def template2image(
|
||||
template: str,
|
||||
@@ -81,3 +96,32 @@ async def template2image(
|
||||
wait=wait,
|
||||
device_scale_factor=scale_factor,
|
||||
)
|
||||
|
||||
|
||||
async def url2image(
|
||||
url: str,
|
||||
wait: int = 0,
|
||||
scale_factor: float = 1,
|
||||
type: str = "png",
|
||||
quality: int = 100,
|
||||
**kwargs
|
||||
) -> bytes:
|
||||
"""
|
||||
Args:
|
||||
quality:
|
||||
type:
|
||||
url: str: URL
|
||||
wait: int: 等待时间
|
||||
scale_factor: float: 缩放因子
|
||||
**kwargs: page 参数
|
||||
Returns:
|
||||
图片二进制数据
|
||||
"""
|
||||
async with get_new_page(scale_factor) as page:
|
||||
await page.goto(url)
|
||||
await page.wait_for_timeout(wait)
|
||||
return await page.screenshot(
|
||||
full_page=True,
|
||||
type=type,
|
||||
quality=quality
|
||||
)
|
||||
|
||||
@@ -67,8 +67,8 @@ def init_log():
|
||||
warning = lang.get("log.warning", default="WARNING")
|
||||
error = lang.get("log.error", default="==ERROR")
|
||||
|
||||
logger.level("DEBUG", color="<blue>", icon=f"{'*️⃣' if show_icon else ''}{debug}")
|
||||
logger.level("INFO", color="<white>", icon=f"{'ℹ️' if show_icon else ''}{info}")
|
||||
logger.level("DEBUG", color="<blue>", icon=f"{'🐛' if show_icon else ''}{debug}")
|
||||
logger.level("INFO", color="<normal>", icon=f"{'ℹ️' if show_icon else ''}{info}")
|
||||
logger.level("SUCCESS", color="<green>", icon=f"{'✅' if show_icon else ''}{success}")
|
||||
logger.level("WARNING", color="<yellow>", icon=f"{'⚠️' if show_icon else ''}{warning}")
|
||||
logger.level("ERROR", color="<red>", icon=f"{'⭕' if show_icon else ''}{error}")
|
||||
|
||||
+10
-15
@@ -15,13 +15,13 @@ def clamp(value: float, min_value: float, max_value: float) -> float | int:
|
||||
return max(min(value, max_value), min_value)
|
||||
|
||||
|
||||
def convert_size(size: int, precision: int = 2, add_unit: bool = True, suffix: str = "iB") -> str:
|
||||
def convert_size(size: int, precision: int = 2, add_unit: bool = True, suffix: str = " XiB") -> str | float:
|
||||
"""把字节数转换为人类可读的字符串,计算正负
|
||||
|
||||
Args:
|
||||
|
||||
add_unit: 是否添加单位,False后则suffix无效
|
||||
suffix: iB或B
|
||||
suffix: XiB或XB
|
||||
precision: 浮点数的小数点位数
|
||||
size (int): 字节数
|
||||
|
||||
@@ -29,23 +29,18 @@ def convert_size(size: int, precision: int = 2, add_unit: bool = True, suffix: s
|
||||
|
||||
str: The human-readable string, e.g. "1.23 GB".
|
||||
"""
|
||||
is_negative = False
|
||||
if size < 0:
|
||||
is_negative = True
|
||||
size = -size
|
||||
|
||||
for unit in ["", "K", "M", "G", "T", "P", "E", "Z", "Y"]:
|
||||
is_negative = size < 0
|
||||
size = abs(size)
|
||||
for unit in ("", "K", "M", "G", "T", "P", "E", "Z"):
|
||||
if size < 1024:
|
||||
if add_unit:
|
||||
result = f"{size:.{precision}f} {unit}" + suffix
|
||||
return f"-{result}" if is_negative else result
|
||||
else:
|
||||
return f"{size:.{precision}f}"
|
||||
break
|
||||
size /= 1024
|
||||
if is_negative:
|
||||
size = -size
|
||||
if add_unit:
|
||||
return f"{size:.{precision}f} Y" + suffix
|
||||
return f"{size:.{precision}f}{suffix.replace('X', unit)}"
|
||||
else:
|
||||
return f"{size:.{precision}f}"
|
||||
return size
|
||||
|
||||
|
||||
def keywords_in_text(keywords: list[str], text: str, all_matched: bool) -> bool:
|
||||
|
||||
+2
-9
@@ -1,13 +1,10 @@
|
||||
aiohttp==3.9.3
|
||||
aiofiles==23.2.1
|
||||
arclet-alconna==1.8.5
|
||||
arclet-alconna-tools==0.7.0
|
||||
colored==2.2.4
|
||||
dash==2.16.1
|
||||
GitPython==3.1.42
|
||||
jinja2==3.1.3
|
||||
markdown==3.3.6
|
||||
nonebot2[fastapi]==2.2.1
|
||||
nonebot2[fastapi,httpx,websockets]==2.2.1
|
||||
nonebot-plugin-htmlrender==0.3.1
|
||||
nonebot-adapter-onebot==2.4.3
|
||||
nonebot-plugin-alconna==0.41.0
|
||||
playwright==1.17.2
|
||||
@@ -16,13 +13,9 @@ py-cpuinfo==9.0.0
|
||||
pydantic==1.10.14
|
||||
Pygments==2.17.2
|
||||
pytz==2024.1
|
||||
python-markdown-math==0.8
|
||||
pymdown-extensions==10.7.1
|
||||
PyYAML~=6.0.1
|
||||
starlette~=0.36.3
|
||||
loguru==0.7.2
|
||||
importlib_metadata==7.0.2
|
||||
requests==2.31.0
|
||||
pillow==10.2.0
|
||||
pyppeteer==2.0.0
|
||||
weasyprint==61.2
|
||||
|
||||
Reference in New Issue
Block a user