mirror of
https://github.com/LiteyukiStudio/LiteyukiBot.git
synced 2024-11-11 05:17:24 +08:00
🐛 fix 通道类回调函数在进程间传递时无法序列号的问题
This commit is contained in:
parent
3bd40e7271
commit
7107d03b72
@ -1,25 +1,26 @@
|
||||
import asyncio
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import asyncio
|
||||
from typing import Any, Optional
|
||||
from multiprocessing import freeze_support
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
|
||||
from liteyuki.bot.lifespan import (LIFESPAN_FUNC, Lifespan)
|
||||
from liteyuki.comm.channel import Channel
|
||||
from liteyuki.comm.channel import Channel, set_channel
|
||||
from liteyuki.core import IS_MAIN_PROCESS
|
||||
from liteyuki.core.manager import ProcessManager
|
||||
from liteyuki.core.spawn_process import mb_run, nb_run
|
||||
from liteyuki.log import init_log, logger
|
||||
from liteyuki.plugin import load_plugins
|
||||
from liteyuki.utils import run_coroutine
|
||||
|
||||
__all__ = [
|
||||
"LiteyukiBot",
|
||||
"get_bot"
|
||||
]
|
||||
|
||||
"""是否为主进程"""
|
||||
|
||||
|
||||
class LiteyukiBot:
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -29,11 +30,12 @@ class LiteyukiBot:
|
||||
self.init(**self.config) # 初始化
|
||||
|
||||
self.lifespan: Lifespan = Lifespan()
|
||||
self.chan = Channel() # 进程通信通道
|
||||
self.pm: ProcessManager = ProcessManager(bot=self, chan=self.chan)
|
||||
|
||||
self.process_manager: ProcessManager = ProcessManager(bot=self)
|
||||
self.loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(self.loop)
|
||||
self.loop_thread = threading.Thread(target=self.loop.run_forever, daemon=True)
|
||||
self.call_restart_count = 0
|
||||
|
||||
print("\033[34m" + r"""
|
||||
__ ______ ________ ________ __ __ __ __ __ __ ______
|
||||
@ -53,15 +55,83 @@ $$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/
|
||||
self.loop_thread.start() # 启动事件循环
|
||||
asyncio.run(self.lifespan.before_start()) # 启动前钩子
|
||||
|
||||
self.pm.add_target("nonebot", nb_run, **self.config)
|
||||
self.pm.start("nonebot")
|
||||
self.process_manager.add_target("nonebot", nb_run, **self.config)
|
||||
self.process_manager.start("nonebot")
|
||||
|
||||
self.pm.add_target("melobot", mb_run, **self.config)
|
||||
self.pm.start("melobot")
|
||||
self.process_manager.add_target("melobot", mb_run, **self.config)
|
||||
self.process_manager.start("melobot")
|
||||
|
||||
asyncio.run(self.lifespan.after_start()) # 启动后钩子
|
||||
|
||||
def restart(self, name: Optional[str] = None):
|
||||
self.start_watcher() # 启动文件监视器
|
||||
|
||||
def start_watcher(self):
|
||||
if self.config.get("debug", False):
|
||||
|
||||
code_directories = {}
|
||||
|
||||
src_directories = (
|
||||
"liteyuki",
|
||||
"src/liteyuki_main",
|
||||
"src/liteyuki_plugins",
|
||||
"src/nonebot_plugins",
|
||||
"src/utils",
|
||||
)
|
||||
src_excludes_extensions = (
|
||||
"pyc",
|
||||
)
|
||||
|
||||
logger.debug("Liteyuki Reload enabled, watching for file changes...")
|
||||
restart = self.restart_process
|
||||
|
||||
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
|
||||
logger.info(f"{event.src_path} modified, reloading bot...")
|
||||
restart()
|
||||
|
||||
code_modified_handler = CodeModifiedHandler()
|
||||
|
||||
observer = Observer()
|
||||
for directory in src_directories:
|
||||
observer.schedule(code_modified_handler, directory, recursive=True)
|
||||
observer.start()
|
||||
|
||||
def restart(self, delay: int = 0):
|
||||
"""
|
||||
重启轻雪本体
|
||||
Returns:
|
||||
|
||||
"""
|
||||
|
||||
if self.call_restart_count < 1:
|
||||
executable = sys.executable
|
||||
args = sys.argv
|
||||
logger.info("Restarting LiteyukiBot...")
|
||||
time.sleep(delay)
|
||||
if platform.system() == "Windows":
|
||||
cmd = "start"
|
||||
elif platform.system() == "Linux":
|
||||
cmd = "nohup"
|
||||
elif platform.system() == "Darwin":
|
||||
cmd = "open"
|
||||
else:
|
||||
cmd = "nohup"
|
||||
self.process_manager.terminate_all()
|
||||
# 等待所有进程退出
|
||||
self.process_manager.chan_active.receive("main")
|
||||
# 进程退出后重启
|
||||
threading.Thread(target=os.system, args=(f"{cmd} {executable} {' '.join(args)}",)).start()
|
||||
sys.exit(0)
|
||||
self.call_restart_count += 1
|
||||
|
||||
def restart_process(self, name: Optional[str] = None):
|
||||
"""
|
||||
停止轻雪
|
||||
Args:
|
||||
@ -75,10 +145,10 @@ $$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/
|
||||
self.loop.create_task(self.lifespan.before_shutdown()) # 停止前钩子
|
||||
|
||||
if name:
|
||||
self.chan.send(1, name)
|
||||
self.chan_active.send(1, name)
|
||||
else:
|
||||
for name in self.pm.targets:
|
||||
self.chan.send(1, name)
|
||||
for name in self.process_manager.targets:
|
||||
self.chan_active.send(1, name)
|
||||
|
||||
def init(self, *args, **kwargs):
|
||||
"""
|
||||
|
@ -9,11 +9,22 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
@Software: PyCharm
|
||||
该模块用于轻雪主进程和Nonebot子进程之间的通信
|
||||
"""
|
||||
from liteyuki.comm.channel import Channel, chan
|
||||
from liteyuki.comm.channel import (
|
||||
Channel,
|
||||
chan,
|
||||
get_channel,
|
||||
set_channel,
|
||||
set_channels,
|
||||
get_channels
|
||||
)
|
||||
from liteyuki.comm.event import Event
|
||||
|
||||
__all__ = [
|
||||
"Channel",
|
||||
"chan",
|
||||
"Event",
|
||||
"get_channel",
|
||||
"set_channel",
|
||||
"set_channels",
|
||||
"get_channels"
|
||||
]
|
||||
|
@ -10,8 +10,12 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
本模块定义了一个通用的通道类,用于进程间通信
|
||||
"""
|
||||
import functools
|
||||
import multiprocessing
|
||||
import threading
|
||||
from multiprocessing import Pipe
|
||||
from typing import Any, Optional, Callable, Awaitable, List, TypeAlias
|
||||
from uuid import uuid4
|
||||
|
||||
from liteyuki.utils import is_coroutine_callable, run_coroutine
|
||||
|
||||
@ -23,76 +27,89 @@ SYNC_FILTER_FUNC: TypeAlias = Callable[[Any], bool]
|
||||
ASYNC_FILTER_FUNC: TypeAlias = Callable[[Any], Awaitable[bool]]
|
||||
FILTER_FUNC: TypeAlias = SYNC_FILTER_FUNC | ASYNC_FILTER_FUNC
|
||||
|
||||
IS_MAIN_PROCESS = multiprocessing.current_process().name == "MainProcess"
|
||||
|
||||
_channel: dict[str, "Channel"] = {}
|
||||
_callback_funcs: dict[str, ON_RECEIVE_FUNC] = {}
|
||||
|
||||
|
||||
class Channel:
|
||||
"""
|
||||
通道类,用于进程间通信
|
||||
通道类,用于进程间通信,进程内不可用,仅限主进程和子进程之间通信
|
||||
有两种接收工作方式,但是只能选择一种,主动接收和被动接收,主动接收使用 `receive` 方法,被动接收使用 `on_receive` 装饰器
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.receive_conn, self.send_conn = Pipe()
|
||||
def __init__(self, _id: str):
|
||||
self.main_send_conn, self.sub_receive_conn = Pipe()
|
||||
self.sub_send_conn, self.main_receive_conn = Pipe()
|
||||
self._closed = False
|
||||
self._on_receive_funcs: List[ON_RECEIVE_FUNC] = []
|
||||
self._on_receive_funcs_with_receiver: dict[str, List[ON_RECEIVE_FUNC]] = {}
|
||||
self._on_main_receive_funcs: list[str] = []
|
||||
self._on_sub_receive_funcs: list[str] = []
|
||||
self.name: str = _id
|
||||
|
||||
def send(self, data: Any, receiver: Optional[str] = None):
|
||||
self.is_main_receive_loop_running = False
|
||||
self.is_sub_receive_loop_running = False
|
||||
|
||||
def __str__(self):
|
||||
return f"Channel({self.name})"
|
||||
|
||||
def send(self, data: Any):
|
||||
"""
|
||||
发送数据
|
||||
Args:
|
||||
data: 数据
|
||||
receiver: 接收者,如果为None则广播
|
||||
"""
|
||||
if self._closed:
|
||||
raise RuntimeError("Cannot send to a closed channel")
|
||||
self.send_conn.send((data, receiver))
|
||||
if IS_MAIN_PROCESS:
|
||||
print("主进程发送数据:", data)
|
||||
self.main_send_conn.send(data)
|
||||
else:
|
||||
print("子进程发送数据:", data)
|
||||
self.sub_send_conn.send(data)
|
||||
|
||||
def receive(self, receiver: str = None) -> Any:
|
||||
def receive(self) -> Any:
|
||||
"""
|
||||
接收数据
|
||||
Args:
|
||||
receiver: 接收者,如果为None则接收任意数据
|
||||
"""
|
||||
if self._closed:
|
||||
raise RuntimeError("Cannot receive from a closed channel")
|
||||
|
||||
while True:
|
||||
# 判断receiver是否为None或者receiver是否等于接收者,是则接收数据,否则不动数据
|
||||
data, receiver_ = self.receive_conn.recv()
|
||||
if receiver is None or receiver == receiver_:
|
||||
self._run_on_receive_funcs(data, receiver_)
|
||||
return data
|
||||
self.send_conn.send((data, receiver_))
|
||||
if IS_MAIN_PROCESS:
|
||||
data = self.main_receive_conn.recv()
|
||||
print("主进程接收数据:", data)
|
||||
else:
|
||||
data = self.sub_receive_conn.recv()
|
||||
print("子进程接收数据:", data)
|
||||
|
||||
def peek(self) -> Optional[Any]:
|
||||
"""
|
||||
查看管道中的数据,不移除
|
||||
Returns:
|
||||
"""
|
||||
if self._closed:
|
||||
raise RuntimeError("Cannot peek from a closed channel")
|
||||
if self.receive_conn.poll():
|
||||
data, receiver = self.receive_conn.recv()
|
||||
self.receive_conn.send((data, receiver))
|
||||
return data
|
||||
return None
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
关闭通道
|
||||
"""
|
||||
self._closed = True
|
||||
self.receive_conn.close()
|
||||
self.send_conn.close()
|
||||
self.sub_receive_conn.close()
|
||||
self.main_send_conn.close()
|
||||
self.sub_send_conn.close()
|
||||
self.main_receive_conn.close()
|
||||
|
||||
def on_receive(self, filter_func: Optional[FILTER_FUNC] = None, receiver: Optional[str] = None) -> Callable[[ON_RECEIVE_FUNC], ON_RECEIVE_FUNC]:
|
||||
def on_receive(self, filter_func: Optional[FILTER_FUNC] = None) -> Callable[[ON_RECEIVE_FUNC], ON_RECEIVE_FUNC]:
|
||||
"""
|
||||
接收数据并执行函数
|
||||
Args:
|
||||
filter_func: 过滤函数,为None则不过滤
|
||||
receiver: 接收者, 为None则接收任意数据
|
||||
Returns:
|
||||
装饰器,装饰一个函数在接收到数据后执行
|
||||
"""
|
||||
if (not self.is_sub_receive_loop_running) and not IS_MAIN_PROCESS:
|
||||
threading.Thread(target=self._start_sub_receive_loop).start()
|
||||
|
||||
if (not self.is_main_receive_loop_running) and IS_MAIN_PROCESS:
|
||||
threading.Thread(target=self._start_main_receive_loop).start()
|
||||
|
||||
def decorator(func: ON_RECEIVE_FUNC) -> ON_RECEIVE_FUNC:
|
||||
async def wrapper(data: Any) -> Any:
|
||||
@ -105,28 +122,53 @@ class Channel:
|
||||
return
|
||||
return await func(data)
|
||||
|
||||
if receiver is None:
|
||||
self._on_receive_funcs.append(wrapper)
|
||||
function_id = str(uuid4())
|
||||
_callback_funcs[function_id] = wrapper
|
||||
if IS_MAIN_PROCESS:
|
||||
self._on_main_receive_funcs.append(function_id)
|
||||
else:
|
||||
if receiver not in self._on_receive_funcs_with_receiver:
|
||||
self._on_receive_funcs_with_receiver[receiver] = []
|
||||
self._on_receive_funcs_with_receiver[receiver].append(wrapper)
|
||||
self._on_sub_receive_funcs.append(function_id)
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
def _run_on_receive_funcs(self, data: Any, receiver: Optional[str] = None):
|
||||
def _run_on_main_receive_funcs(self, data: Any):
|
||||
"""
|
||||
运行接收函数
|
||||
Args:
|
||||
data: 数据
|
||||
"""
|
||||
if receiver is None:
|
||||
for func in self._on_receive_funcs:
|
||||
run_coroutine(func(data))
|
||||
else:
|
||||
for func in self._on_receive_funcs_with_receiver.get(receiver, []):
|
||||
run_coroutine(func(data))
|
||||
for func_id in self._on_main_receive_funcs:
|
||||
func = _callback_funcs[func_id]
|
||||
run_coroutine(func(data))
|
||||
|
||||
def _run_on_sub_receive_funcs(self, data: Any):
|
||||
"""
|
||||
运行接收函数
|
||||
Args:
|
||||
data: 数据
|
||||
"""
|
||||
for func_id in self._on_sub_receive_funcs:
|
||||
func = _callback_funcs[func_id]
|
||||
run_coroutine(func(data))
|
||||
|
||||
def _start_main_receive_loop(self):
|
||||
"""
|
||||
开始接收数据
|
||||
"""
|
||||
self.is_main_receive_loop_running = True
|
||||
while not self._closed:
|
||||
data = self.main_receive_conn.recv()
|
||||
self._run_on_main_receive_funcs(data)
|
||||
|
||||
def _start_sub_receive_loop(self):
|
||||
"""
|
||||
开始接收数据
|
||||
"""
|
||||
self.is_sub_receive_loop_running = True
|
||||
while not self._closed:
|
||||
data = self.sub_receive_conn.recv()
|
||||
self._run_on_sub_receive_funcs(data)
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
@ -136,4 +178,42 @@ class Channel:
|
||||
|
||||
|
||||
"""默认通道实例,可直接从模块导入使用"""
|
||||
chan = Channel()
|
||||
chan = Channel("default")
|
||||
|
||||
|
||||
def set_channel(name: str, channel: Channel):
|
||||
"""
|
||||
设置通道实例
|
||||
Args:
|
||||
name: 通道名称
|
||||
channel: 通道实例
|
||||
"""
|
||||
_channel[name] = channel
|
||||
|
||||
|
||||
def set_channels(channels: dict[str, Channel]):
|
||||
"""
|
||||
设置通道实例
|
||||
Args:
|
||||
channels: 通道名称
|
||||
"""
|
||||
for name, channel in channels.items():
|
||||
_channel[name] = channel
|
||||
|
||||
|
||||
def get_channel(name: str) -> Optional[Channel]:
|
||||
"""
|
||||
获取通道实例
|
||||
Args:
|
||||
name: 通道名称
|
||||
Returns:
|
||||
"""
|
||||
return _channel.get(name, None)
|
||||
|
||||
|
||||
def get_channels() -> dict[str, Channel]:
|
||||
"""
|
||||
获取通道实例
|
||||
Returns:
|
||||
"""
|
||||
return _channel
|
||||
|
@ -13,7 +13,7 @@ import threading
|
||||
from multiprocessing import Process
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from liteyuki.comm import Channel
|
||||
from liteyuki.comm import Channel, get_channel, set_channels
|
||||
from liteyuki.log import logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -31,12 +31,18 @@ class ProcessManager:
|
||||
在主进程中被调用
|
||||
"""
|
||||
|
||||
def __init__(self, bot: "LiteyukiBot", chan: Channel):
|
||||
def __init__(self, bot: "LiteyukiBot"):
|
||||
self.bot = bot
|
||||
self.chan = chan
|
||||
self.targets: dict[str, tuple[callable, tuple, dict]] = {}
|
||||
self.processes: dict[str, Process] = {}
|
||||
|
||||
set_channels({
|
||||
"nonebot-active" : Channel(_id="nonebot-active"),
|
||||
"melobot-active" : Channel(_id="melobot-active"),
|
||||
"nonebot-passive": Channel(_id="nonebot-passive"),
|
||||
"melobot-passive": Channel(_id="melobot-passive"),
|
||||
})
|
||||
|
||||
def start(self, name: str, delay: int = 0):
|
||||
"""
|
||||
开启后自动监控进程,并添加到进程字典中
|
||||
@ -47,19 +53,21 @@ class ProcessManager:
|
||||
Returns:
|
||||
|
||||
"""
|
||||
|
||||
if name not in self.targets:
|
||||
raise KeyError(f"Process {name} not found.")
|
||||
|
||||
def _start():
|
||||
should_exit = False
|
||||
while not should_exit:
|
||||
process = Process(target=self.targets[name][0], args=(self.chan, *self.targets[name][1]), kwargs=self.targets[name][2])
|
||||
chan_active = get_channel(f"{name}-active")
|
||||
chan_passive = get_channel(f"{name}-passive")
|
||||
process = Process(target=self.targets[name][0], args=(chan_active, chan_passive, *self.targets[name][1]),
|
||||
kwargs=self.targets[name][2])
|
||||
self.processes[name] = process
|
||||
process.start()
|
||||
while not should_exit:
|
||||
# 0退出 1重启
|
||||
data = self.chan.receive(name)
|
||||
data = chan_active.receive()
|
||||
if data == 1:
|
||||
logger.info(f"Restarting process {name}")
|
||||
asyncio.run(self.bot.lifespan.before_shutdown())
|
||||
@ -103,3 +111,7 @@ class ProcessManager:
|
||||
process.join(TIMEOUT)
|
||||
if process.is_alive():
|
||||
process.kill()
|
||||
|
||||
def terminate_all(self):
|
||||
for name in self.targets:
|
||||
self.terminate(name)
|
||||
|
@ -3,6 +3,7 @@ from typing import Optional, TYPE_CHECKING
|
||||
import nonebot
|
||||
|
||||
from liteyuki.core.nb import adapter_manager, driver_manager
|
||||
from liteyuki.comm.channel import set_channel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from liteyuki.comm.channel import Channel
|
||||
@ -10,23 +11,23 @@ if TYPE_CHECKING:
|
||||
timeout_limit: int = 20
|
||||
|
||||
"""导出对象,用于主进程与nonebot通信"""
|
||||
chan_in_spawn_nb: Optional["Channel"] = None
|
||||
_channels = {}
|
||||
|
||||
|
||||
def nb_run(chan, *args, **kwargs):
|
||||
def nb_run(chan_active: "Channel", chan_passive: "Channel", *args, **kwargs):
|
||||
"""
|
||||
初始化NoneBot并运行在子进程
|
||||
Args:
|
||||
|
||||
chan:
|
||||
*args:
|
||||
chan_active:
|
||||
chan_passive:
|
||||
**kwargs:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
global chan_in_spawn_nb
|
||||
chan_in_spawn_nb = chan
|
||||
set_channel("nonebot-active", chan_active)
|
||||
set_channel("nonebot-passive", chan_passive)
|
||||
nonebot.init(**kwargs)
|
||||
driver_manager.init(config=kwargs)
|
||||
adapter_manager.init(kwargs)
|
||||
@ -35,17 +36,21 @@ def nb_run(chan, *args, **kwargs):
|
||||
nonebot.run()
|
||||
|
||||
|
||||
def mb_run(chan, *args, **kwargs):
|
||||
def mb_run(chan_active: "Channel", chan_passive: "Channel", *args, **kwargs):
|
||||
"""
|
||||
初始化MeloBot并运行在子进程
|
||||
Args:
|
||||
chan
|
||||
chan_active
|
||||
chan_passive
|
||||
*args:
|
||||
**kwargs:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
set_channel("melobot-active", chan_active)
|
||||
set_channel("melobot-passive", chan_passive)
|
||||
|
||||
# bot = MeloBot(__name__)
|
||||
# bot.init(AbstractConnector(cd_time=0))
|
||||
# bot.run()
|
||||
|
@ -7,14 +7,19 @@
|
||||
# @Email : snowykami@outlook.com
|
||||
# @File : asa.py
|
||||
# @Software: PyCharm
|
||||
import asyncio
|
||||
|
||||
from liteyuki.plugin import PluginMetadata
|
||||
from liteyuki import get_bot, logger
|
||||
from liteyuki.comm.channel import get_channel
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="lifespan_monitor",
|
||||
)
|
||||
|
||||
bot = get_bot()
|
||||
nbp_chan = get_channel("nonebot-passive")
|
||||
mbp_chan = get_channel("melobot-passive")
|
||||
|
||||
|
||||
@bot.on_before_start
|
||||
@ -24,6 +29,7 @@ def _():
|
||||
|
||||
@bot.on_before_shutdown
|
||||
def _():
|
||||
print(get_channel("main"))
|
||||
logger.info("生命周期监控器:准备停止")
|
||||
|
||||
|
||||
@ -35,3 +41,17 @@ def _():
|
||||
@bot.on_after_start
|
||||
def _():
|
||||
logger.info("生命周期监控器:启动完成")
|
||||
|
||||
|
||||
@bot.on_after_start
|
||||
async def _():
|
||||
logger.info("生命周期监控器:启动完成")
|
||||
while True:
|
||||
await asyncio.sleep(3)
|
||||
nbp_chan.send("send by main")
|
||||
|
||||
|
||||
@mbp_chan.on_receive()
|
||||
@nbp_chan.on_receive()
|
||||
async def _(data):
|
||||
print("主进程收到数据", data)
|
||||
|
10
liteyuki/plugins/reloader_monitor.py
Normal file
10
liteyuki/plugins/reloader_monitor.py
Normal file
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/8/10 下午5:18
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : reloader_monitor.py
|
||||
@Software: PyCharm
|
||||
"""
|
Binary file not shown.
@ -6,29 +6,25 @@ import nonebot
|
||||
import pip
|
||||
from nonebot import Bot, get_driver, require
|
||||
from nonebot.adapters import onebot, satori
|
||||
from nonebot.adapters.onebot.v11 import Message, escape, unescape
|
||||
from nonebot.adapters.onebot.v11 import Message, unescape
|
||||
from nonebot.exception import MockApiException
|
||||
from nonebot.internal.matcher import Matcher
|
||||
from nonebot.permission import SUPERUSER
|
||||
|
||||
from liteyuki import Channel
|
||||
# from src.liteyuki.core import Reloader
|
||||
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.spawn_process import chan_in_spawn_nb
|
||||
|
||||
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, Subcommand, Arparma, MultiVar
|
||||
from nonebot_plugin_alconna import on_alconna, Alconna, Args, Subcommand, Arparma, MultiVar
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
|
||||
driver = get_driver()
|
||||
@ -81,7 +77,6 @@ async def _(bot: T_Bot, event: T_MessageEvent):
|
||||
).handle()
|
||||
# Satori OK
|
||||
async def _(matcher: Matcher, bot: T_Bot, event: T_MessageEvent):
|
||||
global channel_in_spawn_process
|
||||
await matcher.send("Liteyuki reloading")
|
||||
temp_data = common_db.where_one(TempConfig(), default=TempConfig())
|
||||
|
||||
@ -91,8 +86,8 @@ async def _(matcher: Matcher, bot: T_Bot, event: T_MessageEvent):
|
||||
"reload_time" : time.time(),
|
||||
"reload_bot_id" : bot.self_id,
|
||||
"reload_session_type": event_utils.get_message_type(event),
|
||||
"reload_session_id" : (event.group_id if event.message_type == "group" else event.user_id) if not isinstance(event,
|
||||
satori.event.Event) else event.chan.id,
|
||||
"reload_session_id" : (event.group_id if event.message_type == "group" else event.user_id)
|
||||
if not isinstance(event, satori.event.Event) else event.chan_active.id,
|
||||
"delta_time" : 0
|
||||
}
|
||||
)
|
||||
@ -358,7 +353,7 @@ async def _(bot: T_Bot):
|
||||
delta_time = temp_data.data.get("delta_time", 0)
|
||||
common_db.save(temp_data) # 更新数据
|
||||
|
||||
if delta_time <= 20.0: # 启动时间太长就别发了,丢人
|
||||
if delta_time <= 20.0: # 启动时间太长就别发了,丢人
|
||||
if isinstance(bot, satori.Bot):
|
||||
await bot.send_message(
|
||||
channel_id=reload_session_id,
|
||||
|
@ -11,35 +11,12 @@ 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 enabled, watching for file changes...")
|
||||
|
||||
|
||||
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} modified, reloading bot...")
|
||||
reload()
|
||||
|
||||
|
||||
class ResourceModifiedHandler(FileSystemEventHandler):
|
||||
"""
|
||||
@ -51,12 +28,9 @@ if get_config("debug", False):
|
||||
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()
|
||||
|
@ -21,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):
|
||||
# 安全模式下,不加载插件
|
||||
|
3
src/liteyuki_plugins/README.md
Normal file
3
src/liteyuki_plugins/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# 说明
|
||||
|
||||
此目录为**轻雪插件**目录,非其他插件目录。
|
@ -1,18 +1,27 @@
|
||||
import multiprocessing
|
||||
|
||||
from nonebot.plugin import PluginMetadata
|
||||
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,
|
||||
}
|
||||
)
|
||||
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 nonebot import get_driver
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="Minecraft工具箱",
|
||||
description="一些Minecraft相关工具箱",
|
||||
usage="我觉得你应该会用",
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki" : True,
|
||||
"toggleable" : True,
|
||||
"default_enable": True,
|
||||
}
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot import get_driver
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="Minecraft工具箱",
|
||||
description="一些Minecraft相关工具箱",
|
||||
usage="我觉得你应该会用",
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki" : True,
|
||||
"toggleable" : True,
|
||||
"default_enable": 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,168 +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
|
||||
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,255 +1,255 @@
|
||||
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)
|
||||
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,186 +1,186 @@
|
||||
# 轻雪资源包管理器
|
||||
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:
|
||||
await matcher.finish(reply)
|
||||
# 轻雪资源包管理器
|
||||
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:
|
||||
await matcher.finish(reply)
|
@ -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,21 @@
|
||||
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,18 +1,18 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .monitors import *
|
||||
from .matchers 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 .monitors import *
|
||||
from .matchers 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,106 +1,106 @@
|
||||
import asyncio
|
||||
import random
|
||||
|
||||
import nonebot
|
||||
from nonebot import Bot, on_message, get_driver, require
|
||||
from nonebot.internal.matcher import Matcher
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.rule import to_me
|
||||
from nonebot.typing import T_State
|
||||
|
||||
from src.utils.base.ly_typing import T_MessageEvent
|
||||
from .utils import get_keywords
|
||||
from src.utils.base.word_bank import get_reply
|
||||
from src.utils.event import get_message_type
|
||||
from src.utils.base.permission import GROUP_ADMIN, GROUP_OWNER
|
||||
from src.utils.base.data_manager import group_db, Group
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
from nonebot_plugin_alconna import on_alconna, Alconna, Args, Arparma
|
||||
|
||||
nicknames = set()
|
||||
driver = get_driver()
|
||||
group_reply_probability: dict[str, float] = {
|
||||
}
|
||||
default_reply_probability = 0.05
|
||||
cut_probability = 0.4 # 分几句话的概率
|
||||
|
||||
|
||||
@on_alconna(
|
||||
Alconna(
|
||||
"set-reply-probability",
|
||||
Args["probability", float, default_reply_probability],
|
||||
),
|
||||
aliases={"设置回复概率"},
|
||||
permission=SUPERUSER | GROUP_ADMIN | GROUP_OWNER,
|
||||
).handle()
|
||||
async def _(result: Arparma, event: T_MessageEvent, matcher: Matcher):
|
||||
# 修改内存和数据库的概率值
|
||||
if get_message_type(event) == "group":
|
||||
group_id = event.group_id
|
||||
probability = result.main_args.get("probability")
|
||||
# 保存到数据库
|
||||
group: Group = group_db.where_one(Group(), "group_id = ?", group_id, default=Group(group_id=str(group_id)))
|
||||
group.config["reply_probability"] = probability
|
||||
group_db.save(group)
|
||||
|
||||
await matcher.send(f"已将群组{group_id}的回复概率设置为{probability}")
|
||||
return
|
||||
|
||||
|
||||
@group_db.on_save
|
||||
def _(model: Group):
|
||||
"""
|
||||
在数据库更新时,更新内存中的回复概率
|
||||
Args:
|
||||
model:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
group_reply_probability[model.group_id] = model.config.get("reply_probability", default_reply_probability)
|
||||
|
||||
|
||||
@driver.on_bot_connect
|
||||
async def _(bot: Bot):
|
||||
global nicknames
|
||||
nicknames.update(bot.config.nickname)
|
||||
# 从数据库加载群组的回复概率
|
||||
groups = group_db.where_all(Group(), default=[])
|
||||
for group in groups:
|
||||
group_reply_probability[group.group_id] = group.config.get("reply_probability", default_reply_probability)
|
||||
|
||||
|
||||
@on_message(priority=100).handle()
|
||||
async def _(event: T_MessageEvent, bot: Bot, state: T_State, matcher: Matcher):
|
||||
kws = await get_keywords(event.message.extract_plain_text())
|
||||
|
||||
tome = False
|
||||
if await to_me()(event=event, bot=bot, state=state):
|
||||
tome = True
|
||||
else:
|
||||
for kw in kws:
|
||||
if kw in nicknames:
|
||||
tome = True
|
||||
break
|
||||
|
||||
# 回复概率
|
||||
message_type = get_message_type(event)
|
||||
if tome or message_type == "private":
|
||||
p = 1.0
|
||||
else:
|
||||
p = group_reply_probability.get(event.group_id, default_reply_probability)
|
||||
|
||||
if random.random() < p:
|
||||
if reply := get_reply(kws):
|
||||
if random.random() < cut_probability:
|
||||
reply = reply.replace("。", "||").replace(",", "||").replace("!", "||").replace("?", "||")
|
||||
replies = reply.split("||")
|
||||
for r in replies:
|
||||
if r: # 防止空字符串
|
||||
await asyncio.sleep(random.random() * 2)
|
||||
await matcher.send(r)
|
||||
else:
|
||||
await asyncio.sleep(random.random() * 3)
|
||||
await matcher.send(reply)
|
||||
return
|
||||
import asyncio
|
||||
import random
|
||||
|
||||
import nonebot
|
||||
from nonebot import Bot, on_message, get_driver, require
|
||||
from nonebot.internal.matcher import Matcher
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.rule import to_me
|
||||
from nonebot.typing import T_State
|
||||
|
||||
from src.utils.base.ly_typing import T_MessageEvent
|
||||
from .utils import get_keywords
|
||||
from src.utils.base.word_bank import get_reply
|
||||
from src.utils.event import get_message_type
|
||||
from src.utils.base.permission import GROUP_ADMIN, GROUP_OWNER
|
||||
from src.utils.base.data_manager import group_db, Group
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
from nonebot_plugin_alconna import on_alconna, Alconna, Args, Arparma
|
||||
|
||||
nicknames = set()
|
||||
driver = get_driver()
|
||||
group_reply_probability: dict[str, float] = {
|
||||
}
|
||||
default_reply_probability = 0.05
|
||||
cut_probability = 0.4 # 分几句话的概率
|
||||
|
||||
|
||||
@on_alconna(
|
||||
Alconna(
|
||||
"set-reply-probability",
|
||||
Args["probability", float, default_reply_probability],
|
||||
),
|
||||
aliases={"设置回复概率"},
|
||||
permission=SUPERUSER | GROUP_ADMIN | GROUP_OWNER,
|
||||
).handle()
|
||||
async def _(result: Arparma, event: T_MessageEvent, matcher: Matcher):
|
||||
# 修改内存和数据库的概率值
|
||||
if get_message_type(event) == "group":
|
||||
group_id = event.group_id
|
||||
probability = result.main_args.get("probability")
|
||||
# 保存到数据库
|
||||
group: Group = group_db.where_one(Group(), "group_id = ?", group_id, default=Group(group_id=str(group_id)))
|
||||
group.config["reply_probability"] = probability
|
||||
group_db.save(group)
|
||||
|
||||
await matcher.send(f"已将群组{group_id}的回复概率设置为{probability}")
|
||||
return
|
||||
|
||||
|
||||
@group_db.on_save
|
||||
def _(model: Group):
|
||||
"""
|
||||
在数据库更新时,更新内存中的回复概率
|
||||
Args:
|
||||
model:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
group_reply_probability[model.group_id] = model.config.get("reply_probability", default_reply_probability)
|
||||
|
||||
|
||||
@driver.on_bot_connect
|
||||
async def _(bot: Bot):
|
||||
global nicknames
|
||||
nicknames.update(bot.config.nickname)
|
||||
# 从数据库加载群组的回复概率
|
||||
groups = group_db.where_all(Group(), default=[])
|
||||
for group in groups:
|
||||
group_reply_probability[group.group_id] = group.config.get("reply_probability", default_reply_probability)
|
||||
|
||||
|
||||
@on_message(priority=100).handle()
|
||||
async def _(event: T_MessageEvent, bot: Bot, state: T_State, matcher: Matcher):
|
||||
kws = await get_keywords(event.message.extract_plain_text())
|
||||
|
||||
tome = False
|
||||
if await to_me()(event=event, bot=bot, state=state):
|
||||
tome = True
|
||||
else:
|
||||
for kw in kws:
|
||||
if kw in nicknames:
|
||||
tome = True
|
||||
break
|
||||
|
||||
# 回复概率
|
||||
message_type = get_message_type(event)
|
||||
if tome or message_type == "private":
|
||||
p = 1.0
|
||||
else:
|
||||
p = group_reply_probability.get(event.group_id, default_reply_probability)
|
||||
|
||||
if random.random() < p:
|
||||
if reply := get_reply(kws):
|
||||
if random.random() < cut_probability:
|
||||
reply = reply.replace("。", "||").replace(",", "||").replace("!", "||").replace("?", "||")
|
||||
replies = reply.split("||")
|
||||
for r in replies:
|
||||
if r: # 防止空字符串
|
||||
await asyncio.sleep(random.random() * 2)
|
||||
await matcher.send(r)
|
||||
else:
|
||||
await asyncio.sleep(random.random() * 3)
|
||||
await matcher.send(reply)
|
||||
return
|
@ -1,13 +1,13 @@
|
||||
from jieba import lcut
|
||||
from nonebot.utils import run_sync
|
||||
|
||||
|
||||
@run_sync
|
||||
def get_keywords(text: str) -> list[str, ...]:
|
||||
"""
|
||||
获取关键词
|
||||
Args:
|
||||
text: 文本
|
||||
Returns:
|
||||
"""
|
||||
return lcut(text)
|
||||
from jieba import lcut
|
||||
from nonebot.utils import run_sync
|
||||
|
||||
|
||||
@run_sync
|
||||
def get_keywords(text: str) -> list[str, ...]:
|
||||
"""
|
||||
获取关键词
|
||||
Args:
|
||||
text: 文本
|
||||
Returns:
|
||||
"""
|
||||
return lcut(text)
|
@ -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,172 +1,172 @@
|
||||
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" : ulang.get("stat.message")
|
||||
+ f" Period {convert_seconds_to_time(period)}" + f" Duration {convert_seconds_to_time(duration)}"
|
||||
+ (f" Group {group_id}" if group_id else "") + (f" Bot {bot_id}" if bot_id else "") + (
|
||||
f" User {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" Type {rank_type}" + f" Limit {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" : ulang.get("stat.message")
|
||||
+ f" Period {convert_seconds_to_time(period)}" + f" Duration {convert_seconds_to_time(duration)}"
|
||||
+ (f" Group {group_id}" if group_id else "") + (f" Bot {bot_id}" if bot_id else "") + (
|
||||
f" User {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" Type {rank_type}" + f" Limit {limit}",
|
||||
"ranking": ranking
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return await template2image(get_path("templates/stat_rank.html"), templates, debug=True)
|
@ -1,134 +1,134 @@
|
||||
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))
|
||||
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,92 @@
|
||||
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)
|
||||
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)
|
@ -1,24 +1,24 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .status import *
|
||||
|
||||
__author__ = "snowykami"
|
||||
__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,
|
||||
}
|
||||
)
|
||||
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .status import *
|
||||
|
||||
__author__ = "snowykami"
|
||||
__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,292 +1,292 @@
|
||||
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: "Android Phone",
|
||||
2: "Android Watch",
|
||||
3: "Mac",
|
||||
5: "iPad",
|
||||
6: "Android Pad",
|
||||
}
|
||||
|
||||
"""
|
||||
Universal Interface
|
||||
data
|
||||
- bot
|
||||
- name: str
|
||||
icon: str
|
||||
id: int
|
||||
protocol_name: str
|
||||
groups: int
|
||||
friends: int
|
||||
message_sent: int
|
||||
message_received: int
|
||||
app_name: str
|
||||
- hardware
|
||||
- cpu
|
||||
- percent: float
|
||||
- name: str
|
||||
- mem
|
||||
- percent: float
|
||||
- total: int
|
||||
- used: int
|
||||
- free: int
|
||||
- swap
|
||||
- percent: float
|
||||
- total: int
|
||||
- used: int
|
||||
- free: int
|
||||
- disk: list
|
||||
- name: str
|
||||
- percent: float
|
||||
- total: int
|
||||
"""
|
||||
# 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",
|
||||
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),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
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", "UnknownImplementation")
|
||||
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"), "Online"
|
||||
),
|
||||
"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", "Unknown")
|
||||
if "AMD" in cpu_brand_raw:
|
||||
brand = "AMD"
|
||||
elif "Intel" in cpu_brand_raw:
|
||||
brand = "Intel"
|
||||
else:
|
||||
brand = "Unknown"
|
||||
result = {
|
||||
"cpu": {
|
||||
"percent": psutil.cpu_percent(),
|
||||
"name": f"{brand} {cpuinfo.get_cpu_info().get('arch', 'Unknown')}",
|
||||
"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: "Android Phone",
|
||||
2: "Android Watch",
|
||||
3: "Mac",
|
||||
5: "iPad",
|
||||
6: "Android Pad",
|
||||
}
|
||||
|
||||
"""
|
||||
Universal Interface
|
||||
data
|
||||
- bot
|
||||
- name: str
|
||||
icon: str
|
||||
id: int
|
||||
protocol_name: str
|
||||
groups: int
|
||||
friends: int
|
||||
message_sent: int
|
||||
message_received: int
|
||||
app_name: str
|
||||
- hardware
|
||||
- cpu
|
||||
- percent: float
|
||||
- name: str
|
||||
- mem
|
||||
- percent: float
|
||||
- total: int
|
||||
- used: int
|
||||
- free: int
|
||||
- swap
|
||||
- percent: float
|
||||
- total: int
|
||||
- used: int
|
||||
- free: int
|
||||
- disk: list
|
||||
- name: str
|
||||
- percent: float
|
||||
- total: int
|
||||
"""
|
||||
# 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",
|
||||
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),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
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", "UnknownImplementation")
|
||||
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"), "Online"
|
||||
),
|
||||
"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", "Unknown")
|
||||
if "AMD" in cpu_brand_raw:
|
||||
brand = "AMD"
|
||||
elif "Intel" in cpu_brand_raw:
|
||||
brand = "Intel"
|
||||
else:
|
||||
brand = "Unknown"
|
||||
result = {
|
||||
"cpu": {
|
||||
"percent": psutil.cpu_percent(),
|
||||
"name": f"{brand} {cpuinfo.get_cpu_info().get('arch', 'Unknown')}",
|
||||
"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,60 +1,60 @@
|
||||
from src.utils import event as event_utils
|
||||
from src.utils.base.language import get_user_lang
|
||||
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
|
||||
|
||||
status_alc = on_alconna(
|
||||
aliases={"状态"},
|
||||
command=Alconna(
|
||||
"status",
|
||||
Subcommand(
|
||||
"memory",
|
||||
alias={"mem", "m", "内存"},
|
||||
),
|
||||
Subcommand(
|
||||
"process",
|
||||
alias={"proc", "p", "进程"},
|
||||
),
|
||||
Subcommand(
|
||||
"refresh",
|
||||
alias={"refr", "r", "刷新"},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
status_card_cache = {} # lang -> bytes
|
||||
|
||||
|
||||
@status_alc.handle()
|
||||
async def _(event: T_MessageEvent, bot: T_Bot):
|
||||
ulang = get_user_lang(event_utils.get_user_id(event))
|
||||
global status_card_cache
|
||||
if 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] > 60
|
||||
):
|
||||
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,
|
||||
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
|
||||
from src.utils import event as event_utils
|
||||
from src.utils.base.language import get_user_lang
|
||||
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
|
||||
|
||||
status_alc = on_alconna(
|
||||
aliases={"状态"},
|
||||
command=Alconna(
|
||||
"status",
|
||||
Subcommand(
|
||||
"memory",
|
||||
alias={"mem", "m", "内存"},
|
||||
),
|
||||
Subcommand(
|
||||
"process",
|
||||
alias={"proc", "p", "进程"},
|
||||
),
|
||||
Subcommand(
|
||||
"refresh",
|
||||
alias={"refr", "r", "刷新"},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
status_card_cache = {} # lang -> bytes
|
||||
|
||||
|
||||
@status_alc.handle()
|
||||
async def _(event: T_MessageEvent, bot: T_Bot):
|
||||
ulang = get_user_lang(event_utils.get_user_id(event))
|
||||
global status_card_cache
|
||||
if 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] > 60
|
||||
):
|
||||
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,
|
||||
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
|
@ -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
|
@ -1,27 +1,27 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot import get_driver
|
||||
from .qweather import *
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪天气",
|
||||
description="基于和风天气api的天气插件",
|
||||
usage="",
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki" : True,
|
||||
"toggleable" : True,
|
||||
"default_enable": True,
|
||||
}
|
||||
)
|
||||
|
||||
from ...utils.base.data_manager import set_memory_data
|
||||
|
||||
driver = get_driver()
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
async def _():
|
||||
# 检查是否为开发者模式
|
||||
is_dev = await check_key_dev(get_config("weather_key", ""))
|
||||
set_memory_data("weather.is_dev", is_dev)
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot import get_driver
|
||||
from .qweather import *
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪天气",
|
||||
description="基于和风天气api的天气插件",
|
||||
usage="",
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki" : True,
|
||||
"toggleable" : True,
|
||||
"default_enable": True,
|
||||
}
|
||||
)
|
||||
|
||||
from ...utils.base.data_manager import set_memory_data
|
||||
|
||||
driver = get_driver()
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
async def _():
|
||||
# 检查是否为开发者模式
|
||||
is_dev = await check_key_dev(get_config("weather_key", ""))
|
||||
set_memory_data("weather.is_dev", is_dev)
|
@ -1,171 +1,171 @@
|
||||
import aiohttp
|
||||
|
||||
from .qw_models import *
|
||||
import httpx
|
||||
|
||||
from ...utils.base.data_manager import get_memory_data
|
||||
from ...utils.base.language import Language
|
||||
|
||||
dev_url = "https://devapi.qweather.com/" # 开发HBa
|
||||
com_url = "https://api.qweather.com/" # 正式环境
|
||||
|
||||
|
||||
def get_qw_lang(lang: str) -> str:
|
||||
if lang in ["zh-HK", "zh-TW"]:
|
||||
return "zh-hant"
|
||||
elif lang.startswith("zh"):
|
||||
return "zh"
|
||||
elif lang.startswith("en"):
|
||||
return "en"
|
||||
else:
|
||||
return lang
|
||||
|
||||
|
||||
async def check_key_dev(key: str) -> bool:
|
||||
url = "https://api.qweather.com/v7/weather/now?"
|
||||
params = {
|
||||
"location": "101010100",
|
||||
"key" : key,
|
||||
}
|
||||
async with aiohttp.ClientSession() as client:
|
||||
resp = await client.get(url, params=params)
|
||||
return (await resp.json()).get("code") != "200" # 查询不到付费数据为开发版
|
||||
|
||||
|
||||
def get_local_data(ulang_code: str) -> dict:
|
||||
"""
|
||||
获取本地化数据
|
||||
Args:
|
||||
ulang_code:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
ulang = Language(ulang_code)
|
||||
return {
|
||||
"monday" : ulang.get("weather.monday"),
|
||||
"tuesday" : ulang.get("weather.tuesday"),
|
||||
"wednesday": ulang.get("weather.wednesday"),
|
||||
"thursday" : ulang.get("weather.thursday"),
|
||||
"friday" : ulang.get("weather.friday"),
|
||||
"saturday" : ulang.get("weather.saturday"),
|
||||
"sunday" : ulang.get("weather.sunday"),
|
||||
"today" : ulang.get("weather.today"),
|
||||
"tomorrow" : ulang.get("weather.tomorrow"),
|
||||
"day" : ulang.get("weather.day"),
|
||||
"night" : ulang.get("weather.night"),
|
||||
"no_aqi" : ulang.get("weather.no_aqi"),
|
||||
}
|
||||
|
||||
|
||||
async def city_lookup(
|
||||
location: str,
|
||||
key: str,
|
||||
adm: str = "",
|
||||
number: int = 20,
|
||||
lang: str = "zh",
|
||||
) -> CityLookup:
|
||||
"""
|
||||
通过关键字搜索城市信息
|
||||
Args:
|
||||
location:
|
||||
key:
|
||||
adm:
|
||||
number:
|
||||
lang: 可传入标准i18n语言代码,如zh-CN、en-US等
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
url = "https://geoapi.qweather.com/v2/city/lookup?"
|
||||
params = {
|
||||
"location": location,
|
||||
"adm" : adm,
|
||||
"number" : number,
|
||||
"key" : key,
|
||||
"lang" : lang,
|
||||
}
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.get(url, params=params)
|
||||
return CityLookup.parse_obj(resp.json())
|
||||
|
||||
|
||||
async def get_weather_now(
|
||||
key: str,
|
||||
location: str,
|
||||
lang: str = "zh",
|
||||
unit: str = "m",
|
||||
dev: bool = get_memory_data("is_dev", True),
|
||||
) -> dict:
|
||||
url_path = "v7/weather/now?"
|
||||
url = dev_url + url_path if dev else com_url + url_path
|
||||
params = {
|
||||
"location": location,
|
||||
"key" : key,
|
||||
"lang" : lang,
|
||||
"unit" : unit,
|
||||
}
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.get(url, params=params)
|
||||
return resp.json()
|
||||
|
||||
|
||||
async def get_weather_daily(
|
||||
key: str,
|
||||
location: str,
|
||||
lang: str = "zh",
|
||||
unit: str = "m",
|
||||
dev: bool = get_memory_data("is_dev", True),
|
||||
) -> dict:
|
||||
url_path = "v7/weather/%dd?" % (7 if dev else 30)
|
||||
url = dev_url + url_path if dev else com_url + url_path
|
||||
params = {
|
||||
"location": location,
|
||||
"key" : key,
|
||||
"lang" : lang,
|
||||
"unit" : unit,
|
||||
}
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.get(url, params=params)
|
||||
return resp.json()
|
||||
|
||||
|
||||
async def get_weather_hourly(
|
||||
key: str,
|
||||
location: str,
|
||||
lang: str = "zh",
|
||||
unit: str = "m",
|
||||
dev: bool = get_memory_data("is_dev", True),
|
||||
) -> dict:
|
||||
url_path = "v7/weather/%dh?" % (24 if dev else 168)
|
||||
url = dev_url + url_path if dev else com_url + url_path
|
||||
params = {
|
||||
"location": location,
|
||||
"key" : key,
|
||||
"lang" : lang,
|
||||
"unit" : unit,
|
||||
}
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.get(url, params=params)
|
||||
return resp.json()
|
||||
|
||||
|
||||
async def get_airquality(
|
||||
key: str,
|
||||
location: str,
|
||||
lang: str,
|
||||
pollutant: bool = False,
|
||||
station: bool = False,
|
||||
dev: bool = get_memory_data("is_dev", True),
|
||||
) -> dict:
|
||||
url_path = f"airquality/v1/now/{location}?"
|
||||
url = dev_url + url_path if dev else com_url + url_path
|
||||
params = {
|
||||
"key" : key,
|
||||
"lang" : lang,
|
||||
"pollutant": pollutant,
|
||||
"station" : station,
|
||||
}
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.get(url, params=params)
|
||||
return resp.json()
|
||||
import aiohttp
|
||||
|
||||
from .qw_models import *
|
||||
import httpx
|
||||
|
||||
from ...utils.base.data_manager import get_memory_data
|
||||
from ...utils.base.language import Language
|
||||
|
||||
dev_url = "https://devapi.qweather.com/" # 开发HBa
|
||||
com_url = "https://api.qweather.com/" # 正式环境
|
||||
|
||||
|
||||
def get_qw_lang(lang: str) -> str:
|
||||
if lang in ["zh-HK", "zh-TW"]:
|
||||
return "zh-hant"
|
||||
elif lang.startswith("zh"):
|
||||
return "zh"
|
||||
elif lang.startswith("en"):
|
||||
return "en"
|
||||
else:
|
||||
return lang
|
||||
|
||||
|
||||
async def check_key_dev(key: str) -> bool:
|
||||
url = "https://api.qweather.com/v7/weather/now?"
|
||||
params = {
|
||||
"location": "101010100",
|
||||
"key" : key,
|
||||
}
|
||||
async with aiohttp.ClientSession() as client:
|
||||
resp = await client.get(url, params=params)
|
||||
return (await resp.json()).get("code") != "200" # 查询不到付费数据为开发版
|
||||
|
||||
|
||||
def get_local_data(ulang_code: str) -> dict:
|
||||
"""
|
||||
获取本地化数据
|
||||
Args:
|
||||
ulang_code:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
ulang = Language(ulang_code)
|
||||
return {
|
||||
"monday" : ulang.get("weather.monday"),
|
||||
"tuesday" : ulang.get("weather.tuesday"),
|
||||
"wednesday": ulang.get("weather.wednesday"),
|
||||
"thursday" : ulang.get("weather.thursday"),
|
||||
"friday" : ulang.get("weather.friday"),
|
||||
"saturday" : ulang.get("weather.saturday"),
|
||||
"sunday" : ulang.get("weather.sunday"),
|
||||
"today" : ulang.get("weather.today"),
|
||||
"tomorrow" : ulang.get("weather.tomorrow"),
|
||||
"day" : ulang.get("weather.day"),
|
||||
"night" : ulang.get("weather.night"),
|
||||
"no_aqi" : ulang.get("weather.no_aqi"),
|
||||
}
|
||||
|
||||
|
||||
async def city_lookup(
|
||||
location: str,
|
||||
key: str,
|
||||
adm: str = "",
|
||||
number: int = 20,
|
||||
lang: str = "zh",
|
||||
) -> CityLookup:
|
||||
"""
|
||||
通过关键字搜索城市信息
|
||||
Args:
|
||||
location:
|
||||
key:
|
||||
adm:
|
||||
number:
|
||||
lang: 可传入标准i18n语言代码,如zh-CN、en-US等
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
url = "https://geoapi.qweather.com/v2/city/lookup?"
|
||||
params = {
|
||||
"location": location,
|
||||
"adm" : adm,
|
||||
"number" : number,
|
||||
"key" : key,
|
||||
"lang" : lang,
|
||||
}
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.get(url, params=params)
|
||||
return CityLookup.parse_obj(resp.json())
|
||||
|
||||
|
||||
async def get_weather_now(
|
||||
key: str,
|
||||
location: str,
|
||||
lang: str = "zh",
|
||||
unit: str = "m",
|
||||
dev: bool = get_memory_data("is_dev", True),
|
||||
) -> dict:
|
||||
url_path = "v7/weather/now?"
|
||||
url = dev_url + url_path if dev else com_url + url_path
|
||||
params = {
|
||||
"location": location,
|
||||
"key" : key,
|
||||
"lang" : lang,
|
||||
"unit" : unit,
|
||||
}
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.get(url, params=params)
|
||||
return resp.json()
|
||||
|
||||
|
||||
async def get_weather_daily(
|
||||
key: str,
|
||||
location: str,
|
||||
lang: str = "zh",
|
||||
unit: str = "m",
|
||||
dev: bool = get_memory_data("is_dev", True),
|
||||
) -> dict:
|
||||
url_path = "v7/weather/%dd?" % (7 if dev else 30)
|
||||
url = dev_url + url_path if dev else com_url + url_path
|
||||
params = {
|
||||
"location": location,
|
||||
"key" : key,
|
||||
"lang" : lang,
|
||||
"unit" : unit,
|
||||
}
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.get(url, params=params)
|
||||
return resp.json()
|
||||
|
||||
|
||||
async def get_weather_hourly(
|
||||
key: str,
|
||||
location: str,
|
||||
lang: str = "zh",
|
||||
unit: str = "m",
|
||||
dev: bool = get_memory_data("is_dev", True),
|
||||
) -> dict:
|
||||
url_path = "v7/weather/%dh?" % (24 if dev else 168)
|
||||
url = dev_url + url_path if dev else com_url + url_path
|
||||
params = {
|
||||
"location": location,
|
||||
"key" : key,
|
||||
"lang" : lang,
|
||||
"unit" : unit,
|
||||
}
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.get(url, params=params)
|
||||
return resp.json()
|
||||
|
||||
|
||||
async def get_airquality(
|
||||
key: str,
|
||||
location: str,
|
||||
lang: str,
|
||||
pollutant: bool = False,
|
||||
station: bool = False,
|
||||
dev: bool = get_memory_data("is_dev", True),
|
||||
) -> dict:
|
||||
url_path = f"airquality/v1/now/{location}?"
|
||||
url = dev_url + url_path if dev else com_url + url_path
|
||||
params = {
|
||||
"key" : key,
|
||||
"lang" : lang,
|
||||
"pollutant": pollutant,
|
||||
"station" : station,
|
||||
}
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.get(url, params=params)
|
||||
return resp.json()
|
@ -1,62 +1,62 @@
|
||||
from src.utils.base.data import LiteModel
|
||||
|
||||
|
||||
class Location(LiteModel):
|
||||
name: str = ""
|
||||
id: str = ""
|
||||
lat: str = ""
|
||||
lon: str = ""
|
||||
adm2: str = ""
|
||||
adm1: str = ""
|
||||
country: str = ""
|
||||
tz: str = ""
|
||||
utcOffset: str = ""
|
||||
isDst: str = ""
|
||||
type: str = ""
|
||||
rank: str = ""
|
||||
fxLink: str = ""
|
||||
sources: str = ""
|
||||
license: str = ""
|
||||
|
||||
|
||||
class CityLookup(LiteModel):
|
||||
code: str = ""
|
||||
location: list[Location] = [Location()]
|
||||
|
||||
|
||||
class Now(LiteModel):
|
||||
obsTime: str = ""
|
||||
temp: str = ""
|
||||
feelsLike: str = ""
|
||||
icon: str = ""
|
||||
text: str = ""
|
||||
wind360: str = ""
|
||||
windDir: str = ""
|
||||
windScale: str = ""
|
||||
windSpeed: str = ""
|
||||
humidity: str = ""
|
||||
precip: str = ""
|
||||
pressure: str = ""
|
||||
vis: str = ""
|
||||
cloud: str = ""
|
||||
dew: str = ""
|
||||
sources: str = ""
|
||||
license: str = ""
|
||||
|
||||
|
||||
class WeatherNow(LiteModel):
|
||||
code: str = ""
|
||||
updateTime: str = ""
|
||||
fxLink: str = ""
|
||||
now: Now = Now()
|
||||
|
||||
|
||||
class Daily(LiteModel):
|
||||
pass
|
||||
|
||||
|
||||
class WeatherDaily(LiteModel):
|
||||
code: str = ""
|
||||
updateTime: str = ""
|
||||
fxLink: str = ""
|
||||
daily: list[str] = []
|
||||
from src.utils.base.data import LiteModel
|
||||
|
||||
|
||||
class Location(LiteModel):
|
||||
name: str = ""
|
||||
id: str = ""
|
||||
lat: str = ""
|
||||
lon: str = ""
|
||||
adm2: str = ""
|
||||
adm1: str = ""
|
||||
country: str = ""
|
||||
tz: str = ""
|
||||
utcOffset: str = ""
|
||||
isDst: str = ""
|
||||
type: str = ""
|
||||
rank: str = ""
|
||||
fxLink: str = ""
|
||||
sources: str = ""
|
||||
license: str = ""
|
||||
|
||||
|
||||
class CityLookup(LiteModel):
|
||||
code: str = ""
|
||||
location: list[Location] = [Location()]
|
||||
|
||||
|
||||
class Now(LiteModel):
|
||||
obsTime: str = ""
|
||||
temp: str = ""
|
||||
feelsLike: str = ""
|
||||
icon: str = ""
|
||||
text: str = ""
|
||||
wind360: str = ""
|
||||
windDir: str = ""
|
||||
windScale: str = ""
|
||||
windSpeed: str = ""
|
||||
humidity: str = ""
|
||||
precip: str = ""
|
||||
pressure: str = ""
|
||||
vis: str = ""
|
||||
cloud: str = ""
|
||||
dew: str = ""
|
||||
sources: str = ""
|
||||
license: str = ""
|
||||
|
||||
|
||||
class WeatherNow(LiteModel):
|
||||
code: str = ""
|
||||
updateTime: str = ""
|
||||
fxLink: str = ""
|
||||
now: Now = Now()
|
||||
|
||||
|
||||
class Daily(LiteModel):
|
||||
pass
|
||||
|
||||
|
||||
class WeatherDaily(LiteModel):
|
||||
code: str = ""
|
||||
updateTime: str = ""
|
||||
fxLink: str = ""
|
||||
daily: list[str] = []
|
@ -1,102 +1,102 @@
|
||||
from nonebot import require, on_endswith
|
||||
from nonebot.adapters import satori
|
||||
from nonebot.adapters.onebot.v11 import MessageSegment
|
||||
from nonebot.internal.matcher import Matcher
|
||||
|
||||
from src.utils.base.config import get_config
|
||||
from src.utils.base.ly_typing import T_MessageEvent
|
||||
|
||||
from .qw_api import *
|
||||
from src.utils.base.data_manager import User, user_db
|
||||
from src.utils.base.language import Language, get_user_lang
|
||||
from src.utils.base.resource import get_path
|
||||
from src.utils.message.html_tool import template2image
|
||||
from src.utils import event as event_utils
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
from nonebot_plugin_alconna import on_alconna, Alconna, Args, MultiVar, Arparma, UniMessage
|
||||
|
||||
wx_alc = on_alconna(
|
||||
aliases={"天气"},
|
||||
command=Alconna(
|
||||
"weather",
|
||||
Args["keywords", MultiVar(str), []],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@wx_alc.handle()
|
||||
async def _(result: Arparma, event: T_MessageEvent, matcher: Matcher):
|
||||
"""await alconna.send("weather", city)"""
|
||||
kws = result.main_args.get("keywords")
|
||||
image = await get_weather_now_card(matcher, event, kws)
|
||||
await wx_alc.finish(UniMessage.image(raw=image))
|
||||
|
||||
|
||||
@on_endswith(("天气", "weather")).handle()
|
||||
async def _(event: T_MessageEvent, matcher: Matcher):
|
||||
"""await alconna.send("weather", city)"""
|
||||
# kws = event.message.extract_plain_text()
|
||||
kws = event.get_plaintext()
|
||||
image = await get_weather_now_card(matcher, event, [kws.replace("天气", "").replace("weather", "")], False)
|
||||
if isinstance(event, satori.event.Event):
|
||||
await matcher.finish(satori.MessageSegment.image(raw=image, mime="image/png"))
|
||||
else:
|
||||
await matcher.finish(MessageSegment.image(image))
|
||||
|
||||
|
||||
async def get_weather_now_card(matcher: Matcher, event: T_MessageEvent, keyword: list[str], tip: bool = True):
|
||||
ulang = get_user_lang(event_utils.get_user_id(event))
|
||||
qw_lang = get_qw_lang(ulang.lang_code)
|
||||
key = get_config("weather_key")
|
||||
is_dev = get_memory_data("weather.is_dev", True)
|
||||
user: User = user_db.where_one(User(), "user_id = ?", event_utils.get_user_id(event), default=User())
|
||||
# params
|
||||
unit = user.profile.get("unit", "m")
|
||||
stored_location = user.profile.get("location", None)
|
||||
|
||||
if not key:
|
||||
await matcher.finish(ulang.get("weather.no_key") if tip else None)
|
||||
|
||||
if keyword:
|
||||
if len(keyword) >= 2:
|
||||
adm = keyword[0]
|
||||
city = keyword[-1]
|
||||
else:
|
||||
adm = ""
|
||||
city = keyword[0]
|
||||
city_info = await city_lookup(city, key, adm=adm, lang=qw_lang)
|
||||
city_name = " ".join(keyword)
|
||||
else:
|
||||
if not stored_location:
|
||||
await matcher.finish(ulang.get("liteyuki.invalid_command", TEXT="location") if tip else None)
|
||||
city_info = await city_lookup(stored_location, key, lang=qw_lang)
|
||||
city_name = stored_location
|
||||
if city_info.code == "200":
|
||||
location_data = city_info.location[0]
|
||||
else:
|
||||
await matcher.finish(ulang.get("weather.city_not_found", CITY=city_name) if tip else None)
|
||||
weather_now = await get_weather_now(key, location_data.id, lang=qw_lang, unit=unit, dev=is_dev)
|
||||
weather_daily = await get_weather_daily(key, location_data.id, lang=qw_lang, unit=unit, dev=is_dev)
|
||||
weather_hourly = await get_weather_hourly(key, location_data.id, lang=qw_lang, unit=unit, dev=is_dev)
|
||||
aqi = await get_airquality(key, location_data.id, lang=qw_lang, dev=is_dev)
|
||||
|
||||
image = await template2image(
|
||||
template=get_path("templates/weather_now.html", abs_path=True),
|
||||
templates={
|
||||
"data": {
|
||||
"params" : {
|
||||
"unit": unit,
|
||||
"lang": ulang.lang_code,
|
||||
},
|
||||
"weatherNow" : weather_now,
|
||||
"weatherDaily" : weather_daily,
|
||||
"weatherHourly": weather_hourly,
|
||||
"aqi" : aqi,
|
||||
"location" : location_data.dump(),
|
||||
"localization" : get_local_data(ulang.lang_code),
|
||||
"is_dev": 1 if is_dev else 0
|
||||
}
|
||||
},
|
||||
)
|
||||
return image
|
||||
from nonebot import require, on_endswith
|
||||
from nonebot.adapters import satori
|
||||
from nonebot.adapters.onebot.v11 import MessageSegment
|
||||
from nonebot.internal.matcher import Matcher
|
||||
|
||||
from src.utils.base.config import get_config
|
||||
from src.utils.base.ly_typing import T_MessageEvent
|
||||
|
||||
from .qw_api import *
|
||||
from src.utils.base.data_manager import User, user_db
|
||||
from src.utils.base.language import Language, get_user_lang
|
||||
from src.utils.base.resource import get_path
|
||||
from src.utils.message.html_tool import template2image
|
||||
from src.utils import event as event_utils
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
from nonebot_plugin_alconna import on_alconna, Alconna, Args, MultiVar, Arparma, UniMessage
|
||||
|
||||
wx_alc = on_alconna(
|
||||
aliases={"天气"},
|
||||
command=Alconna(
|
||||
"weather",
|
||||
Args["keywords", MultiVar(str), []],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@wx_alc.handle()
|
||||
async def _(result: Arparma, event: T_MessageEvent, matcher: Matcher):
|
||||
"""await alconna.send("weather", city)"""
|
||||
kws = result.main_args.get("keywords")
|
||||
image = await get_weather_now_card(matcher, event, kws)
|
||||
await wx_alc.finish(UniMessage.image(raw=image))
|
||||
|
||||
|
||||
@on_endswith(("天气", "weather")).handle()
|
||||
async def _(event: T_MessageEvent, matcher: Matcher):
|
||||
"""await alconna.send("weather", city)"""
|
||||
# kws = event.message.extract_plain_text()
|
||||
kws = event.get_plaintext()
|
||||
image = await get_weather_now_card(matcher, event, [kws.replace("天气", "").replace("weather", "")], False)
|
||||
if isinstance(event, satori.event.Event):
|
||||
await matcher.finish(satori.MessageSegment.image(raw=image, mime="image/png"))
|
||||
else:
|
||||
await matcher.finish(MessageSegment.image(image))
|
||||
|
||||
|
||||
async def get_weather_now_card(matcher: Matcher, event: T_MessageEvent, keyword: list[str], tip: bool = True):
|
||||
ulang = get_user_lang(event_utils.get_user_id(event))
|
||||
qw_lang = get_qw_lang(ulang.lang_code)
|
||||
key = get_config("weather_key")
|
||||
is_dev = get_memory_data("weather.is_dev", True)
|
||||
user: User = user_db.where_one(User(), "user_id = ?", event_utils.get_user_id(event), default=User())
|
||||
# params
|
||||
unit = user.profile.get("unit", "m")
|
||||
stored_location = user.profile.get("location", None)
|
||||
|
||||
if not key:
|
||||
await matcher.finish(ulang.get("weather.no_key") if tip else None)
|
||||
|
||||
if keyword:
|
||||
if len(keyword) >= 2:
|
||||
adm = keyword[0]
|
||||
city = keyword[-1]
|
||||
else:
|
||||
adm = ""
|
||||
city = keyword[0]
|
||||
city_info = await city_lookup(city, key, adm=adm, lang=qw_lang)
|
||||
city_name = " ".join(keyword)
|
||||
else:
|
||||
if not stored_location:
|
||||
await matcher.finish(ulang.get("liteyuki.invalid_command", TEXT="location") if tip else None)
|
||||
city_info = await city_lookup(stored_location, key, lang=qw_lang)
|
||||
city_name = stored_location
|
||||
if city_info.code == "200":
|
||||
location_data = city_info.location[0]
|
||||
else:
|
||||
await matcher.finish(ulang.get("weather.city_not_found", CITY=city_name) if tip else None)
|
||||
weather_now = await get_weather_now(key, location_data.id, lang=qw_lang, unit=unit, dev=is_dev)
|
||||
weather_daily = await get_weather_daily(key, location_data.id, lang=qw_lang, unit=unit, dev=is_dev)
|
||||
weather_hourly = await get_weather_hourly(key, location_data.id, lang=qw_lang, unit=unit, dev=is_dev)
|
||||
aqi = await get_airquality(key, location_data.id, lang=qw_lang, dev=is_dev)
|
||||
|
||||
image = await template2image(
|
||||
template=get_path("templates/weather_now.html", abs_path=True),
|
||||
templates={
|
||||
"data": {
|
||||
"params" : {
|
||||
"unit": unit,
|
||||
"lang": ulang.lang_code,
|
||||
},
|
||||
"weatherNow" : weather_now,
|
||||
"weatherDaily" : weather_daily,
|
||||
"weatherHourly": weather_hourly,
|
||||
"aqi" : aqi,
|
||||
"location" : location_data.dump(),
|
||||
"localization" : get_local_data(ulang.lang_code),
|
||||
"is_dev": 1 if is_dev else 0
|
||||
}
|
||||
},
|
||||
)
|
||||
return image
|
@ -1,7 +1,7 @@
|
||||
import threading
|
||||
|
||||
from nonebot import logger
|
||||
from liteyuki.core.spawn_process import chan_in_spawn_nb
|
||||
from liteyuki.comm.channel import get_channel
|
||||
|
||||
|
||||
def reload(delay: float = 0.0, receiver: str = "nonebot"):
|
||||
@ -14,6 +14,13 @@ def reload(delay: float = 0.0, receiver: str = "nonebot"):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
chan = get_channel(receiver + "-active")
|
||||
if chan is None:
|
||||
logger.error(f"Channel {receiver}-active not found, cannot reload.")
|
||||
return
|
||||
|
||||
chan_in_spawn_nb.send(1, receiver)
|
||||
logger.info(f"Reloading LiteyukiBot({receiver})...")
|
||||
if delay > 0:
|
||||
threading.Timer(delay, chan.send, args=(1,)).start()
|
||||
return
|
||||
else:
|
||||
chan.send(1)
|
||||
|
Loading…
Reference in New Issue
Block a user