mirror of
https://github.com/TriM-Organization/LiteyukiBot-TriM.git
synced 2025-09-06 20:26:24 +00:00
Ⓜ️手动从旧梦 81a191f merge
This commit is contained in:
@ -4,30 +4,27 @@ from typing import Any, AnyStr
|
||||
|
||||
import nonebot
|
||||
import pip
|
||||
from nonebot import Bot, get_driver, require
|
||||
from nonebot.adapters import satori
|
||||
from nonebot import Bot, get_driver, require # type: ignore
|
||||
from nonebot.adapters import onebot, satori
|
||||
from nonebot.adapters.onebot.v11 import Message, escape, unescape
|
||||
from nonebot.exception import MockApiException
|
||||
from nonebot.internal.matcher import Matcher
|
||||
from nonebot.permission import SUPERUSER
|
||||
|
||||
from src.utils import event as event_utils, satori_utils
|
||||
from src.utils.base.config import get_config, load_from_yaml
|
||||
from src.utils.base.data_manager import StoredConfig, TempConfig, common_db
|
||||
from src.utils.base.language import get_user_lang
|
||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
from src.utils.message.message import MarkdownMessage as md, broadcast_to_superusers
|
||||
|
||||
# from src.liteyuki.core import Reloader
|
||||
from src.utils import event as event_utils, satori_utils
|
||||
from liteyuki.core import ProcessingManager
|
||||
from .api import update_liteyuki
|
||||
from liteyuki.bot import get_bot
|
||||
from ..utils.base import reload
|
||||
from ..utils.base.ly_function import get_function
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
require("nonebot_plugin_apscheduler")
|
||||
from nonebot_plugin_alconna import (
|
||||
UniMessage,
|
||||
on_alconna,
|
||||
Alconna,
|
||||
Args,
|
||||
@ -111,16 +108,15 @@ async def _(matcher: Matcher, bot: T_Bot, event: T_MessageEvent):
|
||||
"reload_session_id": (
|
||||
(event.group_id if event.message_type == "group" else event.user_id)
|
||||
if not isinstance(event, satori.event.Event)
|
||||
else event.channel.id
|
||||
else event.chan_active.id
|
||||
),
|
||||
"delta_time": 0,
|
||||
}
|
||||
)
|
||||
|
||||
common_db.save(temp_data)
|
||||
# Reloader.reload(0)
|
||||
bot = get_bot()
|
||||
bot.restart()
|
||||
|
||||
reload()
|
||||
|
||||
|
||||
@on_alconna(
|
||||
@ -380,20 +376,18 @@ async def test_for_md_image(bot: T_Bot, api: str, data: dict):
|
||||
|
||||
@driver.on_startup
|
||||
async def on_startup():
|
||||
# temp_data = common_db.where_one(TempConfig(), default=TempConfig())
|
||||
# # 储存重启信息
|
||||
# if temp_data.data.get("reload", False):
|
||||
# delta_time = time.time() - temp_data.data.get("reload_time", 0)
|
||||
# temp_data.data["delta_time"] = delta_time
|
||||
# common_db.save(temp_data) # 更新数据
|
||||
temp_data = common_db.where_one(TempConfig(), default=TempConfig())
|
||||
# 储存重启信息
|
||||
if temp_data.data.get("reload", False):
|
||||
delta_time = time.time() - temp_data.data.get("reload_time", 0)
|
||||
temp_data.data["delta_time"] = delta_time
|
||||
common_db.save(temp_data) # 更新数据
|
||||
"""
|
||||
该部分迁移至轻雪生命周期
|
||||
该部分将迁移至轻雪生命周期
|
||||
Returns:
|
||||
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@driver.on_shutdown
|
||||
async def on_shutdown():
|
||||
@ -415,10 +409,22 @@ async def _(bot: T_Bot):
|
||||
reload_session_id = temp_data.data.get("reload_session_id", 0)
|
||||
delta_time = temp_data.data.get("delta_time", 0)
|
||||
common_db.save(temp_data) # 更新数据
|
||||
|
||||
return_msg = "轻雪核心 重载耗时 {:.2f} 秒\n灵温 预计体感重载耗时 {:.2f} 秒\n*此数据仅作参考,具体计时请以实际为准".format(
|
||||
delta_time, time.time() - temp_data.data.get("reload_time", 0)
|
||||
)
|
||||
|
||||
if isinstance(bot, satori.Bot):
|
||||
await bot.send_message(
|
||||
channel_id=reload_session_id,
|
||||
message="轻雪核心 重载耗时 {:.2f} 秒\n*此数据仅作参考,具体计时请以实际为准\n灵温 预计体感重载耗时 {:.2f} 秒".format(delta_time,time.time() - temp_data.data.get("reload_time", 0)),
|
||||
message=return_msg,
|
||||
)
|
||||
elif isinstance(bot, onebot.v11.Bot):
|
||||
await bot.send_msg(
|
||||
message_type=reload_session_type,
|
||||
user_id=reload_session_id,
|
||||
group_id=reload_session_id,
|
||||
message=return_msg,
|
||||
)
|
||||
else:
|
||||
await bot.call_api(
|
||||
@ -426,7 +432,7 @@ async def _(bot: T_Bot):
|
||||
message_type=reload_session_type,
|
||||
user_id=reload_session_id,
|
||||
group_id=reload_session_id,
|
||||
message="轻雪核心 重载耗时 {:.2f} 秒\n*此数据仅作参考,具体计时请以实际为准\n灵温 预计体感重载耗时 {:.2f} 秒".format(delta_time,time.time() - temp_data.data.get("reload_time", 0)),
|
||||
message=return_msg,
|
||||
)
|
||||
|
||||
|
||||
@ -439,7 +445,8 @@ async def every_day_update():
|
||||
if result:
|
||||
await broadcast_to_superusers(f"灵温已更新:```\n{logs}\n```")
|
||||
nonebot.logger.info(f"灵温已更新:{logs}")
|
||||
ProcessingManager.restart(3)
|
||||
# ProcessingManager.restart(3)
|
||||
reload()
|
||||
else:
|
||||
nonebot.logger.info(logs)
|
||||
|
||||
|
@ -3,58 +3,31 @@ from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
|
||||
from liteyuki.bot import get_bot
|
||||
from src.utils.base import reload
|
||||
from src.utils.base.config import get_config
|
||||
from liteyuki.core import ProcessingManager
|
||||
from src.utils.base.resource import load_resources
|
||||
|
||||
if get_config("debug", False):
|
||||
|
||||
liteyuki_bot = get_bot()
|
||||
|
||||
src_directories = (
|
||||
"src/liteyuki_main",
|
||||
"src/plugins",
|
||||
"src/utils",
|
||||
)
|
||||
src_excludes_extensions = ("pyc",)
|
||||
|
||||
res_directories = (
|
||||
"src/resources",
|
||||
"resources",
|
||||
)
|
||||
|
||||
nonebot.logger.info("已启用 Liteyuki Reload ,正在监测文件变动。")
|
||||
|
||||
class CodeModifiedHandler(FileSystemEventHandler):
|
||||
"""
|
||||
Handler for code file changes
|
||||
"""
|
||||
|
||||
def on_modified(self, event):
|
||||
if (
|
||||
event.src_path.endswith(src_excludes_extensions)
|
||||
or event.is_directory
|
||||
or "__pycache__" in event.src_path
|
||||
):
|
||||
return
|
||||
nonebot.logger.info(f"文件 {event.src_path} 变更,正在重载…")
|
||||
liteyuki_bot.restart()
|
||||
|
||||
class ResourceModifiedHandler(FileSystemEventHandler):
|
||||
"""
|
||||
Handler for resource file changes
|
||||
"""
|
||||
|
||||
def on_modified(self, event):
|
||||
nonebot.logger.info(f"资源 {event.src_path} 变更,重载资源包…")
|
||||
nonebot.logger.info(f"资源 {event.src_path} 变更,重载资源包……")
|
||||
load_resources()
|
||||
|
||||
code_modified_handler = CodeModifiedHandler()
|
||||
resource_modified_handle = ResourceModifiedHandler()
|
||||
|
||||
observer = Observer()
|
||||
for directory in src_directories:
|
||||
observer.schedule(code_modified_handler, directory, recursive=True)
|
||||
for directory in res_directories:
|
||||
observer.schedule(resource_modified_handle, directory, recursive=True)
|
||||
observer.start()
|
||||
|
@ -1,3 +1,5 @@
|
||||
import asyncio
|
||||
|
||||
import nonebot.plugin
|
||||
from nonebot import get_driver
|
||||
from src.utils import init_log
|
||||
@ -6,7 +8,9 @@ from src.utils.base.data_manager import InstalledPlugin, plugin_db
|
||||
from src.utils.base.resource import load_resources
|
||||
from src.utils.message.tools import check_for_package
|
||||
|
||||
from liteyuki import get_bot
|
||||
from liteyuki import get_bot, chan
|
||||
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
|
||||
load_resources()
|
||||
init_log()
|
||||
@ -17,7 +21,7 @@ liteyuki_bot = get_bot()
|
||||
|
||||
@driver.on_startup
|
||||
async def load_plugins():
|
||||
nonebot.plugin.load_plugins("src/plugins")
|
||||
nonebot.plugin.load_plugins("src/nonebot_plugins")
|
||||
# 从数据库读取已安装的插件
|
||||
if not get_config("safe_mode", False):
|
||||
# 安全模式下,不加载插件
|
||||
@ -34,34 +38,4 @@ async def load_plugins():
|
||||
nonebot.load_plugin(installed_plugin.module_name)
|
||||
nonebot.plugin.load_plugins("plugins")
|
||||
else:
|
||||
nonebot.logger.info("安全模式已启动,未加载任何插件。")
|
||||
|
||||
|
||||
@liteyuki_bot.on_before_start
|
||||
async def _():
|
||||
print("启动前")
|
||||
|
||||
|
||||
@liteyuki_bot.on_after_start
|
||||
async def _():
|
||||
print("启动后")
|
||||
|
||||
|
||||
@liteyuki_bot.on_before_shutdown
|
||||
async def _():
|
||||
print("停止前")
|
||||
|
||||
|
||||
@liteyuki_bot.on_after_shutdown
|
||||
async def _():
|
||||
print("停止后")
|
||||
|
||||
|
||||
@liteyuki_bot.on_before_restart
|
||||
async def _():
|
||||
print("重启前")
|
||||
|
||||
|
||||
@liteyuki_bot.on_after_restart
|
||||
async def _():
|
||||
print("重启后")
|
||||
nonebot.logger.info("当前处于安全模式,未加载任何插件。")
|
||||
|
3
src/liteyuki_plugins/README.md
Normal file
3
src/liteyuki_plugins/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# 说明
|
||||
|
||||
此目录为**轻雪插件**目录,非其他插件目录。
|
@ -1,21 +1,27 @@
|
||||
import multiprocessing
|
||||
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from liteyuki.plugin import get_loaded_plugins
|
||||
from .rt_guide import *
|
||||
from .crt_matchers import *
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="CRT生成工具",
|
||||
description="一些CRT牌子生成器",
|
||||
usage="我觉得你应该会用",
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki" : True,
|
||||
"toggleable" : True,
|
||||
"default_enable": True,
|
||||
}
|
||||
)
|
||||
|
||||
print("已加载插件:", len(get_loaded_plugins()))
|
||||
import multiprocessing
|
||||
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from liteyuki.comm import get_channel
|
||||
from .rt_guide import *
|
||||
from .crt_matchers import *
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="CRT生成工具",
|
||||
description="一些CRT牌子生成器",
|
||||
usage="我觉得你应该会用",
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki" : True,
|
||||
"toggleable" : True,
|
||||
"default_enable": True,
|
||||
}
|
||||
)
|
||||
|
||||
# chan = get_channel("nonebot-passive")
|
||||
#
|
||||
#
|
||||
# @chan.on_receive()
|
||||
# async def _(d):
|
||||
# print("CRT子进程接收到数据:", d)
|
||||
# chan.send("CRT子进程已接收到数据")
|
File diff suppressed because it is too large
Load Diff
@ -1,78 +1,78 @@
|
||||
from urllib.parse import quote
|
||||
|
||||
import aiohttp
|
||||
from nonebot import require
|
||||
|
||||
from src.utils.event import get_user_id
|
||||
from src.utils.base.language import Language
|
||||
from src.utils.base.ly_typing import T_MessageEvent
|
||||
from src.utils.base.resource import get_path
|
||||
from src.utils.message.html_tool import template2image
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
|
||||
from nonebot_plugin_alconna import UniMessage, on_alconna, Alconna, Args, Subcommand, Arparma, Option
|
||||
|
||||
crt_cmd = on_alconna(
|
||||
Alconna(
|
||||
"crt",
|
||||
Subcommand(
|
||||
"route",
|
||||
Args["start", str, "沙坪坝"]["end", str, "上新街"],
|
||||
alias=("r",),
|
||||
help_text="查询两地之间的地铁路线"
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@crt_cmd.assign("route")
|
||||
async def _(result: Arparma, event: T_MessageEvent):
|
||||
# 获取语言
|
||||
ulang = Language(get_user_id(event))
|
||||
|
||||
# 获取参数
|
||||
# 你也别问我为什么要quote两次,问就是CRT官网的锅,只有这样才可以运行
|
||||
start = quote(quote(result.other_args.get("start")))
|
||||
end = quote(quote(result.other_args.get("end")))
|
||||
|
||||
# 判断参数语言
|
||||
query_lang_code = ""
|
||||
if start.isalpha() and end.isalpha():
|
||||
query_lang_code = "Eng"
|
||||
|
||||
# 构造请求 URL
|
||||
url = f"https://www.cqmetro.cn/Front/html/TakeLine!queryYs{query_lang_code}TakeLine.action?entity.startStaName={start}&entity.endStaName={end}"
|
||||
|
||||
# 请求数据
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url) as resp:
|
||||
result = await resp.json()
|
||||
|
||||
# 检查结果/无则终止
|
||||
if not result.get("result"):
|
||||
await crt_cmd.send(ulang.get("crt.no_result"))
|
||||
return
|
||||
|
||||
# 模板传参定义
|
||||
templates = {
|
||||
"data" : {
|
||||
"result": result["result"],
|
||||
},
|
||||
"localization": ulang.get_many(
|
||||
"crt.station",
|
||||
"crt.hour",
|
||||
"crt.minute",
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
# 生成图片
|
||||
image = await template2image(
|
||||
template=get_path("templates/crt_route.html"),
|
||||
templates=templates,
|
||||
debug=True
|
||||
)
|
||||
|
||||
# 发送图片
|
||||
await crt_cmd.send(UniMessage.image(raw=image))
|
||||
from urllib.parse import quote
|
||||
|
||||
import aiohttp
|
||||
from nonebot import require
|
||||
|
||||
from src.utils.event import get_user_id
|
||||
from src.utils.base.language import Language
|
||||
from src.utils.base.ly_typing import T_MessageEvent
|
||||
from src.utils.base.resource import get_path
|
||||
from src.utils.message.html_tool import template2image
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
|
||||
from nonebot_plugin_alconna import UniMessage, on_alconna, Alconna, Args, Subcommand, Arparma, Option
|
||||
|
||||
crt_cmd = on_alconna(
|
||||
Alconna(
|
||||
"crt",
|
||||
Subcommand(
|
||||
"route",
|
||||
Args["start", str, "沙坪坝"]["end", str, "上新街"],
|
||||
alias=("r",),
|
||||
help_text="查询两地之间的地铁路线"
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@crt_cmd.assign("route")
|
||||
async def _(result: Arparma, event: T_MessageEvent):
|
||||
# 获取语言
|
||||
ulang = Language(get_user_id(event))
|
||||
|
||||
# 获取参数
|
||||
# 你也别问我为什么要quote两次,问就是CRT官网的锅,只有这样才可以运行
|
||||
start = quote(quote(result.other_args.get("start")))
|
||||
end = quote(quote(result.other_args.get("end")))
|
||||
|
||||
# 判断参数语言
|
||||
query_lang_code = ""
|
||||
if start.isalpha() and end.isalpha():
|
||||
query_lang_code = "Eng"
|
||||
|
||||
# 构造请求 URL
|
||||
url = f"https://www.cqmetro.cn/Front/html/TakeLine!queryYs{query_lang_code}TakeLine.action?entity.startStaName={start}&entity.endStaName={end}"
|
||||
|
||||
# 请求数据
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url) as resp:
|
||||
result = await resp.json()
|
||||
|
||||
# 检查结果/无则终止
|
||||
if not result.get("result"):
|
||||
await crt_cmd.send(ulang.get("crt.no_result"))
|
||||
return
|
||||
|
||||
# 模板传参定义
|
||||
templates = {
|
||||
"data" : {
|
||||
"result": result["result"],
|
||||
},
|
||||
"localization": ulang.get_many(
|
||||
"crt.station",
|
||||
"crt.hour",
|
||||
"crt.minute",
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
# 生成图片
|
||||
image = await template2image(
|
||||
template=get_path("templates/crt_route.html"),
|
||||
templates=templates,
|
||||
debug=True
|
||||
)
|
||||
|
||||
# 发送图片
|
||||
await crt_cmd.send(UniMessage.image(raw=image))
|
@ -1,419 +1,419 @@
|
||||
import json
|
||||
from typing import List, Any
|
||||
|
||||
from PIL import Image
|
||||
from arclet.alconna import Alconna
|
||||
from nb_cli import run_sync
|
||||
from nonebot import on_command
|
||||
from nonebot_plugin_alconna import on_alconna, Alconna, Subcommand, Args, MultiVar, Arparma, UniMessage
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .canvas import *
|
||||
from ...utils.base.resource import get_path
|
||||
|
||||
resolution = 256
|
||||
|
||||
|
||||
class Entrance(BaseModel):
|
||||
identifier: str
|
||||
size: tuple[int, int]
|
||||
dest: List[str]
|
||||
|
||||
|
||||
class Station(BaseModel):
|
||||
identifier: str
|
||||
chineseName: str
|
||||
englishName: str
|
||||
position: tuple[int, int]
|
||||
|
||||
|
||||
class Line(BaseModel):
|
||||
identifier: str
|
||||
chineseName: str
|
||||
englishName: str
|
||||
color: Any
|
||||
stations: List["Station"]
|
||||
|
||||
|
||||
font_light = get_path("templates/fonts/MiSans/MiSans-Light.woff2")
|
||||
font_bold = get_path("templates/fonts/MiSans/MiSans-Bold.woff2")
|
||||
|
||||
@run_sync
|
||||
def generate_entrance_sign(name: str, aliases: List[str], lineInfo: List[Line], entranceIdentifier: str, ratio: tuple[int | float, int | float],
|
||||
reso: int = resolution):
|
||||
"""
|
||||
Generates an entrance sign for the ride.
|
||||
"""
|
||||
width, height = ratio[0] * reso, ratio[1] * reso
|
||||
baseCanvas = Canvas(Image.new("RGBA", (width, height), Color.WHITE))
|
||||
# 加黑色图框
|
||||
baseCanvas.outline = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0, 0),
|
||||
point=(0, 0),
|
||||
img=Shape.rectangle(
|
||||
size=(width, height),
|
||||
fillet=0,
|
||||
fill=(0, 0, 0, 0),
|
||||
width=15,
|
||||
outline=Color.BLACK
|
||||
)
|
||||
)
|
||||
|
||||
baseCanvas.contentPanel = Panel(
|
||||
uv_size=(width, height),
|
||||
box_size=(width - 28, height - 28),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
)
|
||||
|
||||
linePanelHeight = 0.7 * ratio[1]
|
||||
linePanelWidth = linePanelHeight * 1.3
|
||||
|
||||
# 画线路面板部分
|
||||
|
||||
for i, line in enumerate(lineInfo):
|
||||
linePanel = baseCanvas.contentPanel.__dict__[f"Line_{i}_Panel"] = Panel(
|
||||
uv_size=ratio,
|
||||
box_size=(linePanelWidth, linePanelHeight),
|
||||
parent_point=(i * linePanelWidth / ratio[0], 1),
|
||||
point=(0, 1),
|
||||
)
|
||||
|
||||
linePanel.colorCube = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=(0.15, 1),
|
||||
parent_point=(0.125, 1),
|
||||
point=(0, 1),
|
||||
img=Shape.rectangle(
|
||||
size=(100, 100),
|
||||
fillet=0,
|
||||
fill=line.color,
|
||||
),
|
||||
keep_ratio=False
|
||||
)
|
||||
|
||||
textPanel = linePanel.TextPanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(0.625, 1),
|
||||
parent_point=(1, 1),
|
||||
point=(1, 1)
|
||||
)
|
||||
|
||||
# 中文线路名
|
||||
textPanel.namePanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 2 / 3),
|
||||
parent_point=(0, 0),
|
||||
point=(0, 0),
|
||||
)
|
||||
nameSize = baseCanvas.get_actual_pixel_size("contentPanel.Line_{}_Panel.TextPanel.namePanel".format(i))
|
||||
textPanel.namePanel.text = Text(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
text=line.chineseName,
|
||||
color=Color.BLACK,
|
||||
font_size=int(nameSize[1] * 0.5),
|
||||
force_size=True,
|
||||
font=font_bold
|
||||
|
||||
)
|
||||
|
||||
# 英文线路名
|
||||
textPanel.englishNamePanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1 / 3),
|
||||
parent_point=(0, 1),
|
||||
point=(0, 1),
|
||||
)
|
||||
englishNameSize = baseCanvas.get_actual_pixel_size("contentPanel.Line_{}_Panel.TextPanel.englishNamePanel".format(i))
|
||||
textPanel.englishNamePanel.text = Text(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
text=line.englishName,
|
||||
color=Color.BLACK,
|
||||
font_size=int(englishNameSize[1] * 0.6),
|
||||
force_size=True,
|
||||
font=font_light
|
||||
)
|
||||
|
||||
# 画名称部分
|
||||
namePanel = baseCanvas.contentPanel.namePanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 0.4),
|
||||
parent_point=(0.5, 0),
|
||||
point=(0.5, 0),
|
||||
)
|
||||
|
||||
namePanel.text = Text(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
text=name,
|
||||
color=Color.BLACK,
|
||||
font_size=int(height * 0.3),
|
||||
force_size=True,
|
||||
font=font_bold
|
||||
)
|
||||
|
||||
aliasesPanel = baseCanvas.contentPanel.aliasesPanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 0.5),
|
||||
parent_point=(0.5, 1),
|
||||
point=(0.5, 1),
|
||||
|
||||
)
|
||||
for j, alias in enumerate(aliases):
|
||||
aliasesPanel.__dict__[alias] = Text(
|
||||
uv_size=(1, 1),
|
||||
box_size=(0.35, 0.5),
|
||||
parent_point=(0.5, 0.5 * j),
|
||||
point=(0.5, 0),
|
||||
text=alias,
|
||||
color=Color.BLACK,
|
||||
font_size=int(height * 0.15),
|
||||
font=font_light
|
||||
)
|
||||
|
||||
# 画入口标识
|
||||
entrancePanel = baseCanvas.contentPanel.entrancePanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(0.2, 1),
|
||||
parent_point=(1, 0.5),
|
||||
point=(1, 0.5),
|
||||
)
|
||||
# 中文文本
|
||||
entrancePanel.namePanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 0.5),
|
||||
parent_point=(1, 0),
|
||||
point=(1, 0),
|
||||
)
|
||||
entrancePanel.namePanel.text = Text(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0, 0.5),
|
||||
point=(0, 0.5),
|
||||
text=f"{entranceIdentifier}出入口",
|
||||
color=Color.BLACK,
|
||||
font_size=int(height * 0.2),
|
||||
force_size=True,
|
||||
font=font_bold
|
||||
)
|
||||
# 英文文本
|
||||
entrancePanel.englishNamePanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 0.5),
|
||||
parent_point=(1, 1),
|
||||
point=(1, 1),
|
||||
)
|
||||
entrancePanel.englishNamePanel.text = Text(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0, 0.5),
|
||||
point=(0, 0.5),
|
||||
text=f"Entrance {entranceIdentifier}",
|
||||
color=Color.BLACK,
|
||||
font_size=int(height * 0.15),
|
||||
force_size=True,
|
||||
font=font_light
|
||||
)
|
||||
|
||||
return baseCanvas.base_img.tobytes()
|
||||
|
||||
|
||||
crt_alc = on_alconna(
|
||||
Alconna(
|
||||
"crt",
|
||||
Subcommand(
|
||||
"entrance",
|
||||
Args["name", str]["lines", str, ""]["entrance", int, 1], # /crt entrance 璧山&Bishan 1号线&Line1&#ff0000,27号线&Line1&#ff0000 1A
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@crt_alc.assign("entrance")
|
||||
async def _(result: Arparma):
|
||||
args = result.subcommands.get("entrance").args
|
||||
name = args["name"]
|
||||
lines = args["lines"]
|
||||
entrance = args["entrance"]
|
||||
line_info = []
|
||||
for line in lines.split(","):
|
||||
line_args = line.split("&")
|
||||
line_info.append(Line(
|
||||
identifier=1,
|
||||
chineseName=line_args[0],
|
||||
englishName=line_args[1],
|
||||
color=line_args[2],
|
||||
stations=[]
|
||||
))
|
||||
img_bytes = await generate_entrance_sign(
|
||||
name=name,
|
||||
aliases=name.split("&"),
|
||||
lineInfo=line_info,
|
||||
entranceIdentifier=entrance,
|
||||
ratio=(8, 1),
|
||||
reso=256,
|
||||
)
|
||||
await crt_alc.finish(
|
||||
UniMessage.image(raw=img_bytes)
|
||||
)
|
||||
|
||||
|
||||
def generate_platform_line_pic(line: Line, station: Station, ratio=None, reso: int = resolution):
|
||||
"""
|
||||
生成站台线路图
|
||||
:param line: 线路对象
|
||||
:param station: 本站点对象
|
||||
:param ratio: 比例
|
||||
:param reso: 分辨率,1:reso
|
||||
:return: 两个方向的站牌
|
||||
"""
|
||||
if ratio is None:
|
||||
ratio = [4, 1]
|
||||
width, height = ratio[0] * reso, ratio[1] * reso
|
||||
baseCanvas = Canvas(Image.new("RGBA", (width, height), Color.YELLOW))
|
||||
# 加黑色图框
|
||||
baseCanvas.linePanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(0.8, 0.15),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
)
|
||||
|
||||
# 直线块
|
||||
baseCanvas.linePanel.recLine = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
img=Shape.rectangle(
|
||||
size=(10, 10),
|
||||
fill=line.color,
|
||||
),
|
||||
keep_ratio=False
|
||||
)
|
||||
# 灰色直线块
|
||||
baseCanvas.linePanel.recLineGrey = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
img=Shape.rectangle(
|
||||
size=(10, 10),
|
||||
fill=Color.GREY,
|
||||
),
|
||||
keep_ratio=False
|
||||
)
|
||||
# 生成各站圆点
|
||||
outline_width = 40
|
||||
circleForward = Shape.circular(
|
||||
radius=200,
|
||||
fill=Color.WHITE,
|
||||
width=outline_width,
|
||||
outline=line.color,
|
||||
)
|
||||
|
||||
circleThisPanel = Canvas(Image.new("RGBA", (200, 200), (0, 0, 0, 0)))
|
||||
circleThisPanel.circleOuter = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
img=Shape.circular(
|
||||
radius=200,
|
||||
fill=Color.WHITE,
|
||||
width=outline_width,
|
||||
outline=line.color,
|
||||
),
|
||||
)
|
||||
circleThisPanel.circleOuter.circleInner = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=(0.7, 0.7),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
img=Shape.circular(
|
||||
radius=200,
|
||||
fill=line.color,
|
||||
width=0,
|
||||
outline=line.color,
|
||||
),
|
||||
)
|
||||
|
||||
circleThisPanel.export("a.png", alpha=True)
|
||||
circleThis = circleThisPanel.base_img
|
||||
|
||||
circlePassed = Shape.circular(
|
||||
radius=200,
|
||||
fill=Color.WHITE,
|
||||
width=outline_width,
|
||||
outline=Color.GREY,
|
||||
)
|
||||
|
||||
arrival = False
|
||||
distance = 1 / (len(line.stations) - 1)
|
||||
for i, sta in enumerate(line.stations):
|
||||
box_size = (1.618, 1.618)
|
||||
if sta.identifier == station.identifier:
|
||||
arrival = True
|
||||
baseCanvas.linePanel.recLine.__dict__["station_{}".format(sta.identifier)] = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1.8, 1.8),
|
||||
parent_point=(distance * i, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
img=circleThis,
|
||||
keep_ratio=True
|
||||
)
|
||||
continue
|
||||
if arrival:
|
||||
# 后方站绘制
|
||||
baseCanvas.linePanel.recLine.__dict__["station_{}".format(sta.identifier)] = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=box_size,
|
||||
parent_point=(distance * i, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
img=circleForward,
|
||||
keep_ratio=True
|
||||
)
|
||||
else:
|
||||
# 前方站绘制
|
||||
baseCanvas.linePanel.recLine.__dict__["station_{}".format(sta.identifier)] = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=box_size,
|
||||
parent_point=(distance * i, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
img=circlePassed,
|
||||
keep_ratio=True
|
||||
)
|
||||
return baseCanvas
|
||||
|
||||
|
||||
def generate_platform_sign(name: str, aliases: List[str], lineInfo: List[Line], entranceIdentifier: str, ratio: tuple[int | float, int | float],
|
||||
reso: int = resolution
|
||||
):
|
||||
pass
|
||||
|
||||
# def main():
|
||||
# generate_entrance_sign(
|
||||
# "璧山",
|
||||
# aliases=["Bishan"],
|
||||
# lineInfo=[
|
||||
#
|
||||
# Line(identifier="2", chineseName="1号线", englishName="Line 1", color=Color.RED, stations=[]),
|
||||
# Line(identifier="3", chineseName="27号线", englishName="Line 27", color="#685bc7", stations=[]),
|
||||
# Line(identifier="1", chineseName="璧铜线", englishName="BT Line", color="#685BC7", stations=[]),
|
||||
# ],
|
||||
# entranceIdentifier="1",
|
||||
# ratio=(8, 1)
|
||||
# )
|
||||
#
|
||||
#
|
||||
# main()
|
||||
import json
|
||||
from typing import List, Any
|
||||
|
||||
from PIL import Image
|
||||
from arclet.alconna import Alconna
|
||||
from nb_cli import run_sync
|
||||
from nonebot import on_command
|
||||
from nonebot_plugin_alconna import on_alconna, Alconna, Subcommand, Args, MultiVar, Arparma, UniMessage
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .canvas import *
|
||||
from ...utils.base.resource import get_path
|
||||
|
||||
resolution = 256
|
||||
|
||||
|
||||
class Entrance(BaseModel):
|
||||
identifier: str
|
||||
size: tuple[int, int]
|
||||
dest: List[str]
|
||||
|
||||
|
||||
class Station(BaseModel):
|
||||
identifier: str
|
||||
chineseName: str
|
||||
englishName: str
|
||||
position: tuple[int, int]
|
||||
|
||||
|
||||
class Line(BaseModel):
|
||||
identifier: str
|
||||
chineseName: str
|
||||
englishName: str
|
||||
color: Any
|
||||
stations: List["Station"]
|
||||
|
||||
|
||||
font_light = get_path("templates/fonts/MiSans/MiSans-Light.woff2")
|
||||
font_bold = get_path("templates/fonts/MiSans/MiSans-Bold.woff2")
|
||||
|
||||
@run_sync
|
||||
def generate_entrance_sign(name: str, aliases: List[str], lineInfo: List[Line], entranceIdentifier: str, ratio: tuple[int | float, int | float],
|
||||
reso: int = resolution):
|
||||
"""
|
||||
Generates an entrance sign for the ride.
|
||||
"""
|
||||
width, height = ratio[0] * reso, ratio[1] * reso
|
||||
baseCanvas = Canvas(Image.new("RGBA", (width, height), Color.WHITE))
|
||||
# 加黑色图框
|
||||
baseCanvas.outline = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0, 0),
|
||||
point=(0, 0),
|
||||
img=Shape.rectangle(
|
||||
size=(width, height),
|
||||
fillet=0,
|
||||
fill=(0, 0, 0, 0),
|
||||
width=15,
|
||||
outline=Color.BLACK
|
||||
)
|
||||
)
|
||||
|
||||
baseCanvas.contentPanel = Panel(
|
||||
uv_size=(width, height),
|
||||
box_size=(width - 28, height - 28),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
)
|
||||
|
||||
linePanelHeight = 0.7 * ratio[1]
|
||||
linePanelWidth = linePanelHeight * 1.3
|
||||
|
||||
# 画线路面板部分
|
||||
|
||||
for i, line in enumerate(lineInfo):
|
||||
linePanel = baseCanvas.contentPanel.__dict__[f"Line_{i}_Panel"] = Panel(
|
||||
uv_size=ratio,
|
||||
box_size=(linePanelWidth, linePanelHeight),
|
||||
parent_point=(i * linePanelWidth / ratio[0], 1),
|
||||
point=(0, 1),
|
||||
)
|
||||
|
||||
linePanel.colorCube = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=(0.15, 1),
|
||||
parent_point=(0.125, 1),
|
||||
point=(0, 1),
|
||||
img=Shape.rectangle(
|
||||
size=(100, 100),
|
||||
fillet=0,
|
||||
fill=line.color,
|
||||
),
|
||||
keep_ratio=False
|
||||
)
|
||||
|
||||
textPanel = linePanel.TextPanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(0.625, 1),
|
||||
parent_point=(1, 1),
|
||||
point=(1, 1)
|
||||
)
|
||||
|
||||
# 中文线路名
|
||||
textPanel.namePanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 2 / 3),
|
||||
parent_point=(0, 0),
|
||||
point=(0, 0),
|
||||
)
|
||||
nameSize = baseCanvas.get_actual_pixel_size("contentPanel.Line_{}_Panel.TextPanel.namePanel".format(i))
|
||||
textPanel.namePanel.text = Text(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
text=line.chineseName,
|
||||
color=Color.BLACK,
|
||||
font_size=int(nameSize[1] * 0.5),
|
||||
force_size=True,
|
||||
font=font_bold
|
||||
|
||||
)
|
||||
|
||||
# 英文线路名
|
||||
textPanel.englishNamePanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1 / 3),
|
||||
parent_point=(0, 1),
|
||||
point=(0, 1),
|
||||
)
|
||||
englishNameSize = baseCanvas.get_actual_pixel_size("contentPanel.Line_{}_Panel.TextPanel.englishNamePanel".format(i))
|
||||
textPanel.englishNamePanel.text = Text(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
text=line.englishName,
|
||||
color=Color.BLACK,
|
||||
font_size=int(englishNameSize[1] * 0.6),
|
||||
force_size=True,
|
||||
font=font_light
|
||||
)
|
||||
|
||||
# 画名称部分
|
||||
namePanel = baseCanvas.contentPanel.namePanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 0.4),
|
||||
parent_point=(0.5, 0),
|
||||
point=(0.5, 0),
|
||||
)
|
||||
|
||||
namePanel.text = Text(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
text=name,
|
||||
color=Color.BLACK,
|
||||
font_size=int(height * 0.3),
|
||||
force_size=True,
|
||||
font=font_bold
|
||||
)
|
||||
|
||||
aliasesPanel = baseCanvas.contentPanel.aliasesPanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 0.5),
|
||||
parent_point=(0.5, 1),
|
||||
point=(0.5, 1),
|
||||
|
||||
)
|
||||
for j, alias in enumerate(aliases):
|
||||
aliasesPanel.__dict__[alias] = Text(
|
||||
uv_size=(1, 1),
|
||||
box_size=(0.35, 0.5),
|
||||
parent_point=(0.5, 0.5 * j),
|
||||
point=(0.5, 0),
|
||||
text=alias,
|
||||
color=Color.BLACK,
|
||||
font_size=int(height * 0.15),
|
||||
font=font_light
|
||||
)
|
||||
|
||||
# 画入口标识
|
||||
entrancePanel = baseCanvas.contentPanel.entrancePanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(0.2, 1),
|
||||
parent_point=(1, 0.5),
|
||||
point=(1, 0.5),
|
||||
)
|
||||
# 中文文本
|
||||
entrancePanel.namePanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 0.5),
|
||||
parent_point=(1, 0),
|
||||
point=(1, 0),
|
||||
)
|
||||
entrancePanel.namePanel.text = Text(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0, 0.5),
|
||||
point=(0, 0.5),
|
||||
text=f"{entranceIdentifier}出入口",
|
||||
color=Color.BLACK,
|
||||
font_size=int(height * 0.2),
|
||||
force_size=True,
|
||||
font=font_bold
|
||||
)
|
||||
# 英文文本
|
||||
entrancePanel.englishNamePanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 0.5),
|
||||
parent_point=(1, 1),
|
||||
point=(1, 1),
|
||||
)
|
||||
entrancePanel.englishNamePanel.text = Text(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0, 0.5),
|
||||
point=(0, 0.5),
|
||||
text=f"Entrance {entranceIdentifier}",
|
||||
color=Color.BLACK,
|
||||
font_size=int(height * 0.15),
|
||||
force_size=True,
|
||||
font=font_light
|
||||
)
|
||||
|
||||
return baseCanvas.base_img.tobytes()
|
||||
|
||||
|
||||
crt_alc = on_alconna(
|
||||
Alconna(
|
||||
"crt",
|
||||
Subcommand(
|
||||
"entrance",
|
||||
Args["name", str]["lines", str, ""]["entrance", int, 1], # /crt entrance 璧山&Bishan 1号线&Line1&#ff0000,27号线&Line1&#ff0000 1A
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@crt_alc.assign("entrance")
|
||||
async def _(result: Arparma):
|
||||
args = result.subcommands.get("entrance").args
|
||||
name = args["name"]
|
||||
lines = args["lines"]
|
||||
entrance = args["entrance"]
|
||||
line_info = []
|
||||
for line in lines.split(","):
|
||||
line_args = line.split("&")
|
||||
line_info.append(Line(
|
||||
identifier=1,
|
||||
chineseName=line_args[0],
|
||||
englishName=line_args[1],
|
||||
color=line_args[2],
|
||||
stations=[]
|
||||
))
|
||||
img_bytes = await generate_entrance_sign(
|
||||
name=name,
|
||||
aliases=name.split("&"),
|
||||
lineInfo=line_info,
|
||||
entranceIdentifier=entrance,
|
||||
ratio=(8, 1),
|
||||
reso=256,
|
||||
)
|
||||
await crt_alc.finish(
|
||||
UniMessage.image(raw=img_bytes)
|
||||
)
|
||||
|
||||
|
||||
def generate_platform_line_pic(line: Line, station: Station, ratio=None, reso: int = resolution):
|
||||
"""
|
||||
生成站台线路图
|
||||
:param line: 线路对象
|
||||
:param station: 本站点对象
|
||||
:param ratio: 比例
|
||||
:param reso: 分辨率,1:reso
|
||||
:return: 两个方向的站牌
|
||||
"""
|
||||
if ratio is None:
|
||||
ratio = [4, 1]
|
||||
width, height = ratio[0] * reso, ratio[1] * reso
|
||||
baseCanvas = Canvas(Image.new("RGBA", (width, height), Color.YELLOW))
|
||||
# 加黑色图框
|
||||
baseCanvas.linePanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(0.8, 0.15),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
)
|
||||
|
||||
# 直线块
|
||||
baseCanvas.linePanel.recLine = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
img=Shape.rectangle(
|
||||
size=(10, 10),
|
||||
fill=line.color,
|
||||
),
|
||||
keep_ratio=False
|
||||
)
|
||||
# 灰色直线块
|
||||
baseCanvas.linePanel.recLineGrey = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
img=Shape.rectangle(
|
||||
size=(10, 10),
|
||||
fill=Color.GREY,
|
||||
),
|
||||
keep_ratio=False
|
||||
)
|
||||
# 生成各站圆点
|
||||
outline_width = 40
|
||||
circleForward = Shape.circular(
|
||||
radius=200,
|
||||
fill=Color.WHITE,
|
||||
width=outline_width,
|
||||
outline=line.color,
|
||||
)
|
||||
|
||||
circleThisPanel = Canvas(Image.new("RGBA", (200, 200), (0, 0, 0, 0)))
|
||||
circleThisPanel.circleOuter = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
img=Shape.circular(
|
||||
radius=200,
|
||||
fill=Color.WHITE,
|
||||
width=outline_width,
|
||||
outline=line.color,
|
||||
),
|
||||
)
|
||||
circleThisPanel.circleOuter.circleInner = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=(0.7, 0.7),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
img=Shape.circular(
|
||||
radius=200,
|
||||
fill=line.color,
|
||||
width=0,
|
||||
outline=line.color,
|
||||
),
|
||||
)
|
||||
|
||||
circleThisPanel.export("a.png", alpha=True)
|
||||
circleThis = circleThisPanel.base_img
|
||||
|
||||
circlePassed = Shape.circular(
|
||||
radius=200,
|
||||
fill=Color.WHITE,
|
||||
width=outline_width,
|
||||
outline=Color.GREY,
|
||||
)
|
||||
|
||||
arrival = False
|
||||
distance = 1 / (len(line.stations) - 1)
|
||||
for i, sta in enumerate(line.stations):
|
||||
box_size = (1.618, 1.618)
|
||||
if sta.identifier == station.identifier:
|
||||
arrival = True
|
||||
baseCanvas.linePanel.recLine.__dict__["station_{}".format(sta.identifier)] = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1.8, 1.8),
|
||||
parent_point=(distance * i, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
img=circleThis,
|
||||
keep_ratio=True
|
||||
)
|
||||
continue
|
||||
if arrival:
|
||||
# 后方站绘制
|
||||
baseCanvas.linePanel.recLine.__dict__["station_{}".format(sta.identifier)] = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=box_size,
|
||||
parent_point=(distance * i, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
img=circleForward,
|
||||
keep_ratio=True
|
||||
)
|
||||
else:
|
||||
# 前方站绘制
|
||||
baseCanvas.linePanel.recLine.__dict__["station_{}".format(sta.identifier)] = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=box_size,
|
||||
parent_point=(distance * i, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
img=circlePassed,
|
||||
keep_ratio=True
|
||||
)
|
||||
return baseCanvas
|
||||
|
||||
|
||||
def generate_platform_sign(name: str, aliases: List[str], lineInfo: List[Line], entranceIdentifier: str, ratio: tuple[int | float, int | float],
|
||||
reso: int = resolution
|
||||
):
|
||||
pass
|
||||
|
||||
# def main():
|
||||
# generate_entrance_sign(
|
||||
# "璧山",
|
||||
# aliases=["Bishan"],
|
||||
# lineInfo=[
|
||||
#
|
||||
# Line(identifier="2", chineseName="1号线", englishName="Line 1", color=Color.RED, stations=[]),
|
||||
# Line(identifier="3", chineseName="27号线", englishName="Line 27", color="#685bc7", stations=[]),
|
||||
# Line(identifier="1", chineseName="璧铜线", englishName="BT Line", color="#685BC7", stations=[]),
|
||||
# ],
|
||||
# entranceIdentifier="1",
|
||||
# ratio=(8, 1)
|
||||
# )
|
||||
#
|
||||
#
|
||||
# main()
|
@ -1,125 +1,125 @@
|
||||
import nonebot
|
||||
from nonebot import on_message, require
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
from src.utils.base.data import Database, LiteModel
|
||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
from src.utils.message.message import MarkdownMessage as md
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
from nonebot_plugin_alconna import on_alconna
|
||||
from arclet.alconna import Arparma, Alconna, Args, Option, Subcommand
|
||||
|
||||
|
||||
class Node(LiteModel):
|
||||
TABLE_NAME: str = "node"
|
||||
bot_id: str = ""
|
||||
session_type: str = ""
|
||||
session_id: str = ""
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.bot_id}.{self.session_type}.{self.session_id}"
|
||||
|
||||
|
||||
class Push(LiteModel):
|
||||
TABLE_NAME: str = "push"
|
||||
source: Node = Node()
|
||||
target: Node = Node()
|
||||
inde: int = 0
|
||||
|
||||
|
||||
pushes_db = Database("data/pushes.ldb")
|
||||
pushes_db.auto_migrate(Push(), Node())
|
||||
|
||||
alc = Alconna(
|
||||
"lep",
|
||||
Subcommand(
|
||||
"add",
|
||||
Args["source", str],
|
||||
Args["target", str],
|
||||
Option("bidirectional", Args["bidirectional", bool])
|
||||
),
|
||||
Subcommand(
|
||||
"rm",
|
||||
Args["index", int],
|
||||
|
||||
),
|
||||
Subcommand(
|
||||
"list",
|
||||
)
|
||||
)
|
||||
|
||||
add_push = on_alconna(alc)
|
||||
|
||||
|
||||
@add_push.handle()
|
||||
async def _(result: Arparma):
|
||||
"""bot_id.session_type.session_id"""
|
||||
if result.subcommands.get("add"):
|
||||
source = result.subcommands["add"].args.get("source")
|
||||
target = result.subcommands["add"].args.get("target")
|
||||
if source and target:
|
||||
source = source.split(".")
|
||||
target = target.split(".")
|
||||
push1 = Push(
|
||||
source=Node(bot_id=source[0], session_type=source[1], session_id=source[2]),
|
||||
target=Node(bot_id=target[0], session_type=target[1], session_id=target[2]),
|
||||
inde=len(pushes_db.where_all(Push(), default=[]))
|
||||
)
|
||||
pushes_db.save(push1)
|
||||
|
||||
if result.subcommands["add"].args.get("bidirectional"):
|
||||
push2 = Push(
|
||||
source=Node(bot_id=target[0], session_type=target[1], session_id=target[2]),
|
||||
target=Node(bot_id=source[0], session_type=source[1], session_id=source[2]),
|
||||
inde=len(pushes_db.where_all(Push(), default=[]))
|
||||
)
|
||||
pushes_db.save(push2)
|
||||
await add_push.finish("添加成功")
|
||||
else:
|
||||
await add_push.finish("参数缺失")
|
||||
elif result.subcommands.get("rm"):
|
||||
index = result.subcommands["rm"].args.get("index")
|
||||
if index is not None:
|
||||
try:
|
||||
pushes_db.delete(Push(), "inde = ?", index)
|
||||
await add_push.finish("删除成功")
|
||||
except IndexError:
|
||||
await add_push.finish("索引错误")
|
||||
else:
|
||||
await add_push.finish("参数缺失")
|
||||
elif result.subcommands.get("list"):
|
||||
await add_push.finish(
|
||||
"\n".join([f"{push.inde} {push.source.bot_id}.{push.source.session_type}.{push.source.session_id} -> "
|
||||
f"{push.target.bot_id}.{push.target.session_type}.{push.target.session_id}" for i, push in
|
||||
enumerate(pushes_db.where_all(Push(), default=[]))]))
|
||||
else:
|
||||
await add_push.finish("参数错误")
|
||||
|
||||
|
||||
@on_message(block=False).handle()
|
||||
async def _(event: T_MessageEvent, bot: T_Bot):
|
||||
for push in pushes_db.where_all(Push(), default=[]):
|
||||
if str(push.source) == f"{bot.self_id}.{event.message_type}.{event.user_id if event.message_type == 'private' else event.group_id}":
|
||||
bot2 = nonebot.get_bot(push.target.bot_id)
|
||||
msg_formatted = ""
|
||||
for line in str(event.message).split("\n"):
|
||||
msg_formatted += f"**{line.strip()}**\n"
|
||||
push_message = (
|
||||
f"> From {event.sender.nickname}@{push.source.session_type}.{push.source.session_id}\n> Bot {bot.self_id}\n\n"
|
||||
f"{msg_formatted}")
|
||||
await md.send_md(push_message, bot2, message_type=push.target.session_type,
|
||||
session_id=push.target.session_id)
|
||||
return
|
||||
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪事件推送",
|
||||
description="事件推送插件,支持单向和双向推送,支持跨Bot推送",
|
||||
usage="",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
}
|
||||
)
|
||||
import nonebot
|
||||
from nonebot import on_message, require
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
from src.utils.base.data import Database, LiteModel
|
||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
from src.utils.message.message import MarkdownMessage as md
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
from nonebot_plugin_alconna import on_alconna
|
||||
from arclet.alconna import Arparma, Alconna, Args, Option, Subcommand
|
||||
|
||||
|
||||
class Node(LiteModel):
|
||||
TABLE_NAME: str = "node"
|
||||
bot_id: str = ""
|
||||
session_type: str = ""
|
||||
session_id: str = ""
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.bot_id}.{self.session_type}.{self.session_id}"
|
||||
|
||||
|
||||
class Push(LiteModel):
|
||||
TABLE_NAME: str = "push"
|
||||
source: Node = Node()
|
||||
target: Node = Node()
|
||||
inde: int = 0
|
||||
|
||||
|
||||
pushes_db = Database("data/pushes.ldb")
|
||||
pushes_db.auto_migrate(Push(), Node())
|
||||
|
||||
alc = Alconna(
|
||||
"lep",
|
||||
Subcommand(
|
||||
"add",
|
||||
Args["source", str],
|
||||
Args["target", str],
|
||||
Option("bidirectional", Args["bidirectional", bool])
|
||||
),
|
||||
Subcommand(
|
||||
"rm",
|
||||
Args["index", int],
|
||||
|
||||
),
|
||||
Subcommand(
|
||||
"list",
|
||||
)
|
||||
)
|
||||
|
||||
add_push = on_alconna(alc)
|
||||
|
||||
|
||||
@add_push.handle()
|
||||
async def _(result: Arparma):
|
||||
"""bot_id.session_type.session_id"""
|
||||
if result.subcommands.get("add"):
|
||||
source = result.subcommands["add"].args.get("source")
|
||||
target = result.subcommands["add"].args.get("target")
|
||||
if source and target:
|
||||
source = source.split(".")
|
||||
target = target.split(".")
|
||||
push1 = Push(
|
||||
source=Node(bot_id=source[0], session_type=source[1], session_id=source[2]),
|
||||
target=Node(bot_id=target[0], session_type=target[1], session_id=target[2]),
|
||||
inde=len(pushes_db.where_all(Push(), default=[]))
|
||||
)
|
||||
pushes_db.save(push1)
|
||||
|
||||
if result.subcommands["add"].args.get("bidirectional"):
|
||||
push2 = Push(
|
||||
source=Node(bot_id=target[0], session_type=target[1], session_id=target[2]),
|
||||
target=Node(bot_id=source[0], session_type=source[1], session_id=source[2]),
|
||||
inde=len(pushes_db.where_all(Push(), default=[]))
|
||||
)
|
||||
pushes_db.save(push2)
|
||||
await add_push.finish("添加成功")
|
||||
else:
|
||||
await add_push.finish("参数缺失")
|
||||
elif result.subcommands.get("rm"):
|
||||
index = result.subcommands["rm"].args.get("index")
|
||||
if index is not None:
|
||||
try:
|
||||
pushes_db.delete(Push(), "inde = ?", index)
|
||||
await add_push.finish("删除成功")
|
||||
except IndexError:
|
||||
await add_push.finish("索引错误")
|
||||
else:
|
||||
await add_push.finish("参数缺失")
|
||||
elif result.subcommands.get("list"):
|
||||
await add_push.finish(
|
||||
"\n".join([f"{push.inde} {push.source.bot_id}.{push.source.session_type}.{push.source.session_id} -> "
|
||||
f"{push.target.bot_id}.{push.target.session_type}.{push.target.session_id}" for i, push in
|
||||
enumerate(pushes_db.where_all(Push(), default=[]))]))
|
||||
else:
|
||||
await add_push.finish("参数错误")
|
||||
|
||||
|
||||
@on_message(block=False).handle()
|
||||
async def _(event: T_MessageEvent, bot: T_Bot):
|
||||
for push in pushes_db.where_all(Push(), default=[]):
|
||||
if str(push.source) == f"{bot.self_id}.{event.message_type}.{event.user_id if event.message_type == 'private' else event.group_id}":
|
||||
bot2 = nonebot.get_bot(push.target.bot_id)
|
||||
msg_formatted = ""
|
||||
for line in str(event.message).split("\n"):
|
||||
msg_formatted += f"**{line.strip()}**\n"
|
||||
push_message = (
|
||||
f"> From {event.sender.nickname}@{push.source.session_type}.{push.source.session_id}\n> Bot {bot.self_id}\n\n"
|
||||
f"{msg_formatted}")
|
||||
await md.send_md(push_message, bot2, message_type=push.target.session_type,
|
||||
session_id=push.target.session_id)
|
||||
return
|
||||
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪事件推送",
|
||||
description="事件推送插件,支持单向和双向推送,支持跨Bot推送",
|
||||
usage="",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
}
|
||||
)
|
@ -1,52 +1,52 @@
|
||||
from nonebot import on_command, require
|
||||
from nonebot.adapters.onebot.v11 import MessageSegment
|
||||
from nonebot.params import CommandArg
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent, v11
|
||||
from src.utils.message.message import MarkdownMessage as md, broadcast_to_superusers
|
||||
from src.utils.message.html_tool import *
|
||||
|
||||
md_test = on_command("mdts", permission=SUPERUSER)
|
||||
btn_test = on_command("btnts", permission=SUPERUSER)
|
||||
latex_test = on_command("latex", permission=SUPERUSER)
|
||||
|
||||
|
||||
@md_test.handle()
|
||||
async def _(bot: T_Bot, event: T_MessageEvent, arg: v11.Message = CommandArg()):
|
||||
await md.send_md(
|
||||
v11.utils.unescape(str(arg)),
|
||||
bot,
|
||||
message_type=event.message_type,
|
||||
session_id=event.user_id if event.message_type == "private" else event.group_id
|
||||
)
|
||||
|
||||
|
||||
@btn_test.handle()
|
||||
async def _(bot: T_Bot, event: T_MessageEvent, arg: v11.Message = CommandArg()):
|
||||
await md.send_btn(
|
||||
str(arg),
|
||||
bot,
|
||||
message_type=event.message_type,
|
||||
session_id=event.user_id if event.message_type == "private" else event.group_id
|
||||
)
|
||||
|
||||
|
||||
@latex_test.handle()
|
||||
async def _(bot: T_Bot, event: T_MessageEvent, arg: v11.Message = CommandArg()):
|
||||
latex_text = f"$${v11.utils.unescape(str(arg))}$$"
|
||||
img = await md_to_pic(latex_text)
|
||||
await bot.send(event=event, message=MessageSegment.image(img))
|
||||
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪Markdown测试",
|
||||
description="用于测试Markdown的插件",
|
||||
usage="",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
}
|
||||
)
|
||||
from nonebot import on_command, require
|
||||
from nonebot.adapters.onebot.v11 import MessageSegment
|
||||
from nonebot.params import CommandArg
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent, v11
|
||||
from src.utils.message.message import MarkdownMessage as md, broadcast_to_superusers
|
||||
from src.utils.message.html_tool import *
|
||||
|
||||
md_test = on_command("mdts", permission=SUPERUSER)
|
||||
btn_test = on_command("btnts", permission=SUPERUSER)
|
||||
latex_test = on_command("latex", permission=SUPERUSER)
|
||||
|
||||
|
||||
@md_test.handle()
|
||||
async def _(bot: T_Bot, event: T_MessageEvent, arg: v11.Message = CommandArg()):
|
||||
await md.send_md(
|
||||
v11.utils.unescape(str(arg)),
|
||||
bot,
|
||||
message_type=event.message_type,
|
||||
session_id=event.user_id if event.message_type == "private" else event.group_id
|
||||
)
|
||||
|
||||
|
||||
@btn_test.handle()
|
||||
async def _(bot: T_Bot, event: T_MessageEvent, arg: v11.Message = CommandArg()):
|
||||
await md.send_btn(
|
||||
str(arg),
|
||||
bot,
|
||||
message_type=event.message_type,
|
||||
session_id=event.user_id if event.message_type == "private" else event.group_id
|
||||
)
|
||||
|
||||
|
||||
@latex_test.handle()
|
||||
async def _(bot: T_Bot, event: T_MessageEvent, arg: v11.Message = CommandArg()):
|
||||
latex_text = f"$${v11.utils.unescape(str(arg))}$$"
|
||||
img = await md_to_pic(latex_text)
|
||||
await bot.send(event=event, message=MessageSegment.image(img))
|
||||
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪Markdown测试",
|
||||
description="用于测试Markdown的插件",
|
||||
usage="",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
}
|
||||
)
|
@ -1,15 +1,15 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .minesweeper import *
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪小游戏",
|
||||
description="内置了一些小游戏",
|
||||
usage="",
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
"toggleable" : True,
|
||||
"default_enable" : True,
|
||||
}
|
||||
)
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .minesweeper import *
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪小游戏",
|
||||
description="内置了一些小游戏",
|
||||
usage="",
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
"toggleable" : True,
|
||||
"default_enable" : True,
|
||||
}
|
||||
)
|
@ -1,169 +1,168 @@
|
||||
import random
|
||||
from pydantic import BaseModel
|
||||
from src.utils.message.message import MarkdownMessage as md
|
||||
|
||||
class Dot(BaseModel):
|
||||
row: int
|
||||
col: int
|
||||
mask: bool = True
|
||||
value: int = 0
|
||||
flagged: bool = False
|
||||
|
||||
|
||||
class Minesweeper:
|
||||
# 0-8: number of mines around, 9: mine, -1: undefined
|
||||
NUMS = "⓪①②③④⑤⑥⑦⑧🅑⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳"
|
||||
MASK = "🅜"
|
||||
FLAG = "🅕"
|
||||
MINE = "🅑"
|
||||
|
||||
def __init__(self, rows, cols, num_mines, session_type, session_id):
|
||||
assert rows > 0 and cols > 0 and 0 < num_mines < rows * cols
|
||||
self.session_type = session_type
|
||||
self.session_id = session_id
|
||||
self.rows = rows
|
||||
self.cols = cols
|
||||
self.num_mines = num_mines
|
||||
self.board: list[list[Dot]] = [[Dot(row=i, col=j) for j in range(cols)] for i in range(rows)]
|
||||
self.is_first = True
|
||||
|
||||
def reveal(self, row, col) -> bool:
|
||||
"""
|
||||
展开
|
||||
Args:
|
||||
row:
|
||||
col:
|
||||
|
||||
Returns:
|
||||
游戏是否继续
|
||||
|
||||
"""
|
||||
|
||||
if self.is_first:
|
||||
# 第一次展开,生成地雷
|
||||
self.generate_board(self.board[row][col])
|
||||
self.is_first = False
|
||||
|
||||
if self.board[row][col].value == 9:
|
||||
self.board[row][col].mask = False
|
||||
return False
|
||||
|
||||
if not self.board[row][col].mask:
|
||||
return True
|
||||
|
||||
self.board[row][col].mask = False
|
||||
|
||||
if self.board[row][col].value == 0:
|
||||
self.reveal_neighbors(row, col)
|
||||
return True
|
||||
|
||||
def is_win(self) -> bool:
|
||||
"""
|
||||
是否胜利
|
||||
Returns:
|
||||
"""
|
||||
for row in range(self.rows):
|
||||
for col in range(self.cols):
|
||||
if self.board[row][col].mask and self.board[row][col].value != 9:
|
||||
return False
|
||||
return True
|
||||
|
||||
def generate_board(self, first_dot: Dot):
|
||||
"""
|
||||
避开第一个点,生成地雷
|
||||
Args:
|
||||
first_dot: 第一个点
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
generate_count = 0
|
||||
while generate_count < self.num_mines:
|
||||
row = random.randint(0, self.rows - 1)
|
||||
col = random.randint(0, self.cols - 1)
|
||||
if self.board[row][col].value == 9 or (row, col) == (first_dot.row, first_dot.col):
|
||||
continue
|
||||
self.board[row][col] = Dot(row=row, col=col, mask=True, value=9)
|
||||
generate_count += 1
|
||||
|
||||
for row in range(self.rows):
|
||||
for col in range(self.cols):
|
||||
if self.board[row][col].value != 9:
|
||||
self.board[row][col].value = self.count_adjacent_mines(row, col)
|
||||
|
||||
def count_adjacent_mines(self, row, col):
|
||||
"""
|
||||
计算周围地雷数量
|
||||
Args:
|
||||
row:
|
||||
col:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
count = 0
|
||||
for r in range(max(0, row - 1), min(self.rows, row + 2)):
|
||||
for c in range(max(0, col - 1), min(self.cols, col + 2)):
|
||||
if self.board[r][c].value == 9:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
def reveal_neighbors(self, row, col):
|
||||
"""
|
||||
递归展开,使用深度优先搜索
|
||||
Args:
|
||||
row:
|
||||
col:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
for r in range(max(0, row - 1), min(self.rows, row + 2)):
|
||||
for c in range(max(0, col - 1), min(self.cols, col + 2)):
|
||||
if self.board[r][c].mask:
|
||||
self.board[r][c].mask = False
|
||||
if self.board[r][c].value == 0:
|
||||
self.reveal_neighbors(r, c)
|
||||
|
||||
def mark(self, row, col) -> bool:
|
||||
"""
|
||||
标记
|
||||
Args:
|
||||
row:
|
||||
col:
|
||||
Returns:
|
||||
是否标记成功,如果已经展开则无法标记
|
||||
"""
|
||||
if self.board[row][col].mask:
|
||||
self.board[row][col].flagged = not self.board[row][col].flagged
|
||||
return self.board[row][col].flagged
|
||||
|
||||
def board_markdown(self) -> str:
|
||||
"""
|
||||
打印地雷板
|
||||
Returns:
|
||||
"""
|
||||
dis = " "
|
||||
start = "> " if self.cols >= 10 else ""
|
||||
text = start + self.NUMS[0] + dis*2
|
||||
# 横向两个雷之间的间隔字符
|
||||
# 生成横向索引
|
||||
for i in range(self.cols):
|
||||
text += f"{self.NUMS[i]}" + dis
|
||||
text += "\n\n"
|
||||
for i, row in enumerate(self.board):
|
||||
text += start + f"{self.NUMS[i]}" + dis*2
|
||||
print([d.value for d in row])
|
||||
for dot in row:
|
||||
if dot.mask and not dot.flagged:
|
||||
text += md.btn_cmd(self.MASK, f"minesweeper reveal {dot.row} {dot.col}")
|
||||
elif dot.flagged:
|
||||
text += md.btn_cmd(self.FLAG, f"minesweeper mark {dot.row} {dot.col}")
|
||||
else:
|
||||
text += self.NUMS[dot.value]
|
||||
text += dis
|
||||
text += "\n"
|
||||
btn_mark = md.btn_cmd("标记", f"minesweeper mark ", enter=False)
|
||||
btn_end = md.btn_cmd("结束", "minesweeper end", enter=True)
|
||||
text += f" {btn_mark} {btn_end}"
|
||||
return text
|
||||
import random
|
||||
from pydantic import BaseModel
|
||||
from src.utils.message.message import MarkdownMessage as md
|
||||
|
||||
class Dot(BaseModel):
|
||||
row: int
|
||||
col: int
|
||||
mask: bool = True
|
||||
value: int = 0
|
||||
flagged: bool = False
|
||||
|
||||
|
||||
class Minesweeper:
|
||||
# 0-8: number of mines around, 9: mine, -1: undefined
|
||||
NUMS = "⓪①②③④⑤⑥⑦⑧🅑⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳"
|
||||
MASK = "🅜"
|
||||
FLAG = "🅕"
|
||||
MINE = "🅑"
|
||||
|
||||
def __init__(self, rows, cols, num_mines, session_type, session_id):
|
||||
assert rows > 0 and cols > 0 and 0 < num_mines < rows * cols
|
||||
self.session_type = session_type
|
||||
self.session_id = session_id
|
||||
self.rows = rows
|
||||
self.cols = cols
|
||||
self.num_mines = num_mines
|
||||
self.board: list[list[Dot]] = [[Dot(row=i, col=j) for j in range(cols)] for i in range(rows)]
|
||||
self.is_first = True
|
||||
|
||||
def reveal(self, row, col) -> bool:
|
||||
"""
|
||||
展开
|
||||
Args:
|
||||
row:
|
||||
col:
|
||||
|
||||
Returns:
|
||||
游戏是否继续
|
||||
|
||||
"""
|
||||
|
||||
if self.is_first:
|
||||
# 第一次展开,生成地雷
|
||||
self.generate_board(self.board[row][col])
|
||||
self.is_first = False
|
||||
|
||||
if self.board[row][col].value == 9:
|
||||
self.board[row][col].mask = False
|
||||
return False
|
||||
|
||||
if not self.board[row][col].mask:
|
||||
return True
|
||||
|
||||
self.board[row][col].mask = False
|
||||
|
||||
if self.board[row][col].value == 0:
|
||||
self.reveal_neighbors(row, col)
|
||||
return True
|
||||
|
||||
def is_win(self) -> bool:
|
||||
"""
|
||||
是否胜利
|
||||
Returns:
|
||||
"""
|
||||
for row in range(self.rows):
|
||||
for col in range(self.cols):
|
||||
if self.board[row][col].mask and self.board[row][col].value != 9:
|
||||
return False
|
||||
return True
|
||||
|
||||
def generate_board(self, first_dot: Dot):
|
||||
"""
|
||||
避开第一个点,生成地雷
|
||||
Args:
|
||||
first_dot: 第一个点
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
generate_count = 0
|
||||
while generate_count < self.num_mines:
|
||||
row = random.randint(0, self.rows - 1)
|
||||
col = random.randint(0, self.cols - 1)
|
||||
if self.board[row][col].value == 9 or (row, col) == (first_dot.row, first_dot.col):
|
||||
continue
|
||||
self.board[row][col] = Dot(row=row, col=col, mask=True, value=9)
|
||||
generate_count += 1
|
||||
|
||||
for row in range(self.rows):
|
||||
for col in range(self.cols):
|
||||
if self.board[row][col].value != 9:
|
||||
self.board[row][col].value = self.count_adjacent_mines(row, col)
|
||||
|
||||
def count_adjacent_mines(self, row, col):
|
||||
"""
|
||||
计算周围地雷数量
|
||||
Args:
|
||||
row:
|
||||
col:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
count = 0
|
||||
for r in range(max(0, row - 1), min(self.rows, row + 2)):
|
||||
for c in range(max(0, col - 1), min(self.cols, col + 2)):
|
||||
if self.board[r][c].value == 9:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
def reveal_neighbors(self, row, col):
|
||||
"""
|
||||
递归展开,使用深度优先搜索
|
||||
Args:
|
||||
row:
|
||||
col:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
for r in range(max(0, row - 1), min(self.rows, row + 2)):
|
||||
for c in range(max(0, col - 1), min(self.cols, col + 2)):
|
||||
if self.board[r][c].mask:
|
||||
self.board[r][c].mask = False
|
||||
if self.board[r][c].value == 0:
|
||||
self.reveal_neighbors(r, c)
|
||||
|
||||
def mark(self, row, col) -> bool:
|
||||
"""
|
||||
标记
|
||||
Args:
|
||||
row:
|
||||
col:
|
||||
Returns:
|
||||
是否标记成功,如果已经展开则无法标记
|
||||
"""
|
||||
if self.board[row][col].mask:
|
||||
self.board[row][col].flagged = not self.board[row][col].flagged
|
||||
return self.board[row][col].flagged
|
||||
|
||||
def board_markdown(self) -> str:
|
||||
"""
|
||||
打印地雷板
|
||||
Returns:
|
||||
"""
|
||||
dis = " "
|
||||
start = "> " if self.cols >= 10 else ""
|
||||
text = start + self.NUMS[0] + dis*2
|
||||
# 横向两个雷之间的间隔字符
|
||||
# 生成横向索引
|
||||
for i in range(self.cols):
|
||||
text += f"{self.NUMS[i]}" + dis
|
||||
text += "\n\n"
|
||||
for i, row in enumerate(self.board):
|
||||
text += start + f"{self.NUMS[i]}" + dis*2
|
||||
for dot in row:
|
||||
if dot.mask and not dot.flagged:
|
||||
text += md.btn_cmd(self.MASK, f"minesweeper reveal {dot.row} {dot.col}")
|
||||
elif dot.flagged:
|
||||
text += md.btn_cmd(self.FLAG, f"minesweeper mark {dot.row} {dot.col}")
|
||||
else:
|
||||
text += self.NUMS[dot.value]
|
||||
text += dis
|
||||
text += "\n"
|
||||
btn_mark = md.btn_cmd("标记", f"minesweeper mark ", enter=False)
|
||||
btn_end = md.btn_cmd("结束", "minesweeper end", enter=True)
|
||||
text += f" {btn_mark} {btn_end}"
|
||||
return text
|
@ -1,103 +1,103 @@
|
||||
from nonebot import require
|
||||
|
||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
from src.utils.message.message import MarkdownMessage as md
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
from .game import Minesweeper
|
||||
|
||||
from nonebot_plugin_alconna import Alconna, on_alconna, Subcommand, Args, Arparma
|
||||
|
||||
minesweeper = on_alconna(
|
||||
aliases={"扫雷"},
|
||||
command=Alconna(
|
||||
"minesweeper",
|
||||
Subcommand(
|
||||
"start",
|
||||
Args["row", int, 8]["col", int, 8]["mines", int, 10],
|
||||
alias=["开始"],
|
||||
|
||||
),
|
||||
Subcommand(
|
||||
"end",
|
||||
alias=["结束"]
|
||||
),
|
||||
Subcommand(
|
||||
"reveal",
|
||||
Args["row", int]["col", int],
|
||||
alias=["展开"]
|
||||
|
||||
),
|
||||
Subcommand(
|
||||
"mark",
|
||||
Args["row", int]["col", int],
|
||||
alias=["标记"]
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
minesweeper_cache: list[Minesweeper] = []
|
||||
|
||||
|
||||
def get_minesweeper_cache(event: T_MessageEvent) -> Minesweeper | None:
|
||||
for i in minesweeper_cache:
|
||||
if i.session_type == event.message_type:
|
||||
if i.session_id == event.user_id or i.session_id == event.group_id:
|
||||
return i
|
||||
return None
|
||||
|
||||
|
||||
@minesweeper.handle()
|
||||
async def _(event: T_MessageEvent, result: Arparma, bot: T_Bot):
|
||||
game = get_minesweeper_cache(event)
|
||||
if result.subcommands.get("start"):
|
||||
if game:
|
||||
await minesweeper.finish("当前会话不能同时进行多个扫雷游戏")
|
||||
else:
|
||||
try:
|
||||
new_game = Minesweeper(
|
||||
rows=result.subcommands["start"].args["row"],
|
||||
cols=result.subcommands["start"].args["col"],
|
||||
num_mines=result.subcommands["start"].args["mines"],
|
||||
session_type=event.message_type,
|
||||
session_id=event.user_id if event.message_type == "private" else event.group_id,
|
||||
)
|
||||
minesweeper_cache.append(new_game)
|
||||
await minesweeper.send("游戏开始")
|
||||
await md.send_md(new_game.board_markdown(), bot, event=event)
|
||||
except AssertionError:
|
||||
await minesweeper.finish("参数错误")
|
||||
elif result.subcommands.get("end"):
|
||||
if game:
|
||||
minesweeper_cache.remove(game)
|
||||
await minesweeper.finish("游戏结束")
|
||||
else:
|
||||
await minesweeper.finish("当前没有扫雷游戏")
|
||||
elif result.subcommands.get("reveal"):
|
||||
if not game:
|
||||
await minesweeper.finish("当前没有扫雷游戏")
|
||||
else:
|
||||
row = result.subcommands["reveal"].args["row"]
|
||||
col = result.subcommands["reveal"].args["col"]
|
||||
if not (0 <= row < game.rows and 0 <= col < game.cols):
|
||||
await minesweeper.finish("参数错误")
|
||||
if not game.reveal(row, col):
|
||||
minesweeper_cache.remove(game)
|
||||
await md.send_md(game.board_markdown(), bot, event=event)
|
||||
await minesweeper.finish("游戏结束")
|
||||
await md.send_md(game.board_markdown(), bot, event=event)
|
||||
if game.is_win():
|
||||
minesweeper_cache.remove(game)
|
||||
await minesweeper.finish("游戏胜利")
|
||||
elif result.subcommands.get("mark"):
|
||||
if not game:
|
||||
await minesweeper.finish("当前没有扫雷游戏")
|
||||
else:
|
||||
row = result.subcommands["mark"].args["row"]
|
||||
col = result.subcommands["mark"].args["col"]
|
||||
if not (0 <= row < game.rows and 0 <= col < game.cols):
|
||||
await minesweeper.finish("参数错误")
|
||||
game.board[row][col].flagged = not game.board[row][col].flagged
|
||||
await md.send_md(game.board_markdown(), bot, event=event)
|
||||
else:
|
||||
await minesweeper.finish("参数错误")
|
||||
from nonebot import require
|
||||
|
||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
from src.utils.message.message import MarkdownMessage as md
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
from .game import Minesweeper
|
||||
|
||||
from nonebot_plugin_alconna import Alconna, on_alconna, Subcommand, Args, Arparma
|
||||
|
||||
minesweeper = on_alconna(
|
||||
aliases={"扫雷"},
|
||||
command=Alconna(
|
||||
"minesweeper",
|
||||
Subcommand(
|
||||
"start",
|
||||
Args["row", int, 8]["col", int, 8]["mines", int, 10],
|
||||
alias=["开始"],
|
||||
|
||||
),
|
||||
Subcommand(
|
||||
"end",
|
||||
alias=["结束"]
|
||||
),
|
||||
Subcommand(
|
||||
"reveal",
|
||||
Args["row", int]["col", int],
|
||||
alias=["展开"]
|
||||
|
||||
),
|
||||
Subcommand(
|
||||
"mark",
|
||||
Args["row", int]["col", int],
|
||||
alias=["标记"]
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
minesweeper_cache: list[Minesweeper] = []
|
||||
|
||||
|
||||
def get_minesweeper_cache(event: T_MessageEvent) -> Minesweeper | None:
|
||||
for i in minesweeper_cache:
|
||||
if i.session_type == event.message_type:
|
||||
if i.session_id == event.user_id or i.session_id == event.group_id:
|
||||
return i
|
||||
return None
|
||||
|
||||
|
||||
@minesweeper.handle()
|
||||
async def _(event: T_MessageEvent, result: Arparma, bot: T_Bot):
|
||||
game = get_minesweeper_cache(event)
|
||||
if result.subcommands.get("start"):
|
||||
if game:
|
||||
await minesweeper.finish("当前会话不能同时进行多个扫雷游戏")
|
||||
else:
|
||||
try:
|
||||
new_game = Minesweeper(
|
||||
rows=result.subcommands["start"].args["row"],
|
||||
cols=result.subcommands["start"].args["col"],
|
||||
num_mines=result.subcommands["start"].args["mines"],
|
||||
session_type=event.message_type,
|
||||
session_id=event.user_id if event.message_type == "private" else event.group_id,
|
||||
)
|
||||
minesweeper_cache.append(new_game)
|
||||
await minesweeper.send("游戏开始")
|
||||
await md.send_md(new_game.board_markdown(), bot, event=event)
|
||||
except AssertionError:
|
||||
await minesweeper.finish("参数错误")
|
||||
elif result.subcommands.get("end"):
|
||||
if game:
|
||||
minesweeper_cache.remove(game)
|
||||
await minesweeper.finish("游戏结束")
|
||||
else:
|
||||
await minesweeper.finish("当前没有扫雷游戏")
|
||||
elif result.subcommands.get("reveal"):
|
||||
if not game:
|
||||
await minesweeper.finish("当前没有扫雷游戏")
|
||||
else:
|
||||
row = result.subcommands["reveal"].args["row"]
|
||||
col = result.subcommands["reveal"].args["col"]
|
||||
if not (0 <= row < game.rows and 0 <= col < game.cols):
|
||||
await minesweeper.finish("参数错误")
|
||||
if not game.reveal(row, col):
|
||||
minesweeper_cache.remove(game)
|
||||
await md.send_md(game.board_markdown(), bot, event=event)
|
||||
await minesweeper.finish("游戏结束")
|
||||
await md.send_md(game.board_markdown(), bot, event=event)
|
||||
if game.is_win():
|
||||
minesweeper_cache.remove(game)
|
||||
await minesweeper.finish("游戏胜利")
|
||||
elif result.subcommands.get("mark"):
|
||||
if not game:
|
||||
await minesweeper.finish("当前没有扫雷游戏")
|
||||
else:
|
||||
row = result.subcommands["mark"].args["row"]
|
||||
col = result.subcommands["mark"].args["col"]
|
||||
if not (0 <= row < game.rows and 0 <= col < game.cols):
|
||||
await minesweeper.finish("参数错误")
|
||||
game.board[row][col].flagged = not game.board[row][col].flagged
|
||||
await md.send_md(game.board_markdown(), bot, event=event)
|
||||
else:
|
||||
await minesweeper.finish("参数错误")
|
@ -1,22 +1,22 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .npm import *
|
||||
from .rpm import *
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪包管理器",
|
||||
description="本地插件管理和插件商店支持,资源包管理,支持启用/停用,安装/卸载插件",
|
||||
usage=(
|
||||
"npm list\n"
|
||||
"npm enable/disable <plugin_name>\n"
|
||||
"npm search <keywords...>\n"
|
||||
"npm install/uninstall <plugin_name>\n"
|
||||
),
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
"toggleable" : False,
|
||||
"default_enable" : True,
|
||||
}
|
||||
)
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .npm import *
|
||||
from .rpm import *
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪包管理器",
|
||||
description="本地插件管理和插件商店支持,资源包管理,支持启用/停用,安装/卸载插件",
|
||||
usage=(
|
||||
"npm list\n"
|
||||
"npm enable/disable <plugin_name>\n"
|
||||
"npm search <keywords...>\n"
|
||||
"npm install/uninstall <plugin_name>\n"
|
||||
),
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
"toggleable" : False,
|
||||
"default_enable" : True,
|
||||
}
|
||||
)
|
@ -1,256 +1,294 @@
|
||||
import json
|
||||
from typing import Optional
|
||||
|
||||
import aiofiles
|
||||
import nonebot.plugin
|
||||
from nonebot.adapters import satori
|
||||
|
||||
from src.utils import event as event_utils
|
||||
from src.utils.base.data import LiteModel
|
||||
from src.utils.base.data_manager import GlobalPlugin, Group, User, group_db, plugin_db, user_db
|
||||
from src.utils.base.ly_typing import T_MessageEvent
|
||||
|
||||
__group_data = {} # 群数据缓存, {group_id: Group}
|
||||
__user_data = {} # 用户数据缓存, {user_id: User}
|
||||
__default_enable = {} # 插件默认启用状态缓存, {plugin_name: bool} static
|
||||
__global_enable = {} # 插件全局启用状态缓存, {plugin_name: bool} dynamic
|
||||
|
||||
|
||||
class PluginTag(LiteModel):
|
||||
label: str
|
||||
color: str = '#000000'
|
||||
|
||||
|
||||
class StorePlugin(LiteModel):
|
||||
name: str
|
||||
desc: str
|
||||
module_name: str # 插件商店中的模块名不等于本地的模块名,前者是文件夹名,后者是点分割模块名
|
||||
project_link: str = ""
|
||||
homepage: str = ""
|
||||
author: str = ""
|
||||
type: str | None = None
|
||||
version: str | None = ""
|
||||
time: str = ""
|
||||
tags: list[PluginTag] = []
|
||||
is_official: bool = False
|
||||
|
||||
|
||||
def get_plugin_exist(plugin_name: str) -> bool:
|
||||
"""
|
||||
获取插件是否存在于加载列表
|
||||
Args:
|
||||
plugin_name:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
for plugin in nonebot.plugin.get_loaded_plugins():
|
||||
if plugin.name == plugin_name:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
async def get_store_plugin(plugin_name: str) -> Optional[StorePlugin]:
|
||||
"""
|
||||
获取插件信息
|
||||
|
||||
Args:
|
||||
plugin_name (str): 插件模块名
|
||||
|
||||
Returns:
|
||||
Optional[StorePlugin]: 插件信息
|
||||
"""
|
||||
async with aiofiles.open("data/liteyuki/plugins.json", "r", encoding="utf-8") as f:
|
||||
plugins: list[StorePlugin] = [StorePlugin(**pobj) for pobj in json.loads(await f.read())]
|
||||
for plugin in plugins:
|
||||
if plugin.module_name == plugin_name:
|
||||
return plugin
|
||||
return None
|
||||
|
||||
|
||||
def get_plugin_default_enable(plugin_name: str) -> bool:
|
||||
"""
|
||||
获取插件默认启用状态,由插件定义,不存在则默认为启用,优先从缓存中获取
|
||||
|
||||
Args:
|
||||
plugin_name (str): 插件模块名
|
||||
|
||||
Returns:
|
||||
bool: 插件默认状态
|
||||
"""
|
||||
if plugin_name not in __default_enable:
|
||||
plug = nonebot.plugin.get_plugin(plugin_name)
|
||||
default_enable = (plug.metadata.extra.get("default_enable", True) if plug.metadata else True) if plug else True
|
||||
__default_enable[plugin_name] = default_enable
|
||||
|
||||
return __default_enable[plugin_name]
|
||||
|
||||
|
||||
def get_plugin_session_enable(event: T_MessageEvent, plugin_name: str) -> bool:
|
||||
"""
|
||||
获取插件当前会话启用状态
|
||||
|
||||
Args:
|
||||
event: 会话事件
|
||||
plugin_name (str): 插件模块名
|
||||
|
||||
Returns:
|
||||
bool: 插件当前状态
|
||||
"""
|
||||
if isinstance(event, satori.event.Event):
|
||||
if event.guild is not None:
|
||||
message_type = "group"
|
||||
else:
|
||||
message_type = "private"
|
||||
else:
|
||||
message_type = event.message_type
|
||||
if message_type == "group":
|
||||
group_id = str(event.guild.id if isinstance(event, satori.event.Event) else event.group_id)
|
||||
if group_id not in __group_data:
|
||||
group: Group = group_db.where_one(Group(), "group_id = ?", group_id, default=Group(group_id=group_id))
|
||||
__group_data[str(group_id)] = group
|
||||
|
||||
session = __group_data[group_id]
|
||||
else:
|
||||
# session: User = user_db.first(User(), "user_id = ?", event.user_id, default=User(user_id=str(event.user_id)))
|
||||
user_id = str(event.user.id if isinstance(event, satori.event.Event) else event.user_id)
|
||||
if user_id not in __user_data:
|
||||
user: User = user_db.where_one(User(), "user_id = ?", user_id, default=User(user_id=user_id))
|
||||
__user_data[user_id] = user
|
||||
session = __user_data[user_id]
|
||||
# 默认停用插件在启用列表内表示启用
|
||||
# 默认停用插件不在启用列表内表示停用
|
||||
# 默认启用插件在停用列表内表示停用
|
||||
# 默认启用插件不在停用列表内表示启用
|
||||
default_enable = get_plugin_default_enable(plugin_name)
|
||||
if default_enable:
|
||||
return plugin_name not in session.disabled_plugins
|
||||
else:
|
||||
return plugin_name in session.enabled_plugins
|
||||
|
||||
|
||||
def set_plugin_session_enable(event: T_MessageEvent, plugin_name: str, enable: bool):
|
||||
"""
|
||||
设置插件会话启用状态,同时更新数据库和缓存
|
||||
Args:
|
||||
event:
|
||||
plugin_name:
|
||||
enable:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if event_utils.get_message_type(event) == "group":
|
||||
session: Group = group_db.where_one(Group(), "group_id = ?", str(event_utils.get_group_id(event)),
|
||||
default=Group(group_id=str(event_utils.get_group_id(event))))
|
||||
else:
|
||||
session: User = user_db.where_one(User(), "user_id = ?", str(event_utils.get_user_id(event)),
|
||||
default=User(user_id=str(event_utils.get_user_id(event))))
|
||||
print(session)
|
||||
default_enable = get_plugin_default_enable(plugin_name)
|
||||
if default_enable:
|
||||
if enable:
|
||||
session.disabled_plugins.remove(plugin_name)
|
||||
else:
|
||||
session.disabled_plugins.append(plugin_name)
|
||||
else:
|
||||
if enable:
|
||||
session.enabled_plugins.append(plugin_name)
|
||||
else:
|
||||
session.enabled_plugins.remove(plugin_name)
|
||||
|
||||
if event_utils.get_message_type(event) == "group":
|
||||
__group_data[str(event_utils.get_group_id(event))] = session
|
||||
group_db.save(session)
|
||||
else:
|
||||
__user_data[str(event_utils.get_user_id(event))] = session
|
||||
user_db.save(session)
|
||||
|
||||
|
||||
def get_plugin_global_enable(plugin_name: str) -> bool:
|
||||
"""
|
||||
获取插件全局启用状态, 优先从缓存中获取
|
||||
Args:
|
||||
plugin_name:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if plugin_name not in __global_enable:
|
||||
plugin = plugin_db.where_one(
|
||||
GlobalPlugin(),
|
||||
"module_name = ?",
|
||||
plugin_name,
|
||||
default=GlobalPlugin(module_name=plugin_name, enabled=True))
|
||||
__global_enable[plugin_name] = plugin.enabled
|
||||
|
||||
return __global_enable[plugin_name]
|
||||
|
||||
|
||||
def set_plugin_global_enable(plugin_name: str, enable: bool):
|
||||
"""
|
||||
设置插件全局启用状态,同时更新数据库和缓存
|
||||
Args:
|
||||
plugin_name:
|
||||
enable:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
plugin = plugin_db.where_one(
|
||||
GlobalPlugin(),
|
||||
"module_name = ?",
|
||||
plugin_name,
|
||||
default=GlobalPlugin(module_name=plugin_name, enabled=True))
|
||||
plugin.enabled = enable
|
||||
|
||||
plugin_db.save(plugin)
|
||||
__global_enable[plugin_name] = enable
|
||||
|
||||
|
||||
def get_plugin_can_be_toggle(plugin_name: str) -> bool:
|
||||
"""
|
||||
获取插件是否可以被启用/停用
|
||||
|
||||
Args:
|
||||
plugin_name (str): 插件模块名
|
||||
|
||||
Returns:
|
||||
bool: 插件是否可以被启用/停用
|
||||
"""
|
||||
plug = nonebot.plugin.get_plugin(plugin_name)
|
||||
return plug.metadata.extra.get("toggleable", True) if plug and plug.metadata else True
|
||||
|
||||
|
||||
def get_group_enable(group_id: str) -> bool:
|
||||
"""
|
||||
获取群组是否启用插机器人
|
||||
|
||||
Args:
|
||||
group_id (str): 群组ID
|
||||
|
||||
Returns:
|
||||
bool: 群组是否启用插件
|
||||
"""
|
||||
group_id = str(group_id)
|
||||
if group_id not in __group_data:
|
||||
group: Group = group_db.where_one(Group(), "group_id = ?", group_id, default=Group(group_id=group_id))
|
||||
__group_data[group_id] = group
|
||||
|
||||
return __group_data[group_id].enable
|
||||
|
||||
|
||||
def set_group_enable(group_id: str, enable: bool):
|
||||
"""
|
||||
设置群组是否启用插机器人
|
||||
|
||||
Args:
|
||||
group_id (str): 群组ID
|
||||
enable (bool): 是否启用
|
||||
"""
|
||||
group_id = str(group_id)
|
||||
group: Group = group_db.where_one(Group(), "group_id = ?", group_id, default=Group(group_id=group_id))
|
||||
group.enable = enable
|
||||
|
||||
__group_data[group_id] = group
|
||||
group_db.save(group)
|
||||
import json
|
||||
from typing import Optional
|
||||
|
||||
import aiofiles
|
||||
import nonebot.plugin
|
||||
from nonebot.adapters import satori
|
||||
|
||||
from src.utils import event as event_utils
|
||||
from src.utils.base.data import LiteModel
|
||||
from src.utils.base.data_manager import (
|
||||
GlobalPlugin,
|
||||
Group,
|
||||
User,
|
||||
group_db,
|
||||
plugin_db,
|
||||
user_db,
|
||||
)
|
||||
from src.utils.base.ly_typing import T_MessageEvent
|
||||
|
||||
__group_data = {} # 群数据缓存, {group_id: Group}
|
||||
__user_data = {} # 用户数据缓存, {user_id: User}
|
||||
__default_enable = {} # 插件默认启用状态缓存, {plugin_name: bool} static
|
||||
__global_enable = {} # 插件全局启用状态缓存, {plugin_name: bool} dynamic
|
||||
|
||||
|
||||
class PluginTag(LiteModel):
|
||||
label: str
|
||||
color: str = "#000000"
|
||||
|
||||
|
||||
class StorePlugin(LiteModel):
|
||||
name: str
|
||||
desc: str
|
||||
module_name: (
|
||||
str # 插件商店中的模块名不等于本地的模块名,前者是文件夹名,后者是点分割模块名
|
||||
)
|
||||
project_link: str = ""
|
||||
homepage: str = ""
|
||||
author: str = ""
|
||||
type: str | None = None
|
||||
version: str | None = ""
|
||||
time: str = ""
|
||||
tags: list[PluginTag] = []
|
||||
is_official: bool = False
|
||||
|
||||
|
||||
def get_plugin_exist(plugin_name: str) -> bool:
|
||||
"""
|
||||
获取插件是否存在于加载列表
|
||||
Args:
|
||||
plugin_name:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
for plugin in nonebot.plugin.get_loaded_plugins():
|
||||
if plugin.name == plugin_name:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
async def get_store_plugin(plugin_name: str) -> Optional[StorePlugin]:
|
||||
"""
|
||||
获取插件信息
|
||||
|
||||
Args:
|
||||
plugin_name (str): 插件模块名
|
||||
|
||||
Returns:
|
||||
Optional[StorePlugin]: 插件信息
|
||||
"""
|
||||
async with aiofiles.open("data/liteyuki/plugins.json", "r", encoding="utf-8") as f:
|
||||
plugins: list[StorePlugin] = [
|
||||
StorePlugin(**pobj) for pobj in json.loads(await f.read())
|
||||
]
|
||||
for plugin in plugins:
|
||||
if plugin.module_name == plugin_name:
|
||||
return plugin
|
||||
return None
|
||||
|
||||
|
||||
def get_plugin_default_enable(plugin_name: str) -> bool:
|
||||
"""
|
||||
获取插件默认启用状态,由插件定义,不存在则默认为启用,优先从缓存中获取
|
||||
|
||||
Args:
|
||||
plugin_name (str): 插件模块名
|
||||
|
||||
Returns:
|
||||
bool: 插件默认状态
|
||||
"""
|
||||
if plugin_name not in __default_enable:
|
||||
plug = nonebot.plugin.get_plugin(plugin_name)
|
||||
default_enable = (
|
||||
(plug.metadata.extra.get("default_enable", True) if plug.metadata else True)
|
||||
if plug
|
||||
else True
|
||||
)
|
||||
__default_enable[plugin_name] = default_enable
|
||||
|
||||
return __default_enable[plugin_name]
|
||||
|
||||
|
||||
def get_plugin_session_enable(event: T_MessageEvent, plugin_name: str) -> bool:
|
||||
"""
|
||||
获取插件当前会话启用状态
|
||||
|
||||
Args:
|
||||
event: 会话事件
|
||||
plugin_name (str): 插件模块名
|
||||
|
||||
Returns:
|
||||
bool: 插件当前状态
|
||||
"""
|
||||
if isinstance(event, satori.event.Event):
|
||||
if event.guild is not None:
|
||||
message_type = "group"
|
||||
else:
|
||||
message_type = "private"
|
||||
else:
|
||||
message_type = event.message_type
|
||||
if message_type == "group":
|
||||
group_id = str(
|
||||
event.guild.id if isinstance(event, satori.event.Event) else event.group_id
|
||||
)
|
||||
if group_id not in __group_data:
|
||||
group: Group = group_db.where_one(
|
||||
Group(), "group_id = ?", group_id, default=Group(group_id=group_id)
|
||||
)
|
||||
__group_data[str(group_id)] = group
|
||||
|
||||
session = __group_data[group_id]
|
||||
else:
|
||||
# session: User = user_db.first(User(), "user_id = ?", event.user_id, default=User(user_id=str(event.user_id)))
|
||||
user_id = str(
|
||||
event.user.id if isinstance(event, satori.event.Event) else event.user_id
|
||||
)
|
||||
if user_id not in __user_data:
|
||||
user: User = user_db.where_one(
|
||||
User(), "user_id = ?", user_id, default=User(user_id=user_id)
|
||||
)
|
||||
__user_data[user_id] = user
|
||||
session = __user_data[user_id]
|
||||
# 默认停用插件在启用列表内表示启用
|
||||
# 默认停用插件不在启用列表内表示停用
|
||||
# 默认启用插件在停用列表内表示停用
|
||||
# 默认启用插件不在停用列表内表示启用
|
||||
default_enable = get_plugin_default_enable(plugin_name)
|
||||
if default_enable:
|
||||
return plugin_name not in session.disabled_plugins
|
||||
else:
|
||||
return plugin_name in session.enabled_plugins
|
||||
|
||||
|
||||
def set_plugin_session_enable(event: T_MessageEvent, plugin_name: str, enable: bool):
|
||||
"""
|
||||
设置插件会话启用状态,同时更新数据库和缓存
|
||||
Args:
|
||||
event:
|
||||
plugin_name:
|
||||
enable:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if event_utils.get_message_type(event) == "group":
|
||||
session: Group = group_db.where_one(
|
||||
Group(),
|
||||
"group_id = ?",
|
||||
str(event_utils.get_group_id(event)),
|
||||
default=Group(group_id=str(event_utils.get_group_id(event))),
|
||||
)
|
||||
else:
|
||||
session: User = user_db.where_one(
|
||||
User(),
|
||||
"user_id = ?",
|
||||
str(event_utils.get_user_id(event)),
|
||||
default=User(user_id=str(event_utils.get_user_id(event))),
|
||||
)
|
||||
default_enable = get_plugin_default_enable(plugin_name)
|
||||
if default_enable:
|
||||
if enable:
|
||||
session.disabled_plugins.remove(plugin_name)
|
||||
else:
|
||||
session.disabled_plugins.append(plugin_name)
|
||||
else:
|
||||
if enable:
|
||||
session.enabled_plugins.append(plugin_name)
|
||||
else:
|
||||
session.enabled_plugins.remove(plugin_name)
|
||||
|
||||
if event_utils.get_message_type(event) == "group":
|
||||
__group_data[str(event_utils.get_group_id(event))] = session
|
||||
group_db.save(session)
|
||||
else:
|
||||
__user_data[str(event_utils.get_user_id(event))] = session
|
||||
user_db.save(session)
|
||||
|
||||
|
||||
def get_plugin_global_enable(plugin_name: str) -> bool:
|
||||
"""
|
||||
获取插件全局启用状态, 优先从缓存中获取
|
||||
Args:
|
||||
plugin_name:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if plugin_name not in __global_enable:
|
||||
plugin = plugin_db.where_one(
|
||||
GlobalPlugin(),
|
||||
"module_name = ?",
|
||||
plugin_name,
|
||||
default=GlobalPlugin(module_name=plugin_name, enabled=True),
|
||||
)
|
||||
__global_enable[plugin_name] = plugin.enabled
|
||||
|
||||
return __global_enable[plugin_name]
|
||||
|
||||
|
||||
def set_plugin_global_enable(plugin_name: str, enable: bool):
|
||||
"""
|
||||
设置插件全局启用状态,同时更新数据库和缓存
|
||||
Args:
|
||||
plugin_name:
|
||||
enable:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
plugin = plugin_db.where_one(
|
||||
GlobalPlugin(),
|
||||
"module_name = ?",
|
||||
plugin_name,
|
||||
default=GlobalPlugin(module_name=plugin_name, enabled=True),
|
||||
)
|
||||
plugin.enabled = enable
|
||||
|
||||
plugin_db.save(plugin)
|
||||
__global_enable[plugin_name] = enable
|
||||
|
||||
|
||||
def get_plugin_can_be_toggle(plugin_name: str) -> bool:
|
||||
"""
|
||||
获取插件是否可以被启用/停用
|
||||
|
||||
Args:
|
||||
plugin_name (str): 插件模块名
|
||||
|
||||
Returns:
|
||||
bool: 插件是否可以被启用/停用
|
||||
"""
|
||||
plug = nonebot.plugin.get_plugin(plugin_name)
|
||||
return (
|
||||
plug.metadata.extra.get("toggleable", True) if plug and plug.metadata else True
|
||||
)
|
||||
|
||||
|
||||
def get_group_enable(group_id: str) -> bool:
|
||||
"""
|
||||
获取群组是否启用插机器人
|
||||
|
||||
Args:
|
||||
group_id (str): 群组ID
|
||||
|
||||
Returns:
|
||||
bool: 群组是否启用插件
|
||||
"""
|
||||
group_id = str(group_id)
|
||||
if group_id not in __group_data:
|
||||
group: Group = group_db.where_one(
|
||||
Group(), "group_id = ?", group_id, default=Group(group_id=group_id)
|
||||
)
|
||||
__group_data[group_id] = group
|
||||
|
||||
return __group_data[group_id].enable
|
||||
|
||||
|
||||
def set_group_enable(group_id: str, enable: bool):
|
||||
"""
|
||||
设置群组是否启用插机器人
|
||||
|
||||
Args:
|
||||
group_id (str): 群组ID
|
||||
enable (bool): 是否启用
|
||||
"""
|
||||
group_id = str(group_id)
|
||||
group: Group = group_db.where_one(
|
||||
Group(), "group_id = ?", group_id, default=Group(group_id=group_id)
|
||||
)
|
||||
group.enable = enable
|
||||
|
||||
__group_data[group_id] = group
|
||||
group_db.save(group)
|
File diff suppressed because it is too large
Load Diff
@ -1,189 +1,206 @@
|
||||
# 轻雪资源包管理器
|
||||
import os
|
||||
import zipfile
|
||||
import yaml
|
||||
from nonebot import require
|
||||
from nonebot.internal.matcher import Matcher
|
||||
from nonebot.permission import SUPERUSER
|
||||
|
||||
from src.utils.base.language import get_user_lang
|
||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
from src.utils.message.message import MarkdownMessage as md
|
||||
from src.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")
|
||||
from nonebot_plugin_alconna import Alconna, Args, on_alconna, Arparma, Subcommand
|
||||
|
||||
|
||||
@on_alconna(
|
||||
aliases={"资源包"},
|
||||
command=Alconna(
|
||||
"rpm",
|
||||
Subcommand(
|
||||
"list",
|
||||
Args["page", int, 1]["num", int, 10],
|
||||
alias=["ls", "列表", "列出"],
|
||||
),
|
||||
Subcommand(
|
||||
"load",
|
||||
Args["name", str],
|
||||
alias=["安装"],
|
||||
),
|
||||
Subcommand(
|
||||
"unload",
|
||||
Args["name", str],
|
||||
alias=["卸载"],
|
||||
),
|
||||
Subcommand(
|
||||
"up",
|
||||
Args["name", str],
|
||||
alias=["上移"],
|
||||
),
|
||||
Subcommand(
|
||||
"down",
|
||||
Args["name", str],
|
||||
alias=["下移"],
|
||||
),
|
||||
Subcommand(
|
||||
"top",
|
||||
Args["name", str],
|
||||
alias=["置顶"],
|
||||
),
|
||||
Subcommand(
|
||||
"reload",
|
||||
alias=["重载"],
|
||||
),
|
||||
),
|
||||
permission=SUPERUSER
|
||||
).handle()
|
||||
async def _(bot: T_Bot, event: T_MessageEvent, result: Arparma, matcher: Matcher):
|
||||
ulang = get_user_lang(str(event.user_id))
|
||||
reply = ""
|
||||
send_as_md = False
|
||||
if result.subcommands.get("list"):
|
||||
send_as_md = True
|
||||
loaded_rps = get_loaded_resource_packs()
|
||||
reply += f"{ulang.get('liteyuki.loaded_resources', NUM=len(loaded_rps))}\n"
|
||||
for rp in loaded_rps:
|
||||
btn_unload = md.btn_cmd(
|
||||
ulang.get("npm.uninstall"),
|
||||
f"rpm unload {rp.folder}"
|
||||
)
|
||||
btn_move_up = md.btn_cmd(
|
||||
ulang.get("rpm.move_up"),
|
||||
f"rpm up {rp.folder}"
|
||||
)
|
||||
btn_move_down = md.btn_cmd(
|
||||
ulang.get("rpm.move_down"),
|
||||
f"rpm down {rp.folder}"
|
||||
)
|
||||
btn_move_top = md.btn_cmd(
|
||||
ulang.get("rpm.move_top"),
|
||||
f"rpm top {rp.folder}"
|
||||
)
|
||||
# 添加新行
|
||||
reply += (f"\n**{md.escape(rp.name)}**({md.escape(rp.folder)})\n\n"
|
||||
f"> {btn_move_up} {btn_move_down} {btn_move_top} {btn_unload}\n\n***")
|
||||
reply += f"\n\n{ulang.get('liteyuki.unloaded_resources')}\n"
|
||||
loaded_folders = [rp.folder for rp in get_loaded_resource_packs()]
|
||||
# 遍历resources文件夹,获取未加载的资源包
|
||||
for folder in os.listdir("resources"):
|
||||
if folder not in loaded_folders:
|
||||
if os.path.exists(os.path.join("resources", folder, "metadata.yml")):
|
||||
metadata = ResourceMetadata(
|
||||
**yaml.load(
|
||||
open(
|
||||
os.path.join("resources", folder, "metadata.yml"),
|
||||
encoding="utf-8"
|
||||
),
|
||||
Loader=yaml.FullLoader
|
||||
)
|
||||
)
|
||||
metadata.folder = folder
|
||||
metadata.path = os.path.join("resources", folder)
|
||||
btn_load = md.btn_cmd(
|
||||
ulang.get("npm.install"),
|
||||
f"rpm load {metadata.folder}"
|
||||
)
|
||||
# 添加新行
|
||||
reply += (f"\n**{md.escape(metadata.name)}**({md.escape(metadata.folder)})\n\n"
|
||||
f"> {btn_load}\n\n***")
|
||||
elif os.path.isfile(os.path.join("resources", folder)) and folder.endswith(".zip"):
|
||||
# zip文件
|
||||
# 临时解压并读取metadata.yml
|
||||
with zipfile.ZipFile(os.path.join("resources", folder), "r") as zip_ref:
|
||||
with zip_ref.open("metadata.yml") as f:
|
||||
metadata = ResourceMetadata(
|
||||
**yaml.load(f, Loader=yaml.FullLoader)
|
||||
)
|
||||
btn_load = md.btn_cmd(
|
||||
ulang.get("npm.install"),
|
||||
f"rpm load {folder}"
|
||||
)
|
||||
# 添加新行
|
||||
reply += (f"\n**{md.escape(metadata.name)}**({md.escape(folder)})\n\n"
|
||||
f"> {btn_load}\n\n***")
|
||||
elif result.subcommands.get("load") or result.subcommands.get("unload"):
|
||||
load = result.subcommands.get("load") is not None
|
||||
rp_name = result.args.get("name")
|
||||
r = False # 操作结果
|
||||
if check_exist(rp_name):
|
||||
if load != check_status(rp_name):
|
||||
# 状态不同
|
||||
if load:
|
||||
r = add_resource_pack(rp_name)
|
||||
else:
|
||||
r = remove_resource_pack(rp_name)
|
||||
rp_meta = get_resource_metadata(rp_name)
|
||||
reply += ulang.get(
|
||||
f"liteyuki.{'load' if load else 'unload'}_resource_{'success' if r else 'failed'}",
|
||||
NAME=rp_meta.name
|
||||
)
|
||||
else:
|
||||
# 重复操作
|
||||
reply += ulang.get(f"liteyuki.resource_already_{'load' if load else 'unload'}ed", NAME=rp_name)
|
||||
else:
|
||||
reply += ulang.get("liteyuki.resource_not_found", NAME=rp_name)
|
||||
if r:
|
||||
btn_reload = md.btn_cmd(
|
||||
ulang.get("liteyuki.reload_resources"),
|
||||
f"rpm reload"
|
||||
)
|
||||
reply += "\n" + ulang.get("liteyuki.need_reload", BTN=btn_reload)
|
||||
elif result.subcommands.get("up") or result.subcommands.get("down") or result.subcommands.get("top"):
|
||||
rp_name = result.args.get("name")
|
||||
if result.subcommands.get("up"):
|
||||
delta = -1
|
||||
elif result.subcommands.get("down"):
|
||||
delta = 1
|
||||
else:
|
||||
delta = 0
|
||||
if check_exist(rp_name):
|
||||
if check_status(rp_name):
|
||||
r = change_priority(rp_name, delta)
|
||||
reply += ulang.get(f"liteyuki.change_priority_{'success' if r else 'failed'}", NAME=rp_name)
|
||||
if r:
|
||||
btn_reload = md.btn_cmd(
|
||||
ulang.get("liteyuki.reload_resources"),
|
||||
f"rpm reload"
|
||||
)
|
||||
reply += "\n" + ulang.get("liteyuki.need_reload", BTN=btn_reload)
|
||||
else:
|
||||
reply += ulang.get("liteyuki.resource_not_found", NAME=rp_name)
|
||||
else:
|
||||
reply += ulang.get("liteyuki.resource_not_found", NAME=rp_name)
|
||||
elif result.subcommands.get("reload"):
|
||||
load_resources()
|
||||
reply = ulang.get(
|
||||
"liteyuki.reload_resources_success",
|
||||
NUM=len(get_loaded_resource_packs())
|
||||
)
|
||||
else:
|
||||
pass
|
||||
if send_as_md:
|
||||
await md.send_md(reply, bot, event=event)
|
||||
else:
|
||||
if reply:
|
||||
await matcher.finish(reply)
|
||||
else:
|
||||
await matcher.finish("其实,还没做help\t——神羽")
|
||||
# 轻雪资源包管理器
|
||||
import os
|
||||
import zipfile
|
||||
import yaml
|
||||
from nonebot import require
|
||||
from nonebot.internal.matcher import Matcher
|
||||
from nonebot.permission import SUPERUSER
|
||||
|
||||
from src.utils.base.language import get_user_lang
|
||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
from src.utils.message.message import MarkdownMessage as md
|
||||
from src.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")
|
||||
from nonebot_plugin_alconna import Alconna, Args, on_alconna, Arparma, Subcommand
|
||||
|
||||
|
||||
@on_alconna(
|
||||
aliases={"资源包"},
|
||||
command=Alconna(
|
||||
"rpm",
|
||||
Subcommand(
|
||||
"list",
|
||||
Args["page", int, 1]["num", int, 10],
|
||||
alias=["ls", "列表", "列出"],
|
||||
),
|
||||
Subcommand(
|
||||
"load",
|
||||
Args["name", str],
|
||||
alias=["安装"],
|
||||
),
|
||||
Subcommand(
|
||||
"unload",
|
||||
Args["name", str],
|
||||
alias=["卸载"],
|
||||
),
|
||||
Subcommand(
|
||||
"up",
|
||||
Args["name", str],
|
||||
alias=["上移"],
|
||||
),
|
||||
Subcommand(
|
||||
"down",
|
||||
Args["name", str],
|
||||
alias=["下移"],
|
||||
),
|
||||
Subcommand(
|
||||
"top",
|
||||
Args["name", str],
|
||||
alias=["置顶"],
|
||||
),
|
||||
Subcommand(
|
||||
"reload",
|
||||
alias=["重载"],
|
||||
),
|
||||
),
|
||||
permission=SUPERUSER,
|
||||
).handle()
|
||||
async def _(bot: T_Bot, event: T_MessageEvent, result: Arparma, matcher: Matcher):
|
||||
ulang = get_user_lang(str(event.user_id))
|
||||
reply = ""
|
||||
send_as_md = False
|
||||
if result.subcommands.get("list"):
|
||||
send_as_md = True
|
||||
loaded_rps = get_loaded_resource_packs()
|
||||
reply += f"{ulang.get('liteyuki.loaded_resources', NUM=len(loaded_rps))}\n"
|
||||
for rp in loaded_rps:
|
||||
btn_unload = md.btn_cmd(
|
||||
ulang.get("npm.uninstall"), f"rpm unload {rp.folder}"
|
||||
)
|
||||
btn_move_up = md.btn_cmd(ulang.get("rpm.move_up"), f"rpm up {rp.folder}")
|
||||
btn_move_down = md.btn_cmd(
|
||||
ulang.get("rpm.move_down"), f"rpm down {rp.folder}"
|
||||
)
|
||||
btn_move_top = md.btn_cmd(ulang.get("rpm.move_top"), f"rpm top {rp.folder}")
|
||||
# 添加新行
|
||||
reply += (
|
||||
f"\n**{md.escape(rp.name)}**({md.escape(rp.folder)})\n\n"
|
||||
f"> {btn_move_up} {btn_move_down} {btn_move_top} {btn_unload}\n\n***"
|
||||
)
|
||||
reply += f"\n\n{ulang.get('liteyuki.unloaded_resources')}\n"
|
||||
loaded_folders = [rp.folder for rp in get_loaded_resource_packs()]
|
||||
# 遍历resources文件夹,获取未加载的资源包
|
||||
for folder in os.listdir("resources"):
|
||||
if folder not in loaded_folders:
|
||||
if os.path.exists(os.path.join("resources", folder, "metadata.yml")):
|
||||
metadata = ResourceMetadata(
|
||||
**yaml.load(
|
||||
open(
|
||||
os.path.join("resources", folder, "metadata.yml"),
|
||||
encoding="utf-8",
|
||||
),
|
||||
Loader=yaml.FullLoader,
|
||||
)
|
||||
)
|
||||
metadata.folder = folder
|
||||
metadata.path = os.path.join("resources", folder)
|
||||
btn_load = md.btn_cmd(
|
||||
ulang.get("npm.install"), f"rpm load {metadata.folder}"
|
||||
)
|
||||
# 添加新行
|
||||
reply += (
|
||||
f"\n**{md.escape(metadata.name)}**({md.escape(metadata.folder)})\n\n"
|
||||
f"> {btn_load}\n\n***"
|
||||
)
|
||||
elif os.path.isfile(
|
||||
os.path.join("resources", folder)
|
||||
) and folder.endswith(".zip"):
|
||||
# zip文件
|
||||
# 临时解压并读取metadata.yml
|
||||
with zipfile.ZipFile(
|
||||
os.path.join("resources", folder), "r"
|
||||
) as zip_ref:
|
||||
with zip_ref.open("metadata.yml") as f:
|
||||
metadata = ResourceMetadata(
|
||||
**yaml.load(f, Loader=yaml.FullLoader)
|
||||
)
|
||||
btn_load = md.btn_cmd(
|
||||
ulang.get("npm.install"), f"rpm load {folder}"
|
||||
)
|
||||
# 添加新行
|
||||
reply += (
|
||||
f"\n**{md.escape(metadata.name)}**({md.escape(folder)})\n\n"
|
||||
f"> {btn_load}\n\n***"
|
||||
)
|
||||
elif result.subcommands.get("load") or result.subcommands.get("unload"):
|
||||
load = result.subcommands.get("load") is not None
|
||||
rp_name = result.args.get("name")
|
||||
r = False # 操作结果
|
||||
if check_exist(rp_name):
|
||||
if load != check_status(rp_name):
|
||||
# 状态不同
|
||||
if load:
|
||||
r = add_resource_pack(rp_name)
|
||||
else:
|
||||
r = remove_resource_pack(rp_name)
|
||||
rp_meta = get_resource_metadata(rp_name)
|
||||
reply += ulang.get(
|
||||
f"liteyuki.{'load' if load else 'unload'}_resource_{'success' if r else 'failed'}",
|
||||
NAME=rp_meta.name,
|
||||
)
|
||||
else:
|
||||
# 重复操作
|
||||
reply += ulang.get(
|
||||
f"liteyuki.resource_already_{'load' if load else 'unload'}ed",
|
||||
NAME=rp_name,
|
||||
)
|
||||
else:
|
||||
reply += ulang.get("liteyuki.resource_not_found", NAME=rp_name)
|
||||
if r:
|
||||
btn_reload = md.btn_cmd(
|
||||
ulang.get("liteyuki.reload_resources"), f"rpm reload"
|
||||
)
|
||||
reply += "\n" + ulang.get("liteyuki.need_reload", BTN=btn_reload)
|
||||
elif (
|
||||
result.subcommands.get("up")
|
||||
or result.subcommands.get("down")
|
||||
or result.subcommands.get("top")
|
||||
):
|
||||
rp_name = result.args.get("name")
|
||||
if result.subcommands.get("up"):
|
||||
delta = -1
|
||||
elif result.subcommands.get("down"):
|
||||
delta = 1
|
||||
else:
|
||||
delta = 0
|
||||
if check_exist(rp_name):
|
||||
if check_status(rp_name):
|
||||
r = change_priority(rp_name, delta)
|
||||
reply += ulang.get(
|
||||
f"liteyuki.change_priority_{'success' if r else 'failed'}",
|
||||
NAME=rp_name,
|
||||
)
|
||||
if r:
|
||||
btn_reload = md.btn_cmd(
|
||||
ulang.get("liteyuki.reload_resources"), f"rpm reload"
|
||||
)
|
||||
reply += "\n" + ulang.get("liteyuki.need_reload", BTN=btn_reload)
|
||||
else:
|
||||
reply += ulang.get("liteyuki.resource_not_found", NAME=rp_name)
|
||||
else:
|
||||
reply += ulang.get("liteyuki.resource_not_found", NAME=rp_name)
|
||||
elif result.subcommands.get("reload"):
|
||||
load_resources()
|
||||
reply = ulang.get(
|
||||
"liteyuki.reload_resources_success", NUM=len(get_loaded_resource_packs())
|
||||
)
|
||||
else:
|
||||
pass
|
||||
if send_as_md:
|
||||
await md.send_md(reply, bot, event=event)
|
||||
else:
|
||||
if reply:
|
||||
await matcher.finish(reply)
|
||||
else:
|
||||
await matcher.finish("根本没做帮助页,赶快去催神羽")
|
@ -1,16 +1,16 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .auto_update import *
|
||||
|
||||
__author__ = "expliyh"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="Satori 用户数据自动更新(临时措施)",
|
||||
description="",
|
||||
usage="",
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
"toggleable" : True,
|
||||
"default_enable" : True,
|
||||
}
|
||||
)
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .auto_update import *
|
||||
|
||||
__author__ = "expliyh"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="Satori 用户数据自动更新(临时措施)",
|
||||
description="",
|
||||
usage="",
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
"toggleable" : True,
|
||||
"default_enable" : True,
|
||||
}
|
||||
)
|
@ -1,21 +1,24 @@
|
||||
import nonebot
|
||||
|
||||
from nonebot.message import event_preprocessor
|
||||
# from nonebot_plugin_alconna.typings import Event
|
||||
from src.utils.base.ly_typing import T_MessageEvent
|
||||
from src.utils import satori_utils
|
||||
from nonebot.adapters import satori
|
||||
from nonebot_plugin_alconna.typings import Event
|
||||
from src.plugins.liteyuki_status.counter_for_satori import satori_counter
|
||||
|
||||
|
||||
@event_preprocessor
|
||||
async def pre_handle(event: Event):
|
||||
if isinstance(event, satori.MessageEvent):
|
||||
if event.user.id == event.self_id:
|
||||
satori_counter.msg_sent += 1
|
||||
else:
|
||||
satori_counter.msg_received += 1
|
||||
if event.user.name is not None:
|
||||
if await satori_utils.user_infos.put(event.user):
|
||||
nonebot.logger.info(f"Satori user {event.user.name}<{event.user.id}> updated")
|
||||
import nonebot
|
||||
|
||||
from nonebot.message import event_preprocessor
|
||||
|
||||
# from nonebot_plugin_alconna.typings import Event
|
||||
from src.utils.base.ly_typing import T_MessageEvent
|
||||
from src.utils import satori_utils
|
||||
from nonebot.adapters import satori
|
||||
from nonebot_plugin_alconna.typings import Event
|
||||
from src.nonebot_plugins.liteyuki_status.counter_for_satori import satori_counter
|
||||
|
||||
|
||||
@event_preprocessor
|
||||
async def pre_handle(event: Event):
|
||||
if isinstance(event, satori.MessageEvent):
|
||||
if event.user.id == event.self_id:
|
||||
satori_counter.msg_sent += 1
|
||||
else:
|
||||
satori_counter.msg_received += 1
|
||||
if event.user.name is not None:
|
||||
if await satori_utils.user_infos.put(event.user):
|
||||
nonebot.logger.info(
|
||||
f"Satori user {event.user.name}<{event.user.id}> updated"
|
||||
)
|
@ -1,163 +1,163 @@
|
||||
import datetime
|
||||
import time
|
||||
|
||||
import aiohttp
|
||||
from nonebot import require
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
from src.utils.base.config import get_config
|
||||
from src.utils.base.data import Database, LiteModel
|
||||
from src.utils.base.resource import get_path
|
||||
from src.utils.message.html_tool import template2image
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
require("nonebot_plugin_apscheduler")
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
from nonebot_plugin_alconna import Alconna, AlconnaResult, CommandResult, Subcommand, UniMessage, on_alconna, Args
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="签名服务器状态",
|
||||
description="适用于ntqq的签名状态查看",
|
||||
usage=(
|
||||
"sign count 查看当前签名数\n"
|
||||
"sign data 查看签名数变化\n"
|
||||
"sign chart [limit] 查看签名数变化图表\n"
|
||||
),
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki" : True,
|
||||
"toggleable" : True,
|
||||
"default_enable": True,
|
||||
}
|
||||
)
|
||||
|
||||
SIGN_COUNT_URLS: dict[str, str] = get_config("sign_count_urls", None)
|
||||
SIGN_COUNT_DURATION = get_config("sign_count_duration", 10)
|
||||
|
||||
|
||||
class SignCount(LiteModel):
|
||||
TABLE_NAME: str = "sign_count"
|
||||
time: float = 0.0
|
||||
count: int = 0
|
||||
sid: str = ""
|
||||
|
||||
|
||||
sign_db = Database("data/liteyuki/ntqq_sign.ldb")
|
||||
sign_db.auto_migrate(SignCount())
|
||||
|
||||
sign_status = on_alconna(Alconna(
|
||||
"sign",
|
||||
Subcommand(
|
||||
"chart",
|
||||
Args["limit", int, 10000]
|
||||
),
|
||||
Subcommand(
|
||||
"count"
|
||||
),
|
||||
Subcommand(
|
||||
"data"
|
||||
)
|
||||
))
|
||||
|
||||
cache_img: bytes = None
|
||||
|
||||
|
||||
@sign_status.assign("count")
|
||||
async def _():
|
||||
reply = "Current sign count:"
|
||||
for name, count in (await get_now_sign()).items():
|
||||
reply += f"\n{name}: {count[1]}"
|
||||
await sign_status.send(reply)
|
||||
|
||||
|
||||
@sign_status.assign("data")
|
||||
async def _():
|
||||
query_stamp = [1, 5, 10, 15]
|
||||
|
||||
reply = "QPS from last " + ", ".join([str(i) for i in query_stamp]) + "mins"
|
||||
for name, url in SIGN_COUNT_URLS.items():
|
||||
count_data = []
|
||||
for stamp in query_stamp:
|
||||
count_rows = sign_db.where_all(SignCount(), "sid = ? and time > ?", url, time.time() - 60 * stamp)
|
||||
if len(count_rows) < 2:
|
||||
count_data.append(-1)
|
||||
else:
|
||||
count_data.append((count_rows[-1].count - count_rows[0].count)/(stamp*60))
|
||||
reply += f"\n{name}: " + ", ".join([f"{i:.1f}" for i in count_data])
|
||||
await sign_status.send(reply)
|
||||
|
||||
|
||||
@sign_status.assign("chart")
|
||||
async def _(arp: CommandResult = AlconnaResult()):
|
||||
limit = arp.result.subcommands.get("chart").args.get("limit")
|
||||
if limit == 10000:
|
||||
if cache_img:
|
||||
await sign_status.send(UniMessage.image(raw=cache_img))
|
||||
return
|
||||
img = await generate_chart(limit)
|
||||
await sign_status.send(UniMessage.image(raw=img))
|
||||
|
||||
|
||||
@scheduler.scheduled_job("interval", seconds=SIGN_COUNT_DURATION, next_run_time=datetime.datetime.now())
|
||||
async def update_sign_count():
|
||||
global cache_img
|
||||
if not SIGN_COUNT_URLS:
|
||||
return
|
||||
data = await get_now_sign()
|
||||
for name, count in data.items():
|
||||
await save_sign_count(count[0], count[1], SIGN_COUNT_URLS[name])
|
||||
|
||||
cache_img = await generate_chart(10000)
|
||||
|
||||
|
||||
async def get_now_sign() -> dict[str, tuple[float, int]]:
|
||||
"""
|
||||
Get the sign count and the time of the latest sign
|
||||
Returns:
|
||||
tuple[float, int] | None: (time, count)
|
||||
"""
|
||||
data = {}
|
||||
now = time.time()
|
||||
async with aiohttp.ClientSession() as client:
|
||||
for name, url in SIGN_COUNT_URLS.items():
|
||||
async with client.get(url) as resp:
|
||||
count = (await resp.json())["count"]
|
||||
data[name] = (now, count)
|
||||
return data
|
||||
|
||||
|
||||
async def save_sign_count(timestamp: float, count: int, sid: str):
|
||||
"""
|
||||
Save the sign count to the database
|
||||
Args:
|
||||
sid: the sign id, use url as the id
|
||||
count:
|
||||
timestamp (float): the time of the sign count (int): the count of the sign
|
||||
"""
|
||||
sign_db.save(SignCount(time=timestamp, count=count, sid=sid))
|
||||
|
||||
|
||||
async def generate_chart(limit):
|
||||
data = []
|
||||
for name, url in SIGN_COUNT_URLS.items():
|
||||
count_rows = sign_db.where_all(SignCount(), "sid = ? ORDER BY id DESC LIMIT ?", url, limit)
|
||||
count_rows.reverse()
|
||||
data.append(
|
||||
{
|
||||
"name" : name,
|
||||
# "data": [[row.time, row.count] for row in count_rows]
|
||||
"times" : [row.time for row in count_rows],
|
||||
"counts": [row.count for row in count_rows]
|
||||
}
|
||||
)
|
||||
|
||||
img = await template2image(
|
||||
template=get_path("templates/sign_status.html"),
|
||||
templates={
|
||||
"data": data
|
||||
},
|
||||
)
|
||||
|
||||
return img
|
||||
import datetime
|
||||
import time
|
||||
|
||||
import aiohttp
|
||||
from nonebot import require
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
from src.utils.base.config import get_config
|
||||
from src.utils.base.data import Database, LiteModel
|
||||
from src.utils.base.resource import get_path
|
||||
from src.utils.message.html_tool import template2image
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
require("nonebot_plugin_apscheduler")
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
from nonebot_plugin_alconna import Alconna, AlconnaResult, CommandResult, Subcommand, UniMessage, on_alconna, Args
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="签名服务器状态",
|
||||
description="适用于ntqq的签名状态查看",
|
||||
usage=(
|
||||
"sign count 查看当前签名数\n"
|
||||
"sign data 查看签名数变化\n"
|
||||
"sign chart [limit] 查看签名数变化图表\n"
|
||||
),
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki" : True,
|
||||
"toggleable" : True,
|
||||
"default_enable": True,
|
||||
}
|
||||
)
|
||||
|
||||
SIGN_COUNT_URLS: dict[str, str] = get_config("sign_count_urls", None)
|
||||
SIGN_COUNT_DURATION = get_config("sign_count_duration", 10)
|
||||
|
||||
|
||||
class SignCount(LiteModel):
|
||||
TABLE_NAME: str = "sign_count"
|
||||
time: float = 0.0
|
||||
count: int = 0
|
||||
sid: str = ""
|
||||
|
||||
|
||||
sign_db = Database("data/liteyuki/ntqq_sign.ldb")
|
||||
sign_db.auto_migrate(SignCount())
|
||||
|
||||
sign_status = on_alconna(Alconna(
|
||||
"sign",
|
||||
Subcommand(
|
||||
"chart",
|
||||
Args["limit", int, 10000]
|
||||
),
|
||||
Subcommand(
|
||||
"count"
|
||||
),
|
||||
Subcommand(
|
||||
"data"
|
||||
)
|
||||
))
|
||||
|
||||
cache_img: bytes = None
|
||||
|
||||
|
||||
@sign_status.assign("count")
|
||||
async def _():
|
||||
reply = "Current sign count:"
|
||||
for name, count in (await get_now_sign()).items():
|
||||
reply += f"\n{name}: {count[1]}"
|
||||
await sign_status.send(reply)
|
||||
|
||||
|
||||
@sign_status.assign("data")
|
||||
async def _():
|
||||
query_stamp = [1, 5, 10, 15]
|
||||
|
||||
reply = "QPS from last " + ", ".join([str(i) for i in query_stamp]) + "mins"
|
||||
for name, url in SIGN_COUNT_URLS.items():
|
||||
count_data = []
|
||||
for stamp in query_stamp:
|
||||
count_rows = sign_db.where_all(SignCount(), "sid = ? and time > ?", url, time.time() - 60 * stamp)
|
||||
if len(count_rows) < 2:
|
||||
count_data.append(-1)
|
||||
else:
|
||||
count_data.append((count_rows[-1].count - count_rows[0].count)/(stamp*60))
|
||||
reply += f"\n{name}: " + ", ".join([f"{i:.1f}" for i in count_data])
|
||||
await sign_status.send(reply)
|
||||
|
||||
|
||||
@sign_status.assign("chart")
|
||||
async def _(arp: CommandResult = AlconnaResult()):
|
||||
limit = arp.result.subcommands.get("chart").args.get("limit")
|
||||
if limit == 10000:
|
||||
if cache_img:
|
||||
await sign_status.send(UniMessage.image(raw=cache_img))
|
||||
return
|
||||
img = await generate_chart(limit)
|
||||
await sign_status.send(UniMessage.image(raw=img))
|
||||
|
||||
|
||||
@scheduler.scheduled_job("interval", seconds=SIGN_COUNT_DURATION, next_run_time=datetime.datetime.now())
|
||||
async def update_sign_count():
|
||||
global cache_img
|
||||
if not SIGN_COUNT_URLS:
|
||||
return
|
||||
data = await get_now_sign()
|
||||
for name, count in data.items():
|
||||
await save_sign_count(count[0], count[1], SIGN_COUNT_URLS[name])
|
||||
|
||||
cache_img = await generate_chart(10000)
|
||||
|
||||
|
||||
async def get_now_sign() -> dict[str, tuple[float, int]]:
|
||||
"""
|
||||
Get the sign count and the time of the latest sign
|
||||
Returns:
|
||||
tuple[float, int] | None: (time, count)
|
||||
"""
|
||||
data = {}
|
||||
now = time.time()
|
||||
async with aiohttp.ClientSession() as client:
|
||||
for name, url in SIGN_COUNT_URLS.items():
|
||||
async with client.get(url) as resp:
|
||||
count = (await resp.json())["count"]
|
||||
data[name] = (now, count)
|
||||
return data
|
||||
|
||||
|
||||
async def save_sign_count(timestamp: float, count: int, sid: str):
|
||||
"""
|
||||
Save the sign count to the database
|
||||
Args:
|
||||
sid: the sign id, use url as the id
|
||||
count:
|
||||
timestamp (float): the time of the sign count (int): the count of the sign
|
||||
"""
|
||||
sign_db.save(SignCount(time=timestamp, count=count, sid=sid))
|
||||
|
||||
|
||||
async def generate_chart(limit):
|
||||
data = []
|
||||
for name, url in SIGN_COUNT_URLS.items():
|
||||
count_rows = sign_db.where_all(SignCount(), "sid = ? ORDER BY id DESC LIMIT ?", url, limit)
|
||||
count_rows.reverse()
|
||||
data.append(
|
||||
{
|
||||
"name" : name,
|
||||
# "data": [[row.time, row.count] for row in count_rows]
|
||||
"times" : [row.time for row in count_rows],
|
||||
"counts": [row.count for row in count_rows]
|
||||
}
|
||||
)
|
||||
|
||||
img = await template2image(
|
||||
template=get_path("templates/sign_status.html"),
|
||||
templates={
|
||||
"data": data
|
||||
},
|
||||
)
|
||||
|
||||
return img
|
@ -1,29 +1,29 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .stat_matchers import *
|
||||
from .stat_monitors import *
|
||||
from .stat_restful_api import *
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="统计信息",
|
||||
description="统计机器人的信息,包括消息、群聊等,支持排名、图表等功能",
|
||||
usage=(
|
||||
"```\nstatistic message 查看统计消息\n"
|
||||
"可选参数:\n"
|
||||
" -g|--group [group_id] 指定群聊\n"
|
||||
" -u|--user [user_id] 指定用户\n"
|
||||
" -d|--duration [duration] 指定时长\n"
|
||||
" -p|--period [period] 指定次数统计周期\n"
|
||||
" -b|--bot [bot_id] 指定机器人\n"
|
||||
"命令别名:\n"
|
||||
" statistic|stat message|msg|m\n"
|
||||
"```"
|
||||
),
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
"toggleable": False,
|
||||
"default_enable": True,
|
||||
},
|
||||
)
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .stat_matchers import *
|
||||
from .stat_monitors import *
|
||||
from .stat_restful_api import *
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="统计信息",
|
||||
description="统计机器人的信息,包括消息、群聊等,支持排名、图表等功能",
|
||||
usage=(
|
||||
"```\nstatistic message 查看统计消息\n"
|
||||
"可选参数:\n"
|
||||
" -g|--group [group_id] 指定群聊\n"
|
||||
" -u|--user [user_id] 指定用户\n"
|
||||
" -d|--duration [duration] 指定时长\n"
|
||||
" -p|--period [period] 指定次数统计周期\n"
|
||||
" -b|--bot [bot_id] 指定机器人\n"
|
||||
"命令别名:\n"
|
||||
" statistic|stat message|msg|m\n"
|
||||
"```"
|
||||
),
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
"toggleable": False,
|
||||
"default_enable": True,
|
||||
},
|
||||
)
|
@ -1,21 +1,21 @@
|
||||
from src.utils.base.data import Database, LiteModel
|
||||
|
||||
|
||||
class MessageEventModel(LiteModel):
|
||||
TABLE_NAME: str = "message_event"
|
||||
time: int = 0
|
||||
|
||||
bot_id: str = ""
|
||||
adapter: str = ""
|
||||
|
||||
user_id: str = ""
|
||||
group_id: str = ""
|
||||
|
||||
message_id: str = ""
|
||||
message: list = []
|
||||
message_text: str = ""
|
||||
message_type: str = ""
|
||||
|
||||
|
||||
msg_db = Database("data/liteyuki/msg.ldb")
|
||||
from src.utils.base.data import Database, LiteModel
|
||||
|
||||
|
||||
class MessageEventModel(LiteModel):
|
||||
TABLE_NAME: str = "message_event"
|
||||
time: int = 0
|
||||
|
||||
bot_id: str = ""
|
||||
adapter: str = ""
|
||||
|
||||
user_id: str = ""
|
||||
group_id: str = ""
|
||||
|
||||
message_id: str = ""
|
||||
message: list = []
|
||||
message_text: str = ""
|
||||
message_type: str = ""
|
||||
|
||||
|
||||
msg_db = Database("data/liteyuki/msg.ldb")
|
||||
msg_db.auto_migrate(MessageEventModel())
|
@ -1,168 +1,168 @@
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
from collections import Counter
|
||||
|
||||
from nonebot import Bot
|
||||
|
||||
from src.utils.message.html_tool import template2image
|
||||
from .common import MessageEventModel, msg_db
|
||||
from src.utils.base.language import Language
|
||||
from src.utils.base.resource import get_path
|
||||
from src.utils.message.string_tool import convert_seconds_to_time
|
||||
from ...utils.external.logo import get_group_icon, get_user_icon
|
||||
|
||||
|
||||
async def count_msg_by_bot_id(bot_id: str) -> int:
|
||||
condition = " AND bot_id = ?"
|
||||
condition_args = [bot_id]
|
||||
|
||||
msg_rows = msg_db.where_all(MessageEventModel(), condition, *condition_args)
|
||||
|
||||
return len(msg_rows)
|
||||
|
||||
|
||||
async def get_stat_msg_image(
|
||||
duration: int,
|
||||
period: int,
|
||||
group_id: str = None,
|
||||
bot_id: str = None,
|
||||
user_id: str = None,
|
||||
ulang: Language = Language(),
|
||||
) -> bytes:
|
||||
"""
|
||||
获取统计消息
|
||||
Args:
|
||||
user_id:
|
||||
ulang:
|
||||
bot_id:
|
||||
group_id:
|
||||
duration: 统计时间,单位秒
|
||||
period: 统计周期,单位秒
|
||||
|
||||
Returns:
|
||||
tuple: [int,], [int,] 两个列表,分别为周期中心时间戳和消息数量
|
||||
"""
|
||||
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)
|
||||
|
||||
if user_id:
|
||||
condition += " AND user_id = ?"
|
||||
condition_args.append(user_id)
|
||||
|
||||
msg_rows = msg_db.where_all(MessageEventModel(), condition, *condition_args)
|
||||
timestamps = []
|
||||
msg_count = []
|
||||
msg_rows.sort(key=lambda x: x.time)
|
||||
|
||||
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": "以{}为分割,在{}范围内的 {}".format(
|
||||
convert_seconds_to_time(period),
|
||||
convert_seconds_to_time(duration),
|
||||
ulang.get("stat.message"),
|
||||
)
|
||||
+ (f" 群聊:{group_id}" if group_id else "")
|
||||
+ (f" 机器:{bot_id}" if bot_id else "")
|
||||
+ (f" 用户:{user_id}" if user_id else ""),
|
||||
"times": timestamps,
|
||||
"counts": msg_count,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return await template2image(get_path("templates/stat_msg.html"), templates)
|
||||
|
||||
|
||||
async def get_stat_rank_image(
|
||||
rank_type: str,
|
||||
limit: dict[str, Any],
|
||||
ulang: Language = Language(),
|
||||
bot: Bot = None,
|
||||
) -> bytes:
|
||||
if rank_type == "user":
|
||||
condition = "user_id != ''"
|
||||
condition_args = []
|
||||
else:
|
||||
condition = "group_id != ''"
|
||||
condition_args = []
|
||||
|
||||
for k, v in limit.items():
|
||||
match k:
|
||||
case "user_id":
|
||||
condition += " AND user_id = ?"
|
||||
condition_args.append(v)
|
||||
case "group_id":
|
||||
condition += " AND group_id = ?"
|
||||
condition_args.append(v)
|
||||
case "bot_id":
|
||||
condition += " AND bot_id = ?"
|
||||
condition_args.append(v)
|
||||
case "duration":
|
||||
condition += " AND time > ?"
|
||||
condition_args.append(v)
|
||||
|
||||
msg_rows = msg_db.where_all(MessageEventModel(), condition, *condition_args)
|
||||
|
||||
"""
|
||||
{
|
||||
name: string, # user name or group name
|
||||
count: int, # message count
|
||||
icon: string # icon url
|
||||
}
|
||||
"""
|
||||
|
||||
if rank_type == "user":
|
||||
ranking_counter = Counter([msg.user_id for msg in msg_rows])
|
||||
else:
|
||||
ranking_counter = Counter([msg.group_id for msg in msg_rows])
|
||||
sorted_data = sorted(ranking_counter.items(), key=lambda x: x[1], reverse=True)
|
||||
|
||||
ranking: list[dict[str, Any]] = [
|
||||
{
|
||||
"name": _[0],
|
||||
"count": _[1],
|
||||
"icon": await (
|
||||
get_group_icon(platform="qq", group_id=_[0])
|
||||
if rank_type == "group"
|
||||
else get_user_icon(platform="qq", user_id=_[0])
|
||||
),
|
||||
}
|
||||
for _ in sorted_data[0 : min(len(sorted_data), limit["rank"])]
|
||||
]
|
||||
|
||||
templates = {
|
||||
"data": {
|
||||
"name": ulang.get("stat.rank")
|
||||
+ f" 类别:{rank_type}"
|
||||
+ f" 制约:{limit}",
|
||||
"ranking": ranking,
|
||||
}
|
||||
}
|
||||
|
||||
return await template2image(
|
||||
get_path("templates/stat_rank.html"), templates, debug=True
|
||||
)
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
from collections import Counter
|
||||
|
||||
from nonebot import Bot
|
||||
|
||||
from src.utils.message.html_tool import template2image
|
||||
from .common import MessageEventModel, msg_db
|
||||
from src.utils.base.language import Language
|
||||
from src.utils.base.resource import get_path
|
||||
from src.utils.message.string_tool import convert_seconds_to_time
|
||||
from ...utils.external.logo import get_group_icon, get_user_icon
|
||||
|
||||
|
||||
async def count_msg_by_bot_id(bot_id: str) -> int:
|
||||
condition = " AND bot_id = ?"
|
||||
condition_args = [bot_id]
|
||||
|
||||
msg_rows = msg_db.where_all(MessageEventModel(), condition, *condition_args)
|
||||
|
||||
return len(msg_rows)
|
||||
|
||||
|
||||
async def get_stat_msg_image(
|
||||
duration: int,
|
||||
period: int,
|
||||
group_id: str = None,
|
||||
bot_id: str = None,
|
||||
user_id: str = None,
|
||||
ulang: Language = Language(),
|
||||
) -> bytes:
|
||||
"""
|
||||
获取统计消息
|
||||
Args:
|
||||
user_id:
|
||||
ulang:
|
||||
bot_id:
|
||||
group_id:
|
||||
duration: 统计时间,单位秒
|
||||
period: 统计周期,单位秒
|
||||
|
||||
Returns:
|
||||
tuple: [int,], [int,] 两个列表,分别为周期中心时间戳和消息数量
|
||||
"""
|
||||
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)
|
||||
|
||||
if user_id:
|
||||
condition += " AND user_id = ?"
|
||||
condition_args.append(user_id)
|
||||
|
||||
msg_rows = msg_db.where_all(MessageEventModel(), condition, *condition_args)
|
||||
timestamps = []
|
||||
msg_count = []
|
||||
msg_rows.sort(key=lambda x: x.time)
|
||||
|
||||
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": "以{}为分割,在{}范围内的 {}".format(
|
||||
convert_seconds_to_time(period),
|
||||
convert_seconds_to_time(duration),
|
||||
ulang.get("stat.message"),
|
||||
)
|
||||
+ (f" 群聊:{group_id}" if group_id else "")
|
||||
+ (f" 机器:{bot_id}" if bot_id else "")
|
||||
+ (f" 用户:{user_id}" if user_id else ""),
|
||||
"times": timestamps,
|
||||
"counts": msg_count,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return await template2image(get_path("templates/stat_msg.html"), templates)
|
||||
|
||||
|
||||
async def get_stat_rank_image(
|
||||
rank_type: str,
|
||||
limit: dict[str, Any],
|
||||
ulang: Language = Language(),
|
||||
bot: Bot = None,
|
||||
) -> bytes:
|
||||
if rank_type == "user":
|
||||
condition = "user_id != ''"
|
||||
condition_args = []
|
||||
else:
|
||||
condition = "group_id != ''"
|
||||
condition_args = []
|
||||
|
||||
for k, v in limit.items():
|
||||
match k:
|
||||
case "user_id":
|
||||
condition += " AND user_id = ?"
|
||||
condition_args.append(v)
|
||||
case "group_id":
|
||||
condition += " AND group_id = ?"
|
||||
condition_args.append(v)
|
||||
case "bot_id":
|
||||
condition += " AND bot_id = ?"
|
||||
condition_args.append(v)
|
||||
case "duration":
|
||||
condition += " AND time > ?"
|
||||
condition_args.append(v)
|
||||
|
||||
msg_rows = msg_db.where_all(MessageEventModel(), condition, *condition_args)
|
||||
|
||||
"""
|
||||
{
|
||||
name: string, # user name or group name
|
||||
count: int, # message count
|
||||
icon: string # icon url
|
||||
}
|
||||
"""
|
||||
|
||||
if rank_type == "user":
|
||||
ranking_counter = Counter([msg.user_id for msg in msg_rows])
|
||||
else:
|
||||
ranking_counter = Counter([msg.group_id for msg in msg_rows])
|
||||
sorted_data = sorted(ranking_counter.items(), key=lambda x: x[1], reverse=True)
|
||||
|
||||
ranking: list[dict[str, Any]] = [
|
||||
{
|
||||
"name": _[0],
|
||||
"count": _[1],
|
||||
"icon": await (
|
||||
get_group_icon(platform="qq", group_id=_[0])
|
||||
if rank_type == "group"
|
||||
else get_user_icon(platform="qq", user_id=_[0])
|
||||
),
|
||||
}
|
||||
for _ in sorted_data[0 : min(len(sorted_data), limit["rank"])]
|
||||
]
|
||||
|
||||
templates = {
|
||||
"data": {
|
||||
"name": ulang.get("stat.rank")
|
||||
+ f" 类别:{rank_type}"
|
||||
+ f" 制约:{limit}",
|
||||
"ranking": ranking,
|
||||
}
|
||||
}
|
||||
|
||||
return await template2image(
|
||||
get_path("templates/stat_rank.html"), templates, debug=True
|
||||
)
|
@ -1,140 +1,139 @@
|
||||
from nonebot import Bot, require
|
||||
from src.utils.message.string_tool import convert_duration, convert_time_to_seconds
|
||||
from .data_source import *
|
||||
from src.utils import event as event_utils
|
||||
from src.utils.base.language import Language
|
||||
from src.utils.base.ly_typing import T_MessageEvent
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
|
||||
from nonebot_plugin_alconna import (
|
||||
UniMessage,
|
||||
on_alconna,
|
||||
Alconna,
|
||||
Args,
|
||||
Subcommand,
|
||||
Arparma,
|
||||
Option,
|
||||
MultiVar,
|
||||
)
|
||||
|
||||
stat_msg = on_alconna(
|
||||
Alconna(
|
||||
"statistic",
|
||||
Subcommand(
|
||||
"message",
|
||||
# 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(
|
||||
"-b|--bot", # 生成图表
|
||||
Args["bot_id", str, "current"],
|
||||
help_text="是否指定机器人",
|
||||
),
|
||||
Option(
|
||||
"-g|--group", Args["group_id", str, "current"], help_text="指定群组"
|
||||
),
|
||||
Option("-u|--user", Args["user_id", str, "current"], help_text="指定用户"),
|
||||
alias={"msg", "m"},
|
||||
help_text="查看统计次数内的消息",
|
||||
),
|
||||
Subcommand(
|
||||
"rank",
|
||||
Option(
|
||||
"-u|--user",
|
||||
help_text="以用户为指标",
|
||||
),
|
||||
Option(
|
||||
"-g|--group",
|
||||
help_text="以群组为指标",
|
||||
),
|
||||
Option(
|
||||
"-l|--limit",
|
||||
Args["limit", MultiVar(str)],
|
||||
help_text="限制参数,使用key=val格式",
|
||||
),
|
||||
Option(
|
||||
"-d|--duration",
|
||||
Args["duration", str, "1d"],
|
||||
help_text="统计时间",
|
||||
),
|
||||
Option(
|
||||
"-r|--rank",
|
||||
Args["rank", int, 20],
|
||||
help_text="指定排名",
|
||||
),
|
||||
alias={"r"},
|
||||
),
|
||||
),
|
||||
aliases={"stat"},
|
||||
)
|
||||
|
||||
|
||||
@stat_msg.assign("message")
|
||||
async def _(result: Arparma, event: T_MessageEvent, bot: Bot):
|
||||
ulang = Language(event_utils.get_user_id(event))
|
||||
try:
|
||||
duration = convert_time_to_seconds(
|
||||
result.other_args.get("duration", "2d")
|
||||
) # 秒数
|
||||
period = convert_time_to_seconds(result.other_args.get("period", "1m"))
|
||||
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")
|
||||
user_id = result.other_args.get("user_id")
|
||||
|
||||
if group_id in ["current", "c"]:
|
||||
group_id = str(event_utils.get_group_id(event))
|
||||
|
||||
if group_id in ["all", "a"]:
|
||||
group_id = "all"
|
||||
|
||||
if bot_id in ["current", "c"]:
|
||||
bot_id = str(bot.self_id)
|
||||
|
||||
if user_id in ["current", "c"]:
|
||||
user_id = str(event_utils.get_user_id(event))
|
||||
|
||||
img = await get_stat_msg_image(
|
||||
duration=duration,
|
||||
period=period,
|
||||
group_id=group_id,
|
||||
bot_id=bot_id,
|
||||
user_id=user_id,
|
||||
ulang=ulang,
|
||||
)
|
||||
await stat_msg.send(UniMessage.image(raw=img))
|
||||
|
||||
|
||||
@stat_msg.assign("rank")
|
||||
async def _(result: Arparma, event: T_MessageEvent, bot: Bot):
|
||||
ulang = Language(event_utils.get_user_id(event))
|
||||
rank_type = "user"
|
||||
duration = convert_time_to_seconds(result.other_args.get("duration", "1d"))
|
||||
print(result)
|
||||
if result.subcommands.get("rank").options.get("user"):
|
||||
rank_type = "user"
|
||||
elif result.subcommands.get("rank").options.get("group"):
|
||||
rank_type = "group"
|
||||
|
||||
limit = result.other_args.get("limit", {})
|
||||
if limit:
|
||||
limit = dict([i.split("=") for i in limit])
|
||||
limit["duration"] = time.time() - duration # 起始时间戳
|
||||
limit["rank"] = result.other_args.get("rank", 20)
|
||||
|
||||
img = await get_stat_rank_image(rank_type=rank_type, limit=limit, ulang=ulang)
|
||||
await stat_msg.send(UniMessage.image(raw=img))
|
||||
from nonebot import Bot, require
|
||||
from src.utils.message.string_tool import convert_duration, convert_time_to_seconds
|
||||
from .data_source import *
|
||||
from src.utils import event as event_utils
|
||||
from src.utils.base.language import Language
|
||||
from src.utils.base.ly_typing import T_MessageEvent
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
|
||||
from nonebot_plugin_alconna import (
|
||||
UniMessage,
|
||||
on_alconna,
|
||||
Alconna,
|
||||
Args,
|
||||
Subcommand,
|
||||
Arparma,
|
||||
Option,
|
||||
MultiVar,
|
||||
)
|
||||
|
||||
stat_msg = on_alconna(
|
||||
Alconna(
|
||||
"statistic",
|
||||
Subcommand(
|
||||
"message",
|
||||
# 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(
|
||||
"-b|--bot", # 生成图表
|
||||
Args["bot_id", str, "current"],
|
||||
help_text="是否指定机器人",
|
||||
),
|
||||
Option(
|
||||
"-g|--group", Args["group_id", str, "current"], help_text="指定群组"
|
||||
),
|
||||
Option("-u|--user", Args["user_id", str, "current"], help_text="指定用户"),
|
||||
alias={"msg", "m"},
|
||||
help_text="查看统计次数内的消息",
|
||||
),
|
||||
Subcommand(
|
||||
"rank",
|
||||
Option(
|
||||
"-u|--user",
|
||||
help_text="以用户为指标",
|
||||
),
|
||||
Option(
|
||||
"-g|--group",
|
||||
help_text="以群组为指标",
|
||||
),
|
||||
Option(
|
||||
"-l|--limit",
|
||||
Args["limit", MultiVar(str)],
|
||||
help_text="限制参数,使用key=val格式",
|
||||
),
|
||||
Option(
|
||||
"-d|--duration",
|
||||
Args["duration", str, "1d"],
|
||||
help_text="统计时间",
|
||||
),
|
||||
Option(
|
||||
"-r|--rank",
|
||||
Args["rank", int, 20],
|
||||
help_text="指定排名",
|
||||
),
|
||||
alias={"r"},
|
||||
),
|
||||
),
|
||||
aliases={"stat"},
|
||||
)
|
||||
|
||||
|
||||
@stat_msg.assign("message")
|
||||
async def _(result: Arparma, event: T_MessageEvent, bot: Bot):
|
||||
ulang = Language(event_utils.get_user_id(event))
|
||||
try:
|
||||
duration = convert_time_to_seconds(
|
||||
result.other_args.get("duration", "2d")
|
||||
) # 秒数
|
||||
period = convert_time_to_seconds(result.other_args.get("period", "1m"))
|
||||
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")
|
||||
user_id = result.other_args.get("user_id")
|
||||
|
||||
if group_id in ["current", "c"]:
|
||||
group_id = str(event_utils.get_group_id(event))
|
||||
|
||||
if group_id in ["all", "a"]:
|
||||
group_id = "all"
|
||||
|
||||
if bot_id in ["current", "c"]:
|
||||
bot_id = str(bot.self_id)
|
||||
|
||||
if user_id in ["current", "c"]:
|
||||
user_id = str(event_utils.get_user_id(event))
|
||||
|
||||
img = await get_stat_msg_image(
|
||||
duration=duration,
|
||||
period=period,
|
||||
group_id=group_id,
|
||||
bot_id=bot_id,
|
||||
user_id=user_id,
|
||||
ulang=ulang,
|
||||
)
|
||||
await stat_msg.send(UniMessage.image(raw=img))
|
||||
|
||||
|
||||
@stat_msg.assign("rank")
|
||||
async def _(result: Arparma, event: T_MessageEvent, bot: Bot):
|
||||
ulang = Language(event_utils.get_user_id(event))
|
||||
rank_type = "user"
|
||||
duration = convert_time_to_seconds(result.other_args.get("duration", "1d"))
|
||||
if result.subcommands.get("rank").options.get("user"):
|
||||
rank_type = "user"
|
||||
elif result.subcommands.get("rank").options.get("group"):
|
||||
rank_type = "group"
|
||||
|
||||
limit = result.other_args.get("limit", {})
|
||||
if limit:
|
||||
limit = dict([i.split("=") for i in limit])
|
||||
limit["duration"] = time.time() - duration # 起始时间戳
|
||||
limit["rank"] = result.other_args.get("rank", 20)
|
||||
|
||||
img = await get_stat_rank_image(rank_type=rank_type, limit=limit, ulang=ulang)
|
||||
await stat_msg.send(UniMessage.image(raw=img))
|
@ -1,92 +1,88 @@
|
||||
import time
|
||||
|
||||
from nonebot import require
|
||||
from nonebot.message import event_postprocessor
|
||||
|
||||
from src.utils.base.data import Database, LiteModel
|
||||
from src.utils.base.ly_typing import v11, v12, satori
|
||||
|
||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
|
||||
from .common import MessageEventModel, msg_db
|
||||
from src.utils import event as event_utils
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
|
||||
|
||||
async def general_event_monitor(bot: T_Bot, event: T_MessageEvent):
|
||||
print("POST PROCESS")
|
||||
# if isinstance(bot, satori.Bot):
|
||||
# print("POST PROCESS SATORI EVENT")
|
||||
# return await satori_event_monitor(bot, event)
|
||||
# elif isinstance(bot, v11.Bot):
|
||||
# print("POST PROCESS V11 EVENT")
|
||||
# return await onebot_v11_event_monitor(bot, event)
|
||||
|
||||
|
||||
@event_postprocessor
|
||||
async def onebot_v11_event_monitor(bot: v11.Bot, event: v11.MessageEvent):
|
||||
if event.message_type == "group":
|
||||
event: v11.GroupMessageEvent
|
||||
group_id = str(event.group_id)
|
||||
else:
|
||||
group_id = ""
|
||||
mem = MessageEventModel(
|
||||
time=int(time.time()),
|
||||
bot_id=bot.self_id,
|
||||
adapter="onebot.v11",
|
||||
group_id=group_id,
|
||||
user_id=str(event.user_id),
|
||||
|
||||
message_id=str(event.message_id),
|
||||
|
||||
message=[ms.__dict__ for ms in event.message],
|
||||
message_text=event.raw_message,
|
||||
message_type=event.message_type,
|
||||
)
|
||||
msg_db.save(mem)
|
||||
|
||||
|
||||
@event_postprocessor
|
||||
async def onebot_v12_event_monitor(bot: v12.Bot, event: v12.MessageEvent):
|
||||
if event.message_type == "group":
|
||||
event: v12.GroupMessageEvent
|
||||
group_id = str(event.group_id)
|
||||
else:
|
||||
group_id = ""
|
||||
mem = MessageEventModel(
|
||||
time=int(time.time()),
|
||||
bot_id=bot.self_id,
|
||||
adapter="onebot.v12",
|
||||
group_id=group_id,
|
||||
user_id=str(event.user_id),
|
||||
|
||||
message_id=[ms.__dict__ for ms in event.message],
|
||||
|
||||
message=event.message,
|
||||
message_text=event.raw_message,
|
||||
message_type=event.message_type,
|
||||
)
|
||||
msg_db.save(mem)
|
||||
|
||||
|
||||
@event_postprocessor
|
||||
async def satori_event_monitor(bot: satori.Bot, event: satori.MessageEvent):
|
||||
if event.guild is not None:
|
||||
event: satori.MessageEvent
|
||||
group_id = str(event.guild.id)
|
||||
else:
|
||||
group_id = ""
|
||||
|
||||
mem = MessageEventModel(
|
||||
time=int(time.time()),
|
||||
bot_id=bot.self_id,
|
||||
adapter="satori",
|
||||
group_id=group_id,
|
||||
user_id=str(event.user.id),
|
||||
message_id=[ms.__str__() for ms in event.message],
|
||||
message=event.message,
|
||||
message_text=event.message.content,
|
||||
message_type=event_utils.get_message_type(event),
|
||||
)
|
||||
msg_db.save(mem)
|
||||
import time
|
||||
|
||||
from nonebot import require
|
||||
from nonebot.message import event_postprocessor
|
||||
|
||||
from src.utils.base.data import Database, LiteModel
|
||||
from src.utils.base.ly_typing import v11, v12, satori
|
||||
|
||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
|
||||
from .common import MessageEventModel, msg_db
|
||||
from src.utils import event as event_utils
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
|
||||
|
||||
async def general_event_monitor(bot: T_Bot, event: T_MessageEvent):
|
||||
pass
|
||||
# if isinstance(bot, satori.Bot):
|
||||
# print("POST PROCESS SATORI EVENT")
|
||||
# return await satori_event_monitor(bot, event)
|
||||
# elif isinstance(bot, v11.Bot):
|
||||
# print("POST PROCESS V11 EVENT")
|
||||
# return await onebot_v11_event_monitor(bot, event)
|
||||
|
||||
|
||||
@event_postprocessor
|
||||
async def onebot_v11_event_monitor(bot: v11.Bot, event: v11.MessageEvent):
|
||||
if event.message_type == "group":
|
||||
event: v11.GroupMessageEvent
|
||||
group_id = str(event.group_id)
|
||||
else:
|
||||
group_id = ""
|
||||
mem = MessageEventModel(
|
||||
time=int(time.time()),
|
||||
bot_id=bot.self_id,
|
||||
adapter="onebot.v11",
|
||||
group_id=group_id,
|
||||
user_id=str(event.user_id),
|
||||
message_id=str(event.message_id),
|
||||
message=[ms.__dict__ for ms in event.message],
|
||||
message_text=event.raw_message,
|
||||
message_type=event.message_type,
|
||||
)
|
||||
msg_db.save(mem)
|
||||
|
||||
|
||||
@event_postprocessor
|
||||
async def onebot_v12_event_monitor(bot: v12.Bot, event: v12.MessageEvent):
|
||||
if event.message_type == "group":
|
||||
event: v12.GroupMessageEvent
|
||||
group_id = str(event.group_id)
|
||||
else:
|
||||
group_id = ""
|
||||
mem = MessageEventModel(
|
||||
time=int(time.time()),
|
||||
bot_id=bot.self_id,
|
||||
adapter="onebot.v12",
|
||||
group_id=group_id,
|
||||
user_id=str(event.user_id),
|
||||
message_id=[ms.__dict__ for ms in event.message],
|
||||
message=event.message,
|
||||
message_text=event.raw_message,
|
||||
message_type=event.message_type,
|
||||
)
|
||||
msg_db.save(mem)
|
||||
|
||||
|
||||
@event_postprocessor
|
||||
async def satori_event_monitor(bot: satori.Bot, event: satori.MessageEvent):
|
||||
if event.guild is not None:
|
||||
event: satori.MessageEvent
|
||||
group_id = str(event.guild.id)
|
||||
else:
|
||||
group_id = ""
|
||||
|
||||
mem = MessageEventModel(
|
||||
time=int(time.time()),
|
||||
bot_id=bot.self_id,
|
||||
adapter="satori",
|
||||
group_id=group_id,
|
||||
user_id=str(event.user.id),
|
||||
message_id=[ms.__str__() for ms in event.message],
|
||||
message=event.message,
|
||||
message_text=event.message.content,
|
||||
message_type=event_utils.get_message_type(event),
|
||||
)
|
||||
msg_db.save(mem)
|
@ -1,21 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 hemengyang
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 hemengyang
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -1,107 +1,107 @@
|
||||
import asyncio
|
||||
import concurrent.futures
|
||||
import contextlib
|
||||
import re
|
||||
from functools import partial
|
||||
from io import BytesIO
|
||||
from random import choice
|
||||
from typing import Optional
|
||||
|
||||
import jieba
|
||||
import jieba.analyse
|
||||
import numpy as np
|
||||
from emoji import replace_emoji
|
||||
from PIL import Image
|
||||
from wordcloud import WordCloud
|
||||
|
||||
from .config import global_config, plugin_config
|
||||
|
||||
|
||||
def pre_precess(msg: str) -> str:
|
||||
"""对消息进行预处理"""
|
||||
# 去除网址
|
||||
# https://stackoverflow.com/a/17773849/9212748
|
||||
url_regex = re.compile(
|
||||
r"(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]"
|
||||
r"+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})"
|
||||
)
|
||||
msg = url_regex.sub("", msg)
|
||||
|
||||
# 去除 \u200b
|
||||
msg = re.sub(r"\u200b", "", msg)
|
||||
|
||||
# 去除 emoji
|
||||
# https://github.com/carpedm20/emoji
|
||||
msg = replace_emoji(msg)
|
||||
|
||||
return msg
|
||||
|
||||
|
||||
def analyse_message(msg: str) -> dict[str, float]:
|
||||
"""分析消息
|
||||
|
||||
分词,并统计词频
|
||||
"""
|
||||
# 设置停用词表
|
||||
if plugin_config.wordcloud_stopwords_path:
|
||||
jieba.analyse.set_stop_words(plugin_config.wordcloud_stopwords_path)
|
||||
# 加载用户词典
|
||||
if plugin_config.wordcloud_userdict_path:
|
||||
jieba.load_userdict(str(plugin_config.wordcloud_userdict_path))
|
||||
# 基于 TF-IDF 算法的关键词抽取
|
||||
# 返回所有关键词,因为设置了数量其实也只是 tags[:topK],不如交给词云库处理
|
||||
words = jieba.analyse.extract_tags(msg, topK=0, withWeight=True)
|
||||
return dict(words)
|
||||
|
||||
|
||||
def get_mask(key: str):
|
||||
"""获取 mask"""
|
||||
mask_path = plugin_config.get_mask_path(key)
|
||||
if mask_path.exists():
|
||||
return np.array(Image.open(mask_path))
|
||||
# 如果指定 mask 文件不存在,则尝试默认 mask
|
||||
default_mask_path = plugin_config.get_mask_path()
|
||||
if default_mask_path.exists():
|
||||
return np.array(Image.open(default_mask_path))
|
||||
|
||||
|
||||
def _get_wordcloud(messages: list[str], mask_key: str) -> Optional[bytes]:
|
||||
# 过滤掉命令
|
||||
command_start = tuple(i for i in global_config.command_start if i)
|
||||
message = " ".join(m for m in messages if not m.startswith(command_start))
|
||||
# 预处理
|
||||
message = pre_precess(message)
|
||||
# 分析消息。分词,并统计词频
|
||||
frequency = analyse_message(message)
|
||||
# 词云参数
|
||||
wordcloud_options = {}
|
||||
wordcloud_options.update(plugin_config.wordcloud_options)
|
||||
wordcloud_options.setdefault("font_path", str(plugin_config.wordcloud_font_path))
|
||||
wordcloud_options.setdefault("width", plugin_config.wordcloud_width)
|
||||
wordcloud_options.setdefault("height", plugin_config.wordcloud_height)
|
||||
wordcloud_options.setdefault(
|
||||
"background_color", plugin_config.wordcloud_background_color
|
||||
)
|
||||
# 如果 colormap 是列表,则随机选择一个
|
||||
colormap = (
|
||||
plugin_config.wordcloud_colormap
|
||||
if isinstance(plugin_config.wordcloud_colormap, str)
|
||||
else choice(plugin_config.wordcloud_colormap)
|
||||
)
|
||||
wordcloud_options.setdefault("colormap", colormap)
|
||||
wordcloud_options.setdefault("mask", get_mask(mask_key))
|
||||
with contextlib.suppress(ValueError):
|
||||
wordcloud = WordCloud(**wordcloud_options)
|
||||
image = wordcloud.generate_from_frequencies(frequency).to_image()
|
||||
image_bytes = BytesIO()
|
||||
image.save(image_bytes, format="PNG")
|
||||
return image_bytes.getvalue()
|
||||
|
||||
|
||||
async def get_wordcloud(messages: list[str], mask_key: str) -> Optional[bytes]:
|
||||
loop = asyncio.get_running_loop()
|
||||
pfunc = partial(_get_wordcloud, messages, mask_key)
|
||||
# 虽然不知道具体是哪里泄漏了,但是通过每次关闭线程池可以避免这个问题
|
||||
# https://github.com/he0119/nonebot-plugin-wordcloud/issues/99
|
||||
with concurrent.futures.ThreadPoolExecutor() as pool:
|
||||
return await loop.run_in_executor(pool, pfunc)
|
||||
import asyncio
|
||||
import concurrent.futures
|
||||
import contextlib
|
||||
import re
|
||||
from functools import partial
|
||||
from io import BytesIO
|
||||
from random import choice
|
||||
from typing import Optional
|
||||
|
||||
import jieba
|
||||
import jieba.analyse
|
||||
import numpy as np
|
||||
from emoji import replace_emoji
|
||||
from PIL import Image
|
||||
from wordcloud import WordCloud
|
||||
|
||||
from .config import global_config, plugin_config
|
||||
|
||||
|
||||
def pre_precess(msg: str) -> str:
|
||||
"""对消息进行预处理"""
|
||||
# 去除网址
|
||||
# https://stackoverflow.com/a/17773849/9212748
|
||||
url_regex = re.compile(
|
||||
r"(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]"
|
||||
r"+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})"
|
||||
)
|
||||
msg = url_regex.sub("", msg)
|
||||
|
||||
# 去除 \u200b
|
||||
msg = re.sub(r"\u200b", "", msg)
|
||||
|
||||
# 去除 emoji
|
||||
# https://github.com/carpedm20/emoji
|
||||
msg = replace_emoji(msg)
|
||||
|
||||
return msg
|
||||
|
||||
|
||||
def analyse_message(msg: str) -> dict[str, float]:
|
||||
"""分析消息
|
||||
|
||||
分词,并统计词频
|
||||
"""
|
||||
# 设置停用词表
|
||||
if plugin_config.wordcloud_stopwords_path:
|
||||
jieba.analyse.set_stop_words(plugin_config.wordcloud_stopwords_path)
|
||||
# 加载用户词典
|
||||
if plugin_config.wordcloud_userdict_path:
|
||||
jieba.load_userdict(str(plugin_config.wordcloud_userdict_path))
|
||||
# 基于 TF-IDF 算法的关键词抽取
|
||||
# 返回所有关键词,因为设置了数量其实也只是 tags[:topK],不如交给词云库处理
|
||||
words = jieba.analyse.extract_tags(msg, topK=0, withWeight=True)
|
||||
return dict(words)
|
||||
|
||||
|
||||
def get_mask(key: str):
|
||||
"""获取 mask"""
|
||||
mask_path = plugin_config.get_mask_path(key)
|
||||
if mask_path.exists():
|
||||
return np.array(Image.open(mask_path))
|
||||
# 如果指定 mask 文件不存在,则尝试默认 mask
|
||||
default_mask_path = plugin_config.get_mask_path()
|
||||
if default_mask_path.exists():
|
||||
return np.array(Image.open(default_mask_path))
|
||||
|
||||
|
||||
def _get_wordcloud(messages: list[str], mask_key: str) -> Optional[bytes]:
|
||||
# 过滤掉命令
|
||||
command_start = tuple(i for i in global_config.command_start if i)
|
||||
message = " ".join(m for m in messages if not m.startswith(command_start))
|
||||
# 预处理
|
||||
message = pre_precess(message)
|
||||
# 分析消息。分词,并统计词频
|
||||
frequency = analyse_message(message)
|
||||
# 词云参数
|
||||
wordcloud_options = {}
|
||||
wordcloud_options.update(plugin_config.wordcloud_options)
|
||||
wordcloud_options.setdefault("font_path", str(plugin_config.wordcloud_font_path))
|
||||
wordcloud_options.setdefault("width", plugin_config.wordcloud_width)
|
||||
wordcloud_options.setdefault("height", plugin_config.wordcloud_height)
|
||||
wordcloud_options.setdefault(
|
||||
"background_color", plugin_config.wordcloud_background_color
|
||||
)
|
||||
# 如果 colormap 是列表,则随机选择一个
|
||||
colormap = (
|
||||
plugin_config.wordcloud_colormap
|
||||
if isinstance(plugin_config.wordcloud_colormap, str)
|
||||
else choice(plugin_config.wordcloud_colormap)
|
||||
)
|
||||
wordcloud_options.setdefault("colormap", colormap)
|
||||
wordcloud_options.setdefault("mask", get_mask(mask_key))
|
||||
with contextlib.suppress(ValueError):
|
||||
wordcloud = WordCloud(**wordcloud_options)
|
||||
image = wordcloud.generate_from_frequencies(frequency).to_image()
|
||||
image_bytes = BytesIO()
|
||||
image.save(image_bytes, format="PNG")
|
||||
return image_bytes.getvalue()
|
||||
|
||||
|
||||
async def get_wordcloud(messages: list[str], mask_key: str) -> Optional[bytes]:
|
||||
loop = asyncio.get_running_loop()
|
||||
pfunc = partial(_get_wordcloud, messages, mask_key)
|
||||
# 虽然不知道具体是哪里泄漏了,但是通过每次关闭线程池可以避免这个问题
|
||||
# https://github.com/he0119/nonebot-plugin-wordcloud/issues/99
|
||||
with concurrent.futures.ThreadPoolExecutor() as pool:
|
||||
return await loop.run_in_executor(pool, pfunc)
|
23
src/nonebot_plugins/liteyuki_status/__init__.py
Normal file
23
src/nonebot_plugins/liteyuki_status/__init__.py
Normal file
@ -0,0 +1,23 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .status import *
|
||||
|
||||
__author__ = "神羽SnowyKami & 金羿Eilles"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="状态查看器",
|
||||
description="",
|
||||
usage=(
|
||||
"MARKDOWN### 状态查看器\n"
|
||||
"查看机器人的状态\n"
|
||||
"### 用法\n"
|
||||
"- `/status` 查看基本情况\n"
|
||||
"- `/status memory` 查看内存使用情况\n"
|
||||
"- `/status process` 查看进程情况\n"
|
||||
),
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
"toggleable": False,
|
||||
"default_enable": True,
|
||||
},
|
||||
)
|
@ -1,296 +1,296 @@
|
||||
import platform
|
||||
import time
|
||||
|
||||
import nonebot
|
||||
import psutil
|
||||
from cpuinfo import cpuinfo
|
||||
from nonebot import require
|
||||
from nonebot.adapters import satori
|
||||
|
||||
from src.utils import __NAME__, __VERSION__
|
||||
from src.utils.base.config import get_config
|
||||
from src.utils.base.data_manager import TempConfig, common_db
|
||||
from src.utils.base.language import Language
|
||||
from src.utils.base.resource import get_loaded_resource_packs, get_path
|
||||
from src.utils.message.html_tool import template2image
|
||||
from src.utils import satori_utils
|
||||
from .counter_for_satori import satori_counter
|
||||
from git import Repo
|
||||
|
||||
# require("nonebot_plugin_apscheduler")
|
||||
# from nonebot_plugin_apscheduler import scheduler
|
||||
|
||||
commit_hash = Repo(".").head.commit.hexsha
|
||||
|
||||
protocol_names = {
|
||||
0: "苹果iPad",
|
||||
1: "安卓掌机",
|
||||
2: "安卓穿戴",
|
||||
3: "Mac主机",
|
||||
5: "苹果iPad",
|
||||
6: "安卓平板",
|
||||
}
|
||||
|
||||
"""
|
||||
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
|
||||
"""
|
||||
# status_card_cache = {} # lang -> bytes
|
||||
|
||||
|
||||
# 60s刷新一次
|
||||
# 之前写的什么鬼玩意,这么重要的功能这样写???
|
||||
# @scheduler.scheduled_job("cron", second="*/40")
|
||||
# async def refresh_status_card():
|
||||
# nonebot.logger.debug("Refreshing status card cache.")
|
||||
# global status_card_cache
|
||||
# status_card_cache = {}
|
||||
# bot_data = await get_bots_data()
|
||||
# hardware_data = await get_hardware_data()
|
||||
# liteyuki_data = await get_liteyuki_data()
|
||||
# for lang in status_card_cache.keys():
|
||||
# status_card_cache[lang] = await generate_status_card(
|
||||
# bot_data,
|
||||
# hardware_data,
|
||||
# liteyuki_data,
|
||||
# lang=lang,
|
||||
# use_cache=False
|
||||
# )
|
||||
|
||||
|
||||
# 获取状态卡片
|
||||
# bot_id 参数已经是bot参数的一部分了,不需要保留,但为了“兼容性”……
|
||||
async def generate_status_card(
|
||||
bot: dict,
|
||||
hardware: dict,
|
||||
liteyuki: dict,
|
||||
lang="zh-CN",
|
||||
motto={"text":"风朗气清","source":"成语一则"},
|
||||
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),
|
||||
"motto": motto,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
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"),
|
||||
"bots": lang.get("status.bots"),
|
||||
"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"),
|
||||
"days": lang.get("status.days"),
|
||||
"hours": lang.get("status.hours"),
|
||||
"minutes": lang.get("status.minutes"),
|
||||
"seconds": lang.get("status.seconds"),
|
||||
"runtime": lang.get("status.runtime"),
|
||||
"threads": lang.get("status.threads"),
|
||||
"cores": lang.get("status.cores"),
|
||||
"process": lang.get("status.process"),
|
||||
"resources": lang.get("status.resources"),
|
||||
"description": lang.get("status.description"),
|
||||
}
|
||||
|
||||
|
||||
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 = {}
|
||||
if isinstance(bot, satori.Bot):
|
||||
try:
|
||||
bot_name = (await satori_utils.user_infos.get(bot.self_id)).name
|
||||
groups = str(await satori_utils.count_groups(bot))
|
||||
friends = str(await satori_utils.count_friends(bot))
|
||||
status = {}
|
||||
version_info = await bot.get_version_info()
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
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", "未知应用接口")
|
||||
if app_name in ["Lagrange.OneBot", "LLOneBot", "Shamrock", "NapCat.Onebot"]:
|
||||
icon = f"https://q.qlogo.cn/g?b=qq&nk={bot_id}&s=640"
|
||||
elif isinstance(bot, satori.Bot):
|
||||
app_name = "Satori"
|
||||
icon = (await bot.login_get()).user.avatar
|
||||
else:
|
||||
icon = None
|
||||
bot_data = {
|
||||
"name": bot_name,
|
||||
"icon": icon,
|
||||
"id": bot_id,
|
||||
"protocol_name": protocol_names.get(
|
||||
version_info.get("protocol_name"), "在线"
|
||||
),
|
||||
"groups": groups,
|
||||
"friends": friends,
|
||||
"message_sent": (
|
||||
satori_counter.msg_sent
|
||||
if isinstance(bot, satori.Bot)
|
||||
else statistics.get("message_sent", 0)
|
||||
),
|
||||
"message_received": (
|
||||
satori_counter.msg_received
|
||||
if isinstance(bot, satori.Bot)
|
||||
else statistics.get("message_received", 0)
|
||||
),
|
||||
"app_name": app_name,
|
||||
}
|
||||
result["bots"].append(bot_data)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def get_hardware_data() -> dict:
|
||||
mem = psutil.virtual_memory()
|
||||
all_processes = psutil.Process().children(recursive=True)
|
||||
all_processes.append(psutil.Process())
|
||||
|
||||
mem_used_process = 0
|
||||
process_mem = {}
|
||||
for process in all_processes:
|
||||
try:
|
||||
ps_name = process.name().replace(".exe", "")
|
||||
if ps_name not in process_mem:
|
||||
process_mem[ps_name] = 0
|
||||
process_mem[ps_name] += process.memory_info().rss
|
||||
mem_used_process += process.memory_info().rss
|
||||
except Exception:
|
||||
pass
|
||||
swap = psutil.swap_memory()
|
||||
cpu_brand_raw = cpuinfo.get_cpu_info().get("brand_raw", "未知处理器")
|
||||
if "AMD" in cpu_brand_raw:
|
||||
brand = "AMD"
|
||||
elif "Intel" in cpu_brand_raw:
|
||||
brand = "英特尔"
|
||||
elif "Nvidia" in cpu_brand_raw:
|
||||
brand = "英伟达"
|
||||
else:
|
||||
brand = "未知处理器"
|
||||
result = {
|
||||
"cpu": {
|
||||
"percent": psutil.cpu_percent(),
|
||||
"name": f"{brand} {cpuinfo.get_cpu_info().get('arch', '未知架构')}",
|
||||
"cores": psutil.cpu_count(logical=False),
|
||||
"threads": psutil.cpu_count(logical=True),
|
||||
"freq": psutil.cpu_freq().current, # MHz
|
||||
},
|
||||
"memory": {
|
||||
"percent": mem.percent,
|
||||
"total": mem.total,
|
||||
"used": mem.used,
|
||||
"free": mem.free,
|
||||
"usedProcess": mem_used_process,
|
||||
},
|
||||
"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)
|
||||
if disk_usage.total == 0:
|
||||
continue # 虚拟磁盘
|
||||
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.where_one(TempConfig(), default=TempConfig())
|
||||
result = {
|
||||
"name": list(get_config("nickname", [__NAME__]))[0],
|
||||
"version": f"{__VERSION__}{'-' + commit_hash[:7] if (commit_hash and len(commit_hash) > 8) else ''}",
|
||||
"plugins": len(nonebot.get_loaded_plugins()),
|
||||
"resources": len(get_loaded_resource_packs()),
|
||||
"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()), # 运行时间秒数
|
||||
"bots": len(nonebot.get_bots()),
|
||||
}
|
||||
return result
|
||||
import platform
|
||||
import time
|
||||
|
||||
import nonebot
|
||||
import psutil
|
||||
from cpuinfo import cpuinfo
|
||||
from nonebot import require
|
||||
from nonebot.adapters import satori
|
||||
|
||||
from src.utils import __NAME__, __VERSION__
|
||||
from src.utils.base.config import get_config
|
||||
from src.utils.base.data_manager import TempConfig, common_db
|
||||
from src.utils.base.language import Language
|
||||
from src.utils.base.resource import get_loaded_resource_packs, get_path
|
||||
from src.utils.message.html_tool import template2image
|
||||
from src.utils import satori_utils
|
||||
from .counter_for_satori import satori_counter
|
||||
from git import Repo
|
||||
|
||||
# require("nonebot_plugin_apscheduler")
|
||||
# from nonebot_plugin_apscheduler import scheduler
|
||||
|
||||
commit_hash = Repo(".").head.commit.hexsha
|
||||
|
||||
protocol_names = {
|
||||
0: "苹果iPad",
|
||||
1: "安卓掌机",
|
||||
2: "安卓穿戴",
|
||||
3: "Mac主机",
|
||||
5: "苹果iPad",
|
||||
6: "安卓平板",
|
||||
}
|
||||
|
||||
"""
|
||||
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
|
||||
"""
|
||||
# status_card_cache = {} # lang -> bytes
|
||||
|
||||
|
||||
# 60s刷新一次
|
||||
# 之前写的什么鬼玩意,这么重要的功能这样写???
|
||||
# @scheduler.scheduled_job("cron", second="*/40")
|
||||
# async def refresh_status_card():
|
||||
# nonebot.logger.debug("Refreshing status card cache.")
|
||||
# global status_card_cache
|
||||
# status_card_cache = {}
|
||||
# bot_data = await get_bots_data()
|
||||
# hardware_data = await get_hardware_data()
|
||||
# liteyuki_data = await get_liteyuki_data()
|
||||
# for lang in status_card_cache.keys():
|
||||
# status_card_cache[lang] = await generate_status_card(
|
||||
# bot_data,
|
||||
# hardware_data,
|
||||
# liteyuki_data,
|
||||
# lang=lang,
|
||||
# use_cache=False
|
||||
# )
|
||||
|
||||
|
||||
# 获取状态卡片
|
||||
# bot_id 参数已经是bot参数的一部分了,不需要保留,但为了“兼容性”……
|
||||
async def generate_status_card(
|
||||
bot: dict,
|
||||
hardware: dict,
|
||||
liteyuki: dict,
|
||||
lang="zh-CN",
|
||||
motto={"text": "风朗气清", "source": "成语一则"},
|
||||
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),
|
||||
"motto": motto,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
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"),
|
||||
"bots": lang.get("status.bots"),
|
||||
"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"),
|
||||
"days": lang.get("status.days"),
|
||||
"hours": lang.get("status.hours"),
|
||||
"minutes": lang.get("status.minutes"),
|
||||
"seconds": lang.get("status.seconds"),
|
||||
"runtime": lang.get("status.runtime"),
|
||||
"threads": lang.get("status.threads"),
|
||||
"cores": lang.get("status.cores"),
|
||||
"process": lang.get("status.process"),
|
||||
"resources": lang.get("status.resources"),
|
||||
"description": lang.get("status.description"),
|
||||
}
|
||||
|
||||
|
||||
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 = {}
|
||||
if isinstance(bot, satori.Bot):
|
||||
try:
|
||||
bot_name = (await satori_utils.user_infos.get(bot.self_id)).name
|
||||
groups = str(await satori_utils.count_groups(bot))
|
||||
friends = str(await satori_utils.count_friends(bot))
|
||||
status = {}
|
||||
version_info = await bot.get_version_info()
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
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", "未知应用接口")
|
||||
if app_name in ["Lagrange.OneBot", "LLOneBot", "Shamrock", "NapCat.Onebot"]:
|
||||
icon = f"https://q.qlogo.cn/g?b=qq&nk={bot_id}&s=640"
|
||||
elif isinstance(bot, satori.Bot):
|
||||
app_name = "Satori"
|
||||
icon = (await bot.login_get()).user.avatar
|
||||
else:
|
||||
icon = None
|
||||
bot_data = {
|
||||
"name": bot_name,
|
||||
"icon": icon,
|
||||
"id": bot_id,
|
||||
"protocol_name": protocol_names.get(
|
||||
version_info.get("protocol_name"), "在线"
|
||||
),
|
||||
"groups": groups,
|
||||
"friends": friends,
|
||||
"message_sent": (
|
||||
satori_counter.msg_sent
|
||||
if isinstance(bot, satori.Bot)
|
||||
else statistics.get("message_sent", 0)
|
||||
),
|
||||
"message_received": (
|
||||
satori_counter.msg_received
|
||||
if isinstance(bot, satori.Bot)
|
||||
else statistics.get("message_received", 0)
|
||||
),
|
||||
"app_name": app_name,
|
||||
}
|
||||
result["bots"].append(bot_data)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def get_hardware_data() -> dict:
|
||||
mem = psutil.virtual_memory()
|
||||
all_processes = psutil.Process().children(recursive=True)
|
||||
all_processes.append(psutil.Process())
|
||||
|
||||
mem_used_process = 0
|
||||
process_mem = {}
|
||||
for process in all_processes:
|
||||
try:
|
||||
ps_name = process.name().replace(".exe", "")
|
||||
if ps_name not in process_mem:
|
||||
process_mem[ps_name] = 0
|
||||
process_mem[ps_name] += process.memory_info().rss
|
||||
mem_used_process += process.memory_info().rss
|
||||
except Exception:
|
||||
pass
|
||||
swap = psutil.swap_memory()
|
||||
cpu_brand_raw = cpuinfo.get_cpu_info().get("brand_raw", "未知处理器")
|
||||
if "AMD" in cpu_brand_raw:
|
||||
brand = "AMD"
|
||||
elif "Intel" in cpu_brand_raw:
|
||||
brand = "英特尔"
|
||||
elif "Nvidia" in cpu_brand_raw:
|
||||
brand = "英伟达"
|
||||
else:
|
||||
brand = "未知处理器"
|
||||
result = {
|
||||
"cpu": {
|
||||
"percent": psutil.cpu_percent(),
|
||||
"name": f"{brand} {cpuinfo.get_cpu_info().get('arch', '未知架构')}",
|
||||
"cores": psutil.cpu_count(logical=False),
|
||||
"threads": psutil.cpu_count(logical=True),
|
||||
"freq": psutil.cpu_freq().current, # MHz
|
||||
},
|
||||
"memory": {
|
||||
"percent": mem.percent,
|
||||
"total": mem.total,
|
||||
"used": mem.used,
|
||||
"free": mem.free,
|
||||
"usedProcess": mem_used_process,
|
||||
},
|
||||
"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)
|
||||
if disk_usage.total == 0:
|
||||
continue # 虚拟磁盘
|
||||
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.where_one(TempConfig(), default=TempConfig())
|
||||
result = {
|
||||
"name": list(get_config("nickname", [__NAME__]))[0],
|
||||
"version": f"{__VERSION__}{'-' + commit_hash[:7] if (commit_hash and len(commit_hash) > 8) else ''}",
|
||||
"plugins": len(nonebot.get_loaded_plugins()),
|
||||
"resources": len(get_loaded_resource_packs()),
|
||||
"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()), # 运行时间秒数
|
||||
"bots": len(nonebot.get_bots()),
|
||||
}
|
||||
return result
|
@ -1,10 +1,10 @@
|
||||
class SatoriCounter:
|
||||
msg_sent: int
|
||||
msg_received: int
|
||||
|
||||
def __init__(self):
|
||||
self.msg_sent = 0
|
||||
self.msg_received = 0
|
||||
|
||||
|
||||
satori_counter = SatoriCounter()
|
||||
class SatoriCounter:
|
||||
msg_sent: int
|
||||
msg_received: int
|
||||
|
||||
def __init__(self):
|
||||
self.msg_sent = 0
|
||||
self.msg_received = 0
|
||||
|
||||
|
||||
satori_counter = SatoriCounter()
|
@ -1,303 +1,303 @@
|
||||
import zhDateTime
|
||||
import requests
|
||||
import random
|
||||
|
||||
from src.utils import event as event_utils
|
||||
from src.utils.base.language import get_user_lang, get_default_lang_code, Language
|
||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
from .api import *
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
from nonebot_plugin_alconna import (
|
||||
on_alconna,
|
||||
Alconna,
|
||||
Subcommand,
|
||||
UniMessage,
|
||||
Option,
|
||||
store_true,
|
||||
# AlconnaQuery,
|
||||
# Query,
|
||||
Arparma,
|
||||
Args,
|
||||
)
|
||||
|
||||
require("nonebot_plugin_apscheduler")
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
|
||||
status_alc = on_alconna(
|
||||
aliases={"状态"},
|
||||
command=Alconna(
|
||||
"status",
|
||||
Option(
|
||||
"-r|--refresh",
|
||||
default=False,
|
||||
alias={"refr", "r", "刷新"},
|
||||
action=store_true,
|
||||
),
|
||||
Subcommand(
|
||||
"memory",
|
||||
alias={"mem", "m", "内存"},
|
||||
),
|
||||
Subcommand(
|
||||
"process",
|
||||
alias={"proc", "p", "进程"},
|
||||
),
|
||||
# Subcommand(
|
||||
# "refresh",
|
||||
# alias={"refr", "r", "刷新"},
|
||||
# ),
|
||||
),
|
||||
)
|
||||
|
||||
yanlun = on_alconna(
|
||||
aliases={"yanlun", "言·论", "yan_lun"},
|
||||
command=Alconna(
|
||||
"言论",
|
||||
Option(
|
||||
"-r|--refresh",
|
||||
default=False,
|
||||
alias={"刷新", "更新", "update"},
|
||||
action=store_true,
|
||||
),
|
||||
Option("-c|--count", default=False, alias={"统计"}, action=store_true),
|
||||
Option("-l|--length", default=1.0, args=Args["length", float | int, 1.0]),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
yanlun_path = (
|
||||
"https://gitee.com/TriM-Organization/LinglunStudio/raw/master/resources/myWords.txt"
|
||||
)
|
||||
|
||||
|
||||
# 每天4点更新
|
||||
@scheduler.scheduled_job("cron", hour=4)
|
||||
async def every_day_update():
|
||||
ulang = Language(get_default_lang_code(), "zh-WY")
|
||||
nonebot.logger.success(ulang.get("yanlun.refresh.success", COUNT=update_yanlun()))
|
||||
|
||||
|
||||
def update_yanlun():
|
||||
global yanlun_texts
|
||||
|
||||
solar_datetime = zhDateTime.DateTime.now()
|
||||
lunar_datetime = solar_datetime.to_lunar()
|
||||
solar_date = (solar_datetime.month, solar_datetime.day)
|
||||
lunar_date = (lunar_datetime.lunar_month, lunar_datetime.lunar_day)
|
||||
|
||||
if solar_date == (4, 3):
|
||||
yanlun_texts = ["金羿ELS 生日快乐~!", "Happy Birthday, Eilles!"]
|
||||
elif solar_date == (8, 6):
|
||||
yanlun_texts = ["诸葛亮与八卦阵 生日快乐~!", "Happy Birthday, bgArray~!"]
|
||||
elif solar_date == (8, 16):
|
||||
yanlun_texts = ["鱼旧梦 生日快乐~!", "Happy Birthday, ElapsingDreams~!"]
|
||||
|
||||
else:
|
||||
try:
|
||||
yanlun_texts = (
|
||||
requests.get(
|
||||
yanlun_path,
|
||||
)
|
||||
.text.strip("\n")
|
||||
.split("\n")
|
||||
)
|
||||
except (ConnectionError, requests.HTTPError, requests.RequestException) as E:
|
||||
nonebot.logger.warning(f"读取言·论信息发生 互联网连接 错误:\n{E}")
|
||||
yanlun_texts = ["以梦想为驱使 创造属于自己的未来"]
|
||||
# noinspection PyBroadException
|
||||
except BaseException as E:
|
||||
nonebot.logger.warning(f"读取言·论信息发生 未知 错误:\n{E}")
|
||||
yanlun_texts = ["灵光焕发 深艺献心"]
|
||||
|
||||
return len(yanlun_texts)
|
||||
|
||||
|
||||
update_yanlun()
|
||||
|
||||
|
||||
def random_yanlun() -> tuple:
|
||||
seq = random.choice(yanlun_texts).replace(" ", "\t").split("\t——", 1)
|
||||
return seq[0], "" if len(seq) == 1 else seq[1]
|
||||
|
||||
|
||||
status_card_cache = {} # lang -> bytes
|
||||
|
||||
|
||||
@status_alc.handle()
|
||||
async def _(
|
||||
result: Arparma,
|
||||
event: T_MessageEvent,
|
||||
bot: T_Bot,
|
||||
# refresh: Query[bool] = AlconnaQuery("refresh.value", False),
|
||||
):
|
||||
ulang = get_user_lang(event_utils.get_user_id(event)) # type: ignore
|
||||
global status_card_cache
|
||||
if (
|
||||
result.options["refresh"].value
|
||||
or ulang.lang_code not in status_card_cache.keys()
|
||||
or (
|
||||
ulang.lang_code in status_card_cache.keys()
|
||||
and time.time() - status_card_cache[ulang.lang_code][1] > 300 # 缓存
|
||||
)
|
||||
):
|
||||
status_card_cache[ulang.lang_code] = (
|
||||
await generate_status_card(
|
||||
bot=await get_bots_data(),
|
||||
hardware=await get_hardware_data(),
|
||||
liteyuki=await get_liteyuki_data(),
|
||||
lang=ulang.lang_code,
|
||||
motto=dict(zip(["text", "source"], random_yanlun())),
|
||||
bot_id=bot.self_id,
|
||||
),
|
||||
time.time(),
|
||||
)
|
||||
image = status_card_cache[ulang.lang_code][0]
|
||||
await status_alc.finish(UniMessage.image(raw=image))
|
||||
|
||||
|
||||
@status_alc.assign("memory")
|
||||
async def _():
|
||||
print("memory")
|
||||
|
||||
|
||||
@status_alc.assign("process")
|
||||
async def _():
|
||||
print("process")
|
||||
|
||||
|
||||
@yanlun.handle()
|
||||
async def _(
|
||||
result: Arparma,
|
||||
event: T_MessageEvent,
|
||||
bot: T_Bot,
|
||||
# refresh: Query[bool] = AlconnaQuery("refresh.value", False),
|
||||
# count: Query[bool] = AlconnaQuery("count.value", False),
|
||||
):
|
||||
# print(result.options)
|
||||
ulang = get_user_lang(event_utils.get_user_id(event)) # type: ignore
|
||||
if result.options["refresh"].value:
|
||||
global yanlun_texts
|
||||
try:
|
||||
yanlun_texts = (
|
||||
requests.get(
|
||||
yanlun_path,
|
||||
)
|
||||
.text.strip("\n")
|
||||
.split("\n")
|
||||
)
|
||||
await yanlun.send(
|
||||
UniMessage.text(
|
||||
ulang.get("yanlun.refresh.success", COUNT=len(yanlun_texts))
|
||||
)
|
||||
)
|
||||
except (ConnectionError, requests.HTTPError, requests.RequestException) as E:
|
||||
await yanlun.send(
|
||||
UniMessage.text(
|
||||
ulang.get(
|
||||
"yanlun.refresh.failed",
|
||||
ERR=ulang.get("yanlun.errtype.net"),
|
||||
ERRCODE=f"\n{E}",
|
||||
)
|
||||
)
|
||||
)
|
||||
yanlun_texts = ["以梦想为驱使 创造属于自己的未来"]
|
||||
# noinspection PyBroadException
|
||||
except BaseException as E:
|
||||
await yanlun.send(
|
||||
UniMessage.text(
|
||||
ulang.get(
|
||||
"yanlun.refresh.failed",
|
||||
ERR=ulang.get("yanlun.errtype.unknown"),
|
||||
ERRCODE=f"\n{E}",
|
||||
)
|
||||
)
|
||||
)
|
||||
yanlun_texts = ["灵光焕发 深艺献心"]
|
||||
if result.options["count"].value:
|
||||
authors = [
|
||||
(
|
||||
("B站")
|
||||
if ("\t——B站" in i.upper() or " ——B站" in i.upper())
|
||||
else (
|
||||
i.split("\t——")[1].replace(" ", "")
|
||||
if "\t——" in i
|
||||
else (
|
||||
i.split(" ——")[1].replace(" ", "")
|
||||
if " ——" in i
|
||||
else ("MYH")
|
||||
)
|
||||
)
|
||||
)
|
||||
for i in yanlun_texts
|
||||
]
|
||||
|
||||
total = len(yanlun_texts)
|
||||
|
||||
chart = sorted(
|
||||
[(i, authors.count(i)) for i in set(authors)],
|
||||
key=lambda x: x[1],
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
await yanlun.send(
|
||||
UniMessage.text(
|
||||
ulang.get("yanlun.count.head").replace("ttt", "\t")
|
||||
+ "\n"
|
||||
+ "".join(
|
||||
[
|
||||
(
|
||||
"{}\t{}({}%)\n".format(
|
||||
aut, cnt, int(cnt * 10000 / total + 0.5) / 100
|
||||
)
|
||||
if cnt * 100 / total >= chart[10][1] * 100 / total
|
||||
else ""
|
||||
)
|
||||
for aut, cnt in chart
|
||||
]
|
||||
)
|
||||
+ ulang.get("yanlun.count.tail", NUM=total)
|
||||
)
|
||||
)
|
||||
|
||||
final_length = 0
|
||||
try:
|
||||
final_length += result.options["length"].args["length"]
|
||||
except:
|
||||
final_length = 1
|
||||
|
||||
(
|
||||
(
|
||||
await yanlun.finish(
|
||||
UniMessage.text(
|
||||
"\n".join([random.choice(yanlun_texts) for i in range(iill)])
|
||||
if iill <= 100
|
||||
else ulang.get("yanlun.length.toolong")
|
||||
)
|
||||
)
|
||||
if iill > 0
|
||||
else await yanlun.finish(
|
||||
UniMessage.text(ulang.get("yanlun.length.tooshort"))
|
||||
)
|
||||
)
|
||||
if (iill := int(final_length)) == final_length
|
||||
else await yanlun.finish(UniMessage.text(ulang.get("yanlun.length.float")))
|
||||
)
|
||||
|
||||
|
||||
time_query = on_alconna(
|
||||
command=Alconna(
|
||||
"时间",
|
||||
),
|
||||
aliases={"时间查询", "timeq", "timequery"},
|
||||
)
|
||||
|
||||
|
||||
@time_query.handle()
|
||||
async def _(
|
||||
event: T_MessageEvent,
|
||||
bot: T_Bot,
|
||||
):
|
||||
# ulang = get_user_lang(event_utils.get_user_id(event)) # type: ignore
|
||||
await time_query.finish(
|
||||
UniMessage.text(zhDateTime.DateTime.now().to_lunar().hanzify())
|
||||
)
|
||||
import zhDateTime
|
||||
import requests
|
||||
import random
|
||||
|
||||
from src.utils import event as event_utils
|
||||
from src.utils.base.language import get_user_lang, get_default_lang_code, Language
|
||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
from .api import *
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
from nonebot_plugin_alconna import (
|
||||
on_alconna,
|
||||
Alconna,
|
||||
Subcommand,
|
||||
UniMessage,
|
||||
Option,
|
||||
store_true,
|
||||
# AlconnaQuery,
|
||||
# Query,
|
||||
Arparma,
|
||||
Args,
|
||||
)
|
||||
|
||||
require("nonebot_plugin_apscheduler")
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
|
||||
status_alc = on_alconna(
|
||||
aliases={"状态"},
|
||||
command=Alconna(
|
||||
"status",
|
||||
Option(
|
||||
"-r|--refresh",
|
||||
default=False,
|
||||
alias={"refr", "r", "刷新"},
|
||||
action=store_true,
|
||||
),
|
||||
Subcommand(
|
||||
"memory",
|
||||
alias={"mem", "m", "内存"},
|
||||
),
|
||||
Subcommand(
|
||||
"process",
|
||||
alias={"proc", "p", "进程"},
|
||||
),
|
||||
# Subcommand(
|
||||
# "refresh",
|
||||
# alias={"refr", "r", "刷新"},
|
||||
# ),
|
||||
),
|
||||
)
|
||||
|
||||
yanlun = on_alconna(
|
||||
aliases={"yanlun", "言·论", "yan_lun"},
|
||||
command=Alconna(
|
||||
"言论",
|
||||
Option(
|
||||
"-r|--refresh",
|
||||
default=False,
|
||||
alias={"刷新", "更新", "update"},
|
||||
action=store_true,
|
||||
),
|
||||
Option("-c|--count", default=False, alias={"统计"}, action=store_true),
|
||||
Option("-l|--length", default=1.0, args=Args["length", float | int, 1.0]),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
yanlun_path = (
|
||||
"https://gitee.com/TriM-Organization/LinglunStudio/raw/master/resources/myWords.txt"
|
||||
)
|
||||
|
||||
|
||||
# 每天4点更新
|
||||
@scheduler.scheduled_job("cron", hour=4)
|
||||
async def every_day_update():
|
||||
ulang = Language(get_default_lang_code(), "zh-WY")
|
||||
nonebot.logger.success(ulang.get("yanlun.refresh.success", COUNT=update_yanlun()))
|
||||
|
||||
|
||||
def update_yanlun():
|
||||
global yanlun_texts
|
||||
|
||||
solar_datetime = zhDateTime.DateTime.now()
|
||||
lunar_datetime = solar_datetime.to_lunar()
|
||||
solar_date = (solar_datetime.month, solar_datetime.day)
|
||||
lunar_date = (lunar_datetime.lunar_month, lunar_datetime.lunar_day)
|
||||
|
||||
if solar_date == (4, 3):
|
||||
yanlun_texts = ["金羿ELS 生日快乐~!", "Happy Birthday, Eilles!"]
|
||||
elif solar_date == (8, 6):
|
||||
yanlun_texts = ["诸葛亮与八卦阵 生日快乐~!", "Happy Birthday, bgArray~!"]
|
||||
elif solar_date == (8, 16):
|
||||
yanlun_texts = ["鱼旧梦 生日快乐~!", "Happy Birthday, ElapsingDreams~!"]
|
||||
|
||||
else:
|
||||
try:
|
||||
yanlun_texts = (
|
||||
requests.get(
|
||||
yanlun_path,
|
||||
)
|
||||
.text.strip("\n")
|
||||
.split("\n")
|
||||
)
|
||||
except (ConnectionError, requests.HTTPError, requests.RequestException) as E:
|
||||
nonebot.logger.warning(f"读取言·论信息发生 互联网连接 错误:\n{E}")
|
||||
yanlun_texts = ["以梦想为驱使 创造属于自己的未来"]
|
||||
# noinspection PyBroadException
|
||||
except BaseException as E:
|
||||
nonebot.logger.warning(f"读取言·论信息发生 未知 错误:\n{E}")
|
||||
yanlun_texts = ["灵光焕发 深艺献心"]
|
||||
|
||||
return len(yanlun_texts)
|
||||
|
||||
|
||||
update_yanlun()
|
||||
|
||||
|
||||
def random_yanlun() -> tuple:
|
||||
seq = random.choice(yanlun_texts).replace(" ", "\t").split("\t——", 1)
|
||||
return seq[0], "" if len(seq) == 1 else seq[1]
|
||||
|
||||
|
||||
status_card_cache = {} # lang -> bytes
|
||||
|
||||
|
||||
@status_alc.handle()
|
||||
async def _(
|
||||
result: Arparma,
|
||||
event: T_MessageEvent,
|
||||
bot: T_Bot,
|
||||
# refresh: Query[bool] = AlconnaQuery("refresh.value", False),
|
||||
):
|
||||
ulang = get_user_lang(event_utils.get_user_id(event)) # type: ignore
|
||||
global status_card_cache
|
||||
if (
|
||||
result.options["refresh"].value
|
||||
or ulang.lang_code not in status_card_cache.keys()
|
||||
or (
|
||||
ulang.lang_code in status_card_cache.keys()
|
||||
and time.time() - status_card_cache[ulang.lang_code][1] > 300 # 缓存
|
||||
)
|
||||
):
|
||||
status_card_cache[ulang.lang_code] = (
|
||||
await generate_status_card(
|
||||
bot=await get_bots_data(),
|
||||
hardware=await get_hardware_data(),
|
||||
liteyuki=await get_liteyuki_data(),
|
||||
lang=ulang.lang_code,
|
||||
motto=dict(zip(["text", "source"], random_yanlun())),
|
||||
bot_id=bot.self_id,
|
||||
),
|
||||
time.time(),
|
||||
)
|
||||
image = status_card_cache[ulang.lang_code][0]
|
||||
await status_alc.finish(UniMessage.image(raw=image))
|
||||
|
||||
|
||||
@status_alc.assign("memory")
|
||||
async def _():
|
||||
pass
|
||||
|
||||
|
||||
@status_alc.assign("process")
|
||||
async def _():
|
||||
pass
|
||||
|
||||
|
||||
@yanlun.handle()
|
||||
async def _(
|
||||
result: Arparma,
|
||||
event: T_MessageEvent,
|
||||
bot: T_Bot,
|
||||
# refresh: Query[bool] = AlconnaQuery("refresh.value", False),
|
||||
# count: Query[bool] = AlconnaQuery("count.value", False),
|
||||
):
|
||||
# print(result.options)
|
||||
ulang = get_user_lang(event_utils.get_user_id(event)) # type: ignore
|
||||
if result.options["refresh"].value:
|
||||
global yanlun_texts
|
||||
try:
|
||||
yanlun_texts = (
|
||||
requests.get(
|
||||
yanlun_path,
|
||||
)
|
||||
.text.strip("\n")
|
||||
.split("\n")
|
||||
)
|
||||
await yanlun.send(
|
||||
UniMessage.text(
|
||||
ulang.get("yanlun.refresh.success", COUNT=len(yanlun_texts))
|
||||
)
|
||||
)
|
||||
except (ConnectionError, requests.HTTPError, requests.RequestException) as E:
|
||||
await yanlun.send(
|
||||
UniMessage.text(
|
||||
ulang.get(
|
||||
"yanlun.refresh.failed",
|
||||
ERR=ulang.get("yanlun.errtype.net"),
|
||||
ERRCODE=f"\n{E}",
|
||||
)
|
||||
)
|
||||
)
|
||||
yanlun_texts = ["以梦想为驱使 创造属于自己的未来"]
|
||||
# noinspection PyBroadException
|
||||
except BaseException as E:
|
||||
await yanlun.send(
|
||||
UniMessage.text(
|
||||
ulang.get(
|
||||
"yanlun.refresh.failed",
|
||||
ERR=ulang.get("yanlun.errtype.unknown"),
|
||||
ERRCODE=f"\n{E}",
|
||||
)
|
||||
)
|
||||
)
|
||||
yanlun_texts = ["灵光焕发 深艺献心"]
|
||||
if result.options["count"].value:
|
||||
authors = [
|
||||
(
|
||||
("B站")
|
||||
if ("\t——B站" in i.upper() or " ——B站" in i.upper())
|
||||
else (
|
||||
i.split("\t——")[1].replace(" ", "")
|
||||
if "\t——" in i
|
||||
else (
|
||||
i.split(" ——")[1].replace(" ", "")
|
||||
if " ——" in i
|
||||
else ("MYH")
|
||||
)
|
||||
)
|
||||
)
|
||||
for i in yanlun_texts
|
||||
]
|
||||
|
||||
total = len(yanlun_texts)
|
||||
|
||||
chart = sorted(
|
||||
[(i, authors.count(i)) for i in set(authors)],
|
||||
key=lambda x: x[1],
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
await yanlun.send(
|
||||
UniMessage.text(
|
||||
ulang.get("yanlun.count.head").replace("ttt", "\t")
|
||||
+ "\n"
|
||||
+ "".join(
|
||||
[
|
||||
(
|
||||
"{}\t{}({}%)\n".format(
|
||||
aut, cnt, int(cnt * 10000 / total + 0.5) / 100
|
||||
)
|
||||
if cnt * 100 / total >= chart[10][1] * 100 / total
|
||||
else ""
|
||||
)
|
||||
for aut, cnt in chart
|
||||
]
|
||||
)
|
||||
+ ulang.get("yanlun.count.tail", NUM=total)
|
||||
)
|
||||
)
|
||||
|
||||
final_length = 0
|
||||
try:
|
||||
final_length += result.options["length"].args["length"]
|
||||
except:
|
||||
final_length = 1
|
||||
|
||||
(
|
||||
(
|
||||
await yanlun.finish(
|
||||
UniMessage.text(
|
||||
"\n".join([random.choice(yanlun_texts) for i in range(iill)])
|
||||
if iill <= 100
|
||||
else ulang.get("yanlun.length.toolong")
|
||||
)
|
||||
)
|
||||
if iill > 0
|
||||
else await yanlun.finish(
|
||||
UniMessage.text(ulang.get("yanlun.length.tooshort"))
|
||||
)
|
||||
)
|
||||
if (iill := int(final_length)) == final_length
|
||||
else await yanlun.finish(UniMessage.text(ulang.get("yanlun.length.float")))
|
||||
)
|
||||
|
||||
|
||||
time_query = on_alconna(
|
||||
command=Alconna(
|
||||
"时间",
|
||||
),
|
||||
aliases={"时间查询", "timeq", "timequery"},
|
||||
)
|
||||
|
||||
|
||||
@time_query.handle()
|
||||
async def _(
|
||||
event: T_MessageEvent,
|
||||
bot: T_Bot,
|
||||
):
|
||||
# ulang = get_user_lang(event_utils.get_user_id(event)) # type: ignore
|
||||
await time_query.finish(
|
||||
UniMessage.text(zhDateTime.DateTime.now().to_lunar().hanzify())
|
||||
)
|
@ -1,17 +1,17 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .api import *
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="联合黑名单(测试中...)",
|
||||
description="",
|
||||
usage="",
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
"toggleable" : True,
|
||||
"default_enable" : True,
|
||||
}
|
||||
)
|
||||
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .api import *
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="联合黑名单(测试中...)",
|
||||
description="",
|
||||
usage="",
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
"toggleable" : True,
|
||||
"default_enable" : True,
|
||||
}
|
||||
)
|
||||
|
@ -1,59 +1,59 @@
|
||||
import datetime
|
||||
|
||||
import aiohttp
|
||||
import httpx
|
||||
import nonebot
|
||||
from nonebot import require
|
||||
from nonebot.exception import IgnoredException
|
||||
from nonebot.message import event_preprocessor
|
||||
from nonebot_plugin_alconna.typings import Event
|
||||
|
||||
require("nonebot_plugin_apscheduler")
|
||||
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
|
||||
blacklist_data: dict[str, set[str]] = {}
|
||||
blacklist: set[str] = set()
|
||||
|
||||
|
||||
@scheduler.scheduled_job("interval", minutes=10, next_run_time=datetime.datetime.now())
|
||||
async def update_blacklist():
|
||||
await request_for_blacklist()
|
||||
|
||||
|
||||
async def request_for_blacklist():
|
||||
global blacklist
|
||||
urls = [
|
||||
"https://cdn.liteyuki.icu/static/ubl/"
|
||||
]
|
||||
|
||||
platforms = [
|
||||
"qq"
|
||||
]
|
||||
|
||||
for plat in platforms:
|
||||
for url in urls:
|
||||
url += f"{plat}.txt"
|
||||
async with aiohttp.ClientSession() as client:
|
||||
resp = await client.get(url)
|
||||
blacklist_data[plat] = set((await resp.text()).splitlines())
|
||||
blacklist = get_uni_set()
|
||||
nonebot.logger.info("blacklists updated")
|
||||
|
||||
|
||||
def get_uni_set() -> set:
|
||||
s = set()
|
||||
for new_set in blacklist_data.values():
|
||||
s.update(new_set)
|
||||
return s
|
||||
|
||||
|
||||
@event_preprocessor
|
||||
async def pre_handle(event: Event):
|
||||
try:
|
||||
user_id = str(event.get_user_id())
|
||||
except:
|
||||
return
|
||||
|
||||
if user_id in get_uni_set():
|
||||
raise IgnoredException("UserId in blacklist")
|
||||
import datetime
|
||||
|
||||
import aiohttp
|
||||
import httpx
|
||||
import nonebot
|
||||
from nonebot import require
|
||||
from nonebot.exception import IgnoredException
|
||||
from nonebot.message import event_preprocessor
|
||||
from nonebot_plugin_alconna.typings import Event
|
||||
|
||||
require("nonebot_plugin_apscheduler")
|
||||
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
|
||||
blacklist_data: dict[str, set[str]] = {}
|
||||
blacklist: set[str] = set()
|
||||
|
||||
|
||||
@scheduler.scheduled_job("interval", minutes=10, next_run_time=datetime.datetime.now())
|
||||
async def update_blacklist():
|
||||
await request_for_blacklist()
|
||||
|
||||
|
||||
async def request_for_blacklist():
|
||||
global blacklist
|
||||
urls = [
|
||||
"https://cdn.liteyuki.icu/static/ubl/"
|
||||
]
|
||||
|
||||
platforms = [
|
||||
"qq"
|
||||
]
|
||||
|
||||
for plat in platforms:
|
||||
for url in urls:
|
||||
url += f"{plat}.txt"
|
||||
async with aiohttp.ClientSession() as client:
|
||||
resp = await client.get(url)
|
||||
blacklist_data[plat] = set((await resp.text()).splitlines())
|
||||
blacklist = get_uni_set()
|
||||
nonebot.logger.info("blacklists updated")
|
||||
|
||||
|
||||
def get_uni_set() -> set:
|
||||
s = set()
|
||||
for new_set in blacklist_data.values():
|
||||
s.update(new_set)
|
||||
return s
|
||||
|
||||
|
||||
@event_preprocessor
|
||||
async def pre_handle(event: Event):
|
||||
try:
|
||||
user_id = str(event.get_user_id())
|
||||
except:
|
||||
return
|
||||
|
||||
if user_id in get_uni_set():
|
||||
raise IgnoredException("UserId in blacklist")
|
@ -1,16 +1,16 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
from .profile_manager import *
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪用户管理",
|
||||
description="用户管理插件",
|
||||
usage="",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki" : True,
|
||||
"toggleable" : False,
|
||||
"default_enable": True,
|
||||
}
|
||||
)
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
from .profile_manager import *
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪用户管理",
|
||||
description="用户管理插件",
|
||||
usage="",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki" : True,
|
||||
"toggleable" : False,
|
||||
"default_enable": True,
|
||||
}
|
||||
)
|
@ -1,23 +1,23 @@
|
||||
representative_timezones_list = [
|
||||
"Etc/GMT+12", # 国际日期变更线西
|
||||
"Pacific/Honolulu", # 夏威夷标准时间
|
||||
"America/Anchorage", # 阿拉斯加标准时间
|
||||
"America/Los_Angeles", # 美国太平洋标准时间
|
||||
"America/Denver", # 美国山地标准时间
|
||||
"America/Chicago", # 美国中部标准时间
|
||||
"America/New_York", # 美国东部标准时间
|
||||
"Europe/London", # 英国标准时间
|
||||
"Europe/Paris", # 中欧标准时间
|
||||
"Europe/Moscow", # 莫斯科标准时间
|
||||
"Asia/Dubai", # 阿联酋标准时间
|
||||
"Asia/Kolkata", # 印度标准时间
|
||||
"Asia/Shanghai", # 中国标准时间
|
||||
"Asia/Hong_Kong", # 中国香港标准时间
|
||||
"Asia/Chongqing", # 中国重庆标准时间
|
||||
"Asia/Macau", # 中国澳门标准时间
|
||||
"Asia/Taipei", # 中国台湾标准时间
|
||||
"Asia/Tokyo", # 日本标准时间
|
||||
"Australia/Sydney", # 澳大利亚东部标准时间
|
||||
"Pacific/Auckland" # 新西兰标准时间
|
||||
]
|
||||
representative_timezones_list = [
|
||||
"Etc/GMT+12", # 国际日期变更线西
|
||||
"Pacific/Honolulu", # 夏威夷标准时间
|
||||
"America/Anchorage", # 阿拉斯加标准时间
|
||||
"America/Los_Angeles", # 美国太平洋标准时间
|
||||
"America/Denver", # 美国山地标准时间
|
||||
"America/Chicago", # 美国中部标准时间
|
||||
"America/New_York", # 美国东部标准时间
|
||||
"Europe/London", # 英国标准时间
|
||||
"Europe/Paris", # 中欧标准时间
|
||||
"Europe/Moscow", # 莫斯科标准时间
|
||||
"Asia/Dubai", # 阿联酋标准时间
|
||||
"Asia/Kolkata", # 印度标准时间
|
||||
"Asia/Shanghai", # 中国标准时间
|
||||
"Asia/Hong_Kong", # 中国香港标准时间
|
||||
"Asia/Chongqing", # 中国重庆标准时间
|
||||
"Asia/Macau", # 中国澳门标准时间
|
||||
"Asia/Taipei", # 中国台湾标准时间
|
||||
"Asia/Tokyo", # 日本标准时间
|
||||
"Australia/Sydney", # 澳大利亚东部标准时间
|
||||
"Pacific/Auckland" # 新西兰标准时间
|
||||
]
|
||||
representative_timezones_list.sort()
|
@ -1,150 +1,150 @@
|
||||
from typing import Optional
|
||||
|
||||
import pytz
|
||||
from nonebot import require
|
||||
|
||||
from src.utils.base.data import LiteModel, Database
|
||||
from src.utils.base.data_manager import User, user_db, group_db
|
||||
from src.utils.base.language import Language, change_user_lang, get_all_lang, get_user_lang
|
||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
from src.utils.message.message import MarkdownMessage as md
|
||||
from .const import representative_timezones_list
|
||||
from src.utils import event as event_utils
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
from nonebot_plugin_alconna import Alconna, Args, Arparma, Subcommand, on_alconna
|
||||
|
||||
profile_alc = on_alconna(
|
||||
Alconna(
|
||||
"profile",
|
||||
Subcommand(
|
||||
"set",
|
||||
Args["key", str]["value", str, None],
|
||||
alias=["s", "设置"],
|
||||
),
|
||||
Subcommand(
|
||||
"get",
|
||||
Args["key", str],
|
||||
alias=["g", "查询"],
|
||||
),
|
||||
),
|
||||
aliases={"用户信息"}
|
||||
)
|
||||
|
||||
|
||||
# json储存
|
||||
class Profile(LiteModel):
|
||||
lang: str = "zh-CN"
|
||||
nickname: str = ""
|
||||
timezone: str = "Asia/Shanghai"
|
||||
location: str = ""
|
||||
|
||||
|
||||
@profile_alc.handle()
|
||||
async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot):
|
||||
user: User = user_db.where_one(User(), "user_id = ?", event_utils.get_user_id(event),
|
||||
default=User(user_id=str(event_utils.get_user_id(event))))
|
||||
ulang = get_user_lang(str(event_utils.get_user_id(event)))
|
||||
if result.subcommands.get("set"):
|
||||
if result.subcommands["set"].args.get("value"):
|
||||
# 对合法性进行校验后设置
|
||||
r = set_profile(result.args["key"], result.args["value"], str(event_utils.get_user_id(event)))
|
||||
if r:
|
||||
user.profile[result.args["key"]] = result.args["value"]
|
||||
user_db.save(user) # 数据库保存
|
||||
await profile_alc.finish(
|
||||
ulang.get(
|
||||
"user.profile.set_success",
|
||||
ATTR=ulang.get(f"user.profile.{result.args['key']}"),
|
||||
VALUE=result.args["value"]
|
||||
)
|
||||
)
|
||||
else:
|
||||
await profile_alc.finish(ulang.get("user.profile.set_failed", ATTR=ulang.get(f"user.profile.{result.args['key']}")))
|
||||
else:
|
||||
# 未输入值,尝试呼出菜单
|
||||
menu = get_profile_menu(result.args["key"], ulang)
|
||||
if menu:
|
||||
await md.send_md(menu, bot, event=event)
|
||||
else:
|
||||
await profile_alc.finish(ulang.get("user.profile.input_value", ATTR=ulang.get(f"user.profile.{result.args['key']}")))
|
||||
|
||||
user.profile[result.args["key"]] = result.args["value"]
|
||||
|
||||
elif result.subcommands.get("get"):
|
||||
if result.args["key"] in user.profile:
|
||||
await profile_alc.finish(user.profile[result.args["key"]])
|
||||
else:
|
||||
await profile_alc.finish("无此键值")
|
||||
else:
|
||||
profile = Profile(**user.profile)
|
||||
|
||||
for k, v in user.profile.items():
|
||||
profile.__setattr__(k, v)
|
||||
|
||||
reply = f"# {ulang.get('user.profile.info')}\n***\n"
|
||||
|
||||
hidden_attr = ["id", "TABLE_NAME"]
|
||||
enter_attr = ["lang", "timezone"]
|
||||
|
||||
for key in sorted(profile.dict().keys()):
|
||||
if key in hidden_attr:
|
||||
continue
|
||||
val = profile.dict()[key]
|
||||
key_text = ulang.get(f"user.profile.{key}")
|
||||
btn_set = md.btn_cmd(ulang.get("user.profile.edit"), f"profile set {key}",
|
||||
enter=True if key in enter_attr else False)
|
||||
reply += (f"\n**{key_text}** **{val}**\n"
|
||||
f"\n> {ulang.get(f'user.profile.{key}.desc')}"
|
||||
f"\n> {btn_set} \n\n***\n")
|
||||
await md.send_md(reply, bot, event=event)
|
||||
|
||||
|
||||
def get_profile_menu(key: str, ulang: Language) -> Optional[str]:
|
||||
"""获取属性的markdown菜单
|
||||
Args:
|
||||
ulang: 用户语言
|
||||
key: 属性键
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
setting_name = ulang.get(f"user.profile.{key}")
|
||||
|
||||
no_menu = ["id", "nickname", "location"]
|
||||
|
||||
if key in no_menu:
|
||||
return None
|
||||
|
||||
reply = f"**{setting_name} {ulang.get('user.profile.settings')}**\n***\n"
|
||||
if key == "lang":
|
||||
for lang_code, lang_name in get_all_lang().items():
|
||||
btn_set_lang = md.btn_cmd(f"{lang_name}({lang_code})", f"profile set {key} {lang_code}")
|
||||
reply += f"\n{btn_set_lang}\n***\n"
|
||||
elif key == "timezone":
|
||||
for tz in representative_timezones_list:
|
||||
btn_set_tz = md.btn_cmd(tz, f"profile set {key} {tz}")
|
||||
reply += f"{btn_set_tz}\n***\n"
|
||||
return reply
|
||||
|
||||
|
||||
def set_profile(key: str, value: str, user_id: str) -> bool:
|
||||
"""设置属性,使用if分支对每一个合法性进行检查
|
||||
Args:
|
||||
user_id:
|
||||
key:
|
||||
value:
|
||||
|
||||
Returns:
|
||||
是否成功设置,输入合法性不通过返回False
|
||||
|
||||
"""
|
||||
if key == "lang":
|
||||
if value in get_all_lang():
|
||||
change_user_lang(user_id, value)
|
||||
return True
|
||||
elif key == "timezone":
|
||||
if value in pytz.all_timezones:
|
||||
return True
|
||||
elif key == "nickname":
|
||||
return True
|
||||
from typing import Optional
|
||||
|
||||
import pytz
|
||||
from nonebot import require
|
||||
|
||||
from src.utils.base.data import LiteModel, Database
|
||||
from src.utils.base.data_manager import User, user_db, group_db
|
||||
from src.utils.base.language import Language, change_user_lang, get_all_lang, get_user_lang
|
||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
from src.utils.message.message import MarkdownMessage as md
|
||||
from .const import representative_timezones_list
|
||||
from src.utils import event as event_utils
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
from nonebot_plugin_alconna import Alconna, Args, Arparma, Subcommand, on_alconna
|
||||
|
||||
profile_alc = on_alconna(
|
||||
Alconna(
|
||||
"profile",
|
||||
Subcommand(
|
||||
"set",
|
||||
Args["key", str]["value", str, None],
|
||||
alias=["s", "设置"],
|
||||
),
|
||||
Subcommand(
|
||||
"get",
|
||||
Args["key", str],
|
||||
alias=["g", "查询"],
|
||||
),
|
||||
),
|
||||
aliases={"用户信息"}
|
||||
)
|
||||
|
||||
|
||||
# json储存
|
||||
class Profile(LiteModel):
|
||||
lang: str = "zh-CN"
|
||||
nickname: str = ""
|
||||
timezone: str = "Asia/Shanghai"
|
||||
location: str = ""
|
||||
|
||||
|
||||
@profile_alc.handle()
|
||||
async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot):
|
||||
user: User = user_db.where_one(User(), "user_id = ?", event_utils.get_user_id(event),
|
||||
default=User(user_id=str(event_utils.get_user_id(event))))
|
||||
ulang = get_user_lang(str(event_utils.get_user_id(event)))
|
||||
if result.subcommands.get("set"):
|
||||
if result.subcommands["set"].args.get("value"):
|
||||
# 对合法性进行校验后设置
|
||||
r = set_profile(result.args["key"], result.args["value"], str(event_utils.get_user_id(event)))
|
||||
if r:
|
||||
user.profile[result.args["key"]] = result.args["value"]
|
||||
user_db.save(user) # 数据库保存
|
||||
await profile_alc.finish(
|
||||
ulang.get(
|
||||
"user.profile.set_success",
|
||||
ATTR=ulang.get(f"user.profile.{result.args['key']}"),
|
||||
VALUE=result.args["value"]
|
||||
)
|
||||
)
|
||||
else:
|
||||
await profile_alc.finish(ulang.get("user.profile.set_failed", ATTR=ulang.get(f"user.profile.{result.args['key']}")))
|
||||
else:
|
||||
# 未输入值,尝试呼出菜单
|
||||
menu = get_profile_menu(result.args["key"], ulang)
|
||||
if menu:
|
||||
await md.send_md(menu, bot, event=event)
|
||||
else:
|
||||
await profile_alc.finish(ulang.get("user.profile.input_value", ATTR=ulang.get(f"user.profile.{result.args['key']}")))
|
||||
|
||||
user.profile[result.args["key"]] = result.args["value"]
|
||||
|
||||
elif result.subcommands.get("get"):
|
||||
if result.args["key"] in user.profile:
|
||||
await profile_alc.finish(user.profile[result.args["key"]])
|
||||
else:
|
||||
await profile_alc.finish("无此键值")
|
||||
else:
|
||||
profile = Profile(**user.profile)
|
||||
|
||||
for k, v in user.profile.items():
|
||||
profile.__setattr__(k, v)
|
||||
|
||||
reply = f"# {ulang.get('user.profile.info')}\n***\n"
|
||||
|
||||
hidden_attr = ["id", "TABLE_NAME"]
|
||||
enter_attr = ["lang", "timezone"]
|
||||
|
||||
for key in sorted(profile.dict().keys()):
|
||||
if key in hidden_attr:
|
||||
continue
|
||||
val = profile.dict()[key]
|
||||
key_text = ulang.get(f"user.profile.{key}")
|
||||
btn_set = md.btn_cmd(ulang.get("user.profile.edit"), f"profile set {key}",
|
||||
enter=True if key in enter_attr else False)
|
||||
reply += (f"\n**{key_text}** **{val}**\n"
|
||||
f"\n> {ulang.get(f'user.profile.{key}.desc')}"
|
||||
f"\n> {btn_set} \n\n***\n")
|
||||
await md.send_md(reply, bot, event=event)
|
||||
|
||||
|
||||
def get_profile_menu(key: str, ulang: Language) -> Optional[str]:
|
||||
"""获取属性的markdown菜单
|
||||
Args:
|
||||
ulang: 用户语言
|
||||
key: 属性键
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
setting_name = ulang.get(f"user.profile.{key}")
|
||||
|
||||
no_menu = ["id", "nickname", "location"]
|
||||
|
||||
if key in no_menu:
|
||||
return None
|
||||
|
||||
reply = f"**{setting_name} {ulang.get('user.profile.settings')}**\n***\n"
|
||||
if key == "lang":
|
||||
for lang_code, lang_name in get_all_lang().items():
|
||||
btn_set_lang = md.btn_cmd(f"{lang_name}({lang_code})", f"profile set {key} {lang_code}")
|
||||
reply += f"\n{btn_set_lang}\n***\n"
|
||||
elif key == "timezone":
|
||||
for tz in representative_timezones_list:
|
||||
btn_set_tz = md.btn_cmd(tz, f"profile set {key} {tz}")
|
||||
reply += f"{btn_set_tz}\n***\n"
|
||||
return reply
|
||||
|
||||
|
||||
def set_profile(key: str, value: str, user_id: str) -> bool:
|
||||
"""设置属性,使用if分支对每一个合法性进行检查
|
||||
Args:
|
||||
user_id:
|
||||
key:
|
||||
value:
|
||||
|
||||
Returns:
|
||||
是否成功设置,输入合法性不通过返回False
|
||||
|
||||
"""
|
||||
if key == "lang":
|
||||
if value in get_all_lang():
|
||||
change_user_lang(user_id, value)
|
||||
return True
|
||||
elif key == "timezone":
|
||||
if value in pytz.all_timezones:
|
||||
return True
|
||||
elif key == "nickname":
|
||||
return True
|
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,20 @@
|
||||
{
|
||||
"donateCodePath": "https://foruda.gitee.com/images/1690165328188128032/d7f24fb5_9911226.jpeg",
|
||||
"donateSite": "",
|
||||
"helpPicPath": "https://foruda.gitee.com/images/1685873169584963569/95fe9b0b_9911226.png",
|
||||
"maxCacheSize": 1048576,
|
||||
"maxCacheTime": {
|
||||
".json": 1800,
|
||||
".mcfunction": 900,
|
||||
".mid .midi": 1800
|
||||
},
|
||||
"maxPersonConvert": {
|
||||
"music": 20,
|
||||
"structure": 20
|
||||
},
|
||||
"maxSingleFileSize": {
|
||||
".json": 8192,
|
||||
".mcfunction": 524288,
|
||||
".mid .midi": 131072
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 662 KiB After Width: | Height: | Size: 662 KiB |
@ -992,7 +992,9 @@ async def _(
|
||||
res_id = await bot.call_api(
|
||||
"upload_group_file", group_id=event.group_id, name=fn, file=fp
|
||||
)
|
||||
await linglun_convert.send(UniMessage.text("文件已上传群文件,请在群文件查看。"))
|
||||
await linglun_convert.send(
|
||||
UniMessage.text("文件已上传群文件,请在群文件查看。")
|
||||
)
|
||||
# await linglun_convert.send(UniMessage.file(res_id,path=fp,name=fn))
|
||||
else:
|
||||
await bot.call_api(
|
@ -413,7 +413,9 @@ async def _(
|
||||
await bot.call_api(
|
||||
"upload_group_file", group_id=event.group_id, name=fn, file=fp
|
||||
)
|
||||
await mspv_sync.send(UniMessage.text("文件已上传群文件,请在群文件查看。"))
|
||||
await mspv_sync.send(
|
||||
UniMessage.text("文件已上传群文件,请在群文件查看。")
|
||||
)
|
||||
else:
|
||||
await bot.call_api(
|
||||
"upload_private_file", user_id=event.user_id, name=fn, file=fp
|
@ -7,7 +7,6 @@ bot_info_router = APIRouter(prefix="/api/bot-info")
|
||||
|
||||
@device_info_router.get("/")
|
||||
async def device_info():
|
||||
print("Hello Device Info")
|
||||
return {
|
||||
"message": "Hello Device Info"
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .status import *
|
||||
|
||||
__author__ = "神羽SnowyKami & 金羿Eilles"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="状态查看器",
|
||||
description="",
|
||||
usage=(
|
||||
"MARKDOWN### 状态查看器\n"
|
||||
"查看机器人的状态\n"
|
||||
"### 用法\n"
|
||||
"- `/status` 查看基本情况\n"
|
||||
"- `/status memory` 查看内存使用情况\n"
|
||||
"- `/status process` 查看进程情况\n"
|
||||
),
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
"toggleable" : False,
|
||||
"default_enable" : True,
|
||||
}
|
||||
)
|
||||
|
@ -13,7 +13,6 @@ import requests
|
||||
|
||||
from src.utils.base.config import load_from_yaml, config
|
||||
from src.utils.base.log import init_log
|
||||
from src.utils.base.data_manager import TempConfig, auto_migrate, common_db
|
||||
from git import Repo
|
||||
|
||||
|
||||
@ -53,11 +52,10 @@ def init():
|
||||
|
||||
"""
|
||||
# 检测python版本是否高于3.10
|
||||
auto_migrate()
|
||||
init_log()
|
||||
if sys.version_info < (3, 10):
|
||||
nonebot.logger.error(
|
||||
"此应用需要 Python3.10 以上的版本运行,你需要更新自己的 Python 运行环境了。"
|
||||
"此应用需要 Python3.10 以上的版本运行,你需要抱怨神羽的 Python 兼容性了。"
|
||||
)
|
||||
exit(1)
|
||||
|
||||
@ -69,9 +67,9 @@ def init():
|
||||
f"无法读取 Git 仓库 {e},你是否是从仓库内下载的Zip文件?请使用git clone。"
|
||||
)
|
||||
|
||||
temp_data: TempConfig = common_db.where_one(TempConfig(), default=TempConfig()) # type: ignore
|
||||
temp_data.data["start_time"] = time.time()
|
||||
common_db.save(temp_data)
|
||||
# temp_data: TempConfig = common_db.where_one(TempConfig(), default=TempConfig())
|
||||
# temp_data.data["start_time"] = time.time()
|
||||
# common_db.save(temp_data)
|
||||
|
||||
# 在加载完成语言后再初始化日志
|
||||
nonebot.logger.info("尹灵温 正在初始化…")
|
||||
@ -84,6 +82,13 @@ def init():
|
||||
f.write("[tool.nonebot]\n")
|
||||
|
||||
nonebot.logger.info(
|
||||
f"正在 {sys.executable} Python{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro} 上运行 尹灵温"
|
||||
"正在 {} Python{}.{}.{} 上运行 尹灵温".format(
|
||||
sys.executable,
|
||||
sys.version_info.major,
|
||||
sys.version_info.minor,
|
||||
sys.version_info.micro,
|
||||
)
|
||||
)
|
||||
nonebot.logger.info(
|
||||
"{} {}({}) 正在运行".format(__NAME__, __VERSION__, __VERSION_I__)
|
||||
)
|
||||
nonebot.logger.info(f"{__NAME__} {__VERSION__}({__VERSION_I__}) 正在运行")
|
||||
|
@ -1,14 +0,0 @@
|
||||
from . import (
|
||||
satori,
|
||||
onebot
|
||||
)
|
||||
|
||||
|
||||
def init(config: dict):
|
||||
onebot.init()
|
||||
satori.init(config)
|
||||
|
||||
|
||||
def register():
|
||||
onebot.register()
|
||||
satori.register()
|
@ -1,12 +0,0 @@
|
||||
import nonebot
|
||||
from nonebot.adapters.onebot import v11, v12
|
||||
|
||||
|
||||
def init():
|
||||
pass
|
||||
|
||||
|
||||
def register():
|
||||
driver = nonebot.get_driver()
|
||||
driver.register_adapter(v11.Adapter)
|
||||
driver.register_adapter(v12.Adapter)
|
@ -1,26 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
import nonebot
|
||||
from nonebot.adapters import satori
|
||||
|
||||
|
||||
def init(config: dict):
|
||||
if config.get("satori", None) is None:
|
||||
nonebot.logger.info("未查见 Satori 的配置文档,将跳过 Satori 初始化")
|
||||
return None
|
||||
satori_config = config.get("satori")
|
||||
if not satori_config.get("enable", False):
|
||||
nonebot.logger.info("未启用 Satori ,将跳过 Satori 初始化")
|
||||
return None
|
||||
if os.getenv("SATORI_CLIENTS", None) is not None:
|
||||
nonebot.logger.info("Satori 客户端已设入环境变量,跳过此步。")
|
||||
os.environ["SATORI_CLIENTS"] = json.dumps(satori_config.get("hosts", []), ensure_ascii=False)
|
||||
config['satori_clients'] = satori_config.get("hosts", [])
|
||||
return
|
||||
|
||||
|
||||
def register():
|
||||
if os.getenv("SATORI_CLIENTS", None) is not None:
|
||||
driver = nonebot.get_driver()
|
||||
driver.register_adapter(satori.Adapter)
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user