独立status插件...

This commit is contained in:
远野千束 2024-04-22 23:55:33 +08:00
parent ece71ca1e7
commit 53bc6df30f
11 changed files with 336 additions and 15 deletions

View File

@ -48,7 +48,7 @@ for lang in get_all_lang():
} }
@scheduler.scheduled_job("cron", second="*/20") @scheduler.scheduled_job("cron", minute="*/20")
async def _(): async def _():
nonebot.logger.info("数据已刷新") nonebot.logger.info("数据已刷新")
for lang_code in get_all_lang(): for lang_code in get_all_lang():

View File

@ -0,0 +1,195 @@
import platform
import time
import nonebot
import psutil
from cpuinfo import cpuinfo
from liteyuki.utils import __NAME__, __VERSION__
from liteyuki.utils.base.data_manager import TempConfig, common_db
from liteyuki.utils.base.language import Language
from liteyuki.utils.base.resource import get_path
from liteyuki.utils.message.html_tool import template2image
protocol_names = {
0: "iPad",
1: "Android Phone",
2: "Android Watch",
3: "Mac",
5: "iPad",
6: "Android Pad",
}
"""
Universal Interface
data
- bot
- name: str
icon: str
id: int
protocol_name: str
groups: int
friends: int
message_sent: int
message_received: int
app_name: str
- hardware
- cpu
- percent: float
- name: str
- mem
- percent: float
- total: int
- used: int
- free: int
- swap
- percent: float
- total: int
- used: int
- free: int
- disk: list
- name: str
- percent: float
- total: int
"""
async def generate_status_card(bot: dict, hardware: dict, liteyuki: dict, lang="zh-CN", bot_id="0") -> bytes:
return await template2image(
get_path("templates/status.html", abs_path=True),
{
"data": {
"bot" : bot,
"hardware" : hardware,
"liteyuki" : liteyuki,
"localization": get_local_data(lang)
}
},
debug=True
)
def get_local_data(lang_code) -> dict:
lang = Language(lang_code)
return {
"friends" : lang.get("status.friends"),
"groups" : lang.get("status.groups"),
"plugins" : lang.get("status.plugins"),
"message_sent" : lang.get("status.message_sent"),
"message_received": lang.get("status.message_received"),
"cpu" : lang.get("status.cpu"),
"memory" : lang.get("status.memory"),
"swap" : lang.get("status.swap"),
"disk" : lang.get("status.disk"),
"usage" : lang.get("status.usage"),
"total" : lang.get("status.total"),
"used" : lang.get("status.used"),
"free" : lang.get("status.free"),
}
async def get_bots_data(self_id: str = "0") -> dict:
"""获取当前所有机器人数据
Returns:
"""
result = {
"self_id": self_id,
"bots" : [],
}
for bot_id, bot in nonebot.get_bots().items():
groups = 0
friends = 0
status = {}
bot_name = bot_id
version_info = {}
try:
# API fetch
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 = None
bot_data = {
"name" : bot_name,
"icon" : icon,
"id" : bot_id,
"protocol_name" : protocol_names.get(version_info.get("protocol_name"), "Online"),
"groups" : groups,
"friends" : friends,
"message_sent" : statistics.get("message_sent"),
"message_received": statistics.get("message_received"),
"app_name" : app_name
}
result["bots"].append(bot_data)
return result
async def get_hardware_data() -> dict:
mem = psutil.virtual_memory()
swap = psutil.swap_memory()
cpu_brand_raw = cpuinfo.get_cpu_info().get("brand_raw", "Unknown")
if "AMD" in cpu_brand_raw:
brand = "AMD"
elif "Intel" in cpu_brand_raw:
brand = "Intel"
else:
brand = "Unknown"
result = {
"cpu" : {
"percent": psutil.cpu_percent(),
"name" : f"{brand} {cpuinfo.get_cpu_info().get('arch', 'Unknown')}"
},
"mem" : {
"percent": mem.percent,
"total" : mem.total,
"used" : mem.used,
"free" : mem.free
},
"swap": {
"percent": swap.percent,
"total" : swap.total,
"used" : swap.used,
"free" : swap.free
},
"disk": [],
}
for disk in psutil.disk_partitions(all=True):
try:
disk_usage = psutil.disk_usage(disk.mountpoint)
result["disk"].append({
"name" : disk.mountpoint,
"percent": disk_usage.percent,
"total" : disk_usage.total,
"used" : disk_usage.used,
"free" : disk_usage.free
})
except:
pass
return result
async def get_liteyuki_data() -> dict:
temp_data: TempConfig = common_db.first(TempConfig(), default=TempConfig())
result = {
"name" : __NAME__,
"version": __VERSION__,
"plugins": len(nonebot.get_loaded_plugins()),
"nonebot": f"{nonebot.__version__}",
"python" : f"{platform.python_implementation()} {platform.python_version()}",
"system" : f"{platform.system()} {platform.release()}",
"runtime": time.time() - temp_data.data.get("start_time", time.time()), # 运行时间秒数
}
return result

View File

@ -1,12 +1,18 @@
from nonebot import require from nonebot import require
from liteyuki.utils.base.resource import get_path
from liteyuki.utils.message.html_tool import template2image
from liteyuki.utils.base.language import get_user_lang
from .api import *
from ...utils.base.ly_typing import T_Bot, T_MessageEvent
require("nonebot_plugin_alconna") require("nonebot_plugin_alconna")
from nonebot_plugin_alconna import on_alconna, Alconna, Args, Subcommand, Arparma from nonebot_plugin_alconna import on_alconna, Alconna, Args, Subcommand, Arparma, UniMessage
status_alc = on_alconna( status_alc = on_alconna(
aliases={"status"}, aliases={"#状态"},
command=Alconna( command=Alconna(
"status", "#status",
Subcommand( Subcommand(
"memory", "memory",
alias={"mem", "m", "内存"}, alias={"mem", "m", "内存"},
@ -19,6 +25,19 @@ status_alc = on_alconna(
) )
@status_alc.handle()
async def _(event: T_MessageEvent, bot: T_Bot):
ulang = get_user_lang(event.user_id)
image = await generate_status_card(
bot=await get_bots_data(),
hardware=await get_hardware_data(),
liteyuki=await get_liteyuki_data(),
lang=ulang.lang_code,
bot_id=bot.self_id
)
await status_alc.finish(UniMessage.image(raw=image))
@status_alc.assign("memory") @status_alc.assign("memory")
async def _(): async def _():
print("memory") print("memory")

View File

@ -133,3 +133,17 @@ rpm.move_top=置顶
weather.city_not_found=未找到城市 {CITY} weather.city_not_found=未找到城市 {CITY}
weather.weather_not_found=未找到城市 {CITY} 的天气信息 weather.weather_not_found=未找到城市 {CITY} 的天气信息
weather.no_key=未设置天气api key请在配置文件添加weather_key weather.no_key=未设置天气api key请在配置文件添加weather_key
status.friends=好友
status.groups=群
status.plugins=插件
status.message_sent=发送消息
status.message_received=接收消息
status.cpu=处理器
status.memory=内存
status.swap=交换空间
status.disk=磁盘
status.usage=使用率
status.total=总计
status.used=已用
status.free=空闲

View File

@ -1,9 +1,84 @@
const data = JSON.parse(document.getElementById('data').innerText); const data = JSON.parse(document.getElementById('data').innerText);
const bot_data = data['bot']; // 机器人数据
const hardware_data = data['hardware']; // 硬件数据
const liteyuki_data = data['liteyuki']; // LiteYuki数据
const local_data = data['localization']; // 本地化语言数据
console.log(data)
/**
* 创建饼图
* @param title
* @param {Array<{name: string, value: number}>} data 数据
*/
function createPieChartOption(title, data) { function createPieChartOption(title, data) {
// data为各项占比列表 // data为各项占比列表
return {
animation: false,
title: {
text: title,
left: 'center',
top: 'center',
textStyle: {
color: '#fff',
fontSize: 30
}
},
tooltip: {
show: true,
trigger: 'item',
backgroundColor: '#fff',
},
color: data.length === 3 ? ['#00a6ff', '#a2d8f4', "#ffffff44"] : ['#a2d8f4', '#ffffff44'],
series: [
{
name: 'info',
type: 'pie',
radius: ['80%', '100%'],
center: ['50%', '50%'],
itemStyle: {
normal: {
label: {
show: false
},
labelLine: {
show: false
}
},
emphasis: {
label: {
show: true,
textStyle: {
fontSize: '50',
fontWeight: 'bold'
}
}
}
},
data: data
}
]
}
} }
/**
* 创建柱状图
* @param title
* @param percent 数据
*/
function createBarChartOption(title, percent) { function createBarChartOption(title, percent) {
// percent为百分比最大值为100 // percent为百分比最大值为100
} }
// 主函数
function main() {
bot_data['bots'].forEach(
(bot, index) => {
let botInfoDiv = document.importNode(document.getElementById('bot-template').content, true)
document.body.insertBefore(botInfoDiv, document.getElementById('hardware-info'))
botInfoDiv.className = 'info-box bot-info'
}
)
}
main()

View File

@ -7,9 +7,24 @@
<link rel="stylesheet" href="./css/status.css"> <link rel="stylesheet" href="./css/status.css">
</head> </head>
<body> <body>
<template id="bot-template">
<div class="info-box bot-info">
<div id="bot-icon">
<img id="bot-icon-img" src="" alt="">
</div>
<div id="bot-detail">
<div id="bot-name">
Liteyuki
</div>
<div id="bot-tags">
<!-- tag span-->
</div>
</div>
</div>
</template>
<div class="data-storage" id="data">{{ data | tojson }}</div> <div class="data-storage" id="data">{{ data | tojson }}</div>
<div class="info-box" id="system-info"></div> <div class="info-box" id="hardware-info"></div>
<div class="info-box" id="disk-info"></div> <div class="info-box" id="disk-info"></div>
<div class="info-box" id="motto-info"></div> <div class="info-box" id="motto-info"></div>

View File

@ -2,6 +2,7 @@ import json
import os.path import os.path
import platform import platform
import sys import sys
import time
import nonebot import nonebot
@ -12,7 +13,7 @@ import requests
from liteyuki.utils.base.config import load_from_yaml, config from liteyuki.utils.base.config import load_from_yaml, config
from liteyuki.utils.base.log import init_log from liteyuki.utils.base.log import init_log
from liteyuki.utils.base.data_manager import auto_migrate from liteyuki.utils.base.data_manager import TempConfig, auto_migrate, common_db
major, minor, patch = map(int, __VERSION__.split(".")) major, minor, patch = map(int, __VERSION__.split("."))
__VERSION_I__ = major * 10000 + minor * 100 + patch __VERSION_I__ = major * 10000 + minor * 100 + patch
@ -54,6 +55,10 @@ def init():
if sys.version_info < (3, 10): if sys.version_info < (3, 10):
nonebot.logger.error("This project requires Python3.10+ to run, please upgrade your Python Environment.") nonebot.logger.error("This project requires Python3.10+ to run, please upgrade your Python Environment.")
exit(1) exit(1)
temp_data: TempConfig = common_db.first(TempConfig(), default=TempConfig())
temp_data.data["start_time"] = time.time()
common_db.upsert(temp_data)
auto_migrate() auto_migrate()
# 在加载完成语言后再初始化日志 # 在加载完成语言后再初始化日志
nonebot.logger.info("Liteyuki is initializing...") nonebot.logger.info("Liteyuki is initializing...")

View File

@ -6,10 +6,10 @@ from .data import Database, LiteModel, Database
DATA_PATH = "data/liteyuki" DATA_PATH = "data/liteyuki"
user_db = Database(os.path.join(DATA_PATH, "users.ldb")) user_db: Database = Database(os.path.join(DATA_PATH, "users.ldb"))
group_db = Database(os.path.join(DATA_PATH, "groups.ldb")) group_db: Database = Database(os.path.join(DATA_PATH, "groups.ldb"))
plugin_db = Database(os.path.join(DATA_PATH, "plugins.ldb")) plugin_db: Database = Database(os.path.join(DATA_PATH, "plugins.ldb"))
common_db = Database(os.path.join(DATA_PATH, "common.ldb")) common_db: Database = Database(os.path.join(DATA_PATH, "common.ldb"))
class User(LiteModel): class User(LiteModel):

View File

@ -47,7 +47,7 @@ def load_resource_from_dir(path: str):
_loaded_resource_packs.insert(0, ResourceMetadata(**metadata)) _loaded_resource_packs.insert(0, ResourceMetadata(**metadata))
def get_path(path: str, abs_path: bool = False, default: Any = None, debug: bool=False) -> str | Any: def get_path(path: str, abs_path: bool = True, default: Any = None, debug: bool=False) -> str | Any:
""" """
获取资源包中的文件 获取资源包中的文件
Args: Args:

View File

@ -62,7 +62,6 @@ async def template2image(
if debug: if debug:
# 重载资源 # 重载资源
load_resources()
raw_html = await template_to_html( raw_html = await template_to_html(
template_name=template_name, template_name=template_name,

View File

@ -9,7 +9,6 @@ from liteyuki.utils.base.ly_api import liteyuki_api
init() init()
store_config: dict = common_db.first(StoredConfig(), default=StoredConfig()).config store_config: dict = common_db.first(StoredConfig(), default=StoredConfig()).config
static_config = load_from_yaml("config.yml") static_config = load_from_yaml("config.yml")
store_config.update(static_config) store_config.update(static_config)
nonebot.init(**store_config) nonebot.init(**store_config)