message 统计

This commit is contained in:
远野千束 2024-05-12 02:47:14 +08:00
parent c6f2a29320
commit 041ceb81d8
52 changed files with 371 additions and 160 deletions

View File

@ -1,41 +0,0 @@
def convert_duration(text: str, default) -> float:
"""
转换自然语言时间为秒数
Args:
text: 1d2h3m
default: 出错时返回
Returns:
float: 总秒数
"""
units = {
"d" : 86400,
"h" : 3600,
"m" : 60,
"s" : 1,
"ms": 0.001
}
duration = 0
current_number = ''
current_unit = ''
try:
for char in text:
if char.isdigit():
current_number += char
else:
if current_number:
duration += int(current_number) * units[current_unit]
current_number = ''
if char in units:
current_unit = char
else:
current_unit = ''
if current_number:
duration += int(current_number) * units[current_unit]
return duration
except:
return default

View File

@ -16,7 +16,7 @@ __plugin_meta__ = PluginMetadata(
} }
) )
from ..internal.base.language import Language, get_default_lang_code from ..utils.base.language import Language, get_default_lang_code
print("\033[34m" + r""" print("\033[34m" + r"""
__ ______ ________ ________ __ __ __ __ __ __ ______ __ ______ ________ ________ __ __ __ __ __ __ ______

View File

@ -10,12 +10,12 @@ from nonebot.exception import MockApiException
from nonebot.internal.matcher import Matcher from nonebot.internal.matcher import Matcher
from nonebot.permission import SUPERUSER from nonebot.permission import SUPERUSER
from liteyuki.internal.base.config import get_config, load_from_yaml from liteyuki.utils.base.config import get_config, load_from_yaml
from liteyuki.internal.base.data_manager import StoredConfig, TempConfig, common_db from liteyuki.utils.base.data_manager import StoredConfig, TempConfig, common_db
from liteyuki.internal.base.language import get_user_lang from liteyuki.utils.base.language import get_user_lang
from liteyuki.internal.base.ly_typing import T_Bot, T_MessageEvent from liteyuki.utils.base.ly_typing import T_Bot, T_MessageEvent
from liteyuki.internal.message.message import MarkdownMessage as md, broadcast_to_superusers from liteyuki.utils.message.message import MarkdownMessage as md, broadcast_to_superusers
from liteyuki.internal.base.reloader import Reloader from liteyuki.utils.base.reloader import Reloader
from .api import update_liteyuki from .api import update_liteyuki
require("nonebot_plugin_alconna") require("nonebot_plugin_alconna")

View File

@ -4,9 +4,9 @@ import nonebot
from watchdog.observers import Observer from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler from watchdog.events import FileSystemEventHandler
from liteyuki.internal.base.config import get_config from liteyuki.utils.base.config import get_config
from liteyuki.internal.base.reloader import Reloader from liteyuki.utils.base.reloader import Reloader
from liteyuki.internal.base.resource import load_resources from liteyuki.utils.base.resource import load_resources
if get_config("debug", False): if get_config("debug", False):
nonebot.logger.info("Liteyuki Reload is enable, watching for file changes...") nonebot.logger.info("Liteyuki Reload is enable, watching for file changes...")

View File

@ -1,10 +1,10 @@
import nonebot.plugin import nonebot.plugin
from liteyuki.internal import init_log from liteyuki.utils import init_log
from liteyuki.internal.base.config import get_config from liteyuki.utils.base.config import get_config
from liteyuki.internal.base.data_manager import InstalledPlugin, plugin_db from liteyuki.utils.base.data_manager import InstalledPlugin, plugin_db
from liteyuki.internal.base.resource import load_resources from liteyuki.utils.base.resource import load_resources
from liteyuki.internal.message.tools import check_for_package from liteyuki.utils.message.tools import check_for_package
load_resources() load_resources()
init_log() init_log()

View File

@ -9,7 +9,7 @@ from nonebot_plugin_alconna import on_alconna, Alconna, Subcommand, Args, MultiV
from pydantic import BaseModel from pydantic import BaseModel
from .canvas import * from .canvas import *
from ...internal.base.resource import get_path from ...utils.base.resource import get_path
resolution = 256 resolution = 256

View File

@ -2,9 +2,9 @@ import nonebot
from nonebot import on_message, require from nonebot import on_message, require
from nonebot.plugin import PluginMetadata from nonebot.plugin import PluginMetadata
from liteyuki.internal.base.data import Database, LiteModel from liteyuki.utils.base.data import Database, LiteModel
from liteyuki.internal.base.ly_typing import T_Bot, T_MessageEvent from liteyuki.utils.base.ly_typing import T_Bot, T_MessageEvent
from liteyuki.internal.message.message import MarkdownMessage as md from liteyuki.utils.message.message import MarkdownMessage as md
require("nonebot_plugin_alconna") require("nonebot_plugin_alconna")
from nonebot_plugin_alconna import on_alconna from nonebot_plugin_alconna import on_alconna

View File

@ -4,9 +4,9 @@ from nonebot.params import CommandArg
from nonebot.permission import SUPERUSER from nonebot.permission import SUPERUSER
from nonebot.plugin import PluginMetadata from nonebot.plugin import PluginMetadata
from liteyuki.internal.base.ly_typing import T_Bot, T_MessageEvent, v11 from liteyuki.utils.base.ly_typing import T_Bot, T_MessageEvent, v11
from liteyuki.internal.message.message import MarkdownMessage as md, broadcast_to_superusers from liteyuki.utils.message.message import MarkdownMessage as md, broadcast_to_superusers
from liteyuki.internal.message.html_tool import * from liteyuki.utils.message.html_tool import *
md_test = on_command("mdts", permission=SUPERUSER) md_test = on_command("mdts", permission=SUPERUSER)
btn_test = on_command("btnts", permission=SUPERUSER) btn_test = on_command("btnts", permission=SUPERUSER)

View File

@ -1,6 +1,6 @@
import random import random
from pydantic import BaseModel from pydantic import BaseModel
from liteyuki.internal.message.message import MarkdownMessage as md from liteyuki.utils.message.message import MarkdownMessage as md
class Dot(BaseModel): class Dot(BaseModel):
row: int row: int

View File

@ -1,7 +1,7 @@
from nonebot import require from nonebot import require
from liteyuki.internal.base.ly_typing import T_Bot, T_MessageEvent from liteyuki.utils.base.ly_typing import T_Bot, T_MessageEvent
from liteyuki.internal.message.message import MarkdownMessage as md from liteyuki.utils.message.message import MarkdownMessage as md
require("nonebot_plugin_alconna") require("nonebot_plugin_alconna")
from .game import Minesweeper from .game import Minesweeper

View File

@ -4,9 +4,9 @@ from typing import Optional
import aiofiles import aiofiles
import nonebot.plugin import nonebot.plugin
from liteyuki.internal.base.data import LiteModel from liteyuki.utils.base.data import LiteModel
from liteyuki.internal.base.data_manager import GlobalPlugin, Group, User, group_db, plugin_db, user_db from liteyuki.utils.base.data_manager import GlobalPlugin, Group, User, group_db, plugin_db, user_db
from liteyuki.internal.base.ly_typing import T_MessageEvent from liteyuki.utils.base.ly_typing import T_MessageEvent
__group_data = {} # 群数据缓存, {group_id: Group} __group_data = {} # 群数据缓存, {group_id: Group}
__user_data = {} # 用户数据缓存, {user_id: User} __user_data = {} # 用户数据缓存, {user_id: User}

View File

@ -14,13 +14,13 @@ from nonebot.permission import SUPERUSER
from nonebot.plugin import Plugin, PluginMetadata from nonebot.plugin import Plugin, PluginMetadata
from nonebot.utils import run_sync from nonebot.utils import run_sync
from liteyuki.internal.base.data_manager import InstalledPlugin from liteyuki.utils.base.data_manager import InstalledPlugin
from liteyuki.internal.base.language import get_user_lang from liteyuki.utils.base.language import get_user_lang
from liteyuki.internal.base.ly_typing import T_Bot from liteyuki.utils.base.ly_typing import T_Bot
from liteyuki.internal.message.message import MarkdownMessage as md from liteyuki.utils.message.message import MarkdownMessage as md
from liteyuki.internal.message.markdown import MarkdownComponent as mdc, compile_md, escape_md from liteyuki.utils.message.markdown import MarkdownComponent as mdc, compile_md, escape_md
from liteyuki.internal.base.permission import GROUP_ADMIN, GROUP_OWNER from liteyuki.utils.base.permission import GROUP_ADMIN, GROUP_OWNER
from liteyuki.internal.message.tools import clamp from liteyuki.utils.message.tools import clamp
from .common import * from .common import *
require("nonebot_plugin_alconna") require("nonebot_plugin_alconna")

View File

@ -5,10 +5,10 @@ import yaml
from nonebot import require from nonebot import require
from nonebot.permission import SUPERUSER from nonebot.permission import SUPERUSER
from liteyuki.internal.base.language import get_user_lang from liteyuki.utils.base.language import get_user_lang
from liteyuki.internal.base.ly_typing import T_Bot, T_MessageEvent from liteyuki.utils.base.ly_typing import T_Bot, T_MessageEvent
from liteyuki.internal.message.message import MarkdownMessage as md from liteyuki.utils.message.message import MarkdownMessage as md
from liteyuki.internal.base.resource import (ResourceMetadata, add_resource_pack, change_priority, check_exist, check_status, get_loaded_resource_packs, get_resource_metadata, load_resources, remove_resource_pack) from liteyuki.utils.base.resource import (ResourceMetadata, add_resource_pack, change_priority, check_exist, check_status, get_loaded_resource_packs, get_resource_metadata, load_resources, remove_resource_pack)
require("nonebot_plugin_alconna") require("nonebot_plugin_alconna")
from nonebot_plugin_alconna import Alconna, Args, on_alconna, Arparma, Subcommand from nonebot_plugin_alconna import Alconna, Args, on_alconna, Arparma, Subcommand

View File

@ -5,10 +5,10 @@ import aiohttp
from nonebot import require from nonebot import require
from nonebot.plugin import PluginMetadata from nonebot.plugin import PluginMetadata
from liteyuki.internal.base.config import get_config from liteyuki.utils.base.config import get_config
from liteyuki.internal.base.data import Database, LiteModel from liteyuki.utils.base.data import Database, LiteModel
from liteyuki.internal.base.resource import get_path from liteyuki.utils.base.resource import get_path
from liteyuki.internal.message.html_tool import template2image from liteyuki.utils.message.html_tool import template2image
require("nonebot_plugin_alconna") require("nonebot_plugin_alconna")
require("nonebot_plugin_apscheduler") require("nonebot_plugin_apscheduler")
@ -76,7 +76,7 @@ async def _():
async def _(): async def _():
query_stamp = [1, 5, 10, 15] query_stamp = [1, 5, 10, 15]
reply = "Count from last " + ", ".join([str(i) for i in query_stamp]) + "mins" reply = "QPS from last " + ", ".join([str(i) for i in query_stamp]) + "mins"
for name, url in SIGN_COUNT_URLS.items(): for name, url in SIGN_COUNT_URLS.items():
count_data = [] count_data = []
for stamp in query_stamp: for stamp in query_stamp:
@ -84,8 +84,8 @@ async def _():
if len(count_rows) < 2: if len(count_rows) < 2:
count_data.append(-1) count_data.append(-1)
else: else:
count_data.append(count_rows[-1].count - count_rows[0].count) count_data.append((count_rows[-1].count - count_rows[0].count)/(stamp*60))
reply += f"\n{name}: " + ", ".join([str(i) for i in count_data]) reply += f"\n{name}: " + ", ".join([f"{i:.1f}" for i in count_data])
await sign_status.send(reply) await sign_status.send(reply)

View File

@ -1,4 +1,4 @@
from liteyuki.internal.base.data import Database, LiteModel from liteyuki.utils.base.data import Database, LiteModel
class MessageEventModel(LiteModel): class MessageEventModel(LiteModel):

View File

@ -1,12 +1,22 @@
import time import time
from typing import Any from typing import Any
from liteyuki.utils.message.html_tool import template2image
from .common import MessageEventModel, msg_db from .common import MessageEventModel, msg_db
from liteyuki.utils.base.language import Language
from liteyuki.utils.base.resource import get_path
from liteyuki.utils.message.npl import convert_seconds_to_time
from contextvars import ContextVar
def get_stat_msg_data(duration, period) -> tuple[list[int,], list[int,]]: async def get_stat_msg_image(duration: int, period: int, group_id: str = None, bot_id: str = None, ulang: Language = Language()) -> bytes:
""" """
获取统计消息 获取统计消息
Args: Args:
ctx:
ulang:
bot_id:
group_id:
duration: 统计时间单位秒 duration: 统计时间单位秒
period: 统计周期单位秒 period: 统计周期单位秒
@ -14,16 +24,52 @@ def get_stat_msg_data(duration, period) -> tuple[list[int,], list[int,]]:
tuple: [int,], [int,] 两个列表分别为周期中心时间戳和消息数量 tuple: [int,], [int,] 两个列表分别为周期中心时间戳和消息数量
""" """
now = int(time.time()) now = int(time.time())
start_time = (now - duration)
condition = "time > ?"
condition_args = [start_time]
if group_id:
condition += " AND group_id = ?"
condition_args.append(group_id)
if bot_id:
condition += " AND bot_id = ?"
condition_args.append(bot_id)
msg_rows = msg_db.where_all( msg_rows = msg_db.where_all(
MessageEventModel(), MessageEventModel(),
"time > ?", condition,
now - duration *condition_args
) )
timestamps = [] timestamps = []
msg_count = [] msg_count = []
msg_rows.sort(key=lambda x: x.time) msg_rows.sort(key=lambda x: x.time)
for msg_row in msg_rows:
period_center_time = msg_row.time - msg_row.time % period + period // 2 start_time = max(msg_rows[0].time, start_time)
for i in range(start_time, now, period):
timestamps.append(i + period // 2)
msg_count.append(0)
for msg in msg_rows:
period_start_time = start_time + (msg.time - start_time) // period * period
period_center_time = period_start_time + period // 2
index = timestamps.index(period_center_time)
msg_count[index] += 1
templates = {
"data": [
{
"name" : ulang.get("stat.message")
+ f" Period {convert_seconds_to_time(period)}" + f" Duration {convert_seconds_to_time(duration)}"
+ (f" Group {group_id}" if group_id else "") + (f" Bot {bot_id}" if bot_id else ""),
"times" : timestamps,
"counts": msg_count
}
]
}
return await template2image(get_path("templates/stat_msg.html"), templates, debug=True)
# if not timestamps or period_start_time != timestamps[-1]: # if not timestamps or period_start_time != timestamps[-1]:
# timestamps.append(period_start_time) # timestamps.append(period_start_time)

View File

@ -1,31 +1,39 @@
from nonebot import require from nonebot import Bot, require
from liteyuki.internal.message.npl import convert_duration from liteyuki.utils.message.npl import convert_duration, convert_time_to_seconds
from .stat_api import * from .stat_api import *
from ...utils.base.language import Language
from ...utils.base.ly_typing import T_MessageEvent
require("nonebot_plugin_alconna") require("nonebot_plugin_alconna")
from nonebot_plugin_alconna import on_alconna, Alconna, Args, Subcommand, Arparma, Option from nonebot_plugin_alconna import UniMessage, on_alconna, Alconna, Args, Subcommand, Arparma, Option
stat_msg = on_alconna( stat_msg = on_alconna(
Alconna( Alconna(
"stat", "stat",
Subcommand( Subcommand(
"message", "message",
Args["duration", str, "1d"], # 默认为1天 # Args["duration", str, "2d"]["period", str, "60s"], # 默认为1天
Option(
"-d|--duration",
Args["duration", str, "2d"],
help_text="统计时间",
),
Option(
"-p|--period",
Args["period", str, "60s"],
help_text="统计周期",
),
Option( Option(
"-b|--bot", # 生成图表 "-b|--bot", # 生成图表
Args["bot_id", str, ""], Args["bot_id", str, "current"],
help_text="是否指定机器人", help_text="是否指定机器人",
), ),
Option( Option(
"-g|--group", "-g|--group",
Args["group_id", str, ""], Args["group_id", str, "current"],
help_text="指定群组" help_text="指定群组"
), ),
Option(
"-c|--chart", # 生成图表
help_text="是否生成图表",
),
alias={"msg", "m"}, alias={"msg", "m"},
help_text="查看统计次数内的消息" help_text="查看统计次数内的消息"
) )
@ -34,13 +42,27 @@ stat_msg = on_alconna(
@stat_msg.assign("message") @stat_msg.assign("message")
async def _(result: Arparma): async def _(result: Arparma, event: T_MessageEvent, bot: Bot):
args = result.subcommands.get("message").args ulang = Language(event.user_id)
options = result.subcommands.get("message").options
duration = convert_duration(args.get("duration"), 86400) # 秒数
enable_chart = options.get("chart")
if options.get("group"): try:
group_id = options["group"].args.get("group_id") duration = convert_time_to_seconds(result.other_args.get("duration", "2d")) # 秒数
else: period = convert_time_to_seconds(result.other_args.get("period", "1m"))
msg_rows = get_stat_msg_data(duration) except BaseException as e:
await stat_msg.send(ulang.get("liteyuki.invalid_command", TEXT=str(e.__str__())))
return
group_id = result.other_args.get("group_id")
bot_id = result.other_args.get("bot_id")
if group_id in ["current", "c"]:
group_id = str(event.group_id)
if group_id in ["all", "a"]:
group_id = "all"
if bot_id == ["current", "c"]:
bot_id = str(bot.self_id)
img = await get_stat_msg_image(duration, period, group_id, bot_id)
await stat_msg.send(UniMessage.image(raw=img))

View File

@ -3,8 +3,8 @@ import time
from nonebot import require from nonebot import require
from nonebot.message import event_postprocessor from nonebot.message import event_postprocessor
from liteyuki.internal.base.data import Database, LiteModel from liteyuki.utils.base.data import Database, LiteModel
from liteyuki.internal.base.ly_typing import v11 from liteyuki.utils.base.ly_typing import v11
from .common import MessageEventModel, msg_db from .common import MessageEventModel, msg_db

View File

@ -5,12 +5,12 @@ import nonebot
import psutil import psutil
from cpuinfo import cpuinfo from cpuinfo import cpuinfo
from nonebot import require from nonebot import require
from liteyuki.internal import __NAME__, __VERSION__ from liteyuki.utils import __NAME__, __VERSION__
from liteyuki.internal.base.config import get_config from liteyuki.utils.base.config import get_config
from liteyuki.internal.base.data_manager import TempConfig, common_db from liteyuki.utils.base.data_manager import TempConfig, common_db
from liteyuki.internal.base.language import Language from liteyuki.utils.base.language import Language
from liteyuki.internal.base.resource import get_loaded_resource_packs, get_path from liteyuki.utils.base.resource import get_loaded_resource_packs, get_path
from liteyuki.internal.message.html_tool import template2image from liteyuki.utils.message.html_tool import template2image
require("nonebot_plugin_apscheduler") require("nonebot_plugin_apscheduler")
from nonebot_plugin_apscheduler import scheduler from nonebot_plugin_apscheduler import scheduler

View File

@ -1,10 +1,10 @@
from nonebot import require from nonebot import require
from liteyuki.internal.base.resource import get_path from liteyuki.utils.base.resource import get_path
from liteyuki.internal.message.html_tool import template2image from liteyuki.utils.message.html_tool import template2image
from liteyuki.internal.base.language import get_user_lang from liteyuki.utils.base.language import get_user_lang
from .api import * from .api import *
from ...internal.base.ly_typing import T_Bot, T_MessageEvent 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, UniMessage from nonebot_plugin_alconna import on_alconna, Alconna, Args, Subcommand, Arparma, UniMessage

View File

@ -3,11 +3,11 @@ from typing import Optional
import pytz import pytz
from nonebot import require from nonebot import require
from liteyuki.internal.base.data import LiteModel, Database from liteyuki.utils.base.data import LiteModel, Database
from liteyuki.internal.base.data_manager import User, user_db, group_db from liteyuki.utils.base.data_manager import User, user_db, group_db
from liteyuki.internal.base.language import Language, change_user_lang, get_all_lang, get_user_lang from liteyuki.utils.base.language import Language, change_user_lang, get_all_lang, get_user_lang
from liteyuki.internal.base.ly_typing import T_Bot, T_MessageEvent from liteyuki.utils.base.ly_typing import T_Bot, T_MessageEvent
from liteyuki.internal.message.message import MarkdownMessage as md from liteyuki.utils.message.message import MarkdownMessage as md
from .const import representative_timezones_list from .const import representative_timezones_list
require("nonebot_plugin_alconna") require("nonebot_plugin_alconna")

View File

@ -15,7 +15,7 @@ __plugin_meta__ = PluginMetadata(
} }
) )
from ...internal.base.data_manager import set_memory_data from ...utils.base.data_manager import set_memory_data
driver = get_driver() driver = get_driver()

View File

@ -3,8 +3,8 @@ import aiohttp
from .qw_models import * from .qw_models import *
import httpx import httpx
from ...internal.base.data_manager import get_memory_data from ...utils.base.data_manager import get_memory_data
from ...internal.base.language import Language from ...utils.base.language import Language
dev_url = "https://devapi.qweather.com/" # 开发HBa dev_url = "https://devapi.qweather.com/" # 开发HBa
com_url = "https://api.qweather.com/" # 正式环境 com_url = "https://api.qweather.com/" # 正式环境
@ -27,9 +27,9 @@ async def check_key_dev(key: str) -> bool:
"location": "101010100", "location": "101010100",
"key" : key, "key" : key,
} }
async with httpx.AsyncClient() as client: async with aiohttp.ClientSession() as client:
resp = await client.get(url, params=params) resp = await client.get(url, params=params)
return (resp.json()).get("code") != "200" # 查询不到付费数据为开发版 return (await resp.json()).get("code") != "200" # 查询不到付费数据为开发版
def get_local_data(ulang_code: str) -> dict: def get_local_data(ulang_code: str) -> dict:

View File

@ -1,4 +1,4 @@
from liteyuki.internal.base.data import LiteModel from liteyuki.utils.base.data import LiteModel
class Location(LiteModel): class Location(LiteModel):

View File

@ -2,14 +2,14 @@ from nonebot import require, on_endswith
from nonebot.adapters.onebot.v11 import MessageSegment from nonebot.adapters.onebot.v11 import MessageSegment
from nonebot.internal.matcher import Matcher from nonebot.internal.matcher import Matcher
from liteyuki.internal.base.config import get_config from liteyuki.utils.base.config import get_config
from liteyuki.internal.base.ly_typing import T_MessageEvent from liteyuki.utils.base.ly_typing import T_MessageEvent
from .qw_api import * from .qw_api import *
from liteyuki.internal.base.data_manager import User, user_db from liteyuki.utils.base.data_manager import User, user_db
from liteyuki.internal.base.language import Language, get_user_lang from liteyuki.utils.base.language import Language, get_user_lang
from liteyuki.internal.base.resource import get_path from liteyuki.utils.base.resource import get_path
from liteyuki.internal.message.html_tool import template2image from liteyuki.utils.message.html_tool import template2image
require("nonebot_plugin_alconna") require("nonebot_plugin_alconna")
from nonebot_plugin_alconna import on_alconna, Alconna, Args, MultiVar, Arparma from nonebot_plugin_alconna import on_alconna, Alconna, Args, MultiVar, Arparma

View File

@ -17,7 +17,7 @@ data.forEach((item) => {
item["counts"].forEach((count, index) => { item["counts"].forEach((count, index) => {
// 计算平均值index - 1的count + index的count + index + 1的count /3 // 计算平均值index - 1的count + index的count + index + 1的count /3
if (index > 0) { if (index > 0) {
timeCount.push((item["counts"][index] - item["counts"][index - 1]) / (60 * (item["times"][index] - item["times"][index - 1]))) timeCount.push((item["counts"][index] - item["counts"][index - 1]) / (60*(item["times"][index] - item["times"][index - 1])))
} }
}) })

View File

@ -0,0 +1 @@
stat.message=统计消息

View File

@ -0,0 +1,3 @@
name: 轻雪统计信息附件
description: For Liteyuki statistic
version: 2024.4.26

View File

@ -0,0 +1,4 @@
.sign-chart {
height: 400px;
background-color: rgba(255, 255, 255, 0.7);
}

View File

@ -0,0 +1,54 @@
// 数据类型声明
// import * as echarts from 'echarts';
let data = JSON.parse(document.getElementById("data").innerText) // object
const signChartDivTemplate = document.importNode(document.getElementById("sign-chart-template").content, true)
data.forEach((item) => {
let signChartDiv = signChartDivTemplate.cloneNode(true)
let chartID = item["name"]
// 初始化ECharts实例
// 设置id
signChartDiv.querySelector(".sign-chart").id = chartID
document.body.appendChild(signChartDiv)
let signChart = echarts.init(document.getElementById(chartID))
signChart.setOption(
{
animation: false,
title: {
text: item["name"],
textStyle: {
color: '#000000' // 设置标题文本颜色为红色
}
},
xAxis: {
type: 'category',
data: item["times"].map(timestampToTime),
},
yAxis: {
type: 'value',
min: Math.min(...item["counts"]),
},
series: [
{
data: item["counts"],
type: 'line',
}
]
}
)
})
function timestampToTime(timestamp) {
let date = new Date(timestamp * 1000)
let Y = date.getFullYear() + '-'
let M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'
let D = date.getDate() + ' '
let h = date.getHours() + ':'
let m = date.getMinutes() + ':'
let s = date.getSeconds()
return M + D + h + m + s
}

View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="zh" xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="UTF-8">
<title>Liteyuki Stats Message</title>
<link rel="stylesheet" href="./css/card.css">
<link rel="stylesheet" href="./css/fonts.css">
<link rel="stylesheet" href="./css/stat_msg.css">
</head>
<body>
<template id="sign-chart-template">
<div class="info-box sign-chart">
</div>
</template>
<div class="data-storage" id="data">{{ data | tojson }}</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.5.0/echarts.min.js"></script>
<script src="./js/stat_msg.js"></script>
<script src="./js/card.js"></script>
</body>

View File

@ -11,9 +11,9 @@ __VERSION__ = "6.3.2" # 60201
import requests import requests
from liteyuki.internal.base.config import load_from_yaml, config from liteyuki.utils.base.config import load_from_yaml, config
from liteyuki.internal.base.log import init_log from liteyuki.utils.base.log import init_log
from liteyuki.internal.base.data_manager import TempConfig, auto_migrate, common_db 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

View File

@ -42,7 +42,7 @@ def load_resource_from_dir(path: str):
metadata["path"] = path metadata["path"] = path
metadata["folder"] = os.path.basename(path) metadata["folder"] = os.path.basename(path)
if os.path.exists(os.path.join(path, "lang")): if os.path.exists(os.path.join(path, "lang")):
from liteyuki.internal.base.language import load_from_dir from liteyuki.utils.base.language import load_from_dir
load_from_dir(os.path.join(path, "lang")) load_from_dir(os.path.join(path, "lang"))
_loaded_resource_packs.insert(0, ResourceMetadata(**metadata)) _loaded_resource_packs.insert(0, ResourceMetadata(**metadata))

View File

@ -6,11 +6,10 @@ import aiofiles
import nonebot import nonebot
from nonebot import require from nonebot import require
from liteyuki.internal.base.resource import load_resources
require("nonebot_plugin_htmlrender") require("nonebot_plugin_htmlrender")
from nonebot_plugin_htmlrender import * from nonebot_plugin_htmlrender import *
from .tools import random_hex_string
async def html2image( async def html2image(
@ -76,7 +75,7 @@ async def template2image(
) )
async with aiofiles.open(os.path.join(template_path, "latest-debug.html"), "w", encoding="utf-8") as f: async with aiofiles.open(os.path.join(template_path, "latest-debug.html"), "w", encoding="utf-8") as f:
await f.write(raw_html) await f.write(raw_html)
nonebot.logger.info("Debug HTML: %s" % "latest-debug.html") nonebot.logger.info("Debug HTML: %s" % f"debug-{random_hex_string(6)}.html")
return await template_to_pic( return await template_to_pic(
template_name=template_name, template_name=template_name,

View File

@ -0,0 +1,101 @@
import nonebot
def convert_duration(text: str, default) -> float:
"""
转换自然语言时间为秒数
Args:
text: 1d2h3m
default: 出错时返回
Returns:
float: 总秒数
"""
units = {
"d" : 86400,
"h" : 3600,
"m" : 60,
"s" : 1,
"ms": 0.001
}
duration = 0
current_number = ''
current_unit = ''
try:
for char in text:
if char.isdigit():
current_number += char
else:
if current_number:
duration += int(current_number) * units[current_unit]
current_number = ''
if char in units:
current_unit = char
else:
current_unit = ''
if current_number:
duration += int(current_number) * units[current_unit]
return duration
except BaseException as e:
nonebot.logger.info(f"convert_duration error: {e}")
return default
def convert_time_to_seconds(time_str):
"""转换自然语言时长为秒数
Args:
time_str: 1d2m3s
Returns:
"""
seconds = 0
current_number = ''
for char in time_str:
if char.isdigit() or char == '.':
current_number += char
elif char == 'd':
seconds += float(current_number) * 24 * 60 * 60
current_number = ''
elif char == 'h':
seconds += float(current_number) * 60 * 60
current_number = ''
elif char == 'm':
seconds += float(current_number) * 60
current_number = ''
elif char == 's':
seconds += float(current_number)
current_number = ''
return int(seconds)
def convert_seconds_to_time(seconds):
"""转换秒数为自然语言时长
Args:
seconds: 10000
Returns:
"""
d = seconds // (24 * 60 * 60)
h = (seconds % (24 * 60 * 60)) // (60 * 60)
m = (seconds % (60 * 60)) // 60
s = seconds % 60
# 若值为0则不显示
time_str = ''
if d:
time_str += f"{d}d"
if h:
time_str += f"{h}h"
if m:
time_str += f"{m}m"
if not time_str:
time_str = f"{s}s"
return time_str

10
main.py
View File

@ -1,9 +1,9 @@
import nonebot import nonebot
from nonebot.adapters.onebot import v11, v12 from nonebot.adapters.onebot import v11, v12
from liteyuki.internal import init from liteyuki.utils import init
from liteyuki.internal.base.config import load_from_yaml from liteyuki.utils.base.config import load_from_yaml
from liteyuki.internal.base.data_manager import StoredConfig, common_db from liteyuki.utils.base.data_manager import StoredConfig, common_db
from liteyuki.internal.base.ly_api import liteyuki_api from liteyuki.utils.base.ly_api import liteyuki_api
if __name__ == "__mp_main__": if __name__ == "__mp_main__":
init() init()
@ -25,5 +25,5 @@ if __name__ == "__mp_main__":
liteyuki_api.bug_report(str(e.__repr__())) liteyuki_api.bug_report(str(e.__repr__()))
if __name__ == "__main__": if __name__ == "__main__":
from liteyuki.internal.base.reloader import Reloader from liteyuki.utils.base.reloader import Reloader
nonebot.run() nonebot.run()