mirror of
https://github.com/LiteyukiStudio/LiteyukiBot.git
synced 2024-11-22 21:27:43 +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 threading
|
||||||
import time
|
import time
|
||||||
import asyncio
|
|
||||||
from typing import Any, Optional
|
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.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 import IS_MAIN_PROCESS
|
||||||
from liteyuki.core.manager import ProcessManager
|
from liteyuki.core.manager import ProcessManager
|
||||||
from liteyuki.core.spawn_process import mb_run, nb_run
|
from liteyuki.core.spawn_process import mb_run, nb_run
|
||||||
from liteyuki.log import init_log, logger
|
from liteyuki.log import init_log, logger
|
||||||
from liteyuki.plugin import load_plugins
|
from liteyuki.plugin import load_plugins
|
||||||
from liteyuki.utils import run_coroutine
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"LiteyukiBot",
|
"LiteyukiBot",
|
||||||
"get_bot"
|
"get_bot"
|
||||||
]
|
]
|
||||||
|
|
||||||
"""是否为主进程"""
|
|
||||||
|
|
||||||
|
|
||||||
class LiteyukiBot:
|
class LiteyukiBot:
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@ -29,11 +30,12 @@ class LiteyukiBot:
|
|||||||
self.init(**self.config) # 初始化
|
self.init(**self.config) # 初始化
|
||||||
|
|
||||||
self.lifespan: Lifespan = Lifespan()
|
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()
|
self.loop = asyncio.new_event_loop()
|
||||||
asyncio.set_event_loop(self.loop)
|
asyncio.set_event_loop(self.loop)
|
||||||
self.loop_thread = threading.Thread(target=self.loop.run_forever, daemon=True)
|
self.loop_thread = threading.Thread(target=self.loop.run_forever, daemon=True)
|
||||||
|
self.call_restart_count = 0
|
||||||
|
|
||||||
print("\033[34m" + r"""
|
print("\033[34m" + r"""
|
||||||
__ ______ ________ ________ __ __ __ __ __ __ ______
|
__ ______ ________ ________ __ __ __ __ __ __ ______
|
||||||
@ -53,15 +55,83 @@ $$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/
|
|||||||
self.loop_thread.start() # 启动事件循环
|
self.loop_thread.start() # 启动事件循环
|
||||||
asyncio.run(self.lifespan.before_start()) # 启动前钩子
|
asyncio.run(self.lifespan.before_start()) # 启动前钩子
|
||||||
|
|
||||||
self.pm.add_target("nonebot", nb_run, **self.config)
|
self.process_manager.add_target("nonebot", nb_run, **self.config)
|
||||||
self.pm.start("nonebot")
|
self.process_manager.start("nonebot")
|
||||||
|
|
||||||
self.pm.add_target("melobot", mb_run, **self.config)
|
self.process_manager.add_target("melobot", mb_run, **self.config)
|
||||||
self.pm.start("melobot")
|
self.process_manager.start("melobot")
|
||||||
|
|
||||||
asyncio.run(self.lifespan.after_start()) # 启动后钩子
|
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:
|
Args:
|
||||||
@ -75,10 +145,10 @@ $$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/
|
|||||||
self.loop.create_task(self.lifespan.before_shutdown()) # 停止前钩子
|
self.loop.create_task(self.lifespan.before_shutdown()) # 停止前钩子
|
||||||
|
|
||||||
if name:
|
if name:
|
||||||
self.chan.send(1, name)
|
self.chan_active.send(1, name)
|
||||||
else:
|
else:
|
||||||
for name in self.pm.targets:
|
for name in self.process_manager.targets:
|
||||||
self.chan.send(1, name)
|
self.chan_active.send(1, name)
|
||||||
|
|
||||||
def init(self, *args, **kwargs):
|
def init(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -9,11 +9,22 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|||||||
@Software: PyCharm
|
@Software: PyCharm
|
||||||
该模块用于轻雪主进程和Nonebot子进程之间的通信
|
该模块用于轻雪主进程和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
|
from liteyuki.comm.event import Event
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Channel",
|
"Channel",
|
||||||
"chan",
|
"chan",
|
||||||
"Event",
|
"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 multiprocessing import Pipe
|
||||||
from typing import Any, Optional, Callable, Awaitable, List, TypeAlias
|
from typing import Any, Optional, Callable, Awaitable, List, TypeAlias
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
from liteyuki.utils import is_coroutine_callable, run_coroutine
|
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]]
|
ASYNC_FILTER_FUNC: TypeAlias = Callable[[Any], Awaitable[bool]]
|
||||||
FILTER_FUNC: TypeAlias = SYNC_FILTER_FUNC | ASYNC_FILTER_FUNC
|
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:
|
class Channel:
|
||||||
"""
|
"""
|
||||||
通道类,用于进程间通信
|
通道类,用于进程间通信,进程内不可用,仅限主进程和子进程之间通信
|
||||||
有两种接收工作方式,但是只能选择一种,主动接收和被动接收,主动接收使用 `receive` 方法,被动接收使用 `on_receive` 装饰器
|
有两种接收工作方式,但是只能选择一种,主动接收和被动接收,主动接收使用 `receive` 方法,被动接收使用 `on_receive` 装饰器
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, _id: str):
|
||||||
self.receive_conn, self.send_conn = Pipe()
|
self.main_send_conn, self.sub_receive_conn = Pipe()
|
||||||
|
self.sub_send_conn, self.main_receive_conn = Pipe()
|
||||||
self._closed = False
|
self._closed = False
|
||||||
self._on_receive_funcs: List[ON_RECEIVE_FUNC] = []
|
self._on_main_receive_funcs: list[str] = []
|
||||||
self._on_receive_funcs_with_receiver: dict[str, List[ON_RECEIVE_FUNC]] = {}
|
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:
|
Args:
|
||||||
data: 数据
|
data: 数据
|
||||||
receiver: 接收者,如果为None则广播
|
|
||||||
"""
|
"""
|
||||||
if self._closed:
|
if self._closed:
|
||||||
raise RuntimeError("Cannot send to a closed channel")
|
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:
|
Args:
|
||||||
receiver: 接收者,如果为None则接收任意数据
|
|
||||||
"""
|
"""
|
||||||
if self._closed:
|
if self._closed:
|
||||||
raise RuntimeError("Cannot receive from a closed channel")
|
raise RuntimeError("Cannot receive from a closed channel")
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
# 判断receiver是否为None或者receiver是否等于接收者,是则接收数据,否则不动数据
|
# 判断receiver是否为None或者receiver是否等于接收者,是则接收数据,否则不动数据
|
||||||
data, receiver_ = self.receive_conn.recv()
|
if IS_MAIN_PROCESS:
|
||||||
if receiver is None or receiver == receiver_:
|
data = self.main_receive_conn.recv()
|
||||||
self._run_on_receive_funcs(data, receiver_)
|
print("主进程接收数据:", data)
|
||||||
return data
|
else:
|
||||||
self.send_conn.send((data, receiver_))
|
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 data
|
||||||
return None
|
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""
|
"""
|
||||||
关闭通道
|
关闭通道
|
||||||
"""
|
"""
|
||||||
self._closed = True
|
self._closed = True
|
||||||
self.receive_conn.close()
|
self.sub_receive_conn.close()
|
||||||
self.send_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:
|
Args:
|
||||||
filter_func: 过滤函数,为None则不过滤
|
filter_func: 过滤函数,为None则不过滤
|
||||||
receiver: 接收者, 为None则接收任意数据
|
|
||||||
Returns:
|
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:
|
def decorator(func: ON_RECEIVE_FUNC) -> ON_RECEIVE_FUNC:
|
||||||
async def wrapper(data: Any) -> Any:
|
async def wrapper(data: Any) -> Any:
|
||||||
@ -105,28 +122,53 @@ class Channel:
|
|||||||
return
|
return
|
||||||
return await func(data)
|
return await func(data)
|
||||||
|
|
||||||
if receiver is None:
|
function_id = str(uuid4())
|
||||||
self._on_receive_funcs.append(wrapper)
|
_callback_funcs[function_id] = wrapper
|
||||||
|
if IS_MAIN_PROCESS:
|
||||||
|
self._on_main_receive_funcs.append(function_id)
|
||||||
else:
|
else:
|
||||||
if receiver not in self._on_receive_funcs_with_receiver:
|
self._on_sub_receive_funcs.append(function_id)
|
||||||
self._on_receive_funcs_with_receiver[receiver] = []
|
|
||||||
self._on_receive_funcs_with_receiver[receiver].append(wrapper)
|
|
||||||
return func
|
return func
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
def _run_on_receive_funcs(self, data: Any, receiver: Optional[str] = None):
|
def _run_on_main_receive_funcs(self, data: Any):
|
||||||
"""
|
"""
|
||||||
运行接收函数
|
运行接收函数
|
||||||
Args:
|
Args:
|
||||||
data: 数据
|
data: 数据
|
||||||
"""
|
"""
|
||||||
if receiver is None:
|
for func_id in self._on_main_receive_funcs:
|
||||||
for func in self._on_receive_funcs:
|
func = _callback_funcs[func_id]
|
||||||
run_coroutine(func(data))
|
run_coroutine(func(data))
|
||||||
else:
|
|
||||||
for func in self._on_receive_funcs_with_receiver.get(receiver, []):
|
def _run_on_sub_receive_funcs(self, data: Any):
|
||||||
run_coroutine(func(data))
|
"""
|
||||||
|
运行接收函数
|
||||||
|
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):
|
def __iter__(self):
|
||||||
return 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 multiprocessing import Process
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from liteyuki.comm import Channel
|
from liteyuki.comm import Channel, get_channel, set_channels
|
||||||
from liteyuki.log import logger
|
from liteyuki.log import logger
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
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.bot = bot
|
||||||
self.chan = chan
|
|
||||||
self.targets: dict[str, tuple[callable, tuple, dict]] = {}
|
self.targets: dict[str, tuple[callable, tuple, dict]] = {}
|
||||||
self.processes: dict[str, Process] = {}
|
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):
|
def start(self, name: str, delay: int = 0):
|
||||||
"""
|
"""
|
||||||
开启后自动监控进程,并添加到进程字典中
|
开启后自动监控进程,并添加到进程字典中
|
||||||
@ -47,19 +53,21 @@ class ProcessManager:
|
|||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if name not in self.targets:
|
if name not in self.targets:
|
||||||
raise KeyError(f"Process {name} not found.")
|
raise KeyError(f"Process {name} not found.")
|
||||||
|
|
||||||
def _start():
|
def _start():
|
||||||
should_exit = False
|
should_exit = False
|
||||||
while not should_exit:
|
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
|
self.processes[name] = process
|
||||||
process.start()
|
process.start()
|
||||||
while not should_exit:
|
while not should_exit:
|
||||||
# 0退出 1重启
|
# 0退出 1重启
|
||||||
data = self.chan.receive(name)
|
data = chan_active.receive()
|
||||||
if data == 1:
|
if data == 1:
|
||||||
logger.info(f"Restarting process {name}")
|
logger.info(f"Restarting process {name}")
|
||||||
asyncio.run(self.bot.lifespan.before_shutdown())
|
asyncio.run(self.bot.lifespan.before_shutdown())
|
||||||
@ -103,3 +111,7 @@ class ProcessManager:
|
|||||||
process.join(TIMEOUT)
|
process.join(TIMEOUT)
|
||||||
if process.is_alive():
|
if process.is_alive():
|
||||||
process.kill()
|
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
|
import nonebot
|
||||||
|
|
||||||
from liteyuki.core.nb import adapter_manager, driver_manager
|
from liteyuki.core.nb import adapter_manager, driver_manager
|
||||||
|
from liteyuki.comm.channel import set_channel
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from liteyuki.comm.channel import Channel
|
from liteyuki.comm.channel import Channel
|
||||||
@ -10,23 +11,23 @@ if TYPE_CHECKING:
|
|||||||
timeout_limit: int = 20
|
timeout_limit: int = 20
|
||||||
|
|
||||||
"""导出对象,用于主进程与nonebot通信"""
|
"""导出对象,用于主进程与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并运行在子进程
|
初始化NoneBot并运行在子进程
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
chan:
|
chan_active:
|
||||||
*args:
|
chan_passive:
|
||||||
**kwargs:
|
**kwargs:
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
global chan_in_spawn_nb
|
set_channel("nonebot-active", chan_active)
|
||||||
chan_in_spawn_nb = chan
|
set_channel("nonebot-passive", chan_passive)
|
||||||
nonebot.init(**kwargs)
|
nonebot.init(**kwargs)
|
||||||
driver_manager.init(config=kwargs)
|
driver_manager.init(config=kwargs)
|
||||||
adapter_manager.init(kwargs)
|
adapter_manager.init(kwargs)
|
||||||
@ -35,17 +36,21 @@ def nb_run(chan, *args, **kwargs):
|
|||||||
nonebot.run()
|
nonebot.run()
|
||||||
|
|
||||||
|
|
||||||
def mb_run(chan, *args, **kwargs):
|
def mb_run(chan_active: "Channel", chan_passive: "Channel", *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
初始化MeloBot并运行在子进程
|
初始化MeloBot并运行在子进程
|
||||||
Args:
|
Args:
|
||||||
chan
|
chan_active
|
||||||
|
chan_passive
|
||||||
*args:
|
*args:
|
||||||
**kwargs:
|
**kwargs:
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
set_channel("melobot-active", chan_active)
|
||||||
|
set_channel("melobot-passive", chan_passive)
|
||||||
|
|
||||||
# bot = MeloBot(__name__)
|
# bot = MeloBot(__name__)
|
||||||
# bot.init(AbstractConnector(cd_time=0))
|
# bot.init(AbstractConnector(cd_time=0))
|
||||||
# bot.run()
|
# bot.run()
|
||||||
|
@ -7,14 +7,19 @@
|
|||||||
# @Email : snowykami@outlook.com
|
# @Email : snowykami@outlook.com
|
||||||
# @File : asa.py
|
# @File : asa.py
|
||||||
# @Software: PyCharm
|
# @Software: PyCharm
|
||||||
|
import asyncio
|
||||||
|
|
||||||
from liteyuki.plugin import PluginMetadata
|
from liteyuki.plugin import PluginMetadata
|
||||||
from liteyuki import get_bot, logger
|
from liteyuki import get_bot, logger
|
||||||
|
from liteyuki.comm.channel import get_channel
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="lifespan_monitor",
|
name="lifespan_monitor",
|
||||||
)
|
)
|
||||||
|
|
||||||
bot = get_bot()
|
bot = get_bot()
|
||||||
|
nbp_chan = get_channel("nonebot-passive")
|
||||||
|
mbp_chan = get_channel("melobot-passive")
|
||||||
|
|
||||||
|
|
||||||
@bot.on_before_start
|
@bot.on_before_start
|
||||||
@ -24,6 +29,7 @@ def _():
|
|||||||
|
|
||||||
@bot.on_before_shutdown
|
@bot.on_before_shutdown
|
||||||
def _():
|
def _():
|
||||||
|
print(get_channel("main"))
|
||||||
logger.info("生命周期监控器:准备停止")
|
logger.info("生命周期监控器:准备停止")
|
||||||
|
|
||||||
|
|
||||||
@ -35,3 +41,17 @@ def _():
|
|||||||
@bot.on_after_start
|
@bot.on_after_start
|
||||||
def _():
|
def _():
|
||||||
logger.info("生命周期监控器:启动完成")
|
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
|
import pip
|
||||||
from nonebot import Bot, get_driver, require
|
from nonebot import Bot, get_driver, require
|
||||||
from nonebot.adapters import onebot, satori
|
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.exception import MockApiException
|
||||||
from nonebot.internal.matcher import Matcher
|
from nonebot.internal.matcher import Matcher
|
||||||
from nonebot.permission import SUPERUSER
|
from nonebot.permission import SUPERUSER
|
||||||
|
|
||||||
from liteyuki 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.config import get_config, load_from_yaml
|
||||||
from src.utils.base.data_manager import StoredConfig, TempConfig, common_db
|
from src.utils.base.data_manager import StoredConfig, TempConfig, common_db
|
||||||
from src.utils.base.language import get_user_lang
|
from src.utils.base.language import get_user_lang
|
||||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
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.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 .api import update_liteyuki
|
||||||
from liteyuki.bot import get_bot
|
|
||||||
from ..utils.base import reload
|
from ..utils.base import reload
|
||||||
from ..utils.base.ly_function import get_function
|
from ..utils.base.ly_function import get_function
|
||||||
|
|
||||||
require("nonebot_plugin_alconna")
|
require("nonebot_plugin_alconna")
|
||||||
require("nonebot_plugin_apscheduler")
|
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
|
from nonebot_plugin_apscheduler import scheduler
|
||||||
|
|
||||||
driver = get_driver()
|
driver = get_driver()
|
||||||
@ -81,7 +77,6 @@ async def _(bot: T_Bot, event: T_MessageEvent):
|
|||||||
).handle()
|
).handle()
|
||||||
# Satori OK
|
# Satori OK
|
||||||
async def _(matcher: Matcher, bot: T_Bot, event: T_MessageEvent):
|
async def _(matcher: Matcher, bot: T_Bot, event: T_MessageEvent):
|
||||||
global channel_in_spawn_process
|
|
||||||
await matcher.send("Liteyuki reloading")
|
await matcher.send("Liteyuki reloading")
|
||||||
temp_data = common_db.where_one(TempConfig(), default=TempConfig())
|
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_time" : time.time(),
|
||||||
"reload_bot_id" : bot.self_id,
|
"reload_bot_id" : bot.self_id,
|
||||||
"reload_session_type": event_utils.get_message_type(event),
|
"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,
|
"reload_session_id" : (event.group_id if event.message_type == "group" else event.user_id)
|
||||||
satori.event.Event) else event.chan.id,
|
if not isinstance(event, satori.event.Event) else event.chan_active.id,
|
||||||
"delta_time" : 0
|
"delta_time" : 0
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -358,7 +353,7 @@ async def _(bot: T_Bot):
|
|||||||
delta_time = temp_data.data.get("delta_time", 0)
|
delta_time = temp_data.data.get("delta_time", 0)
|
||||||
common_db.save(temp_data) # 更新数据
|
common_db.save(temp_data) # 更新数据
|
||||||
|
|
||||||
if delta_time <= 20.0: # 启动时间太长就别发了,丢人
|
if delta_time <= 20.0: # 启动时间太长就别发了,丢人
|
||||||
if isinstance(bot, satori.Bot):
|
if isinstance(bot, satori.Bot):
|
||||||
await bot.send_message(
|
await bot.send_message(
|
||||||
channel_id=reload_session_id,
|
channel_id=reload_session_id,
|
||||||
|
@ -11,35 +11,12 @@ if get_config("debug", False):
|
|||||||
|
|
||||||
liteyuki_bot = get_bot()
|
liteyuki_bot = get_bot()
|
||||||
|
|
||||||
src_directories = (
|
|
||||||
"src/liteyuki_main",
|
|
||||||
"src/plugins",
|
|
||||||
"src/utils",
|
|
||||||
)
|
|
||||||
src_excludes_extensions = (
|
|
||||||
"pyc",
|
|
||||||
)
|
|
||||||
|
|
||||||
res_directories = (
|
res_directories = (
|
||||||
"src/resources",
|
"src/resources",
|
||||||
"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):
|
class ResourceModifiedHandler(FileSystemEventHandler):
|
||||||
"""
|
"""
|
||||||
@ -51,12 +28,9 @@ if get_config("debug", False):
|
|||||||
load_resources()
|
load_resources()
|
||||||
|
|
||||||
|
|
||||||
code_modified_handler = CodeModifiedHandler()
|
|
||||||
resource_modified_handle = ResourceModifiedHandler()
|
resource_modified_handle = ResourceModifiedHandler()
|
||||||
|
|
||||||
observer = Observer()
|
observer = Observer()
|
||||||
for directory in src_directories:
|
|
||||||
observer.schedule(code_modified_handler, directory, recursive=True)
|
|
||||||
for directory in res_directories:
|
for directory in res_directories:
|
||||||
observer.schedule(resource_modified_handle, directory, recursive=True)
|
observer.schedule(resource_modified_handle, directory, recursive=True)
|
||||||
observer.start()
|
observer.start()
|
||||||
|
@ -21,7 +21,7 @@ liteyuki_bot = get_bot()
|
|||||||
|
|
||||||
@driver.on_startup
|
@driver.on_startup
|
||||||
async def load_plugins():
|
async def load_plugins():
|
||||||
nonebot.plugin.load_plugins("src/plugins")
|
nonebot.plugin.load_plugins("src/nonebot_plugins")
|
||||||
# 从数据库读取已安装的插件
|
# 从数据库读取已安装的插件
|
||||||
if not get_config("safe_mode", False):
|
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
|
import multiprocessing
|
||||||
|
|
||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
from .rt_guide import *
|
from liteyuki.comm import get_channel
|
||||||
from .crt_matchers import *
|
from .rt_guide import *
|
||||||
|
from .crt_matchers import *
|
||||||
__plugin_meta__ = PluginMetadata(
|
|
||||||
name="CRT生成工具",
|
__plugin_meta__ = PluginMetadata(
|
||||||
description="一些CRT牌子生成器",
|
name="CRT生成工具",
|
||||||
usage="我觉得你应该会用",
|
description="一些CRT牌子生成器",
|
||||||
type="application",
|
usage="我觉得你应该会用",
|
||||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
type="application",
|
||||||
extra={
|
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||||
"liteyuki" : True,
|
extra={
|
||||||
"toggleable" : True,
|
"liteyuki" : True,
|
||||||
"default_enable": 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
|
from urllib.parse import quote
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
from nonebot import require
|
from nonebot import require
|
||||||
|
|
||||||
from src.utils.event import get_user_id
|
from src.utils.event import get_user_id
|
||||||
from src.utils.base.language import Language
|
from src.utils.base.language import Language
|
||||||
from src.utils.base.ly_typing import T_MessageEvent
|
from src.utils.base.ly_typing import T_MessageEvent
|
||||||
from src.utils.base.resource import get_path
|
from src.utils.base.resource import get_path
|
||||||
from src.utils.message.html_tool import template2image
|
from src.utils.message.html_tool import template2image
|
||||||
|
|
||||||
require("nonebot_plugin_alconna")
|
require("nonebot_plugin_alconna")
|
||||||
|
|
||||||
from nonebot_plugin_alconna import UniMessage, on_alconna, Alconna, Args, Subcommand, Arparma, Option
|
from nonebot_plugin_alconna import UniMessage, on_alconna, Alconna, Args, Subcommand, Arparma, Option
|
||||||
|
|
||||||
crt_cmd = on_alconna(
|
crt_cmd = on_alconna(
|
||||||
Alconna(
|
Alconna(
|
||||||
"crt",
|
"crt",
|
||||||
Subcommand(
|
Subcommand(
|
||||||
"route",
|
"route",
|
||||||
Args["start", str, "沙坪坝"]["end", str, "上新街"],
|
Args["start", str, "沙坪坝"]["end", str, "上新街"],
|
||||||
alias=("r",),
|
alias=("r",),
|
||||||
help_text="查询两地之间的地铁路线"
|
help_text="查询两地之间的地铁路线"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@crt_cmd.assign("route")
|
@crt_cmd.assign("route")
|
||||||
async def _(result: Arparma, event: T_MessageEvent):
|
async def _(result: Arparma, event: T_MessageEvent):
|
||||||
# 获取语言
|
# 获取语言
|
||||||
ulang = Language(get_user_id(event))
|
ulang = Language(get_user_id(event))
|
||||||
|
|
||||||
# 获取参数
|
# 获取参数
|
||||||
# 你也别问我为什么要quote两次,问就是CRT官网的锅,只有这样才可以运行
|
# 你也别问我为什么要quote两次,问就是CRT官网的锅,只有这样才可以运行
|
||||||
start = quote(quote(result.other_args.get("start")))
|
start = quote(quote(result.other_args.get("start")))
|
||||||
end = quote(quote(result.other_args.get("end")))
|
end = quote(quote(result.other_args.get("end")))
|
||||||
|
|
||||||
# 判断参数语言
|
# 判断参数语言
|
||||||
query_lang_code = ""
|
query_lang_code = ""
|
||||||
if start.isalpha() and end.isalpha():
|
if start.isalpha() and end.isalpha():
|
||||||
query_lang_code = "Eng"
|
query_lang_code = "Eng"
|
||||||
|
|
||||||
# 构造请求 URL
|
# 构造请求 URL
|
||||||
url = f"https://www.cqmetro.cn/Front/html/TakeLine!queryYs{query_lang_code}TakeLine.action?entity.startStaName={start}&entity.endStaName={end}"
|
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 aiohttp.ClientSession() as session:
|
||||||
async with session.get(url) as resp:
|
async with session.get(url) as resp:
|
||||||
result = await resp.json()
|
result = await resp.json()
|
||||||
|
|
||||||
# 检查结果/无则终止
|
# 检查结果/无则终止
|
||||||
if not result.get("result"):
|
if not result.get("result"):
|
||||||
await crt_cmd.send(ulang.get("crt.no_result"))
|
await crt_cmd.send(ulang.get("crt.no_result"))
|
||||||
return
|
return
|
||||||
|
|
||||||
# 模板传参定义
|
# 模板传参定义
|
||||||
templates = {
|
templates = {
|
||||||
"data" : {
|
"data" : {
|
||||||
"result": result["result"],
|
"result": result["result"],
|
||||||
},
|
},
|
||||||
"localization": ulang.get_many(
|
"localization": ulang.get_many(
|
||||||
"crt.station",
|
"crt.station",
|
||||||
"crt.hour",
|
"crt.hour",
|
||||||
"crt.minute",
|
"crt.minute",
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# 生成图片
|
# 生成图片
|
||||||
image = await template2image(
|
image = await template2image(
|
||||||
template=get_path("templates/crt_route.html"),
|
template=get_path("templates/crt_route.html"),
|
||||||
templates=templates,
|
templates=templates,
|
||||||
debug=True
|
debug=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# 发送图片
|
# 发送图片
|
||||||
await crt_cmd.send(UniMessage.image(raw=image))
|
await crt_cmd.send(UniMessage.image(raw=image))
|
@ -1,419 +1,419 @@
|
|||||||
import json
|
import json
|
||||||
from typing import List, Any
|
from typing import List, Any
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from arclet.alconna import Alconna
|
from arclet.alconna import Alconna
|
||||||
from nb_cli import run_sync
|
from nb_cli import run_sync
|
||||||
from nonebot import on_command
|
from nonebot import on_command
|
||||||
from nonebot_plugin_alconna import on_alconna, Alconna, Subcommand, Args, MultiVar, Arparma, UniMessage
|
from nonebot_plugin_alconna import on_alconna, Alconna, Subcommand, Args, MultiVar, Arparma, UniMessage
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from .canvas import *
|
from .canvas import *
|
||||||
from ...utils.base.resource import get_path
|
from ...utils.base.resource import get_path
|
||||||
|
|
||||||
resolution = 256
|
resolution = 256
|
||||||
|
|
||||||
|
|
||||||
class Entrance(BaseModel):
|
class Entrance(BaseModel):
|
||||||
identifier: str
|
identifier: str
|
||||||
size: tuple[int, int]
|
size: tuple[int, int]
|
||||||
dest: List[str]
|
dest: List[str]
|
||||||
|
|
||||||
|
|
||||||
class Station(BaseModel):
|
class Station(BaseModel):
|
||||||
identifier: str
|
identifier: str
|
||||||
chineseName: str
|
chineseName: str
|
||||||
englishName: str
|
englishName: str
|
||||||
position: tuple[int, int]
|
position: tuple[int, int]
|
||||||
|
|
||||||
|
|
||||||
class Line(BaseModel):
|
class Line(BaseModel):
|
||||||
identifier: str
|
identifier: str
|
||||||
chineseName: str
|
chineseName: str
|
||||||
englishName: str
|
englishName: str
|
||||||
color: Any
|
color: Any
|
||||||
stations: List["Station"]
|
stations: List["Station"]
|
||||||
|
|
||||||
|
|
||||||
font_light = get_path("templates/fonts/MiSans/MiSans-Light.woff2")
|
font_light = get_path("templates/fonts/MiSans/MiSans-Light.woff2")
|
||||||
font_bold = get_path("templates/fonts/MiSans/MiSans-Bold.woff2")
|
font_bold = get_path("templates/fonts/MiSans/MiSans-Bold.woff2")
|
||||||
|
|
||||||
@run_sync
|
@run_sync
|
||||||
def generate_entrance_sign(name: str, aliases: List[str], lineInfo: List[Line], entranceIdentifier: str, ratio: tuple[int | float, int | float],
|
def generate_entrance_sign(name: str, aliases: List[str], lineInfo: List[Line], entranceIdentifier: str, ratio: tuple[int | float, int | float],
|
||||||
reso: int = resolution):
|
reso: int = resolution):
|
||||||
"""
|
"""
|
||||||
Generates an entrance sign for the ride.
|
Generates an entrance sign for the ride.
|
||||||
"""
|
"""
|
||||||
width, height = ratio[0] * reso, ratio[1] * reso
|
width, height = ratio[0] * reso, ratio[1] * reso
|
||||||
baseCanvas = Canvas(Image.new("RGBA", (width, height), Color.WHITE))
|
baseCanvas = Canvas(Image.new("RGBA", (width, height), Color.WHITE))
|
||||||
# 加黑色图框
|
# 加黑色图框
|
||||||
baseCanvas.outline = Img(
|
baseCanvas.outline = Img(
|
||||||
uv_size=(1, 1),
|
uv_size=(1, 1),
|
||||||
box_size=(1, 1),
|
box_size=(1, 1),
|
||||||
parent_point=(0, 0),
|
parent_point=(0, 0),
|
||||||
point=(0, 0),
|
point=(0, 0),
|
||||||
img=Shape.rectangle(
|
img=Shape.rectangle(
|
||||||
size=(width, height),
|
size=(width, height),
|
||||||
fillet=0,
|
fillet=0,
|
||||||
fill=(0, 0, 0, 0),
|
fill=(0, 0, 0, 0),
|
||||||
width=15,
|
width=15,
|
||||||
outline=Color.BLACK
|
outline=Color.BLACK
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
baseCanvas.contentPanel = Panel(
|
baseCanvas.contentPanel = Panel(
|
||||||
uv_size=(width, height),
|
uv_size=(width, height),
|
||||||
box_size=(width - 28, height - 28),
|
box_size=(width - 28, height - 28),
|
||||||
parent_point=(0.5, 0.5),
|
parent_point=(0.5, 0.5),
|
||||||
point=(0.5, 0.5),
|
point=(0.5, 0.5),
|
||||||
)
|
)
|
||||||
|
|
||||||
linePanelHeight = 0.7 * ratio[1]
|
linePanelHeight = 0.7 * ratio[1]
|
||||||
linePanelWidth = linePanelHeight * 1.3
|
linePanelWidth = linePanelHeight * 1.3
|
||||||
|
|
||||||
# 画线路面板部分
|
# 画线路面板部分
|
||||||
|
|
||||||
for i, line in enumerate(lineInfo):
|
for i, line in enumerate(lineInfo):
|
||||||
linePanel = baseCanvas.contentPanel.__dict__[f"Line_{i}_Panel"] = Panel(
|
linePanel = baseCanvas.contentPanel.__dict__[f"Line_{i}_Panel"] = Panel(
|
||||||
uv_size=ratio,
|
uv_size=ratio,
|
||||||
box_size=(linePanelWidth, linePanelHeight),
|
box_size=(linePanelWidth, linePanelHeight),
|
||||||
parent_point=(i * linePanelWidth / ratio[0], 1),
|
parent_point=(i * linePanelWidth / ratio[0], 1),
|
||||||
point=(0, 1),
|
point=(0, 1),
|
||||||
)
|
)
|
||||||
|
|
||||||
linePanel.colorCube = Img(
|
linePanel.colorCube = Img(
|
||||||
uv_size=(1, 1),
|
uv_size=(1, 1),
|
||||||
box_size=(0.15, 1),
|
box_size=(0.15, 1),
|
||||||
parent_point=(0.125, 1),
|
parent_point=(0.125, 1),
|
||||||
point=(0, 1),
|
point=(0, 1),
|
||||||
img=Shape.rectangle(
|
img=Shape.rectangle(
|
||||||
size=(100, 100),
|
size=(100, 100),
|
||||||
fillet=0,
|
fillet=0,
|
||||||
fill=line.color,
|
fill=line.color,
|
||||||
),
|
),
|
||||||
keep_ratio=False
|
keep_ratio=False
|
||||||
)
|
)
|
||||||
|
|
||||||
textPanel = linePanel.TextPanel = Panel(
|
textPanel = linePanel.TextPanel = Panel(
|
||||||
uv_size=(1, 1),
|
uv_size=(1, 1),
|
||||||
box_size=(0.625, 1),
|
box_size=(0.625, 1),
|
||||||
parent_point=(1, 1),
|
parent_point=(1, 1),
|
||||||
point=(1, 1)
|
point=(1, 1)
|
||||||
)
|
)
|
||||||
|
|
||||||
# 中文线路名
|
# 中文线路名
|
||||||
textPanel.namePanel = Panel(
|
textPanel.namePanel = Panel(
|
||||||
uv_size=(1, 1),
|
uv_size=(1, 1),
|
||||||
box_size=(1, 2 / 3),
|
box_size=(1, 2 / 3),
|
||||||
parent_point=(0, 0),
|
parent_point=(0, 0),
|
||||||
point=(0, 0),
|
point=(0, 0),
|
||||||
)
|
)
|
||||||
nameSize = baseCanvas.get_actual_pixel_size("contentPanel.Line_{}_Panel.TextPanel.namePanel".format(i))
|
nameSize = baseCanvas.get_actual_pixel_size("contentPanel.Line_{}_Panel.TextPanel.namePanel".format(i))
|
||||||
textPanel.namePanel.text = Text(
|
textPanel.namePanel.text = Text(
|
||||||
uv_size=(1, 1),
|
uv_size=(1, 1),
|
||||||
box_size=(1, 1),
|
box_size=(1, 1),
|
||||||
parent_point=(0.5, 0.5),
|
parent_point=(0.5, 0.5),
|
||||||
point=(0.5, 0.5),
|
point=(0.5, 0.5),
|
||||||
text=line.chineseName,
|
text=line.chineseName,
|
||||||
color=Color.BLACK,
|
color=Color.BLACK,
|
||||||
font_size=int(nameSize[1] * 0.5),
|
font_size=int(nameSize[1] * 0.5),
|
||||||
force_size=True,
|
force_size=True,
|
||||||
font=font_bold
|
font=font_bold
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# 英文线路名
|
# 英文线路名
|
||||||
textPanel.englishNamePanel = Panel(
|
textPanel.englishNamePanel = Panel(
|
||||||
uv_size=(1, 1),
|
uv_size=(1, 1),
|
||||||
box_size=(1, 1 / 3),
|
box_size=(1, 1 / 3),
|
||||||
parent_point=(0, 1),
|
parent_point=(0, 1),
|
||||||
point=(0, 1),
|
point=(0, 1),
|
||||||
)
|
)
|
||||||
englishNameSize = baseCanvas.get_actual_pixel_size("contentPanel.Line_{}_Panel.TextPanel.englishNamePanel".format(i))
|
englishNameSize = baseCanvas.get_actual_pixel_size("contentPanel.Line_{}_Panel.TextPanel.englishNamePanel".format(i))
|
||||||
textPanel.englishNamePanel.text = Text(
|
textPanel.englishNamePanel.text = Text(
|
||||||
uv_size=(1, 1),
|
uv_size=(1, 1),
|
||||||
box_size=(1, 1),
|
box_size=(1, 1),
|
||||||
parent_point=(0.5, 0.5),
|
parent_point=(0.5, 0.5),
|
||||||
point=(0.5, 0.5),
|
point=(0.5, 0.5),
|
||||||
text=line.englishName,
|
text=line.englishName,
|
||||||
color=Color.BLACK,
|
color=Color.BLACK,
|
||||||
font_size=int(englishNameSize[1] * 0.6),
|
font_size=int(englishNameSize[1] * 0.6),
|
||||||
force_size=True,
|
force_size=True,
|
||||||
font=font_light
|
font=font_light
|
||||||
)
|
)
|
||||||
|
|
||||||
# 画名称部分
|
# 画名称部分
|
||||||
namePanel = baseCanvas.contentPanel.namePanel = Panel(
|
namePanel = baseCanvas.contentPanel.namePanel = Panel(
|
||||||
uv_size=(1, 1),
|
uv_size=(1, 1),
|
||||||
box_size=(1, 0.4),
|
box_size=(1, 0.4),
|
||||||
parent_point=(0.5, 0),
|
parent_point=(0.5, 0),
|
||||||
point=(0.5, 0),
|
point=(0.5, 0),
|
||||||
)
|
)
|
||||||
|
|
||||||
namePanel.text = Text(
|
namePanel.text = Text(
|
||||||
uv_size=(1, 1),
|
uv_size=(1, 1),
|
||||||
box_size=(1, 1),
|
box_size=(1, 1),
|
||||||
parent_point=(0.5, 0.5),
|
parent_point=(0.5, 0.5),
|
||||||
point=(0.5, 0.5),
|
point=(0.5, 0.5),
|
||||||
text=name,
|
text=name,
|
||||||
color=Color.BLACK,
|
color=Color.BLACK,
|
||||||
font_size=int(height * 0.3),
|
font_size=int(height * 0.3),
|
||||||
force_size=True,
|
force_size=True,
|
||||||
font=font_bold
|
font=font_bold
|
||||||
)
|
)
|
||||||
|
|
||||||
aliasesPanel = baseCanvas.contentPanel.aliasesPanel = Panel(
|
aliasesPanel = baseCanvas.contentPanel.aliasesPanel = Panel(
|
||||||
uv_size=(1, 1),
|
uv_size=(1, 1),
|
||||||
box_size=(1, 0.5),
|
box_size=(1, 0.5),
|
||||||
parent_point=(0.5, 1),
|
parent_point=(0.5, 1),
|
||||||
point=(0.5, 1),
|
point=(0.5, 1),
|
||||||
|
|
||||||
)
|
)
|
||||||
for j, alias in enumerate(aliases):
|
for j, alias in enumerate(aliases):
|
||||||
aliasesPanel.__dict__[alias] = Text(
|
aliasesPanel.__dict__[alias] = Text(
|
||||||
uv_size=(1, 1),
|
uv_size=(1, 1),
|
||||||
box_size=(0.35, 0.5),
|
box_size=(0.35, 0.5),
|
||||||
parent_point=(0.5, 0.5 * j),
|
parent_point=(0.5, 0.5 * j),
|
||||||
point=(0.5, 0),
|
point=(0.5, 0),
|
||||||
text=alias,
|
text=alias,
|
||||||
color=Color.BLACK,
|
color=Color.BLACK,
|
||||||
font_size=int(height * 0.15),
|
font_size=int(height * 0.15),
|
||||||
font=font_light
|
font=font_light
|
||||||
)
|
)
|
||||||
|
|
||||||
# 画入口标识
|
# 画入口标识
|
||||||
entrancePanel = baseCanvas.contentPanel.entrancePanel = Panel(
|
entrancePanel = baseCanvas.contentPanel.entrancePanel = Panel(
|
||||||
uv_size=(1, 1),
|
uv_size=(1, 1),
|
||||||
box_size=(0.2, 1),
|
box_size=(0.2, 1),
|
||||||
parent_point=(1, 0.5),
|
parent_point=(1, 0.5),
|
||||||
point=(1, 0.5),
|
point=(1, 0.5),
|
||||||
)
|
)
|
||||||
# 中文文本
|
# 中文文本
|
||||||
entrancePanel.namePanel = Panel(
|
entrancePanel.namePanel = Panel(
|
||||||
uv_size=(1, 1),
|
uv_size=(1, 1),
|
||||||
box_size=(1, 0.5),
|
box_size=(1, 0.5),
|
||||||
parent_point=(1, 0),
|
parent_point=(1, 0),
|
||||||
point=(1, 0),
|
point=(1, 0),
|
||||||
)
|
)
|
||||||
entrancePanel.namePanel.text = Text(
|
entrancePanel.namePanel.text = Text(
|
||||||
uv_size=(1, 1),
|
uv_size=(1, 1),
|
||||||
box_size=(1, 1),
|
box_size=(1, 1),
|
||||||
parent_point=(0, 0.5),
|
parent_point=(0, 0.5),
|
||||||
point=(0, 0.5),
|
point=(0, 0.5),
|
||||||
text=f"{entranceIdentifier}出入口",
|
text=f"{entranceIdentifier}出入口",
|
||||||
color=Color.BLACK,
|
color=Color.BLACK,
|
||||||
font_size=int(height * 0.2),
|
font_size=int(height * 0.2),
|
||||||
force_size=True,
|
force_size=True,
|
||||||
font=font_bold
|
font=font_bold
|
||||||
)
|
)
|
||||||
# 英文文本
|
# 英文文本
|
||||||
entrancePanel.englishNamePanel = Panel(
|
entrancePanel.englishNamePanel = Panel(
|
||||||
uv_size=(1, 1),
|
uv_size=(1, 1),
|
||||||
box_size=(1, 0.5),
|
box_size=(1, 0.5),
|
||||||
parent_point=(1, 1),
|
parent_point=(1, 1),
|
||||||
point=(1, 1),
|
point=(1, 1),
|
||||||
)
|
)
|
||||||
entrancePanel.englishNamePanel.text = Text(
|
entrancePanel.englishNamePanel.text = Text(
|
||||||
uv_size=(1, 1),
|
uv_size=(1, 1),
|
||||||
box_size=(1, 1),
|
box_size=(1, 1),
|
||||||
parent_point=(0, 0.5),
|
parent_point=(0, 0.5),
|
||||||
point=(0, 0.5),
|
point=(0, 0.5),
|
||||||
text=f"Entrance {entranceIdentifier}",
|
text=f"Entrance {entranceIdentifier}",
|
||||||
color=Color.BLACK,
|
color=Color.BLACK,
|
||||||
font_size=int(height * 0.15),
|
font_size=int(height * 0.15),
|
||||||
force_size=True,
|
force_size=True,
|
||||||
font=font_light
|
font=font_light
|
||||||
)
|
)
|
||||||
|
|
||||||
return baseCanvas.base_img.tobytes()
|
return baseCanvas.base_img.tobytes()
|
||||||
|
|
||||||
|
|
||||||
crt_alc = on_alconna(
|
crt_alc = on_alconna(
|
||||||
Alconna(
|
Alconna(
|
||||||
"crt",
|
"crt",
|
||||||
Subcommand(
|
Subcommand(
|
||||||
"entrance",
|
"entrance",
|
||||||
Args["name", str]["lines", str, ""]["entrance", int, 1], # /crt entrance 璧山&Bishan 1号线&Line1&#ff0000,27号线&Line1&#ff0000 1A
|
Args["name", str]["lines", str, ""]["entrance", int, 1], # /crt entrance 璧山&Bishan 1号线&Line1&#ff0000,27号线&Line1&#ff0000 1A
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@crt_alc.assign("entrance")
|
@crt_alc.assign("entrance")
|
||||||
async def _(result: Arparma):
|
async def _(result: Arparma):
|
||||||
args = result.subcommands.get("entrance").args
|
args = result.subcommands.get("entrance").args
|
||||||
name = args["name"]
|
name = args["name"]
|
||||||
lines = args["lines"]
|
lines = args["lines"]
|
||||||
entrance = args["entrance"]
|
entrance = args["entrance"]
|
||||||
line_info = []
|
line_info = []
|
||||||
for line in lines.split(","):
|
for line in lines.split(","):
|
||||||
line_args = line.split("&")
|
line_args = line.split("&")
|
||||||
line_info.append(Line(
|
line_info.append(Line(
|
||||||
identifier=1,
|
identifier=1,
|
||||||
chineseName=line_args[0],
|
chineseName=line_args[0],
|
||||||
englishName=line_args[1],
|
englishName=line_args[1],
|
||||||
color=line_args[2],
|
color=line_args[2],
|
||||||
stations=[]
|
stations=[]
|
||||||
))
|
))
|
||||||
img_bytes = await generate_entrance_sign(
|
img_bytes = await generate_entrance_sign(
|
||||||
name=name,
|
name=name,
|
||||||
aliases=name.split("&"),
|
aliases=name.split("&"),
|
||||||
lineInfo=line_info,
|
lineInfo=line_info,
|
||||||
entranceIdentifier=entrance,
|
entranceIdentifier=entrance,
|
||||||
ratio=(8, 1),
|
ratio=(8, 1),
|
||||||
reso=256,
|
reso=256,
|
||||||
)
|
)
|
||||||
await crt_alc.finish(
|
await crt_alc.finish(
|
||||||
UniMessage.image(raw=img_bytes)
|
UniMessage.image(raw=img_bytes)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def generate_platform_line_pic(line: Line, station: Station, ratio=None, reso: int = resolution):
|
def generate_platform_line_pic(line: Line, station: Station, ratio=None, reso: int = resolution):
|
||||||
"""
|
"""
|
||||||
生成站台线路图
|
生成站台线路图
|
||||||
:param line: 线路对象
|
:param line: 线路对象
|
||||||
:param station: 本站点对象
|
:param station: 本站点对象
|
||||||
:param ratio: 比例
|
:param ratio: 比例
|
||||||
:param reso: 分辨率,1:reso
|
:param reso: 分辨率,1:reso
|
||||||
:return: 两个方向的站牌
|
:return: 两个方向的站牌
|
||||||
"""
|
"""
|
||||||
if ratio is None:
|
if ratio is None:
|
||||||
ratio = [4, 1]
|
ratio = [4, 1]
|
||||||
width, height = ratio[0] * reso, ratio[1] * reso
|
width, height = ratio[0] * reso, ratio[1] * reso
|
||||||
baseCanvas = Canvas(Image.new("RGBA", (width, height), Color.YELLOW))
|
baseCanvas = Canvas(Image.new("RGBA", (width, height), Color.YELLOW))
|
||||||
# 加黑色图框
|
# 加黑色图框
|
||||||
baseCanvas.linePanel = Panel(
|
baseCanvas.linePanel = Panel(
|
||||||
uv_size=(1, 1),
|
uv_size=(1, 1),
|
||||||
box_size=(0.8, 0.15),
|
box_size=(0.8, 0.15),
|
||||||
parent_point=(0.5, 0.5),
|
parent_point=(0.5, 0.5),
|
||||||
point=(0.5, 0.5),
|
point=(0.5, 0.5),
|
||||||
)
|
)
|
||||||
|
|
||||||
# 直线块
|
# 直线块
|
||||||
baseCanvas.linePanel.recLine = Img(
|
baseCanvas.linePanel.recLine = Img(
|
||||||
uv_size=(1, 1),
|
uv_size=(1, 1),
|
||||||
box_size=(1, 1),
|
box_size=(1, 1),
|
||||||
parent_point=(0.5, 0.5),
|
parent_point=(0.5, 0.5),
|
||||||
point=(0.5, 0.5),
|
point=(0.5, 0.5),
|
||||||
img=Shape.rectangle(
|
img=Shape.rectangle(
|
||||||
size=(10, 10),
|
size=(10, 10),
|
||||||
fill=line.color,
|
fill=line.color,
|
||||||
),
|
),
|
||||||
keep_ratio=False
|
keep_ratio=False
|
||||||
)
|
)
|
||||||
# 灰色直线块
|
# 灰色直线块
|
||||||
baseCanvas.linePanel.recLineGrey = Img(
|
baseCanvas.linePanel.recLineGrey = Img(
|
||||||
uv_size=(1, 1),
|
uv_size=(1, 1),
|
||||||
box_size=(1, 1),
|
box_size=(1, 1),
|
||||||
parent_point=(0.5, 0.5),
|
parent_point=(0.5, 0.5),
|
||||||
point=(0.5, 0.5),
|
point=(0.5, 0.5),
|
||||||
img=Shape.rectangle(
|
img=Shape.rectangle(
|
||||||
size=(10, 10),
|
size=(10, 10),
|
||||||
fill=Color.GREY,
|
fill=Color.GREY,
|
||||||
),
|
),
|
||||||
keep_ratio=False
|
keep_ratio=False
|
||||||
)
|
)
|
||||||
# 生成各站圆点
|
# 生成各站圆点
|
||||||
outline_width = 40
|
outline_width = 40
|
||||||
circleForward = Shape.circular(
|
circleForward = Shape.circular(
|
||||||
radius=200,
|
radius=200,
|
||||||
fill=Color.WHITE,
|
fill=Color.WHITE,
|
||||||
width=outline_width,
|
width=outline_width,
|
||||||
outline=line.color,
|
outline=line.color,
|
||||||
)
|
)
|
||||||
|
|
||||||
circleThisPanel = Canvas(Image.new("RGBA", (200, 200), (0, 0, 0, 0)))
|
circleThisPanel = Canvas(Image.new("RGBA", (200, 200), (0, 0, 0, 0)))
|
||||||
circleThisPanel.circleOuter = Img(
|
circleThisPanel.circleOuter = Img(
|
||||||
uv_size=(1, 1),
|
uv_size=(1, 1),
|
||||||
box_size=(1, 1),
|
box_size=(1, 1),
|
||||||
parent_point=(0.5, 0.5),
|
parent_point=(0.5, 0.5),
|
||||||
point=(0.5, 0.5),
|
point=(0.5, 0.5),
|
||||||
img=Shape.circular(
|
img=Shape.circular(
|
||||||
radius=200,
|
radius=200,
|
||||||
fill=Color.WHITE,
|
fill=Color.WHITE,
|
||||||
width=outline_width,
|
width=outline_width,
|
||||||
outline=line.color,
|
outline=line.color,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
circleThisPanel.circleOuter.circleInner = Img(
|
circleThisPanel.circleOuter.circleInner = Img(
|
||||||
uv_size=(1, 1),
|
uv_size=(1, 1),
|
||||||
box_size=(0.7, 0.7),
|
box_size=(0.7, 0.7),
|
||||||
parent_point=(0.5, 0.5),
|
parent_point=(0.5, 0.5),
|
||||||
point=(0.5, 0.5),
|
point=(0.5, 0.5),
|
||||||
img=Shape.circular(
|
img=Shape.circular(
|
||||||
radius=200,
|
radius=200,
|
||||||
fill=line.color,
|
fill=line.color,
|
||||||
width=0,
|
width=0,
|
||||||
outline=line.color,
|
outline=line.color,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
circleThisPanel.export("a.png", alpha=True)
|
circleThisPanel.export("a.png", alpha=True)
|
||||||
circleThis = circleThisPanel.base_img
|
circleThis = circleThisPanel.base_img
|
||||||
|
|
||||||
circlePassed = Shape.circular(
|
circlePassed = Shape.circular(
|
||||||
radius=200,
|
radius=200,
|
||||||
fill=Color.WHITE,
|
fill=Color.WHITE,
|
||||||
width=outline_width,
|
width=outline_width,
|
||||||
outline=Color.GREY,
|
outline=Color.GREY,
|
||||||
)
|
)
|
||||||
|
|
||||||
arrival = False
|
arrival = False
|
||||||
distance = 1 / (len(line.stations) - 1)
|
distance = 1 / (len(line.stations) - 1)
|
||||||
for i, sta in enumerate(line.stations):
|
for i, sta in enumerate(line.stations):
|
||||||
box_size = (1.618, 1.618)
|
box_size = (1.618, 1.618)
|
||||||
if sta.identifier == station.identifier:
|
if sta.identifier == station.identifier:
|
||||||
arrival = True
|
arrival = True
|
||||||
baseCanvas.linePanel.recLine.__dict__["station_{}".format(sta.identifier)] = Img(
|
baseCanvas.linePanel.recLine.__dict__["station_{}".format(sta.identifier)] = Img(
|
||||||
uv_size=(1, 1),
|
uv_size=(1, 1),
|
||||||
box_size=(1.8, 1.8),
|
box_size=(1.8, 1.8),
|
||||||
parent_point=(distance * i, 0.5),
|
parent_point=(distance * i, 0.5),
|
||||||
point=(0.5, 0.5),
|
point=(0.5, 0.5),
|
||||||
img=circleThis,
|
img=circleThis,
|
||||||
keep_ratio=True
|
keep_ratio=True
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
if arrival:
|
if arrival:
|
||||||
# 后方站绘制
|
# 后方站绘制
|
||||||
baseCanvas.linePanel.recLine.__dict__["station_{}".format(sta.identifier)] = Img(
|
baseCanvas.linePanel.recLine.__dict__["station_{}".format(sta.identifier)] = Img(
|
||||||
uv_size=(1, 1),
|
uv_size=(1, 1),
|
||||||
box_size=box_size,
|
box_size=box_size,
|
||||||
parent_point=(distance * i, 0.5),
|
parent_point=(distance * i, 0.5),
|
||||||
point=(0.5, 0.5),
|
point=(0.5, 0.5),
|
||||||
img=circleForward,
|
img=circleForward,
|
||||||
keep_ratio=True
|
keep_ratio=True
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# 前方站绘制
|
# 前方站绘制
|
||||||
baseCanvas.linePanel.recLine.__dict__["station_{}".format(sta.identifier)] = Img(
|
baseCanvas.linePanel.recLine.__dict__["station_{}".format(sta.identifier)] = Img(
|
||||||
uv_size=(1, 1),
|
uv_size=(1, 1),
|
||||||
box_size=box_size,
|
box_size=box_size,
|
||||||
parent_point=(distance * i, 0.5),
|
parent_point=(distance * i, 0.5),
|
||||||
point=(0.5, 0.5),
|
point=(0.5, 0.5),
|
||||||
img=circlePassed,
|
img=circlePassed,
|
||||||
keep_ratio=True
|
keep_ratio=True
|
||||||
)
|
)
|
||||||
return baseCanvas
|
return baseCanvas
|
||||||
|
|
||||||
|
|
||||||
def generate_platform_sign(name: str, aliases: List[str], lineInfo: List[Line], entranceIdentifier: str, ratio: tuple[int | float, int | float],
|
def generate_platform_sign(name: str, aliases: List[str], lineInfo: List[Line], entranceIdentifier: str, ratio: tuple[int | float, int | float],
|
||||||
reso: int = resolution
|
reso: int = resolution
|
||||||
):
|
):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# def main():
|
# def main():
|
||||||
# generate_entrance_sign(
|
# generate_entrance_sign(
|
||||||
# "璧山",
|
# "璧山",
|
||||||
# aliases=["Bishan"],
|
# aliases=["Bishan"],
|
||||||
# lineInfo=[
|
# lineInfo=[
|
||||||
#
|
#
|
||||||
# Line(identifier="2", chineseName="1号线", englishName="Line 1", color=Color.RED, stations=[]),
|
# 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="3", chineseName="27号线", englishName="Line 27", color="#685bc7", stations=[]),
|
||||||
# Line(identifier="1", chineseName="璧铜线", englishName="BT Line", color="#685BC7", stations=[]),
|
# Line(identifier="1", chineseName="璧铜线", englishName="BT Line", color="#685BC7", stations=[]),
|
||||||
# ],
|
# ],
|
||||||
# entranceIdentifier="1",
|
# entranceIdentifier="1",
|
||||||
# ratio=(8, 1)
|
# ratio=(8, 1)
|
||||||
# )
|
# )
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
# main()
|
# main()
|
@ -1,125 +1,125 @@
|
|||||||
import nonebot
|
import nonebot
|
||||||
from nonebot import on_message, require
|
from nonebot import on_message, require
|
||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
|
|
||||||
from src.utils.base.data import Database, LiteModel
|
from src.utils.base.data import Database, LiteModel
|
||||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||||
from src.utils.message.message import MarkdownMessage as md
|
from src.utils.message.message import MarkdownMessage as md
|
||||||
|
|
||||||
require("nonebot_plugin_alconna")
|
require("nonebot_plugin_alconna")
|
||||||
from nonebot_plugin_alconna import on_alconna
|
from nonebot_plugin_alconna import on_alconna
|
||||||
from arclet.alconna import Arparma, Alconna, Args, Option, Subcommand
|
from arclet.alconna import Arparma, Alconna, Args, Option, Subcommand
|
||||||
|
|
||||||
|
|
||||||
class Node(LiteModel):
|
class Node(LiteModel):
|
||||||
TABLE_NAME: str = "node"
|
TABLE_NAME: str = "node"
|
||||||
bot_id: str = ""
|
bot_id: str = ""
|
||||||
session_type: str = ""
|
session_type: str = ""
|
||||||
session_id: str = ""
|
session_id: str = ""
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.bot_id}.{self.session_type}.{self.session_id}"
|
return f"{self.bot_id}.{self.session_type}.{self.session_id}"
|
||||||
|
|
||||||
|
|
||||||
class Push(LiteModel):
|
class Push(LiteModel):
|
||||||
TABLE_NAME: str = "push"
|
TABLE_NAME: str = "push"
|
||||||
source: Node = Node()
|
source: Node = Node()
|
||||||
target: Node = Node()
|
target: Node = Node()
|
||||||
inde: int = 0
|
inde: int = 0
|
||||||
|
|
||||||
|
|
||||||
pushes_db = Database("data/pushes.ldb")
|
pushes_db = Database("data/pushes.ldb")
|
||||||
pushes_db.auto_migrate(Push(), Node())
|
pushes_db.auto_migrate(Push(), Node())
|
||||||
|
|
||||||
alc = Alconna(
|
alc = Alconna(
|
||||||
"lep",
|
"lep",
|
||||||
Subcommand(
|
Subcommand(
|
||||||
"add",
|
"add",
|
||||||
Args["source", str],
|
Args["source", str],
|
||||||
Args["target", str],
|
Args["target", str],
|
||||||
Option("bidirectional", Args["bidirectional", bool])
|
Option("bidirectional", Args["bidirectional", bool])
|
||||||
),
|
),
|
||||||
Subcommand(
|
Subcommand(
|
||||||
"rm",
|
"rm",
|
||||||
Args["index", int],
|
Args["index", int],
|
||||||
|
|
||||||
),
|
),
|
||||||
Subcommand(
|
Subcommand(
|
||||||
"list",
|
"list",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
add_push = on_alconna(alc)
|
add_push = on_alconna(alc)
|
||||||
|
|
||||||
|
|
||||||
@add_push.handle()
|
@add_push.handle()
|
||||||
async def _(result: Arparma):
|
async def _(result: Arparma):
|
||||||
"""bot_id.session_type.session_id"""
|
"""bot_id.session_type.session_id"""
|
||||||
if result.subcommands.get("add"):
|
if result.subcommands.get("add"):
|
||||||
source = result.subcommands["add"].args.get("source")
|
source = result.subcommands["add"].args.get("source")
|
||||||
target = result.subcommands["add"].args.get("target")
|
target = result.subcommands["add"].args.get("target")
|
||||||
if source and target:
|
if source and target:
|
||||||
source = source.split(".")
|
source = source.split(".")
|
||||||
target = target.split(".")
|
target = target.split(".")
|
||||||
push1 = Push(
|
push1 = Push(
|
||||||
source=Node(bot_id=source[0], session_type=source[1], session_id=source[2]),
|
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]),
|
target=Node(bot_id=target[0], session_type=target[1], session_id=target[2]),
|
||||||
inde=len(pushes_db.where_all(Push(), default=[]))
|
inde=len(pushes_db.where_all(Push(), default=[]))
|
||||||
)
|
)
|
||||||
pushes_db.save(push1)
|
pushes_db.save(push1)
|
||||||
|
|
||||||
if result.subcommands["add"].args.get("bidirectional"):
|
if result.subcommands["add"].args.get("bidirectional"):
|
||||||
push2 = Push(
|
push2 = Push(
|
||||||
source=Node(bot_id=target[0], session_type=target[1], session_id=target[2]),
|
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]),
|
target=Node(bot_id=source[0], session_type=source[1], session_id=source[2]),
|
||||||
inde=len(pushes_db.where_all(Push(), default=[]))
|
inde=len(pushes_db.where_all(Push(), default=[]))
|
||||||
)
|
)
|
||||||
pushes_db.save(push2)
|
pushes_db.save(push2)
|
||||||
await add_push.finish("添加成功")
|
await add_push.finish("添加成功")
|
||||||
else:
|
else:
|
||||||
await add_push.finish("参数缺失")
|
await add_push.finish("参数缺失")
|
||||||
elif result.subcommands.get("rm"):
|
elif result.subcommands.get("rm"):
|
||||||
index = result.subcommands["rm"].args.get("index")
|
index = result.subcommands["rm"].args.get("index")
|
||||||
if index is not None:
|
if index is not None:
|
||||||
try:
|
try:
|
||||||
pushes_db.delete(Push(), "inde = ?", index)
|
pushes_db.delete(Push(), "inde = ?", index)
|
||||||
await add_push.finish("删除成功")
|
await add_push.finish("删除成功")
|
||||||
except IndexError:
|
except IndexError:
|
||||||
await add_push.finish("索引错误")
|
await add_push.finish("索引错误")
|
||||||
else:
|
else:
|
||||||
await add_push.finish("参数缺失")
|
await add_push.finish("参数缺失")
|
||||||
elif result.subcommands.get("list"):
|
elif result.subcommands.get("list"):
|
||||||
await add_push.finish(
|
await add_push.finish(
|
||||||
"\n".join([f"{push.inde} {push.source.bot_id}.{push.source.session_type}.{push.source.session_id} -> "
|
"\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
|
f"{push.target.bot_id}.{push.target.session_type}.{push.target.session_id}" for i, push in
|
||||||
enumerate(pushes_db.where_all(Push(), default=[]))]))
|
enumerate(pushes_db.where_all(Push(), default=[]))]))
|
||||||
else:
|
else:
|
||||||
await add_push.finish("参数错误")
|
await add_push.finish("参数错误")
|
||||||
|
|
||||||
|
|
||||||
@on_message(block=False).handle()
|
@on_message(block=False).handle()
|
||||||
async def _(event: T_MessageEvent, bot: T_Bot):
|
async def _(event: T_MessageEvent, bot: T_Bot):
|
||||||
for push in pushes_db.where_all(Push(), default=[]):
|
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}":
|
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)
|
bot2 = nonebot.get_bot(push.target.bot_id)
|
||||||
msg_formatted = ""
|
msg_formatted = ""
|
||||||
for line in str(event.message).split("\n"):
|
for line in str(event.message).split("\n"):
|
||||||
msg_formatted += f"**{line.strip()}**\n"
|
msg_formatted += f"**{line.strip()}**\n"
|
||||||
push_message = (
|
push_message = (
|
||||||
f"> From {event.sender.nickname}@{push.source.session_type}.{push.source.session_id}\n> Bot {bot.self_id}\n\n"
|
f"> From {event.sender.nickname}@{push.source.session_type}.{push.source.session_id}\n> Bot {bot.self_id}\n\n"
|
||||||
f"{msg_formatted}")
|
f"{msg_formatted}")
|
||||||
await md.send_md(push_message, bot2, message_type=push.target.session_type,
|
await md.send_md(push_message, bot2, message_type=push.target.session_type,
|
||||||
session_id=push.target.session_id)
|
session_id=push.target.session_id)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
__author__ = "snowykami"
|
__author__ = "snowykami"
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="轻雪事件推送",
|
name="轻雪事件推送",
|
||||||
description="事件推送插件,支持单向和双向推送,支持跨Bot推送",
|
description="事件推送插件,支持单向和双向推送,支持跨Bot推送",
|
||||||
usage="",
|
usage="",
|
||||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||||
extra={
|
extra={
|
||||||
"liteyuki": True,
|
"liteyuki": True,
|
||||||
}
|
}
|
||||||
)
|
)
|
@ -1,52 +1,52 @@
|
|||||||
from nonebot import on_command, require
|
from nonebot import on_command, require
|
||||||
from nonebot.adapters.onebot.v11 import MessageSegment
|
from nonebot.adapters.onebot.v11 import MessageSegment
|
||||||
from nonebot.params import CommandArg
|
from nonebot.params import CommandArg
|
||||||
from nonebot.permission import SUPERUSER
|
from nonebot.permission import SUPERUSER
|
||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
|
|
||||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent, v11
|
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.message import MarkdownMessage as md, broadcast_to_superusers
|
||||||
from src.utils.message.html_tool import *
|
from src.utils.message.html_tool import *
|
||||||
|
|
||||||
md_test = on_command("mdts", permission=SUPERUSER)
|
md_test = on_command("mdts", permission=SUPERUSER)
|
||||||
btn_test = on_command("btnts", permission=SUPERUSER)
|
btn_test = on_command("btnts", permission=SUPERUSER)
|
||||||
latex_test = on_command("latex", permission=SUPERUSER)
|
latex_test = on_command("latex", permission=SUPERUSER)
|
||||||
|
|
||||||
|
|
||||||
@md_test.handle()
|
@md_test.handle()
|
||||||
async def _(bot: T_Bot, event: T_MessageEvent, arg: v11.Message = CommandArg()):
|
async def _(bot: T_Bot, event: T_MessageEvent, arg: v11.Message = CommandArg()):
|
||||||
await md.send_md(
|
await md.send_md(
|
||||||
v11.utils.unescape(str(arg)),
|
v11.utils.unescape(str(arg)),
|
||||||
bot,
|
bot,
|
||||||
message_type=event.message_type,
|
message_type=event.message_type,
|
||||||
session_id=event.user_id if event.message_type == "private" else event.group_id
|
session_id=event.user_id if event.message_type == "private" else event.group_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@btn_test.handle()
|
@btn_test.handle()
|
||||||
async def _(bot: T_Bot, event: T_MessageEvent, arg: v11.Message = CommandArg()):
|
async def _(bot: T_Bot, event: T_MessageEvent, arg: v11.Message = CommandArg()):
|
||||||
await md.send_btn(
|
await md.send_btn(
|
||||||
str(arg),
|
str(arg),
|
||||||
bot,
|
bot,
|
||||||
message_type=event.message_type,
|
message_type=event.message_type,
|
||||||
session_id=event.user_id if event.message_type == "private" else event.group_id
|
session_id=event.user_id if event.message_type == "private" else event.group_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@latex_test.handle()
|
@latex_test.handle()
|
||||||
async def _(bot: T_Bot, event: T_MessageEvent, arg: v11.Message = CommandArg()):
|
async def _(bot: T_Bot, event: T_MessageEvent, arg: v11.Message = CommandArg()):
|
||||||
latex_text = f"$${v11.utils.unescape(str(arg))}$$"
|
latex_text = f"$${v11.utils.unescape(str(arg))}$$"
|
||||||
img = await md_to_pic(latex_text)
|
img = await md_to_pic(latex_text)
|
||||||
await bot.send(event=event, message=MessageSegment.image(img))
|
await bot.send(event=event, message=MessageSegment.image(img))
|
||||||
|
|
||||||
|
|
||||||
__author__ = "snowykami"
|
__author__ = "snowykami"
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="轻雪Markdown测试",
|
name="轻雪Markdown测试",
|
||||||
description="用于测试Markdown的插件",
|
description="用于测试Markdown的插件",
|
||||||
usage="",
|
usage="",
|
||||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||||
extra={
|
extra={
|
||||||
"liteyuki": True,
|
"liteyuki": True,
|
||||||
}
|
}
|
||||||
)
|
)
|
@ -1,15 +1,15 @@
|
|||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
from nonebot import get_driver
|
from nonebot import get_driver
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="Minecraft工具箱",
|
name="Minecraft工具箱",
|
||||||
description="一些Minecraft相关工具箱",
|
description="一些Minecraft相关工具箱",
|
||||||
usage="我觉得你应该会用",
|
usage="我觉得你应该会用",
|
||||||
type="application",
|
type="application",
|
||||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||||
extra={
|
extra={
|
||||||
"liteyuki" : True,
|
"liteyuki" : True,
|
||||||
"toggleable" : True,
|
"toggleable" : True,
|
||||||
"default_enable": True,
|
"default_enable": True,
|
||||||
}
|
}
|
||||||
)
|
)
|
@ -1,15 +1,15 @@
|
|||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
from .minesweeper import *
|
from .minesweeper import *
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="轻雪小游戏",
|
name="轻雪小游戏",
|
||||||
description="内置了一些小游戏",
|
description="内置了一些小游戏",
|
||||||
usage="",
|
usage="",
|
||||||
type="application",
|
type="application",
|
||||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||||
extra={
|
extra={
|
||||||
"liteyuki": True,
|
"liteyuki": True,
|
||||||
"toggleable" : True,
|
"toggleable" : True,
|
||||||
"default_enable" : True,
|
"default_enable" : True,
|
||||||
}
|
}
|
||||||
)
|
)
|
@ -1,168 +1,168 @@
|
|||||||
import random
|
import random
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from src.utils.message.message import MarkdownMessage as md
|
from src.utils.message.message import MarkdownMessage as md
|
||||||
|
|
||||||
class Dot(BaseModel):
|
class Dot(BaseModel):
|
||||||
row: int
|
row: int
|
||||||
col: int
|
col: int
|
||||||
mask: bool = True
|
mask: bool = True
|
||||||
value: int = 0
|
value: int = 0
|
||||||
flagged: bool = False
|
flagged: bool = False
|
||||||
|
|
||||||
|
|
||||||
class Minesweeper:
|
class Minesweeper:
|
||||||
# 0-8: number of mines around, 9: mine, -1: undefined
|
# 0-8: number of mines around, 9: mine, -1: undefined
|
||||||
NUMS = "⓪①②③④⑤⑥⑦⑧🅑⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳"
|
NUMS = "⓪①②③④⑤⑥⑦⑧🅑⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳"
|
||||||
MASK = "🅜"
|
MASK = "🅜"
|
||||||
FLAG = "🅕"
|
FLAG = "🅕"
|
||||||
MINE = "🅑"
|
MINE = "🅑"
|
||||||
|
|
||||||
def __init__(self, rows, cols, num_mines, session_type, session_id):
|
def __init__(self, rows, cols, num_mines, session_type, session_id):
|
||||||
assert rows > 0 and cols > 0 and 0 < num_mines < rows * cols
|
assert rows > 0 and cols > 0 and 0 < num_mines < rows * cols
|
||||||
self.session_type = session_type
|
self.session_type = session_type
|
||||||
self.session_id = session_id
|
self.session_id = session_id
|
||||||
self.rows = rows
|
self.rows = rows
|
||||||
self.cols = cols
|
self.cols = cols
|
||||||
self.num_mines = num_mines
|
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.board: list[list[Dot]] = [[Dot(row=i, col=j) for j in range(cols)] for i in range(rows)]
|
||||||
self.is_first = True
|
self.is_first = True
|
||||||
|
|
||||||
def reveal(self, row, col) -> bool:
|
def reveal(self, row, col) -> bool:
|
||||||
"""
|
"""
|
||||||
展开
|
展开
|
||||||
Args:
|
Args:
|
||||||
row:
|
row:
|
||||||
col:
|
col:
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
游戏是否继续
|
游戏是否继续
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.is_first:
|
if self.is_first:
|
||||||
# 第一次展开,生成地雷
|
# 第一次展开,生成地雷
|
||||||
self.generate_board(self.board[row][col])
|
self.generate_board(self.board[row][col])
|
||||||
self.is_first = False
|
self.is_first = False
|
||||||
|
|
||||||
if self.board[row][col].value == 9:
|
if self.board[row][col].value == 9:
|
||||||
self.board[row][col].mask = False
|
self.board[row][col].mask = False
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not self.board[row][col].mask:
|
if not self.board[row][col].mask:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
self.board[row][col].mask = False
|
self.board[row][col].mask = False
|
||||||
|
|
||||||
if self.board[row][col].value == 0:
|
if self.board[row][col].value == 0:
|
||||||
self.reveal_neighbors(row, col)
|
self.reveal_neighbors(row, col)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def is_win(self) -> bool:
|
def is_win(self) -> bool:
|
||||||
"""
|
"""
|
||||||
是否胜利
|
是否胜利
|
||||||
Returns:
|
Returns:
|
||||||
"""
|
"""
|
||||||
for row in range(self.rows):
|
for row in range(self.rows):
|
||||||
for col in range(self.cols):
|
for col in range(self.cols):
|
||||||
if self.board[row][col].mask and self.board[row][col].value != 9:
|
if self.board[row][col].mask and self.board[row][col].value != 9:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def generate_board(self, first_dot: Dot):
|
def generate_board(self, first_dot: Dot):
|
||||||
"""
|
"""
|
||||||
避开第一个点,生成地雷
|
避开第一个点,生成地雷
|
||||||
Args:
|
Args:
|
||||||
first_dot: 第一个点
|
first_dot: 第一个点
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
generate_count = 0
|
generate_count = 0
|
||||||
while generate_count < self.num_mines:
|
while generate_count < self.num_mines:
|
||||||
row = random.randint(0, self.rows - 1)
|
row = random.randint(0, self.rows - 1)
|
||||||
col = random.randint(0, self.cols - 1)
|
col = random.randint(0, self.cols - 1)
|
||||||
if self.board[row][col].value == 9 or (row, col) == (first_dot.row, first_dot.col):
|
if self.board[row][col].value == 9 or (row, col) == (first_dot.row, first_dot.col):
|
||||||
continue
|
continue
|
||||||
self.board[row][col] = Dot(row=row, col=col, mask=True, value=9)
|
self.board[row][col] = Dot(row=row, col=col, mask=True, value=9)
|
||||||
generate_count += 1
|
generate_count += 1
|
||||||
|
|
||||||
for row in range(self.rows):
|
for row in range(self.rows):
|
||||||
for col in range(self.cols):
|
for col in range(self.cols):
|
||||||
if self.board[row][col].value != 9:
|
if self.board[row][col].value != 9:
|
||||||
self.board[row][col].value = self.count_adjacent_mines(row, col)
|
self.board[row][col].value = self.count_adjacent_mines(row, col)
|
||||||
|
|
||||||
def count_adjacent_mines(self, row, col):
|
def count_adjacent_mines(self, row, col):
|
||||||
"""
|
"""
|
||||||
计算周围地雷数量
|
计算周围地雷数量
|
||||||
Args:
|
Args:
|
||||||
row:
|
row:
|
||||||
col:
|
col:
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
count = 0
|
count = 0
|
||||||
for r in range(max(0, row - 1), min(self.rows, row + 2)):
|
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)):
|
for c in range(max(0, col - 1), min(self.cols, col + 2)):
|
||||||
if self.board[r][c].value == 9:
|
if self.board[r][c].value == 9:
|
||||||
count += 1
|
count += 1
|
||||||
return count
|
return count
|
||||||
|
|
||||||
def reveal_neighbors(self, row, col):
|
def reveal_neighbors(self, row, col):
|
||||||
"""
|
"""
|
||||||
递归展开,使用深度优先搜索
|
递归展开,使用深度优先搜索
|
||||||
Args:
|
Args:
|
||||||
row:
|
row:
|
||||||
col:
|
col:
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
for r in range(max(0, row - 1), min(self.rows, row + 2)):
|
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)):
|
for c in range(max(0, col - 1), min(self.cols, col + 2)):
|
||||||
if self.board[r][c].mask:
|
if self.board[r][c].mask:
|
||||||
self.board[r][c].mask = False
|
self.board[r][c].mask = False
|
||||||
if self.board[r][c].value == 0:
|
if self.board[r][c].value == 0:
|
||||||
self.reveal_neighbors(r, c)
|
self.reveal_neighbors(r, c)
|
||||||
|
|
||||||
def mark(self, row, col) -> bool:
|
def mark(self, row, col) -> bool:
|
||||||
"""
|
"""
|
||||||
标记
|
标记
|
||||||
Args:
|
Args:
|
||||||
row:
|
row:
|
||||||
col:
|
col:
|
||||||
Returns:
|
Returns:
|
||||||
是否标记成功,如果已经展开则无法标记
|
是否标记成功,如果已经展开则无法标记
|
||||||
"""
|
"""
|
||||||
if self.board[row][col].mask:
|
if self.board[row][col].mask:
|
||||||
self.board[row][col].flagged = not self.board[row][col].flagged
|
self.board[row][col].flagged = not self.board[row][col].flagged
|
||||||
return self.board[row][col].flagged
|
return self.board[row][col].flagged
|
||||||
|
|
||||||
def board_markdown(self) -> str:
|
def board_markdown(self) -> str:
|
||||||
"""
|
"""
|
||||||
打印地雷板
|
打印地雷板
|
||||||
Returns:
|
Returns:
|
||||||
"""
|
"""
|
||||||
dis = " "
|
dis = " "
|
||||||
start = "> " if self.cols >= 10 else ""
|
start = "> " if self.cols >= 10 else ""
|
||||||
text = start + self.NUMS[0] + dis*2
|
text = start + self.NUMS[0] + dis*2
|
||||||
# 横向两个雷之间的间隔字符
|
# 横向两个雷之间的间隔字符
|
||||||
# 生成横向索引
|
# 生成横向索引
|
||||||
for i in range(self.cols):
|
for i in range(self.cols):
|
||||||
text += f"{self.NUMS[i]}" + dis
|
text += f"{self.NUMS[i]}" + dis
|
||||||
text += "\n\n"
|
text += "\n\n"
|
||||||
for i, row in enumerate(self.board):
|
for i, row in enumerate(self.board):
|
||||||
text += start + f"{self.NUMS[i]}" + dis*2
|
text += start + f"{self.NUMS[i]}" + dis*2
|
||||||
for dot in row:
|
for dot in row:
|
||||||
if dot.mask and not dot.flagged:
|
if dot.mask and not dot.flagged:
|
||||||
text += md.btn_cmd(self.MASK, f"minesweeper reveal {dot.row} {dot.col}")
|
text += md.btn_cmd(self.MASK, f"minesweeper reveal {dot.row} {dot.col}")
|
||||||
elif dot.flagged:
|
elif dot.flagged:
|
||||||
text += md.btn_cmd(self.FLAG, f"minesweeper mark {dot.row} {dot.col}")
|
text += md.btn_cmd(self.FLAG, f"minesweeper mark {dot.row} {dot.col}")
|
||||||
else:
|
else:
|
||||||
text += self.NUMS[dot.value]
|
text += self.NUMS[dot.value]
|
||||||
text += dis
|
text += dis
|
||||||
text += "\n"
|
text += "\n"
|
||||||
btn_mark = md.btn_cmd("标记", f"minesweeper mark ", enter=False)
|
btn_mark = md.btn_cmd("标记", f"minesweeper mark ", enter=False)
|
||||||
btn_end = md.btn_cmd("结束", "minesweeper end", enter=True)
|
btn_end = md.btn_cmd("结束", "minesweeper end", enter=True)
|
||||||
text += f" {btn_mark} {btn_end}"
|
text += f" {btn_mark} {btn_end}"
|
||||||
return text
|
return text
|
@ -1,103 +1,103 @@
|
|||||||
from nonebot import require
|
from nonebot import require
|
||||||
|
|
||||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||||
from src.utils.message.message import MarkdownMessage as md
|
from src.utils.message.message import MarkdownMessage as md
|
||||||
|
|
||||||
require("nonebot_plugin_alconna")
|
require("nonebot_plugin_alconna")
|
||||||
from .game import Minesweeper
|
from .game import Minesweeper
|
||||||
|
|
||||||
from nonebot_plugin_alconna import Alconna, on_alconna, Subcommand, Args, Arparma
|
from nonebot_plugin_alconna import Alconna, on_alconna, Subcommand, Args, Arparma
|
||||||
|
|
||||||
minesweeper = on_alconna(
|
minesweeper = on_alconna(
|
||||||
aliases={"扫雷"},
|
aliases={"扫雷"},
|
||||||
command=Alconna(
|
command=Alconna(
|
||||||
"minesweeper",
|
"minesweeper",
|
||||||
Subcommand(
|
Subcommand(
|
||||||
"start",
|
"start",
|
||||||
Args["row", int, 8]["col", int, 8]["mines", int, 10],
|
Args["row", int, 8]["col", int, 8]["mines", int, 10],
|
||||||
alias=["开始"],
|
alias=["开始"],
|
||||||
|
|
||||||
),
|
),
|
||||||
Subcommand(
|
Subcommand(
|
||||||
"end",
|
"end",
|
||||||
alias=["结束"]
|
alias=["结束"]
|
||||||
),
|
),
|
||||||
Subcommand(
|
Subcommand(
|
||||||
"reveal",
|
"reveal",
|
||||||
Args["row", int]["col", int],
|
Args["row", int]["col", int],
|
||||||
alias=["展开"]
|
alias=["展开"]
|
||||||
|
|
||||||
),
|
),
|
||||||
Subcommand(
|
Subcommand(
|
||||||
"mark",
|
"mark",
|
||||||
Args["row", int]["col", int],
|
Args["row", int]["col", int],
|
||||||
alias=["标记"]
|
alias=["标记"]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
minesweeper_cache: list[Minesweeper] = []
|
minesweeper_cache: list[Minesweeper] = []
|
||||||
|
|
||||||
|
|
||||||
def get_minesweeper_cache(event: T_MessageEvent) -> Minesweeper | None:
|
def get_minesweeper_cache(event: T_MessageEvent) -> Minesweeper | None:
|
||||||
for i in minesweeper_cache:
|
for i in minesweeper_cache:
|
||||||
if i.session_type == event.message_type:
|
if i.session_type == event.message_type:
|
||||||
if i.session_id == event.user_id or i.session_id == event.group_id:
|
if i.session_id == event.user_id or i.session_id == event.group_id:
|
||||||
return i
|
return i
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@minesweeper.handle()
|
@minesweeper.handle()
|
||||||
async def _(event: T_MessageEvent, result: Arparma, bot: T_Bot):
|
async def _(event: T_MessageEvent, result: Arparma, bot: T_Bot):
|
||||||
game = get_minesweeper_cache(event)
|
game = get_minesweeper_cache(event)
|
||||||
if result.subcommands.get("start"):
|
if result.subcommands.get("start"):
|
||||||
if game:
|
if game:
|
||||||
await minesweeper.finish("当前会话不能同时进行多个扫雷游戏")
|
await minesweeper.finish("当前会话不能同时进行多个扫雷游戏")
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
new_game = Minesweeper(
|
new_game = Minesweeper(
|
||||||
rows=result.subcommands["start"].args["row"],
|
rows=result.subcommands["start"].args["row"],
|
||||||
cols=result.subcommands["start"].args["col"],
|
cols=result.subcommands["start"].args["col"],
|
||||||
num_mines=result.subcommands["start"].args["mines"],
|
num_mines=result.subcommands["start"].args["mines"],
|
||||||
session_type=event.message_type,
|
session_type=event.message_type,
|
||||||
session_id=event.user_id if event.message_type == "private" else event.group_id,
|
session_id=event.user_id if event.message_type == "private" else event.group_id,
|
||||||
)
|
)
|
||||||
minesweeper_cache.append(new_game)
|
minesweeper_cache.append(new_game)
|
||||||
await minesweeper.send("游戏开始")
|
await minesweeper.send("游戏开始")
|
||||||
await md.send_md(new_game.board_markdown(), bot, event=event)
|
await md.send_md(new_game.board_markdown(), bot, event=event)
|
||||||
except AssertionError:
|
except AssertionError:
|
||||||
await minesweeper.finish("参数错误")
|
await minesweeper.finish("参数错误")
|
||||||
elif result.subcommands.get("end"):
|
elif result.subcommands.get("end"):
|
||||||
if game:
|
if game:
|
||||||
minesweeper_cache.remove(game)
|
minesweeper_cache.remove(game)
|
||||||
await minesweeper.finish("游戏结束")
|
await minesweeper.finish("游戏结束")
|
||||||
else:
|
else:
|
||||||
await minesweeper.finish("当前没有扫雷游戏")
|
await minesweeper.finish("当前没有扫雷游戏")
|
||||||
elif result.subcommands.get("reveal"):
|
elif result.subcommands.get("reveal"):
|
||||||
if not game:
|
if not game:
|
||||||
await minesweeper.finish("当前没有扫雷游戏")
|
await minesweeper.finish("当前没有扫雷游戏")
|
||||||
else:
|
else:
|
||||||
row = result.subcommands["reveal"].args["row"]
|
row = result.subcommands["reveal"].args["row"]
|
||||||
col = result.subcommands["reveal"].args["col"]
|
col = result.subcommands["reveal"].args["col"]
|
||||||
if not (0 <= row < game.rows and 0 <= col < game.cols):
|
if not (0 <= row < game.rows and 0 <= col < game.cols):
|
||||||
await minesweeper.finish("参数错误")
|
await minesweeper.finish("参数错误")
|
||||||
if not game.reveal(row, col):
|
if not game.reveal(row, col):
|
||||||
minesweeper_cache.remove(game)
|
minesweeper_cache.remove(game)
|
||||||
await md.send_md(game.board_markdown(), bot, event=event)
|
await md.send_md(game.board_markdown(), bot, event=event)
|
||||||
await minesweeper.finish("游戏结束")
|
await minesweeper.finish("游戏结束")
|
||||||
await md.send_md(game.board_markdown(), bot, event=event)
|
await md.send_md(game.board_markdown(), bot, event=event)
|
||||||
if game.is_win():
|
if game.is_win():
|
||||||
minesweeper_cache.remove(game)
|
minesweeper_cache.remove(game)
|
||||||
await minesweeper.finish("游戏胜利")
|
await minesweeper.finish("游戏胜利")
|
||||||
elif result.subcommands.get("mark"):
|
elif result.subcommands.get("mark"):
|
||||||
if not game:
|
if not game:
|
||||||
await minesweeper.finish("当前没有扫雷游戏")
|
await minesweeper.finish("当前没有扫雷游戏")
|
||||||
else:
|
else:
|
||||||
row = result.subcommands["mark"].args["row"]
|
row = result.subcommands["mark"].args["row"]
|
||||||
col = result.subcommands["mark"].args["col"]
|
col = result.subcommands["mark"].args["col"]
|
||||||
if not (0 <= row < game.rows and 0 <= col < game.cols):
|
if not (0 <= row < game.rows and 0 <= col < game.cols):
|
||||||
await minesweeper.finish("参数错误")
|
await minesweeper.finish("参数错误")
|
||||||
game.board[row][col].flagged = not game.board[row][col].flagged
|
game.board[row][col].flagged = not game.board[row][col].flagged
|
||||||
await md.send_md(game.board_markdown(), bot, event=event)
|
await md.send_md(game.board_markdown(), bot, event=event)
|
||||||
else:
|
else:
|
||||||
await minesweeper.finish("参数错误")
|
await minesweeper.finish("参数错误")
|
@ -1,22 +1,22 @@
|
|||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
from .npm import *
|
from .npm import *
|
||||||
from .rpm import *
|
from .rpm import *
|
||||||
|
|
||||||
__author__ = "snowykami"
|
__author__ = "snowykami"
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="轻雪包管理器",
|
name="轻雪包管理器",
|
||||||
description="本地插件管理和插件商店支持,资源包管理,支持启用/停用,安装/卸载插件",
|
description="本地插件管理和插件商店支持,资源包管理,支持启用/停用,安装/卸载插件",
|
||||||
usage=(
|
usage=(
|
||||||
"npm list\n"
|
"npm list\n"
|
||||||
"npm enable/disable <plugin_name>\n"
|
"npm enable/disable <plugin_name>\n"
|
||||||
"npm search <keywords...>\n"
|
"npm search <keywords...>\n"
|
||||||
"npm install/uninstall <plugin_name>\n"
|
"npm install/uninstall <plugin_name>\n"
|
||||||
),
|
),
|
||||||
type="application",
|
type="application",
|
||||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||||
extra={
|
extra={
|
||||||
"liteyuki": True,
|
"liteyuki": True,
|
||||||
"toggleable" : False,
|
"toggleable" : False,
|
||||||
"default_enable" : True,
|
"default_enable" : True,
|
||||||
}
|
}
|
||||||
)
|
)
|
@ -1,255 +1,255 @@
|
|||||||
import json
|
import json
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import aiofiles
|
import aiofiles
|
||||||
import nonebot.plugin
|
import nonebot.plugin
|
||||||
from nonebot.adapters import satori
|
from nonebot.adapters import satori
|
||||||
|
|
||||||
from src.utils import event as event_utils
|
from src.utils import event as event_utils
|
||||||
from src.utils.base.data import LiteModel
|
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.data_manager import GlobalPlugin, Group, User, group_db, plugin_db, user_db
|
||||||
from src.utils.base.ly_typing import T_MessageEvent
|
from src.utils.base.ly_typing import T_MessageEvent
|
||||||
|
|
||||||
__group_data = {} # 群数据缓存, {group_id: Group}
|
__group_data = {} # 群数据缓存, {group_id: Group}
|
||||||
__user_data = {} # 用户数据缓存, {user_id: User}
|
__user_data = {} # 用户数据缓存, {user_id: User}
|
||||||
__default_enable = {} # 插件默认启用状态缓存, {plugin_name: bool} static
|
__default_enable = {} # 插件默认启用状态缓存, {plugin_name: bool} static
|
||||||
__global_enable = {} # 插件全局启用状态缓存, {plugin_name: bool} dynamic
|
__global_enable = {} # 插件全局启用状态缓存, {plugin_name: bool} dynamic
|
||||||
|
|
||||||
|
|
||||||
class PluginTag(LiteModel):
|
class PluginTag(LiteModel):
|
||||||
label: str
|
label: str
|
||||||
color: str = '#000000'
|
color: str = '#000000'
|
||||||
|
|
||||||
|
|
||||||
class StorePlugin(LiteModel):
|
class StorePlugin(LiteModel):
|
||||||
name: str
|
name: str
|
||||||
desc: str
|
desc: str
|
||||||
module_name: str # 插件商店中的模块名不等于本地的模块名,前者是文件夹名,后者是点分割模块名
|
module_name: str # 插件商店中的模块名不等于本地的模块名,前者是文件夹名,后者是点分割模块名
|
||||||
project_link: str = ""
|
project_link: str = ""
|
||||||
homepage: str = ""
|
homepage: str = ""
|
||||||
author: str = ""
|
author: str = ""
|
||||||
type: str | None = None
|
type: str | None = None
|
||||||
version: str | None = ""
|
version: str | None = ""
|
||||||
time: str = ""
|
time: str = ""
|
||||||
tags: list[PluginTag] = []
|
tags: list[PluginTag] = []
|
||||||
is_official: bool = False
|
is_official: bool = False
|
||||||
|
|
||||||
|
|
||||||
def get_plugin_exist(plugin_name: str) -> bool:
|
def get_plugin_exist(plugin_name: str) -> bool:
|
||||||
"""
|
"""
|
||||||
获取插件是否存在于加载列表
|
获取插件是否存在于加载列表
|
||||||
Args:
|
Args:
|
||||||
plugin_name:
|
plugin_name:
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
for plugin in nonebot.plugin.get_loaded_plugins():
|
for plugin in nonebot.plugin.get_loaded_plugins():
|
||||||
if plugin.name == plugin_name:
|
if plugin.name == plugin_name:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
async def get_store_plugin(plugin_name: str) -> Optional[StorePlugin]:
|
async def get_store_plugin(plugin_name: str) -> Optional[StorePlugin]:
|
||||||
"""
|
"""
|
||||||
获取插件信息
|
获取插件信息
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
plugin_name (str): 插件模块名
|
plugin_name (str): 插件模块名
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Optional[StorePlugin]: 插件信息
|
Optional[StorePlugin]: 插件信息
|
||||||
"""
|
"""
|
||||||
async with aiofiles.open("data/liteyuki/plugins.json", "r", encoding="utf-8") as f:
|
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())]
|
plugins: list[StorePlugin] = [StorePlugin(**pobj) for pobj in json.loads(await f.read())]
|
||||||
for plugin in plugins:
|
for plugin in plugins:
|
||||||
if plugin.module_name == plugin_name:
|
if plugin.module_name == plugin_name:
|
||||||
return plugin
|
return plugin
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_plugin_default_enable(plugin_name: str) -> bool:
|
def get_plugin_default_enable(plugin_name: str) -> bool:
|
||||||
"""
|
"""
|
||||||
获取插件默认启用状态,由插件定义,不存在则默认为启用,优先从缓存中获取
|
获取插件默认启用状态,由插件定义,不存在则默认为启用,优先从缓存中获取
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
plugin_name (str): 插件模块名
|
plugin_name (str): 插件模块名
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: 插件默认状态
|
bool: 插件默认状态
|
||||||
"""
|
"""
|
||||||
if plugin_name not in __default_enable:
|
if plugin_name not in __default_enable:
|
||||||
plug = nonebot.plugin.get_plugin(plugin_name)
|
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 = (plug.metadata.extra.get("default_enable", True) if plug.metadata else True) if plug else True
|
||||||
__default_enable[plugin_name] = default_enable
|
__default_enable[plugin_name] = default_enable
|
||||||
|
|
||||||
return __default_enable[plugin_name]
|
return __default_enable[plugin_name]
|
||||||
|
|
||||||
|
|
||||||
def get_plugin_session_enable(event: T_MessageEvent, plugin_name: str) -> bool:
|
def get_plugin_session_enable(event: T_MessageEvent, plugin_name: str) -> bool:
|
||||||
"""
|
"""
|
||||||
获取插件当前会话启用状态
|
获取插件当前会话启用状态
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
event: 会话事件
|
event: 会话事件
|
||||||
plugin_name (str): 插件模块名
|
plugin_name (str): 插件模块名
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: 插件当前状态
|
bool: 插件当前状态
|
||||||
"""
|
"""
|
||||||
if isinstance(event, satori.event.Event):
|
if isinstance(event, satori.event.Event):
|
||||||
if event.guild is not None:
|
if event.guild is not None:
|
||||||
message_type = "group"
|
message_type = "group"
|
||||||
else:
|
else:
|
||||||
message_type = "private"
|
message_type = "private"
|
||||||
else:
|
else:
|
||||||
message_type = event.message_type
|
message_type = event.message_type
|
||||||
if message_type == "group":
|
if message_type == "group":
|
||||||
group_id = str(event.guild.id if isinstance(event, satori.event.Event) else event.group_id)
|
group_id = str(event.guild.id if isinstance(event, satori.event.Event) else event.group_id)
|
||||||
if group_id not in __group_data:
|
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: Group = group_db.where_one(Group(), "group_id = ?", group_id, default=Group(group_id=group_id))
|
||||||
__group_data[str(group_id)] = group
|
__group_data[str(group_id)] = group
|
||||||
|
|
||||||
session = __group_data[group_id]
|
session = __group_data[group_id]
|
||||||
else:
|
else:
|
||||||
# session: User = user_db.first(User(), "user_id = ?", event.user_id, default=User(user_id=str(event.user_id)))
|
# 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)
|
user_id = str(event.user.id if isinstance(event, satori.event.Event) else event.user_id)
|
||||||
if user_id not in __user_data:
|
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: User = user_db.where_one(User(), "user_id = ?", user_id, default=User(user_id=user_id))
|
||||||
__user_data[user_id] = user
|
__user_data[user_id] = user
|
||||||
session = __user_data[user_id]
|
session = __user_data[user_id]
|
||||||
# 默认停用插件在启用列表内表示启用
|
# 默认停用插件在启用列表内表示启用
|
||||||
# 默认停用插件不在启用列表内表示停用
|
# 默认停用插件不在启用列表内表示停用
|
||||||
# 默认启用插件在停用列表内表示停用
|
# 默认启用插件在停用列表内表示停用
|
||||||
# 默认启用插件不在停用列表内表示启用
|
# 默认启用插件不在停用列表内表示启用
|
||||||
default_enable = get_plugin_default_enable(plugin_name)
|
default_enable = get_plugin_default_enable(plugin_name)
|
||||||
if default_enable:
|
if default_enable:
|
||||||
return plugin_name not in session.disabled_plugins
|
return plugin_name not in session.disabled_plugins
|
||||||
else:
|
else:
|
||||||
return plugin_name in session.enabled_plugins
|
return plugin_name in session.enabled_plugins
|
||||||
|
|
||||||
|
|
||||||
def set_plugin_session_enable(event: T_MessageEvent, plugin_name: str, enable: bool):
|
def set_plugin_session_enable(event: T_MessageEvent, plugin_name: str, enable: bool):
|
||||||
"""
|
"""
|
||||||
设置插件会话启用状态,同时更新数据库和缓存
|
设置插件会话启用状态,同时更新数据库和缓存
|
||||||
Args:
|
Args:
|
||||||
event:
|
event:
|
||||||
plugin_name:
|
plugin_name:
|
||||||
enable:
|
enable:
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if event_utils.get_message_type(event) == "group":
|
if event_utils.get_message_type(event) == "group":
|
||||||
session: Group = group_db.where_one(Group(), "group_id = ?", str(event_utils.get_group_id(event)),
|
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))))
|
default=Group(group_id=str(event_utils.get_group_id(event))))
|
||||||
else:
|
else:
|
||||||
session: User = user_db.where_one(User(), "user_id = ?", str(event_utils.get_user_id(event)),
|
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=User(user_id=str(event_utils.get_user_id(event))))
|
||||||
default_enable = get_plugin_default_enable(plugin_name)
|
default_enable = get_plugin_default_enable(plugin_name)
|
||||||
if default_enable:
|
if default_enable:
|
||||||
if enable:
|
if enable:
|
||||||
session.disabled_plugins.remove(plugin_name)
|
session.disabled_plugins.remove(plugin_name)
|
||||||
else:
|
else:
|
||||||
session.disabled_plugins.append(plugin_name)
|
session.disabled_plugins.append(plugin_name)
|
||||||
else:
|
else:
|
||||||
if enable:
|
if enable:
|
||||||
session.enabled_plugins.append(plugin_name)
|
session.enabled_plugins.append(plugin_name)
|
||||||
else:
|
else:
|
||||||
session.enabled_plugins.remove(plugin_name)
|
session.enabled_plugins.remove(plugin_name)
|
||||||
|
|
||||||
if event_utils.get_message_type(event) == "group":
|
if event_utils.get_message_type(event) == "group":
|
||||||
__group_data[str(event_utils.get_group_id(event))] = session
|
__group_data[str(event_utils.get_group_id(event))] = session
|
||||||
group_db.save(session)
|
group_db.save(session)
|
||||||
else:
|
else:
|
||||||
__user_data[str(event_utils.get_user_id(event))] = session
|
__user_data[str(event_utils.get_user_id(event))] = session
|
||||||
user_db.save(session)
|
user_db.save(session)
|
||||||
|
|
||||||
|
|
||||||
def get_plugin_global_enable(plugin_name: str) -> bool:
|
def get_plugin_global_enable(plugin_name: str) -> bool:
|
||||||
"""
|
"""
|
||||||
获取插件全局启用状态, 优先从缓存中获取
|
获取插件全局启用状态, 优先从缓存中获取
|
||||||
Args:
|
Args:
|
||||||
plugin_name:
|
plugin_name:
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if plugin_name not in __global_enable:
|
if plugin_name not in __global_enable:
|
||||||
plugin = plugin_db.where_one(
|
plugin = plugin_db.where_one(
|
||||||
GlobalPlugin(),
|
GlobalPlugin(),
|
||||||
"module_name = ?",
|
"module_name = ?",
|
||||||
plugin_name,
|
plugin_name,
|
||||||
default=GlobalPlugin(module_name=plugin_name, enabled=True))
|
default=GlobalPlugin(module_name=plugin_name, enabled=True))
|
||||||
__global_enable[plugin_name] = plugin.enabled
|
__global_enable[plugin_name] = plugin.enabled
|
||||||
|
|
||||||
return __global_enable[plugin_name]
|
return __global_enable[plugin_name]
|
||||||
|
|
||||||
|
|
||||||
def set_plugin_global_enable(plugin_name: str, enable: bool):
|
def set_plugin_global_enable(plugin_name: str, enable: bool):
|
||||||
"""
|
"""
|
||||||
设置插件全局启用状态,同时更新数据库和缓存
|
设置插件全局启用状态,同时更新数据库和缓存
|
||||||
Args:
|
Args:
|
||||||
plugin_name:
|
plugin_name:
|
||||||
enable:
|
enable:
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
plugin = plugin_db.where_one(
|
plugin = plugin_db.where_one(
|
||||||
GlobalPlugin(),
|
GlobalPlugin(),
|
||||||
"module_name = ?",
|
"module_name = ?",
|
||||||
plugin_name,
|
plugin_name,
|
||||||
default=GlobalPlugin(module_name=plugin_name, enabled=True))
|
default=GlobalPlugin(module_name=plugin_name, enabled=True))
|
||||||
plugin.enabled = enable
|
plugin.enabled = enable
|
||||||
|
|
||||||
plugin_db.save(plugin)
|
plugin_db.save(plugin)
|
||||||
__global_enable[plugin_name] = enable
|
__global_enable[plugin_name] = enable
|
||||||
|
|
||||||
|
|
||||||
def get_plugin_can_be_toggle(plugin_name: str) -> bool:
|
def get_plugin_can_be_toggle(plugin_name: str) -> bool:
|
||||||
"""
|
"""
|
||||||
获取插件是否可以被启用/停用
|
获取插件是否可以被启用/停用
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
plugin_name (str): 插件模块名
|
plugin_name (str): 插件模块名
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: 插件是否可以被启用/停用
|
bool: 插件是否可以被启用/停用
|
||||||
"""
|
"""
|
||||||
plug = nonebot.plugin.get_plugin(plugin_name)
|
plug = nonebot.plugin.get_plugin(plugin_name)
|
||||||
return plug.metadata.extra.get("toggleable", True) if plug and plug.metadata else True
|
return plug.metadata.extra.get("toggleable", True) if plug and plug.metadata else True
|
||||||
|
|
||||||
|
|
||||||
def get_group_enable(group_id: str) -> bool:
|
def get_group_enable(group_id: str) -> bool:
|
||||||
"""
|
"""
|
||||||
获取群组是否启用插机器人
|
获取群组是否启用插机器人
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
group_id (str): 群组ID
|
group_id (str): 群组ID
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: 群组是否启用插件
|
bool: 群组是否启用插件
|
||||||
"""
|
"""
|
||||||
group_id = str(group_id)
|
group_id = str(group_id)
|
||||||
if group_id not in __group_data:
|
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: Group = group_db.where_one(Group(), "group_id = ?", group_id, default=Group(group_id=group_id))
|
||||||
__group_data[group_id] = group
|
__group_data[group_id] = group
|
||||||
|
|
||||||
return __group_data[group_id].enable
|
return __group_data[group_id].enable
|
||||||
|
|
||||||
|
|
||||||
def set_group_enable(group_id: str, enable: bool):
|
def set_group_enable(group_id: str, enable: bool):
|
||||||
"""
|
"""
|
||||||
设置群组是否启用插机器人
|
设置群组是否启用插机器人
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
group_id (str): 群组ID
|
group_id (str): 群组ID
|
||||||
enable (bool): 是否启用
|
enable (bool): 是否启用
|
||||||
"""
|
"""
|
||||||
group_id = str(group_id)
|
group_id = str(group_id)
|
||||||
group: Group = group_db.where_one(Group(), "group_id = ?", group_id, default=Group(group_id=group_id))
|
group: Group = group_db.where_one(Group(), "group_id = ?", group_id, default=Group(group_id=group_id))
|
||||||
group.enable = enable
|
group.enable = enable
|
||||||
|
|
||||||
__group_data[group_id] = group
|
__group_data[group_id] = group
|
||||||
group_db.save(group)
|
group_db.save(group)
|
File diff suppressed because it is too large
Load Diff
@ -1,186 +1,186 @@
|
|||||||
# 轻雪资源包管理器
|
# 轻雪资源包管理器
|
||||||
import os
|
import os
|
||||||
import zipfile
|
import zipfile
|
||||||
import yaml
|
import yaml
|
||||||
from nonebot import require
|
from nonebot import require
|
||||||
from nonebot.internal.matcher import Matcher
|
from nonebot.internal.matcher import Matcher
|
||||||
from nonebot.permission import SUPERUSER
|
from nonebot.permission import SUPERUSER
|
||||||
|
|
||||||
from src.utils.base.language import get_user_lang
|
from src.utils.base.language import get_user_lang
|
||||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||||
from src.utils.message.message import MarkdownMessage as md
|
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)
|
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")
|
require("nonebot_plugin_alconna")
|
||||||
from nonebot_plugin_alconna import Alconna, Args, on_alconna, Arparma, Subcommand
|
from nonebot_plugin_alconna import Alconna, Args, on_alconna, Arparma, Subcommand
|
||||||
|
|
||||||
|
|
||||||
@on_alconna(
|
@on_alconna(
|
||||||
aliases={"资源包"},
|
aliases={"资源包"},
|
||||||
command=Alconna(
|
command=Alconna(
|
||||||
"rpm",
|
"rpm",
|
||||||
Subcommand(
|
Subcommand(
|
||||||
"list",
|
"list",
|
||||||
Args["page", int, 1]["num", int, 10],
|
Args["page", int, 1]["num", int, 10],
|
||||||
alias=["ls", "列表", "列出"],
|
alias=["ls", "列表", "列出"],
|
||||||
),
|
),
|
||||||
Subcommand(
|
Subcommand(
|
||||||
"load",
|
"load",
|
||||||
Args["name", str],
|
Args["name", str],
|
||||||
alias=["安装"],
|
alias=["安装"],
|
||||||
),
|
),
|
||||||
Subcommand(
|
Subcommand(
|
||||||
"unload",
|
"unload",
|
||||||
Args["name", str],
|
Args["name", str],
|
||||||
alias=["卸载"],
|
alias=["卸载"],
|
||||||
),
|
),
|
||||||
Subcommand(
|
Subcommand(
|
||||||
"up",
|
"up",
|
||||||
Args["name", str],
|
Args["name", str],
|
||||||
alias=["上移"],
|
alias=["上移"],
|
||||||
),
|
),
|
||||||
Subcommand(
|
Subcommand(
|
||||||
"down",
|
"down",
|
||||||
Args["name", str],
|
Args["name", str],
|
||||||
alias=["下移"],
|
alias=["下移"],
|
||||||
),
|
),
|
||||||
Subcommand(
|
Subcommand(
|
||||||
"top",
|
"top",
|
||||||
Args["name", str],
|
Args["name", str],
|
||||||
alias=["置顶"],
|
alias=["置顶"],
|
||||||
),
|
),
|
||||||
Subcommand(
|
Subcommand(
|
||||||
"reload",
|
"reload",
|
||||||
alias=["重载"],
|
alias=["重载"],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
permission=SUPERUSER
|
permission=SUPERUSER
|
||||||
).handle()
|
).handle()
|
||||||
async def _(bot: T_Bot, event: T_MessageEvent, result: Arparma, matcher: Matcher):
|
async def _(bot: T_Bot, event: T_MessageEvent, result: Arparma, matcher: Matcher):
|
||||||
ulang = get_user_lang(str(event.user_id))
|
ulang = get_user_lang(str(event.user_id))
|
||||||
reply = ""
|
reply = ""
|
||||||
send_as_md = False
|
send_as_md = False
|
||||||
if result.subcommands.get("list"):
|
if result.subcommands.get("list"):
|
||||||
send_as_md = True
|
send_as_md = True
|
||||||
loaded_rps = get_loaded_resource_packs()
|
loaded_rps = get_loaded_resource_packs()
|
||||||
reply += f"{ulang.get('liteyuki.loaded_resources', NUM=len(loaded_rps))}\n"
|
reply += f"{ulang.get('liteyuki.loaded_resources', NUM=len(loaded_rps))}\n"
|
||||||
for rp in loaded_rps:
|
for rp in loaded_rps:
|
||||||
btn_unload = md.btn_cmd(
|
btn_unload = md.btn_cmd(
|
||||||
ulang.get("npm.uninstall"),
|
ulang.get("npm.uninstall"),
|
||||||
f"rpm unload {rp.folder}"
|
f"rpm unload {rp.folder}"
|
||||||
)
|
)
|
||||||
btn_move_up = md.btn_cmd(
|
btn_move_up = md.btn_cmd(
|
||||||
ulang.get("rpm.move_up"),
|
ulang.get("rpm.move_up"),
|
||||||
f"rpm up {rp.folder}"
|
f"rpm up {rp.folder}"
|
||||||
)
|
)
|
||||||
btn_move_down = md.btn_cmd(
|
btn_move_down = md.btn_cmd(
|
||||||
ulang.get("rpm.move_down"),
|
ulang.get("rpm.move_down"),
|
||||||
f"rpm down {rp.folder}"
|
f"rpm down {rp.folder}"
|
||||||
)
|
)
|
||||||
btn_move_top = md.btn_cmd(
|
btn_move_top = md.btn_cmd(
|
||||||
ulang.get("rpm.move_top"),
|
ulang.get("rpm.move_top"),
|
||||||
f"rpm top {rp.folder}"
|
f"rpm top {rp.folder}"
|
||||||
)
|
)
|
||||||
# 添加新行
|
# 添加新行
|
||||||
reply += (f"\n**{md.escape(rp.name)}**({md.escape(rp.folder)})\n\n"
|
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***")
|
f"> {btn_move_up} {btn_move_down} {btn_move_top} {btn_unload}\n\n***")
|
||||||
reply += f"\n\n{ulang.get('liteyuki.unloaded_resources')}\n"
|
reply += f"\n\n{ulang.get('liteyuki.unloaded_resources')}\n"
|
||||||
loaded_folders = [rp.folder for rp in get_loaded_resource_packs()]
|
loaded_folders = [rp.folder for rp in get_loaded_resource_packs()]
|
||||||
# 遍历resources文件夹,获取未加载的资源包
|
# 遍历resources文件夹,获取未加载的资源包
|
||||||
for folder in os.listdir("resources"):
|
for folder in os.listdir("resources"):
|
||||||
if folder not in loaded_folders:
|
if folder not in loaded_folders:
|
||||||
if os.path.exists(os.path.join("resources", folder, "metadata.yml")):
|
if os.path.exists(os.path.join("resources", folder, "metadata.yml")):
|
||||||
metadata = ResourceMetadata(
|
metadata = ResourceMetadata(
|
||||||
**yaml.load(
|
**yaml.load(
|
||||||
open(
|
open(
|
||||||
os.path.join("resources", folder, "metadata.yml"),
|
os.path.join("resources", folder, "metadata.yml"),
|
||||||
encoding="utf-8"
|
encoding="utf-8"
|
||||||
),
|
),
|
||||||
Loader=yaml.FullLoader
|
Loader=yaml.FullLoader
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
metadata.folder = folder
|
metadata.folder = folder
|
||||||
metadata.path = os.path.join("resources", folder)
|
metadata.path = os.path.join("resources", folder)
|
||||||
btn_load = md.btn_cmd(
|
btn_load = md.btn_cmd(
|
||||||
ulang.get("npm.install"),
|
ulang.get("npm.install"),
|
||||||
f"rpm load {metadata.folder}"
|
f"rpm load {metadata.folder}"
|
||||||
)
|
)
|
||||||
# 添加新行
|
# 添加新行
|
||||||
reply += (f"\n**{md.escape(metadata.name)}**({md.escape(metadata.folder)})\n\n"
|
reply += (f"\n**{md.escape(metadata.name)}**({md.escape(metadata.folder)})\n\n"
|
||||||
f"> {btn_load}\n\n***")
|
f"> {btn_load}\n\n***")
|
||||||
elif os.path.isfile(os.path.join("resources", folder)) and folder.endswith(".zip"):
|
elif os.path.isfile(os.path.join("resources", folder)) and folder.endswith(".zip"):
|
||||||
# zip文件
|
# zip文件
|
||||||
# 临时解压并读取metadata.yml
|
# 临时解压并读取metadata.yml
|
||||||
with zipfile.ZipFile(os.path.join("resources", folder), "r") as zip_ref:
|
with zipfile.ZipFile(os.path.join("resources", folder), "r") as zip_ref:
|
||||||
with zip_ref.open("metadata.yml") as f:
|
with zip_ref.open("metadata.yml") as f:
|
||||||
metadata = ResourceMetadata(
|
metadata = ResourceMetadata(
|
||||||
**yaml.load(f, Loader=yaml.FullLoader)
|
**yaml.load(f, Loader=yaml.FullLoader)
|
||||||
)
|
)
|
||||||
btn_load = md.btn_cmd(
|
btn_load = md.btn_cmd(
|
||||||
ulang.get("npm.install"),
|
ulang.get("npm.install"),
|
||||||
f"rpm load {folder}"
|
f"rpm load {folder}"
|
||||||
)
|
)
|
||||||
# 添加新行
|
# 添加新行
|
||||||
reply += (f"\n**{md.escape(metadata.name)}**({md.escape(folder)})\n\n"
|
reply += (f"\n**{md.escape(metadata.name)}**({md.escape(folder)})\n\n"
|
||||||
f"> {btn_load}\n\n***")
|
f"> {btn_load}\n\n***")
|
||||||
elif result.subcommands.get("load") or result.subcommands.get("unload"):
|
elif result.subcommands.get("load") or result.subcommands.get("unload"):
|
||||||
load = result.subcommands.get("load") is not None
|
load = result.subcommands.get("load") is not None
|
||||||
rp_name = result.args.get("name")
|
rp_name = result.args.get("name")
|
||||||
r = False # 操作结果
|
r = False # 操作结果
|
||||||
if check_exist(rp_name):
|
if check_exist(rp_name):
|
||||||
if load != check_status(rp_name):
|
if load != check_status(rp_name):
|
||||||
# 状态不同
|
# 状态不同
|
||||||
if load:
|
if load:
|
||||||
r = add_resource_pack(rp_name)
|
r = add_resource_pack(rp_name)
|
||||||
else:
|
else:
|
||||||
r = remove_resource_pack(rp_name)
|
r = remove_resource_pack(rp_name)
|
||||||
rp_meta = get_resource_metadata(rp_name)
|
rp_meta = get_resource_metadata(rp_name)
|
||||||
reply += ulang.get(
|
reply += ulang.get(
|
||||||
f"liteyuki.{'load' if load else 'unload'}_resource_{'success' if r else 'failed'}",
|
f"liteyuki.{'load' if load else 'unload'}_resource_{'success' if r else 'failed'}",
|
||||||
NAME=rp_meta.name
|
NAME=rp_meta.name
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# 重复操作
|
# 重复操作
|
||||||
reply += ulang.get(f"liteyuki.resource_already_{'load' if load else 'unload'}ed", NAME=rp_name)
|
reply += ulang.get(f"liteyuki.resource_already_{'load' if load else 'unload'}ed", NAME=rp_name)
|
||||||
else:
|
else:
|
||||||
reply += ulang.get("liteyuki.resource_not_found", NAME=rp_name)
|
reply += ulang.get("liteyuki.resource_not_found", NAME=rp_name)
|
||||||
if r:
|
if r:
|
||||||
btn_reload = md.btn_cmd(
|
btn_reload = md.btn_cmd(
|
||||||
ulang.get("liteyuki.reload_resources"),
|
ulang.get("liteyuki.reload_resources"),
|
||||||
f"rpm reload"
|
f"rpm reload"
|
||||||
)
|
)
|
||||||
reply += "\n" + ulang.get("liteyuki.need_reload", BTN=btn_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"):
|
elif result.subcommands.get("up") or result.subcommands.get("down") or result.subcommands.get("top"):
|
||||||
rp_name = result.args.get("name")
|
rp_name = result.args.get("name")
|
||||||
if result.subcommands.get("up"):
|
if result.subcommands.get("up"):
|
||||||
delta = -1
|
delta = -1
|
||||||
elif result.subcommands.get("down"):
|
elif result.subcommands.get("down"):
|
||||||
delta = 1
|
delta = 1
|
||||||
else:
|
else:
|
||||||
delta = 0
|
delta = 0
|
||||||
if check_exist(rp_name):
|
if check_exist(rp_name):
|
||||||
if check_status(rp_name):
|
if check_status(rp_name):
|
||||||
r = change_priority(rp_name, delta)
|
r = change_priority(rp_name, delta)
|
||||||
reply += ulang.get(f"liteyuki.change_priority_{'success' if r else 'failed'}", NAME=rp_name)
|
reply += ulang.get(f"liteyuki.change_priority_{'success' if r else 'failed'}", NAME=rp_name)
|
||||||
if r:
|
if r:
|
||||||
btn_reload = md.btn_cmd(
|
btn_reload = md.btn_cmd(
|
||||||
ulang.get("liteyuki.reload_resources"),
|
ulang.get("liteyuki.reload_resources"),
|
||||||
f"rpm reload"
|
f"rpm reload"
|
||||||
)
|
)
|
||||||
reply += "\n" + ulang.get("liteyuki.need_reload", BTN=btn_reload)
|
reply += "\n" + ulang.get("liteyuki.need_reload", BTN=btn_reload)
|
||||||
else:
|
else:
|
||||||
reply += ulang.get("liteyuki.resource_not_found", NAME=rp_name)
|
reply += ulang.get("liteyuki.resource_not_found", NAME=rp_name)
|
||||||
else:
|
else:
|
||||||
reply += ulang.get("liteyuki.resource_not_found", NAME=rp_name)
|
reply += ulang.get("liteyuki.resource_not_found", NAME=rp_name)
|
||||||
elif result.subcommands.get("reload"):
|
elif result.subcommands.get("reload"):
|
||||||
load_resources()
|
load_resources()
|
||||||
reply = ulang.get(
|
reply = ulang.get(
|
||||||
"liteyuki.reload_resources_success",
|
"liteyuki.reload_resources_success",
|
||||||
NUM=len(get_loaded_resource_packs())
|
NUM=len(get_loaded_resource_packs())
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
if send_as_md:
|
if send_as_md:
|
||||||
await md.send_md(reply, bot, event=event)
|
await md.send_md(reply, bot, event=event)
|
||||||
else:
|
else:
|
||||||
await matcher.finish(reply)
|
await matcher.finish(reply)
|
@ -1,16 +1,16 @@
|
|||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
from .auto_update import *
|
from .auto_update import *
|
||||||
|
|
||||||
__author__ = "expliyh"
|
__author__ = "expliyh"
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="Satori 用户数据自动更新(临时措施)",
|
name="Satori 用户数据自动更新(临时措施)",
|
||||||
description="",
|
description="",
|
||||||
usage="",
|
usage="",
|
||||||
type="application",
|
type="application",
|
||||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||||
extra={
|
extra={
|
||||||
"liteyuki": True,
|
"liteyuki": True,
|
||||||
"toggleable" : True,
|
"toggleable" : True,
|
||||||
"default_enable" : True,
|
"default_enable" : True,
|
||||||
}
|
}
|
||||||
)
|
)
|
@ -1,21 +1,21 @@
|
|||||||
import nonebot
|
import nonebot
|
||||||
|
|
||||||
from nonebot.message import event_preprocessor
|
from nonebot.message import event_preprocessor
|
||||||
# from nonebot_plugin_alconna.typings import Event
|
# from nonebot_plugin_alconna.typings import Event
|
||||||
from src.utils.base.ly_typing import T_MessageEvent
|
from src.utils.base.ly_typing import T_MessageEvent
|
||||||
from src.utils import satori_utils
|
from src.utils import satori_utils
|
||||||
from nonebot.adapters import satori
|
from nonebot.adapters import satori
|
||||||
from nonebot_plugin_alconna.typings import Event
|
from nonebot_plugin_alconna.typings import Event
|
||||||
from src.plugins.liteyuki_status.counter_for_satori import satori_counter
|
from src.nonebot_plugins.liteyuki_status.counter_for_satori import satori_counter
|
||||||
|
|
||||||
|
|
||||||
@event_preprocessor
|
@event_preprocessor
|
||||||
async def pre_handle(event: Event):
|
async def pre_handle(event: Event):
|
||||||
if isinstance(event, satori.MessageEvent):
|
if isinstance(event, satori.MessageEvent):
|
||||||
if event.user.id == event.self_id:
|
if event.user.id == event.self_id:
|
||||||
satori_counter.msg_sent += 1
|
satori_counter.msg_sent += 1
|
||||||
else:
|
else:
|
||||||
satori_counter.msg_received += 1
|
satori_counter.msg_received += 1
|
||||||
if event.user.name is not None:
|
if event.user.name is not None:
|
||||||
if await satori_utils.user_infos.put(event.user):
|
if await satori_utils.user_infos.put(event.user):
|
||||||
nonebot.logger.info(f"Satori user {event.user.name}<{event.user.id}> updated")
|
nonebot.logger.info(f"Satori user {event.user.name}<{event.user.id}> updated")
|
@ -1,163 +1,163 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
from nonebot import require
|
from nonebot import require
|
||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
|
|
||||||
from src.utils.base.config import get_config
|
from src.utils.base.config import get_config
|
||||||
from src.utils.base.data import Database, LiteModel
|
from src.utils.base.data import Database, LiteModel
|
||||||
from src.utils.base.resource import get_path
|
from src.utils.base.resource import get_path
|
||||||
from src.utils.message.html_tool import template2image
|
from src.utils.message.html_tool import template2image
|
||||||
|
|
||||||
require("nonebot_plugin_alconna")
|
require("nonebot_plugin_alconna")
|
||||||
require("nonebot_plugin_apscheduler")
|
require("nonebot_plugin_apscheduler")
|
||||||
from nonebot_plugin_apscheduler import scheduler
|
from nonebot_plugin_apscheduler import scheduler
|
||||||
from nonebot_plugin_alconna import Alconna, AlconnaResult, CommandResult, Subcommand, UniMessage, on_alconna, Args
|
from nonebot_plugin_alconna import Alconna, AlconnaResult, CommandResult, Subcommand, UniMessage, on_alconna, Args
|
||||||
|
|
||||||
__author__ = "snowykami"
|
__author__ = "snowykami"
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="签名服务器状态",
|
name="签名服务器状态",
|
||||||
description="适用于ntqq的签名状态查看",
|
description="适用于ntqq的签名状态查看",
|
||||||
usage=(
|
usage=(
|
||||||
"sign count 查看当前签名数\n"
|
"sign count 查看当前签名数\n"
|
||||||
"sign data 查看签名数变化\n"
|
"sign data 查看签名数变化\n"
|
||||||
"sign chart [limit] 查看签名数变化图表\n"
|
"sign chart [limit] 查看签名数变化图表\n"
|
||||||
),
|
),
|
||||||
type="application",
|
type="application",
|
||||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||||
extra={
|
extra={
|
||||||
"liteyuki" : True,
|
"liteyuki" : True,
|
||||||
"toggleable" : True,
|
"toggleable" : True,
|
||||||
"default_enable": True,
|
"default_enable": True,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
SIGN_COUNT_URLS: dict[str, str] = get_config("sign_count_urls", None)
|
SIGN_COUNT_URLS: dict[str, str] = get_config("sign_count_urls", None)
|
||||||
SIGN_COUNT_DURATION = get_config("sign_count_duration", 10)
|
SIGN_COUNT_DURATION = get_config("sign_count_duration", 10)
|
||||||
|
|
||||||
|
|
||||||
class SignCount(LiteModel):
|
class SignCount(LiteModel):
|
||||||
TABLE_NAME: str = "sign_count"
|
TABLE_NAME: str = "sign_count"
|
||||||
time: float = 0.0
|
time: float = 0.0
|
||||||
count: int = 0
|
count: int = 0
|
||||||
sid: str = ""
|
sid: str = ""
|
||||||
|
|
||||||
|
|
||||||
sign_db = Database("data/liteyuki/ntqq_sign.ldb")
|
sign_db = Database("data/liteyuki/ntqq_sign.ldb")
|
||||||
sign_db.auto_migrate(SignCount())
|
sign_db.auto_migrate(SignCount())
|
||||||
|
|
||||||
sign_status = on_alconna(Alconna(
|
sign_status = on_alconna(Alconna(
|
||||||
"sign",
|
"sign",
|
||||||
Subcommand(
|
Subcommand(
|
||||||
"chart",
|
"chart",
|
||||||
Args["limit", int, 10000]
|
Args["limit", int, 10000]
|
||||||
),
|
),
|
||||||
Subcommand(
|
Subcommand(
|
||||||
"count"
|
"count"
|
||||||
),
|
),
|
||||||
Subcommand(
|
Subcommand(
|
||||||
"data"
|
"data"
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
|
|
||||||
cache_img: bytes = None
|
cache_img: bytes = None
|
||||||
|
|
||||||
|
|
||||||
@sign_status.assign("count")
|
@sign_status.assign("count")
|
||||||
async def _():
|
async def _():
|
||||||
reply = "Current sign count:"
|
reply = "Current sign count:"
|
||||||
for name, count in (await get_now_sign()).items():
|
for name, count in (await get_now_sign()).items():
|
||||||
reply += f"\n{name}: {count[1]}"
|
reply += f"\n{name}: {count[1]}"
|
||||||
await sign_status.send(reply)
|
await sign_status.send(reply)
|
||||||
|
|
||||||
|
|
||||||
@sign_status.assign("data")
|
@sign_status.assign("data")
|
||||||
async def _():
|
async def _():
|
||||||
query_stamp = [1, 5, 10, 15]
|
query_stamp = [1, 5, 10, 15]
|
||||||
|
|
||||||
reply = "QPS from last " + ", ".join([str(i) for i in query_stamp]) + "mins"
|
reply = "QPS from last " + ", ".join([str(i) for i in query_stamp]) + "mins"
|
||||||
for name, url in SIGN_COUNT_URLS.items():
|
for name, url in SIGN_COUNT_URLS.items():
|
||||||
count_data = []
|
count_data = []
|
||||||
for stamp in query_stamp:
|
for stamp in query_stamp:
|
||||||
count_rows = sign_db.where_all(SignCount(), "sid = ? and time > ?", url, time.time() - 60 * stamp)
|
count_rows = sign_db.where_all(SignCount(), "sid = ? and time > ?", url, time.time() - 60 * stamp)
|
||||||
if len(count_rows) < 2:
|
if len(count_rows) < 2:
|
||||||
count_data.append(-1)
|
count_data.append(-1)
|
||||||
else:
|
else:
|
||||||
count_data.append((count_rows[-1].count - count_rows[0].count)/(stamp*60))
|
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])
|
reply += f"\n{name}: " + ", ".join([f"{i:.1f}" for i in count_data])
|
||||||
await sign_status.send(reply)
|
await sign_status.send(reply)
|
||||||
|
|
||||||
|
|
||||||
@sign_status.assign("chart")
|
@sign_status.assign("chart")
|
||||||
async def _(arp: CommandResult = AlconnaResult()):
|
async def _(arp: CommandResult = AlconnaResult()):
|
||||||
limit = arp.result.subcommands.get("chart").args.get("limit")
|
limit = arp.result.subcommands.get("chart").args.get("limit")
|
||||||
if limit == 10000:
|
if limit == 10000:
|
||||||
if cache_img:
|
if cache_img:
|
||||||
await sign_status.send(UniMessage.image(raw=cache_img))
|
await sign_status.send(UniMessage.image(raw=cache_img))
|
||||||
return
|
return
|
||||||
img = await generate_chart(limit)
|
img = await generate_chart(limit)
|
||||||
await sign_status.send(UniMessage.image(raw=img))
|
await sign_status.send(UniMessage.image(raw=img))
|
||||||
|
|
||||||
|
|
||||||
@scheduler.scheduled_job("interval", seconds=SIGN_COUNT_DURATION, next_run_time=datetime.datetime.now())
|
@scheduler.scheduled_job("interval", seconds=SIGN_COUNT_DURATION, next_run_time=datetime.datetime.now())
|
||||||
async def update_sign_count():
|
async def update_sign_count():
|
||||||
global cache_img
|
global cache_img
|
||||||
if not SIGN_COUNT_URLS:
|
if not SIGN_COUNT_URLS:
|
||||||
return
|
return
|
||||||
data = await get_now_sign()
|
data = await get_now_sign()
|
||||||
for name, count in data.items():
|
for name, count in data.items():
|
||||||
await save_sign_count(count[0], count[1], SIGN_COUNT_URLS[name])
|
await save_sign_count(count[0], count[1], SIGN_COUNT_URLS[name])
|
||||||
|
|
||||||
cache_img = await generate_chart(10000)
|
cache_img = await generate_chart(10000)
|
||||||
|
|
||||||
|
|
||||||
async def get_now_sign() -> dict[str, tuple[float, int]]:
|
async def get_now_sign() -> dict[str, tuple[float, int]]:
|
||||||
"""
|
"""
|
||||||
Get the sign count and the time of the latest sign
|
Get the sign count and the time of the latest sign
|
||||||
Returns:
|
Returns:
|
||||||
tuple[float, int] | None: (time, count)
|
tuple[float, int] | None: (time, count)
|
||||||
"""
|
"""
|
||||||
data = {}
|
data = {}
|
||||||
now = time.time()
|
now = time.time()
|
||||||
async with aiohttp.ClientSession() as client:
|
async with aiohttp.ClientSession() as client:
|
||||||
for name, url in SIGN_COUNT_URLS.items():
|
for name, url in SIGN_COUNT_URLS.items():
|
||||||
async with client.get(url) as resp:
|
async with client.get(url) as resp:
|
||||||
count = (await resp.json())["count"]
|
count = (await resp.json())["count"]
|
||||||
data[name] = (now, count)
|
data[name] = (now, count)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
async def save_sign_count(timestamp: float, count: int, sid: str):
|
async def save_sign_count(timestamp: float, count: int, sid: str):
|
||||||
"""
|
"""
|
||||||
Save the sign count to the database
|
Save the sign count to the database
|
||||||
Args:
|
Args:
|
||||||
sid: the sign id, use url as the id
|
sid: the sign id, use url as the id
|
||||||
count:
|
count:
|
||||||
timestamp (float): the time of the sign count (int): the count of the sign
|
timestamp (float): the time of the sign count (int): the count of the sign
|
||||||
"""
|
"""
|
||||||
sign_db.save(SignCount(time=timestamp, count=count, sid=sid))
|
sign_db.save(SignCount(time=timestamp, count=count, sid=sid))
|
||||||
|
|
||||||
|
|
||||||
async def generate_chart(limit):
|
async def generate_chart(limit):
|
||||||
data = []
|
data = []
|
||||||
for name, url in SIGN_COUNT_URLS.items():
|
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 = sign_db.where_all(SignCount(), "sid = ? ORDER BY id DESC LIMIT ?", url, limit)
|
||||||
count_rows.reverse()
|
count_rows.reverse()
|
||||||
data.append(
|
data.append(
|
||||||
{
|
{
|
||||||
"name" : name,
|
"name" : name,
|
||||||
# "data": [[row.time, row.count] for row in count_rows]
|
# "data": [[row.time, row.count] for row in count_rows]
|
||||||
"times" : [row.time for row in count_rows],
|
"times" : [row.time for row in count_rows],
|
||||||
"counts": [row.count for row in count_rows]
|
"counts": [row.count for row in count_rows]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
img = await template2image(
|
img = await template2image(
|
||||||
template=get_path("templates/sign_status.html"),
|
template=get_path("templates/sign_status.html"),
|
||||||
templates={
|
templates={
|
||||||
"data": data
|
"data": data
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
return img
|
return img
|
@ -1,18 +1,18 @@
|
|||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
from .monitors import *
|
from .monitors import *
|
||||||
from .matchers import *
|
from .matchers import *
|
||||||
|
|
||||||
|
|
||||||
__author__ = "snowykami"
|
__author__ = "snowykami"
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="轻雪智障回复",
|
name="轻雪智障回复",
|
||||||
description="",
|
description="",
|
||||||
usage="",
|
usage="",
|
||||||
type="application",
|
type="application",
|
||||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||||
extra={
|
extra={
|
||||||
"liteyuki": True,
|
"liteyuki": True,
|
||||||
"toggleable" : True,
|
"toggleable" : True,
|
||||||
"default_enable" : True,
|
"default_enable" : True,
|
||||||
}
|
}
|
||||||
)
|
)
|
@ -1,106 +1,106 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import random
|
import random
|
||||||
|
|
||||||
import nonebot
|
import nonebot
|
||||||
from nonebot import Bot, on_message, get_driver, require
|
from nonebot import Bot, on_message, get_driver, require
|
||||||
from nonebot.internal.matcher import Matcher
|
from nonebot.internal.matcher import Matcher
|
||||||
from nonebot.permission import SUPERUSER
|
from nonebot.permission import SUPERUSER
|
||||||
from nonebot.rule import to_me
|
from nonebot.rule import to_me
|
||||||
from nonebot.typing import T_State
|
from nonebot.typing import T_State
|
||||||
|
|
||||||
from src.utils.base.ly_typing import T_MessageEvent
|
from src.utils.base.ly_typing import T_MessageEvent
|
||||||
from .utils import get_keywords
|
from .utils import get_keywords
|
||||||
from src.utils.base.word_bank import get_reply
|
from src.utils.base.word_bank import get_reply
|
||||||
from src.utils.event import get_message_type
|
from src.utils.event import get_message_type
|
||||||
from src.utils.base.permission import GROUP_ADMIN, GROUP_OWNER
|
from src.utils.base.permission import GROUP_ADMIN, GROUP_OWNER
|
||||||
from src.utils.base.data_manager import group_db, Group
|
from src.utils.base.data_manager import group_db, Group
|
||||||
|
|
||||||
require("nonebot_plugin_alconna")
|
require("nonebot_plugin_alconna")
|
||||||
from nonebot_plugin_alconna import on_alconna, Alconna, Args, Arparma
|
from nonebot_plugin_alconna import on_alconna, Alconna, Args, Arparma
|
||||||
|
|
||||||
nicknames = set()
|
nicknames = set()
|
||||||
driver = get_driver()
|
driver = get_driver()
|
||||||
group_reply_probability: dict[str, float] = {
|
group_reply_probability: dict[str, float] = {
|
||||||
}
|
}
|
||||||
default_reply_probability = 0.05
|
default_reply_probability = 0.05
|
||||||
cut_probability = 0.4 # 分几句话的概率
|
cut_probability = 0.4 # 分几句话的概率
|
||||||
|
|
||||||
|
|
||||||
@on_alconna(
|
@on_alconna(
|
||||||
Alconna(
|
Alconna(
|
||||||
"set-reply-probability",
|
"set-reply-probability",
|
||||||
Args["probability", float, default_reply_probability],
|
Args["probability", float, default_reply_probability],
|
||||||
),
|
),
|
||||||
aliases={"设置回复概率"},
|
aliases={"设置回复概率"},
|
||||||
permission=SUPERUSER | GROUP_ADMIN | GROUP_OWNER,
|
permission=SUPERUSER | GROUP_ADMIN | GROUP_OWNER,
|
||||||
).handle()
|
).handle()
|
||||||
async def _(result: Arparma, event: T_MessageEvent, matcher: Matcher):
|
async def _(result: Arparma, event: T_MessageEvent, matcher: Matcher):
|
||||||
# 修改内存和数据库的概率值
|
# 修改内存和数据库的概率值
|
||||||
if get_message_type(event) == "group":
|
if get_message_type(event) == "group":
|
||||||
group_id = event.group_id
|
group_id = event.group_id
|
||||||
probability = result.main_args.get("probability")
|
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: Group = group_db.where_one(Group(), "group_id = ?", group_id, default=Group(group_id=str(group_id)))
|
||||||
group.config["reply_probability"] = probability
|
group.config["reply_probability"] = probability
|
||||||
group_db.save(group)
|
group_db.save(group)
|
||||||
|
|
||||||
await matcher.send(f"已将群组{group_id}的回复概率设置为{probability}")
|
await matcher.send(f"已将群组{group_id}的回复概率设置为{probability}")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
@group_db.on_save
|
@group_db.on_save
|
||||||
def _(model: Group):
|
def _(model: Group):
|
||||||
"""
|
"""
|
||||||
在数据库更新时,更新内存中的回复概率
|
在数据库更新时,更新内存中的回复概率
|
||||||
Args:
|
Args:
|
||||||
model:
|
model:
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
group_reply_probability[model.group_id] = model.config.get("reply_probability", default_reply_probability)
|
group_reply_probability[model.group_id] = model.config.get("reply_probability", default_reply_probability)
|
||||||
|
|
||||||
|
|
||||||
@driver.on_bot_connect
|
@driver.on_bot_connect
|
||||||
async def _(bot: Bot):
|
async def _(bot: Bot):
|
||||||
global nicknames
|
global nicknames
|
||||||
nicknames.update(bot.config.nickname)
|
nicknames.update(bot.config.nickname)
|
||||||
# 从数据库加载群组的回复概率
|
# 从数据库加载群组的回复概率
|
||||||
groups = group_db.where_all(Group(), default=[])
|
groups = group_db.where_all(Group(), default=[])
|
||||||
for group in groups:
|
for group in groups:
|
||||||
group_reply_probability[group.group_id] = group.config.get("reply_probability", default_reply_probability)
|
group_reply_probability[group.group_id] = group.config.get("reply_probability", default_reply_probability)
|
||||||
|
|
||||||
|
|
||||||
@on_message(priority=100).handle()
|
@on_message(priority=100).handle()
|
||||||
async def _(event: T_MessageEvent, bot: Bot, state: T_State, matcher: Matcher):
|
async def _(event: T_MessageEvent, bot: Bot, state: T_State, matcher: Matcher):
|
||||||
kws = await get_keywords(event.message.extract_plain_text())
|
kws = await get_keywords(event.message.extract_plain_text())
|
||||||
|
|
||||||
tome = False
|
tome = False
|
||||||
if await to_me()(event=event, bot=bot, state=state):
|
if await to_me()(event=event, bot=bot, state=state):
|
||||||
tome = True
|
tome = True
|
||||||
else:
|
else:
|
||||||
for kw in kws:
|
for kw in kws:
|
||||||
if kw in nicknames:
|
if kw in nicknames:
|
||||||
tome = True
|
tome = True
|
||||||
break
|
break
|
||||||
|
|
||||||
# 回复概率
|
# 回复概率
|
||||||
message_type = get_message_type(event)
|
message_type = get_message_type(event)
|
||||||
if tome or message_type == "private":
|
if tome or message_type == "private":
|
||||||
p = 1.0
|
p = 1.0
|
||||||
else:
|
else:
|
||||||
p = group_reply_probability.get(event.group_id, default_reply_probability)
|
p = group_reply_probability.get(event.group_id, default_reply_probability)
|
||||||
|
|
||||||
if random.random() < p:
|
if random.random() < p:
|
||||||
if reply := get_reply(kws):
|
if reply := get_reply(kws):
|
||||||
if random.random() < cut_probability:
|
if random.random() < cut_probability:
|
||||||
reply = reply.replace("。", "||").replace(",", "||").replace("!", "||").replace("?", "||")
|
reply = reply.replace("。", "||").replace(",", "||").replace("!", "||").replace("?", "||")
|
||||||
replies = reply.split("||")
|
replies = reply.split("||")
|
||||||
for r in replies:
|
for r in replies:
|
||||||
if r: # 防止空字符串
|
if r: # 防止空字符串
|
||||||
await asyncio.sleep(random.random() * 2)
|
await asyncio.sleep(random.random() * 2)
|
||||||
await matcher.send(r)
|
await matcher.send(r)
|
||||||
else:
|
else:
|
||||||
await asyncio.sleep(random.random() * 3)
|
await asyncio.sleep(random.random() * 3)
|
||||||
await matcher.send(reply)
|
await matcher.send(reply)
|
||||||
return
|
return
|
@ -1,13 +1,13 @@
|
|||||||
from jieba import lcut
|
from jieba import lcut
|
||||||
from nonebot.utils import run_sync
|
from nonebot.utils import run_sync
|
||||||
|
|
||||||
|
|
||||||
@run_sync
|
@run_sync
|
||||||
def get_keywords(text: str) -> list[str, ...]:
|
def get_keywords(text: str) -> list[str, ...]:
|
||||||
"""
|
"""
|
||||||
获取关键词
|
获取关键词
|
||||||
Args:
|
Args:
|
||||||
text: 文本
|
text: 文本
|
||||||
Returns:
|
Returns:
|
||||||
"""
|
"""
|
||||||
return lcut(text)
|
return lcut(text)
|
@ -1,29 +1,29 @@
|
|||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
from .stat_matchers import *
|
from .stat_matchers import *
|
||||||
from .stat_monitors import *
|
from .stat_monitors import *
|
||||||
from .stat_restful_api import *
|
from .stat_restful_api import *
|
||||||
|
|
||||||
__author__ = "snowykami"
|
__author__ = "snowykami"
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="统计信息",
|
name="统计信息",
|
||||||
description="统计机器人的信息,包括消息、群聊等,支持排名、图表等功能",
|
description="统计机器人的信息,包括消息、群聊等,支持排名、图表等功能",
|
||||||
usage=(
|
usage=(
|
||||||
"```\nstatistic message 查看统计消息\n"
|
"```\nstatistic message 查看统计消息\n"
|
||||||
"可选参数:\n"
|
"可选参数:\n"
|
||||||
" -g|--group [group_id] 指定群聊\n"
|
" -g|--group [group_id] 指定群聊\n"
|
||||||
" -u|--user [user_id] 指定用户\n"
|
" -u|--user [user_id] 指定用户\n"
|
||||||
" -d|--duration [duration] 指定时长\n"
|
" -d|--duration [duration] 指定时长\n"
|
||||||
" -p|--period [period] 指定次数统计周期\n"
|
" -p|--period [period] 指定次数统计周期\n"
|
||||||
" -b|--bot [bot_id] 指定机器人\n"
|
" -b|--bot [bot_id] 指定机器人\n"
|
||||||
"命令别名:\n"
|
"命令别名:\n"
|
||||||
" statistic|stat message|msg|m\n"
|
" statistic|stat message|msg|m\n"
|
||||||
"```"
|
"```"
|
||||||
),
|
),
|
||||||
type="application",
|
type="application",
|
||||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||||
extra={
|
extra={
|
||||||
"liteyuki" : True,
|
"liteyuki" : True,
|
||||||
"toggleable" : False,
|
"toggleable" : False,
|
||||||
"default_enable": True,
|
"default_enable": True,
|
||||||
}
|
}
|
||||||
)
|
)
|
@ -1,21 +1,21 @@
|
|||||||
from src.utils.base.data import Database, LiteModel
|
from src.utils.base.data import Database, LiteModel
|
||||||
|
|
||||||
|
|
||||||
class MessageEventModel(LiteModel):
|
class MessageEventModel(LiteModel):
|
||||||
TABLE_NAME: str = "message_event"
|
TABLE_NAME: str = "message_event"
|
||||||
time: int = 0
|
time: int = 0
|
||||||
|
|
||||||
bot_id: str = ""
|
bot_id: str = ""
|
||||||
adapter: str = ""
|
adapter: str = ""
|
||||||
|
|
||||||
user_id: str = ""
|
user_id: str = ""
|
||||||
group_id: str = ""
|
group_id: str = ""
|
||||||
|
|
||||||
message_id: str = ""
|
message_id: str = ""
|
||||||
message: list = []
|
message: list = []
|
||||||
message_text: str = ""
|
message_text: str = ""
|
||||||
message_type: str = ""
|
message_type: str = ""
|
||||||
|
|
||||||
|
|
||||||
msg_db = Database("data/liteyuki/msg.ldb")
|
msg_db = Database("data/liteyuki/msg.ldb")
|
||||||
msg_db.auto_migrate(MessageEventModel())
|
msg_db.auto_migrate(MessageEventModel())
|
@ -1,172 +1,172 @@
|
|||||||
import time
|
import time
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
|
|
||||||
from nonebot import Bot
|
from nonebot import Bot
|
||||||
|
|
||||||
from src.utils.message.html_tool import template2image
|
from src.utils.message.html_tool import template2image
|
||||||
from .common import MessageEventModel, msg_db
|
from .common import MessageEventModel, msg_db
|
||||||
from src.utils.base.language import Language
|
from src.utils.base.language import Language
|
||||||
from src.utils.base.resource import get_path
|
from src.utils.base.resource import get_path
|
||||||
from src.utils.message.string_tool import convert_seconds_to_time
|
from src.utils.message.string_tool import convert_seconds_to_time
|
||||||
from ...utils.external.logo import get_group_icon, get_user_icon
|
from ...utils.external.logo import get_group_icon, get_user_icon
|
||||||
|
|
||||||
|
|
||||||
async def count_msg_by_bot_id(bot_id: str) -> int:
|
async def count_msg_by_bot_id(bot_id: str) -> int:
|
||||||
condition = " AND bot_id = ?"
|
condition = " AND bot_id = ?"
|
||||||
condition_args = [bot_id]
|
condition_args = [bot_id]
|
||||||
|
|
||||||
msg_rows = msg_db.where_all(
|
msg_rows = msg_db.where_all(
|
||||||
MessageEventModel(),
|
MessageEventModel(),
|
||||||
condition,
|
condition,
|
||||||
*condition_args
|
*condition_args
|
||||||
)
|
)
|
||||||
|
|
||||||
return len(msg_rows)
|
return len(msg_rows)
|
||||||
|
|
||||||
|
|
||||||
async def get_stat_msg_image(
|
async def get_stat_msg_image(
|
||||||
duration: int,
|
duration: int,
|
||||||
period: int,
|
period: int,
|
||||||
group_id: str = None,
|
group_id: str = None,
|
||||||
bot_id: str = None,
|
bot_id: str = None,
|
||||||
user_id: str = None,
|
user_id: str = None,
|
||||||
ulang: Language = Language()
|
ulang: Language = Language()
|
||||||
) -> bytes:
|
) -> bytes:
|
||||||
"""
|
"""
|
||||||
获取统计消息
|
获取统计消息
|
||||||
Args:
|
Args:
|
||||||
user_id:
|
user_id:
|
||||||
ulang:
|
ulang:
|
||||||
bot_id:
|
bot_id:
|
||||||
group_id:
|
group_id:
|
||||||
duration: 统计时间,单位秒
|
duration: 统计时间,单位秒
|
||||||
period: 统计周期,单位秒
|
period: 统计周期,单位秒
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple: [int,], [int,] 两个列表,分别为周期中心时间戳和消息数量
|
tuple: [int,], [int,] 两个列表,分别为周期中心时间戳和消息数量
|
||||||
"""
|
"""
|
||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
start_time = (now - duration)
|
start_time = (now - duration)
|
||||||
|
|
||||||
condition = "time > ?"
|
condition = "time > ?"
|
||||||
condition_args = [start_time]
|
condition_args = [start_time]
|
||||||
|
|
||||||
if group_id:
|
if group_id:
|
||||||
condition += " AND group_id = ?"
|
condition += " AND group_id = ?"
|
||||||
condition_args.append(group_id)
|
condition_args.append(group_id)
|
||||||
if bot_id:
|
if bot_id:
|
||||||
condition += " AND bot_id = ?"
|
condition += " AND bot_id = ?"
|
||||||
condition_args.append(bot_id)
|
condition_args.append(bot_id)
|
||||||
|
|
||||||
if user_id:
|
if user_id:
|
||||||
condition += " AND user_id = ?"
|
condition += " AND user_id = ?"
|
||||||
condition_args.append(user_id)
|
condition_args.append(user_id)
|
||||||
|
|
||||||
msg_rows = msg_db.where_all(
|
msg_rows = msg_db.where_all(
|
||||||
MessageEventModel(),
|
MessageEventModel(),
|
||||||
condition,
|
condition,
|
||||||
*condition_args
|
*condition_args
|
||||||
)
|
)
|
||||||
timestamps = []
|
timestamps = []
|
||||||
msg_count = []
|
msg_count = []
|
||||||
msg_rows.sort(key=lambda x: x.time)
|
msg_rows.sort(key=lambda x: x.time)
|
||||||
|
|
||||||
start_time = max(msg_rows[0].time, start_time)
|
start_time = max(msg_rows[0].time, start_time)
|
||||||
|
|
||||||
for i in range(start_time, now, period):
|
for i in range(start_time, now, period):
|
||||||
timestamps.append(i + period // 2)
|
timestamps.append(i + period // 2)
|
||||||
msg_count.append(0)
|
msg_count.append(0)
|
||||||
|
|
||||||
for msg in msg_rows:
|
for msg in msg_rows:
|
||||||
period_start_time = start_time + (msg.time - start_time) // period * period
|
period_start_time = start_time + (msg.time - start_time) // period * period
|
||||||
period_center_time = period_start_time + period // 2
|
period_center_time = period_start_time + period // 2
|
||||||
index = timestamps.index(period_center_time)
|
index = timestamps.index(period_center_time)
|
||||||
msg_count[index] += 1
|
msg_count[index] += 1
|
||||||
|
|
||||||
templates = {
|
templates = {
|
||||||
"data": [
|
"data": [
|
||||||
{
|
{
|
||||||
"name" : ulang.get("stat.message")
|
"name" : ulang.get("stat.message")
|
||||||
+ f" Period {convert_seconds_to_time(period)}" + f" Duration {convert_seconds_to_time(duration)}"
|
+ 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" Group {group_id}" if group_id else "") + (f" Bot {bot_id}" if bot_id else "") + (
|
||||||
f" User {user_id}" if user_id else ""),
|
f" User {user_id}" if user_id else ""),
|
||||||
"times" : timestamps,
|
"times" : timestamps,
|
||||||
"counts": msg_count
|
"counts": msg_count
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
return await template2image(get_path("templates/stat_msg.html"), templates)
|
return await template2image(get_path("templates/stat_msg.html"), templates)
|
||||||
|
|
||||||
|
|
||||||
async def get_stat_rank_image(
|
async def get_stat_rank_image(
|
||||||
rank_type: str,
|
rank_type: str,
|
||||||
limit: dict[str, Any],
|
limit: dict[str, Any],
|
||||||
ulang: Language = Language(),
|
ulang: Language = Language(),
|
||||||
bot: Bot = None,
|
bot: Bot = None,
|
||||||
) -> bytes:
|
) -> bytes:
|
||||||
if rank_type == "user":
|
if rank_type == "user":
|
||||||
condition = "user_id != ''"
|
condition = "user_id != ''"
|
||||||
condition_args = []
|
condition_args = []
|
||||||
else:
|
else:
|
||||||
condition = "group_id != ''"
|
condition = "group_id != ''"
|
||||||
condition_args = []
|
condition_args = []
|
||||||
|
|
||||||
for k, v in limit.items():
|
for k, v in limit.items():
|
||||||
match k:
|
match k:
|
||||||
case "user_id":
|
case "user_id":
|
||||||
condition += " AND user_id = ?"
|
condition += " AND user_id = ?"
|
||||||
condition_args.append(v)
|
condition_args.append(v)
|
||||||
case "group_id":
|
case "group_id":
|
||||||
condition += " AND group_id = ?"
|
condition += " AND group_id = ?"
|
||||||
condition_args.append(v)
|
condition_args.append(v)
|
||||||
case "bot_id":
|
case "bot_id":
|
||||||
condition += " AND bot_id = ?"
|
condition += " AND bot_id = ?"
|
||||||
condition_args.append(v)
|
condition_args.append(v)
|
||||||
case "duration":
|
case "duration":
|
||||||
condition += " AND time > ?"
|
condition += " AND time > ?"
|
||||||
condition_args.append(v)
|
condition_args.append(v)
|
||||||
|
|
||||||
msg_rows = msg_db.where_all(
|
msg_rows = msg_db.where_all(
|
||||||
MessageEventModel(),
|
MessageEventModel(),
|
||||||
condition,
|
condition,
|
||||||
*condition_args
|
*condition_args
|
||||||
)
|
)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
{
|
{
|
||||||
name: string, # user name or group name
|
name: string, # user name or group name
|
||||||
count: int, # message count
|
count: int, # message count
|
||||||
icon: string # icon url
|
icon: string # icon url
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if rank_type == "user":
|
if rank_type == "user":
|
||||||
ranking_counter = Counter([msg.user_id for msg in msg_rows])
|
ranking_counter = Counter([msg.user_id for msg in msg_rows])
|
||||||
else:
|
else:
|
||||||
ranking_counter = Counter([msg.group_id for msg in msg_rows])
|
ranking_counter = Counter([msg.group_id for msg in msg_rows])
|
||||||
sorted_data = sorted(ranking_counter.items(), key=lambda x: x[1], reverse=True)
|
sorted_data = sorted(ranking_counter.items(), key=lambda x: x[1], reverse=True)
|
||||||
|
|
||||||
ranking: list[dict[str, Any]] = [
|
ranking: list[dict[str, Any]] = [
|
||||||
{
|
{
|
||||||
"name" : _[0],
|
"name" : _[0],
|
||||||
"count": _[1],
|
"count": _[1],
|
||||||
"icon" : await (get_group_icon(platform="qq", group_id=_[0]) if rank_type == "group" else get_user_icon(
|
"icon" : await (get_group_icon(platform="qq", group_id=_[0]) if rank_type == "group" else get_user_icon(
|
||||||
platform="qq", user_id=_[0]
|
platform="qq", user_id=_[0]
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
for _ in sorted_data[0:min(len(sorted_data), limit["rank"])]
|
for _ in sorted_data[0:min(len(sorted_data), limit["rank"])]
|
||||||
]
|
]
|
||||||
|
|
||||||
templates = {
|
templates = {
|
||||||
"data":
|
"data":
|
||||||
{
|
{
|
||||||
"name" : ulang.get("stat.rank") + f" Type {rank_type}" + f" Limit {limit}",
|
"name" : ulang.get("stat.rank") + f" Type {rank_type}" + f" Limit {limit}",
|
||||||
"ranking": ranking
|
"ranking": ranking
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return await template2image(get_path("templates/stat_rank.html"), templates, debug=True)
|
return await template2image(get_path("templates/stat_rank.html"), templates, debug=True)
|
@ -1,134 +1,134 @@
|
|||||||
from nonebot import Bot, require
|
from nonebot import Bot, require
|
||||||
from src.utils.message.string_tool import convert_duration, convert_time_to_seconds
|
from src.utils.message.string_tool import convert_duration, convert_time_to_seconds
|
||||||
from .data_source import *
|
from .data_source import *
|
||||||
from src.utils import event as event_utils
|
from src.utils import event as event_utils
|
||||||
from src.utils.base.language import Language
|
from src.utils.base.language import Language
|
||||||
from src.utils.base.ly_typing import T_MessageEvent
|
from src.utils.base.ly_typing import T_MessageEvent
|
||||||
|
|
||||||
require("nonebot_plugin_alconna")
|
require("nonebot_plugin_alconna")
|
||||||
|
|
||||||
from nonebot_plugin_alconna import (
|
from nonebot_plugin_alconna import (
|
||||||
UniMessage,
|
UniMessage,
|
||||||
on_alconna,
|
on_alconna,
|
||||||
Alconna,
|
Alconna,
|
||||||
Args,
|
Args,
|
||||||
Subcommand,
|
Subcommand,
|
||||||
Arparma,
|
Arparma,
|
||||||
Option,
|
Option,
|
||||||
MultiVar
|
MultiVar
|
||||||
)
|
)
|
||||||
|
|
||||||
stat_msg = on_alconna(
|
stat_msg = on_alconna(
|
||||||
Alconna(
|
Alconna(
|
||||||
"statistic",
|
"statistic",
|
||||||
Subcommand(
|
Subcommand(
|
||||||
"message",
|
"message",
|
||||||
# Args["duration", str, "2d"]["period", str, "60s"], # 默认为1天
|
# Args["duration", str, "2d"]["period", str, "60s"], # 默认为1天
|
||||||
Option(
|
Option(
|
||||||
"-d|--duration",
|
"-d|--duration",
|
||||||
Args["duration", str, "2d"],
|
Args["duration", str, "2d"],
|
||||||
help_text="统计时间",
|
help_text="统计时间",
|
||||||
),
|
),
|
||||||
Option(
|
Option(
|
||||||
"-p|--period",
|
"-p|--period",
|
||||||
Args["period", str, "60s"],
|
Args["period", str, "60s"],
|
||||||
help_text="统计周期",
|
help_text="统计周期",
|
||||||
),
|
),
|
||||||
Option(
|
Option(
|
||||||
"-b|--bot", # 生成图表
|
"-b|--bot", # 生成图表
|
||||||
Args["bot_id", str, "current"],
|
Args["bot_id", str, "current"],
|
||||||
help_text="是否指定机器人",
|
help_text="是否指定机器人",
|
||||||
),
|
),
|
||||||
Option(
|
Option(
|
||||||
"-g|--group",
|
"-g|--group",
|
||||||
Args["group_id", str, "current"],
|
Args["group_id", str, "current"],
|
||||||
help_text="指定群组"
|
help_text="指定群组"
|
||||||
),
|
),
|
||||||
Option(
|
Option(
|
||||||
"-u|--user",
|
"-u|--user",
|
||||||
Args["user_id", str, "current"],
|
Args["user_id", str, "current"],
|
||||||
help_text="指定用户"
|
help_text="指定用户"
|
||||||
),
|
),
|
||||||
alias={"msg", "m"},
|
alias={"msg", "m"},
|
||||||
help_text="查看统计次数内的消息"
|
help_text="查看统计次数内的消息"
|
||||||
),
|
),
|
||||||
Subcommand(
|
Subcommand(
|
||||||
"rank",
|
"rank",
|
||||||
Option(
|
Option(
|
||||||
"-u|--user",
|
"-u|--user",
|
||||||
help_text="以用户为指标",
|
help_text="以用户为指标",
|
||||||
),
|
),
|
||||||
Option(
|
Option(
|
||||||
"-g|--group",
|
"-g|--group",
|
||||||
help_text="以群组为指标",
|
help_text="以群组为指标",
|
||||||
),
|
),
|
||||||
Option(
|
Option(
|
||||||
"-l|--limit",
|
"-l|--limit",
|
||||||
Args["limit", MultiVar(str)],
|
Args["limit", MultiVar(str)],
|
||||||
help_text="限制参数,使用key=val格式",
|
help_text="限制参数,使用key=val格式",
|
||||||
),
|
),
|
||||||
Option(
|
Option(
|
||||||
"-d|--duration",
|
"-d|--duration",
|
||||||
Args["duration", str, "1d"],
|
Args["duration", str, "1d"],
|
||||||
help_text="统计时间",
|
help_text="统计时间",
|
||||||
),
|
),
|
||||||
Option(
|
Option(
|
||||||
"-r|--rank",
|
"-r|--rank",
|
||||||
Args["rank", int, 20],
|
Args["rank", int, 20],
|
||||||
help_text="指定排名",
|
help_text="指定排名",
|
||||||
),
|
),
|
||||||
alias={"r"},
|
alias={"r"},
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
aliases={"stat"}
|
aliases={"stat"}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@stat_msg.assign("message")
|
@stat_msg.assign("message")
|
||||||
async def _(result: Arparma, event: T_MessageEvent, bot: Bot):
|
async def _(result: Arparma, event: T_MessageEvent, bot: Bot):
|
||||||
ulang = Language(event_utils.get_user_id(event))
|
ulang = Language(event_utils.get_user_id(event))
|
||||||
try:
|
try:
|
||||||
duration = convert_time_to_seconds(result.other_args.get("duration", "2d")) # 秒数
|
duration = convert_time_to_seconds(result.other_args.get("duration", "2d")) # 秒数
|
||||||
period = convert_time_to_seconds(result.other_args.get("period", "1m"))
|
period = convert_time_to_seconds(result.other_args.get("period", "1m"))
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
await stat_msg.send(ulang.get("liteyuki.invalid_command", TEXT=str(e.__str__())))
|
await stat_msg.send(ulang.get("liteyuki.invalid_command", TEXT=str(e.__str__())))
|
||||||
return
|
return
|
||||||
|
|
||||||
group_id = result.other_args.get("group_id")
|
group_id = result.other_args.get("group_id")
|
||||||
bot_id = result.other_args.get("bot_id")
|
bot_id = result.other_args.get("bot_id")
|
||||||
user_id = result.other_args.get("user_id")
|
user_id = result.other_args.get("user_id")
|
||||||
|
|
||||||
if group_id in ["current", "c"]:
|
if group_id in ["current", "c"]:
|
||||||
group_id = str(event_utils.get_group_id(event))
|
group_id = str(event_utils.get_group_id(event))
|
||||||
|
|
||||||
if group_id in ["all", "a"]:
|
if group_id in ["all", "a"]:
|
||||||
group_id = "all"
|
group_id = "all"
|
||||||
|
|
||||||
if bot_id in ["current", "c"]:
|
if bot_id in ["current", "c"]:
|
||||||
bot_id = str(bot.self_id)
|
bot_id = str(bot.self_id)
|
||||||
|
|
||||||
if user_id in ["current", "c"]:
|
if user_id in ["current", "c"]:
|
||||||
user_id = str(event_utils.get_user_id(event))
|
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)
|
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))
|
await stat_msg.send(UniMessage.image(raw=img))
|
||||||
|
|
||||||
|
|
||||||
@stat_msg.assign("rank")
|
@stat_msg.assign("rank")
|
||||||
async def _(result: Arparma, event: T_MessageEvent, bot: Bot):
|
async def _(result: Arparma, event: T_MessageEvent, bot: Bot):
|
||||||
ulang = Language(event_utils.get_user_id(event))
|
ulang = Language(event_utils.get_user_id(event))
|
||||||
rank_type = "user"
|
rank_type = "user"
|
||||||
duration = convert_time_to_seconds(result.other_args.get("duration", "1d"))
|
duration = convert_time_to_seconds(result.other_args.get("duration", "1d"))
|
||||||
if result.subcommands.get("rank").options.get("user"):
|
if result.subcommands.get("rank").options.get("user"):
|
||||||
rank_type = "user"
|
rank_type = "user"
|
||||||
elif result.subcommands.get("rank").options.get("group"):
|
elif result.subcommands.get("rank").options.get("group"):
|
||||||
rank_type = "group"
|
rank_type = "group"
|
||||||
|
|
||||||
limit = result.other_args.get("limit", {})
|
limit = result.other_args.get("limit", {})
|
||||||
if limit:
|
if limit:
|
||||||
limit = dict([i.split("=") for i in limit])
|
limit = dict([i.split("=") for i in limit])
|
||||||
limit["duration"] = time.time() - duration # 起始时间戳
|
limit["duration"] = time.time() - duration # 起始时间戳
|
||||||
limit["rank"] = result.other_args.get("rank", 20)
|
limit["rank"] = result.other_args.get("rank", 20)
|
||||||
|
|
||||||
img = await get_stat_rank_image(rank_type=rank_type, limit=limit, ulang=ulang)
|
img = await get_stat_rank_image(rank_type=rank_type, limit=limit, ulang=ulang)
|
||||||
await stat_msg.send(UniMessage.image(raw=img))
|
await stat_msg.send(UniMessage.image(raw=img))
|
@ -1,92 +1,92 @@
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from nonebot import require
|
from nonebot import require
|
||||||
from nonebot.message import event_postprocessor
|
from nonebot.message import event_postprocessor
|
||||||
|
|
||||||
from src.utils.base.data import Database, LiteModel
|
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 v11, v12, satori
|
||||||
|
|
||||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||||
|
|
||||||
from .common import MessageEventModel, msg_db
|
from .common import MessageEventModel, msg_db
|
||||||
from src.utils import event as event_utils
|
from src.utils import event as event_utils
|
||||||
|
|
||||||
require("nonebot_plugin_alconna")
|
require("nonebot_plugin_alconna")
|
||||||
|
|
||||||
|
|
||||||
async def general_event_monitor(bot: T_Bot, event: T_MessageEvent):
|
async def general_event_monitor(bot: T_Bot, event: T_MessageEvent):
|
||||||
pass
|
pass
|
||||||
# if isinstance(bot, satori.Bot):
|
# if isinstance(bot, satori.Bot):
|
||||||
# print("POST PROCESS SATORI EVENT")
|
# print("POST PROCESS SATORI EVENT")
|
||||||
# return await satori_event_monitor(bot, event)
|
# return await satori_event_monitor(bot, event)
|
||||||
# elif isinstance(bot, v11.Bot):
|
# elif isinstance(bot, v11.Bot):
|
||||||
# print("POST PROCESS V11 EVENT")
|
# print("POST PROCESS V11 EVENT")
|
||||||
# return await onebot_v11_event_monitor(bot, event)
|
# return await onebot_v11_event_monitor(bot, event)
|
||||||
|
|
||||||
|
|
||||||
@event_postprocessor
|
@event_postprocessor
|
||||||
async def onebot_v11_event_monitor(bot: v11.Bot, event: v11.MessageEvent):
|
async def onebot_v11_event_monitor(bot: v11.Bot, event: v11.MessageEvent):
|
||||||
if event.message_type == "group":
|
if event.message_type == "group":
|
||||||
event: v11.GroupMessageEvent
|
event: v11.GroupMessageEvent
|
||||||
group_id = str(event.group_id)
|
group_id = str(event.group_id)
|
||||||
else:
|
else:
|
||||||
group_id = ""
|
group_id = ""
|
||||||
mem = MessageEventModel(
|
mem = MessageEventModel(
|
||||||
time=int(time.time()),
|
time=int(time.time()),
|
||||||
bot_id=bot.self_id,
|
bot_id=bot.self_id,
|
||||||
adapter="onebot.v11",
|
adapter="onebot.v11",
|
||||||
group_id=group_id,
|
group_id=group_id,
|
||||||
user_id=str(event.user_id),
|
user_id=str(event.user_id),
|
||||||
|
|
||||||
message_id=str(event.message_id),
|
message_id=str(event.message_id),
|
||||||
|
|
||||||
message=[ms.__dict__ for ms in event.message],
|
message=[ms.__dict__ for ms in event.message],
|
||||||
message_text=event.raw_message,
|
message_text=event.raw_message,
|
||||||
message_type=event.message_type,
|
message_type=event.message_type,
|
||||||
)
|
)
|
||||||
msg_db.save(mem)
|
msg_db.save(mem)
|
||||||
|
|
||||||
|
|
||||||
@event_postprocessor
|
@event_postprocessor
|
||||||
async def onebot_v12_event_monitor(bot: v12.Bot, event: v12.MessageEvent):
|
async def onebot_v12_event_monitor(bot: v12.Bot, event: v12.MessageEvent):
|
||||||
if event.message_type == "group":
|
if event.message_type == "group":
|
||||||
event: v12.GroupMessageEvent
|
event: v12.GroupMessageEvent
|
||||||
group_id = str(event.group_id)
|
group_id = str(event.group_id)
|
||||||
else:
|
else:
|
||||||
group_id = ""
|
group_id = ""
|
||||||
mem = MessageEventModel(
|
mem = MessageEventModel(
|
||||||
time=int(time.time()),
|
time=int(time.time()),
|
||||||
bot_id=bot.self_id,
|
bot_id=bot.self_id,
|
||||||
adapter="onebot.v12",
|
adapter="onebot.v12",
|
||||||
group_id=group_id,
|
group_id=group_id,
|
||||||
user_id=str(event.user_id),
|
user_id=str(event.user_id),
|
||||||
|
|
||||||
message_id=[ms.__dict__ for ms in event.message],
|
message_id=[ms.__dict__ for ms in event.message],
|
||||||
|
|
||||||
message=event.message,
|
message=event.message,
|
||||||
message_text=event.raw_message,
|
message_text=event.raw_message,
|
||||||
message_type=event.message_type,
|
message_type=event.message_type,
|
||||||
)
|
)
|
||||||
msg_db.save(mem)
|
msg_db.save(mem)
|
||||||
|
|
||||||
|
|
||||||
@event_postprocessor
|
@event_postprocessor
|
||||||
async def satori_event_monitor(bot: satori.Bot, event: satori.MessageEvent):
|
async def satori_event_monitor(bot: satori.Bot, event: satori.MessageEvent):
|
||||||
if event.guild is not None:
|
if event.guild is not None:
|
||||||
event: satori.MessageEvent
|
event: satori.MessageEvent
|
||||||
group_id = str(event.guild.id)
|
group_id = str(event.guild.id)
|
||||||
else:
|
else:
|
||||||
group_id = ""
|
group_id = ""
|
||||||
|
|
||||||
mem = MessageEventModel(
|
mem = MessageEventModel(
|
||||||
time=int(time.time()),
|
time=int(time.time()),
|
||||||
bot_id=bot.self_id,
|
bot_id=bot.self_id,
|
||||||
adapter="satori",
|
adapter="satori",
|
||||||
group_id=group_id,
|
group_id=group_id,
|
||||||
user_id=str(event.user.id),
|
user_id=str(event.user.id),
|
||||||
message_id=[ms.__str__() for ms in event.message],
|
message_id=[ms.__str__() for ms in event.message],
|
||||||
message=event.message,
|
message=event.message,
|
||||||
message_text=event.message.content,
|
message_text=event.message.content,
|
||||||
message_type=event_utils.get_message_type(event),
|
message_type=event_utils.get_message_type(event),
|
||||||
)
|
)
|
||||||
msg_db.save(mem)
|
msg_db.save(mem)
|
@ -1,21 +1,21 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2022 hemengyang
|
Copyright (c) 2022 hemengyang
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
in the Software without restriction, including without limitation the rights
|
in the Software without restriction, including without limitation the rights
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
furnished to do so, subject to the following conditions:
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
The above copyright notice and this permission notice shall be included in all
|
||||||
copies or substantial portions of the Software.
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
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
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
@ -1,107 +1,107 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
import contextlib
|
import contextlib
|
||||||
import re
|
import re
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from random import choice
|
from random import choice
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import jieba
|
import jieba
|
||||||
import jieba.analyse
|
import jieba.analyse
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from emoji import replace_emoji
|
from emoji import replace_emoji
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from wordcloud import WordCloud
|
from wordcloud import WordCloud
|
||||||
|
|
||||||
from .config import global_config, plugin_config
|
from .config import global_config, plugin_config
|
||||||
|
|
||||||
|
|
||||||
def pre_precess(msg: str) -> str:
|
def pre_precess(msg: str) -> str:
|
||||||
"""对消息进行预处理"""
|
"""对消息进行预处理"""
|
||||||
# 去除网址
|
# 去除网址
|
||||||
# https://stackoverflow.com/a/17773849/9212748
|
# https://stackoverflow.com/a/17773849/9212748
|
||||||
url_regex = re.compile(
|
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"(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,})"
|
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)
|
msg = url_regex.sub("", msg)
|
||||||
|
|
||||||
# 去除 \u200b
|
# 去除 \u200b
|
||||||
msg = re.sub(r"\u200b", "", msg)
|
msg = re.sub(r"\u200b", "", msg)
|
||||||
|
|
||||||
# 去除 emoji
|
# 去除 emoji
|
||||||
# https://github.com/carpedm20/emoji
|
# https://github.com/carpedm20/emoji
|
||||||
msg = replace_emoji(msg)
|
msg = replace_emoji(msg)
|
||||||
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|
||||||
def analyse_message(msg: str) -> dict[str, float]:
|
def analyse_message(msg: str) -> dict[str, float]:
|
||||||
"""分析消息
|
"""分析消息
|
||||||
|
|
||||||
分词,并统计词频
|
分词,并统计词频
|
||||||
"""
|
"""
|
||||||
# 设置停用词表
|
# 设置停用词表
|
||||||
if plugin_config.wordcloud_stopwords_path:
|
if plugin_config.wordcloud_stopwords_path:
|
||||||
jieba.analyse.set_stop_words(plugin_config.wordcloud_stopwords_path)
|
jieba.analyse.set_stop_words(plugin_config.wordcloud_stopwords_path)
|
||||||
# 加载用户词典
|
# 加载用户词典
|
||||||
if plugin_config.wordcloud_userdict_path:
|
if plugin_config.wordcloud_userdict_path:
|
||||||
jieba.load_userdict(str(plugin_config.wordcloud_userdict_path))
|
jieba.load_userdict(str(plugin_config.wordcloud_userdict_path))
|
||||||
# 基于 TF-IDF 算法的关键词抽取
|
# 基于 TF-IDF 算法的关键词抽取
|
||||||
# 返回所有关键词,因为设置了数量其实也只是 tags[:topK],不如交给词云库处理
|
# 返回所有关键词,因为设置了数量其实也只是 tags[:topK],不如交给词云库处理
|
||||||
words = jieba.analyse.extract_tags(msg, topK=0, withWeight=True)
|
words = jieba.analyse.extract_tags(msg, topK=0, withWeight=True)
|
||||||
return dict(words)
|
return dict(words)
|
||||||
|
|
||||||
|
|
||||||
def get_mask(key: str):
|
def get_mask(key: str):
|
||||||
"""获取 mask"""
|
"""获取 mask"""
|
||||||
mask_path = plugin_config.get_mask_path(key)
|
mask_path = plugin_config.get_mask_path(key)
|
||||||
if mask_path.exists():
|
if mask_path.exists():
|
||||||
return np.array(Image.open(mask_path))
|
return np.array(Image.open(mask_path))
|
||||||
# 如果指定 mask 文件不存在,则尝试默认 mask
|
# 如果指定 mask 文件不存在,则尝试默认 mask
|
||||||
default_mask_path = plugin_config.get_mask_path()
|
default_mask_path = plugin_config.get_mask_path()
|
||||||
if default_mask_path.exists():
|
if default_mask_path.exists():
|
||||||
return np.array(Image.open(default_mask_path))
|
return np.array(Image.open(default_mask_path))
|
||||||
|
|
||||||
|
|
||||||
def _get_wordcloud(messages: list[str], mask_key: str) -> Optional[bytes]:
|
def _get_wordcloud(messages: list[str], mask_key: str) -> Optional[bytes]:
|
||||||
# 过滤掉命令
|
# 过滤掉命令
|
||||||
command_start = tuple(i for i in global_config.command_start if i)
|
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 = " ".join(m for m in messages if not m.startswith(command_start))
|
||||||
# 预处理
|
# 预处理
|
||||||
message = pre_precess(message)
|
message = pre_precess(message)
|
||||||
# 分析消息。分词,并统计词频
|
# 分析消息。分词,并统计词频
|
||||||
frequency = analyse_message(message)
|
frequency = analyse_message(message)
|
||||||
# 词云参数
|
# 词云参数
|
||||||
wordcloud_options = {}
|
wordcloud_options = {}
|
||||||
wordcloud_options.update(plugin_config.wordcloud_options)
|
wordcloud_options.update(plugin_config.wordcloud_options)
|
||||||
wordcloud_options.setdefault("font_path", str(plugin_config.wordcloud_font_path))
|
wordcloud_options.setdefault("font_path", str(plugin_config.wordcloud_font_path))
|
||||||
wordcloud_options.setdefault("width", plugin_config.wordcloud_width)
|
wordcloud_options.setdefault("width", plugin_config.wordcloud_width)
|
||||||
wordcloud_options.setdefault("height", plugin_config.wordcloud_height)
|
wordcloud_options.setdefault("height", plugin_config.wordcloud_height)
|
||||||
wordcloud_options.setdefault(
|
wordcloud_options.setdefault(
|
||||||
"background_color", plugin_config.wordcloud_background_color
|
"background_color", plugin_config.wordcloud_background_color
|
||||||
)
|
)
|
||||||
# 如果 colormap 是列表,则随机选择一个
|
# 如果 colormap 是列表,则随机选择一个
|
||||||
colormap = (
|
colormap = (
|
||||||
plugin_config.wordcloud_colormap
|
plugin_config.wordcloud_colormap
|
||||||
if isinstance(plugin_config.wordcloud_colormap, str)
|
if isinstance(plugin_config.wordcloud_colormap, str)
|
||||||
else choice(plugin_config.wordcloud_colormap)
|
else choice(plugin_config.wordcloud_colormap)
|
||||||
)
|
)
|
||||||
wordcloud_options.setdefault("colormap", colormap)
|
wordcloud_options.setdefault("colormap", colormap)
|
||||||
wordcloud_options.setdefault("mask", get_mask(mask_key))
|
wordcloud_options.setdefault("mask", get_mask(mask_key))
|
||||||
with contextlib.suppress(ValueError):
|
with contextlib.suppress(ValueError):
|
||||||
wordcloud = WordCloud(**wordcloud_options)
|
wordcloud = WordCloud(**wordcloud_options)
|
||||||
image = wordcloud.generate_from_frequencies(frequency).to_image()
|
image = wordcloud.generate_from_frequencies(frequency).to_image()
|
||||||
image_bytes = BytesIO()
|
image_bytes = BytesIO()
|
||||||
image.save(image_bytes, format="PNG")
|
image.save(image_bytes, format="PNG")
|
||||||
return image_bytes.getvalue()
|
return image_bytes.getvalue()
|
||||||
|
|
||||||
|
|
||||||
async def get_wordcloud(messages: list[str], mask_key: str) -> Optional[bytes]:
|
async def get_wordcloud(messages: list[str], mask_key: str) -> Optional[bytes]:
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
pfunc = partial(_get_wordcloud, messages, mask_key)
|
pfunc = partial(_get_wordcloud, messages, mask_key)
|
||||||
# 虽然不知道具体是哪里泄漏了,但是通过每次关闭线程池可以避免这个问题
|
# 虽然不知道具体是哪里泄漏了,但是通过每次关闭线程池可以避免这个问题
|
||||||
# https://github.com/he0119/nonebot-plugin-wordcloud/issues/99
|
# https://github.com/he0119/nonebot-plugin-wordcloud/issues/99
|
||||||
with concurrent.futures.ThreadPoolExecutor() as pool:
|
with concurrent.futures.ThreadPoolExecutor() as pool:
|
||||||
return await loop.run_in_executor(pool, pfunc)
|
return await loop.run_in_executor(pool, pfunc)
|
@ -1,24 +1,24 @@
|
|||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
from .status import *
|
from .status import *
|
||||||
|
|
||||||
__author__ = "snowykami"
|
__author__ = "snowykami"
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="状态查看器",
|
name="状态查看器",
|
||||||
description="",
|
description="",
|
||||||
usage=(
|
usage=(
|
||||||
"MARKDOWN### 状态查看器\n"
|
"MARKDOWN### 状态查看器\n"
|
||||||
"查看机器人的状态\n"
|
"查看机器人的状态\n"
|
||||||
"### 用法\n"
|
"### 用法\n"
|
||||||
"- `/status` 查看基本情况\n"
|
"- `/status` 查看基本情况\n"
|
||||||
"- `/status memory` 查看内存使用情况\n"
|
"- `/status memory` 查看内存使用情况\n"
|
||||||
"- `/status process` 查看进程情况\n"
|
"- `/status process` 查看进程情况\n"
|
||||||
),
|
),
|
||||||
type="application",
|
type="application",
|
||||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||||
extra={
|
extra={
|
||||||
"liteyuki": True,
|
"liteyuki": True,
|
||||||
"toggleable" : False,
|
"toggleable" : False,
|
||||||
"default_enable" : True,
|
"default_enable" : True,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -1,292 +1,292 @@
|
|||||||
import platform
|
import platform
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import nonebot
|
import nonebot
|
||||||
import psutil
|
import psutil
|
||||||
from cpuinfo import cpuinfo
|
from cpuinfo import cpuinfo
|
||||||
from nonebot import require
|
from nonebot import require
|
||||||
from nonebot.adapters import satori
|
from nonebot.adapters import satori
|
||||||
|
|
||||||
from src.utils import __NAME__, __VERSION__
|
from src.utils import __NAME__, __VERSION__
|
||||||
from src.utils.base.config import get_config
|
from src.utils.base.config import get_config
|
||||||
from src.utils.base.data_manager import TempConfig, common_db
|
from src.utils.base.data_manager import TempConfig, common_db
|
||||||
from src.utils.base.language import Language
|
from src.utils.base.language import Language
|
||||||
from src.utils.base.resource import get_loaded_resource_packs, get_path
|
from src.utils.base.resource import get_loaded_resource_packs, get_path
|
||||||
from src.utils.message.html_tool import template2image
|
from src.utils.message.html_tool import template2image
|
||||||
from src.utils import satori_utils
|
from src.utils import satori_utils
|
||||||
from .counter_for_satori import satori_counter
|
from .counter_for_satori import satori_counter
|
||||||
from git import Repo
|
from git import Repo
|
||||||
|
|
||||||
# require("nonebot_plugin_apscheduler")
|
# require("nonebot_plugin_apscheduler")
|
||||||
# from nonebot_plugin_apscheduler import scheduler
|
# from nonebot_plugin_apscheduler import scheduler
|
||||||
|
|
||||||
commit_hash = Repo(".").head.commit.hexsha
|
commit_hash = Repo(".").head.commit.hexsha
|
||||||
|
|
||||||
protocol_names = {
|
protocol_names = {
|
||||||
0: "iPad",
|
0: "iPad",
|
||||||
1: "Android Phone",
|
1: "Android Phone",
|
||||||
2: "Android Watch",
|
2: "Android Watch",
|
||||||
3: "Mac",
|
3: "Mac",
|
||||||
5: "iPad",
|
5: "iPad",
|
||||||
6: "Android Pad",
|
6: "Android Pad",
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Universal Interface
|
Universal Interface
|
||||||
data
|
data
|
||||||
- bot
|
- bot
|
||||||
- name: str
|
- name: str
|
||||||
icon: str
|
icon: str
|
||||||
id: int
|
id: int
|
||||||
protocol_name: str
|
protocol_name: str
|
||||||
groups: int
|
groups: int
|
||||||
friends: int
|
friends: int
|
||||||
message_sent: int
|
message_sent: int
|
||||||
message_received: int
|
message_received: int
|
||||||
app_name: str
|
app_name: str
|
||||||
- hardware
|
- hardware
|
||||||
- cpu
|
- cpu
|
||||||
- percent: float
|
- percent: float
|
||||||
- name: str
|
- name: str
|
||||||
- mem
|
- mem
|
||||||
- percent: float
|
- percent: float
|
||||||
- total: int
|
- total: int
|
||||||
- used: int
|
- used: int
|
||||||
- free: int
|
- free: int
|
||||||
- swap
|
- swap
|
||||||
- percent: float
|
- percent: float
|
||||||
- total: int
|
- total: int
|
||||||
- used: int
|
- used: int
|
||||||
- free: int
|
- free: int
|
||||||
- disk: list
|
- disk: list
|
||||||
- name: str
|
- name: str
|
||||||
- percent: float
|
- percent: float
|
||||||
- total: int
|
- total: int
|
||||||
"""
|
"""
|
||||||
# status_card_cache = {} # lang -> bytes
|
# status_card_cache = {} # lang -> bytes
|
||||||
|
|
||||||
|
|
||||||
# 60s刷新一次
|
# 60s刷新一次
|
||||||
# 之前写的什么鬼玩意,这么重要的功能这样写???
|
# 之前写的什么鬼玩意,这么重要的功能这样写???
|
||||||
# @scheduler.scheduled_job("cron", second="*/40")
|
# @scheduler.scheduled_job("cron", second="*/40")
|
||||||
# async def refresh_status_card():
|
# async def refresh_status_card():
|
||||||
# nonebot.logger.debug("Refreshing status card cache.")
|
# nonebot.logger.debug("Refreshing status card cache.")
|
||||||
# global status_card_cache
|
# global status_card_cache
|
||||||
# status_card_cache = {}
|
# status_card_cache = {}
|
||||||
# bot_data = await get_bots_data()
|
# bot_data = await get_bots_data()
|
||||||
# hardware_data = await get_hardware_data()
|
# hardware_data = await get_hardware_data()
|
||||||
# liteyuki_data = await get_liteyuki_data()
|
# liteyuki_data = await get_liteyuki_data()
|
||||||
# for lang in status_card_cache.keys():
|
# for lang in status_card_cache.keys():
|
||||||
# status_card_cache[lang] = await generate_status_card(
|
# status_card_cache[lang] = await generate_status_card(
|
||||||
# bot_data,
|
# bot_data,
|
||||||
# hardware_data,
|
# hardware_data,
|
||||||
# liteyuki_data,
|
# liteyuki_data,
|
||||||
# lang=lang,
|
# lang=lang,
|
||||||
# use_cache=False
|
# use_cache=False
|
||||||
# )
|
# )
|
||||||
|
|
||||||
|
|
||||||
# 获取状态卡片
|
# 获取状态卡片
|
||||||
# bot_id 参数已经是bot参数的一部分了,不需要保留,但为了“兼容性”……
|
# bot_id 参数已经是bot参数的一部分了,不需要保留,但为了“兼容性”……
|
||||||
async def generate_status_card(
|
async def generate_status_card(
|
||||||
bot: dict,
|
bot: dict,
|
||||||
hardware: dict,
|
hardware: dict,
|
||||||
liteyuki: dict,
|
liteyuki: dict,
|
||||||
lang="zh-CN",
|
lang="zh-CN",
|
||||||
bot_id="0",
|
bot_id="0",
|
||||||
) -> bytes:
|
) -> bytes:
|
||||||
return await template2image(
|
return await template2image(
|
||||||
get_path("templates/status.html", abs_path=True),
|
get_path("templates/status.html", abs_path=True),
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"bot": bot,
|
"bot": bot,
|
||||||
"hardware": hardware,
|
"hardware": hardware,
|
||||||
"liteyuki": liteyuki,
|
"liteyuki": liteyuki,
|
||||||
"localization": get_local_data(lang),
|
"localization": get_local_data(lang),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_local_data(lang_code) -> dict:
|
def get_local_data(lang_code) -> dict:
|
||||||
lang = Language(lang_code)
|
lang = Language(lang_code)
|
||||||
return {
|
return {
|
||||||
"friends": lang.get("status.friends"),
|
"friends": lang.get("status.friends"),
|
||||||
"groups": lang.get("status.groups"),
|
"groups": lang.get("status.groups"),
|
||||||
"plugins": lang.get("status.plugins"),
|
"plugins": lang.get("status.plugins"),
|
||||||
"bots": lang.get("status.bots"),
|
"bots": lang.get("status.bots"),
|
||||||
"message_sent": lang.get("status.message_sent"),
|
"message_sent": lang.get("status.message_sent"),
|
||||||
"message_received": lang.get("status.message_received"),
|
"message_received": lang.get("status.message_received"),
|
||||||
"cpu": lang.get("status.cpu"),
|
"cpu": lang.get("status.cpu"),
|
||||||
"memory": lang.get("status.memory"),
|
"memory": lang.get("status.memory"),
|
||||||
"swap": lang.get("status.swap"),
|
"swap": lang.get("status.swap"),
|
||||||
"disk": lang.get("status.disk"),
|
"disk": lang.get("status.disk"),
|
||||||
"usage": lang.get("status.usage"),
|
"usage": lang.get("status.usage"),
|
||||||
"total": lang.get("status.total"),
|
"total": lang.get("status.total"),
|
||||||
"used": lang.get("status.used"),
|
"used": lang.get("status.used"),
|
||||||
"free": lang.get("status.free"),
|
"free": lang.get("status.free"),
|
||||||
"days": lang.get("status.days"),
|
"days": lang.get("status.days"),
|
||||||
"hours": lang.get("status.hours"),
|
"hours": lang.get("status.hours"),
|
||||||
"minutes": lang.get("status.minutes"),
|
"minutes": lang.get("status.minutes"),
|
||||||
"seconds": lang.get("status.seconds"),
|
"seconds": lang.get("status.seconds"),
|
||||||
"runtime": lang.get("status.runtime"),
|
"runtime": lang.get("status.runtime"),
|
||||||
"threads": lang.get("status.threads"),
|
"threads": lang.get("status.threads"),
|
||||||
"cores": lang.get("status.cores"),
|
"cores": lang.get("status.cores"),
|
||||||
"process": lang.get("status.process"),
|
"process": lang.get("status.process"),
|
||||||
"resources": lang.get("status.resources"),
|
"resources": lang.get("status.resources"),
|
||||||
"description": lang.get("status.description"),
|
"description": lang.get("status.description"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def get_bots_data(self_id: str = "0") -> dict:
|
async def get_bots_data(self_id: str = "0") -> dict:
|
||||||
"""获取当前所有机器人数据
|
"""获取当前所有机器人数据
|
||||||
Returns:
|
Returns:
|
||||||
"""
|
"""
|
||||||
result = {
|
result = {
|
||||||
"self_id": self_id,
|
"self_id": self_id,
|
||||||
"bots": [],
|
"bots": [],
|
||||||
}
|
}
|
||||||
for bot_id, bot in nonebot.get_bots().items():
|
for bot_id, bot in nonebot.get_bots().items():
|
||||||
groups = 0
|
groups = 0
|
||||||
friends = 0
|
friends = 0
|
||||||
status = {}
|
status = {}
|
||||||
bot_name = bot_id
|
bot_name = bot_id
|
||||||
version_info = {}
|
version_info = {}
|
||||||
if isinstance(bot, satori.Bot):
|
if isinstance(bot, satori.Bot):
|
||||||
try:
|
try:
|
||||||
bot_name = (await satori_utils.user_infos.get(bot.self_id)).name
|
bot_name = (await satori_utils.user_infos.get(bot.self_id)).name
|
||||||
groups = str(await satori_utils.count_groups(bot))
|
groups = str(await satori_utils.count_groups(bot))
|
||||||
friends = str(await satori_utils.count_friends(bot))
|
friends = str(await satori_utils.count_friends(bot))
|
||||||
status = {}
|
status = {}
|
||||||
version_info = await bot.get_version_info()
|
version_info = await bot.get_version_info()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
# API fetch
|
# API fetch
|
||||||
bot_name = (await bot.get_login_info())["nickname"]
|
bot_name = (await bot.get_login_info())["nickname"]
|
||||||
groups = len(await bot.get_group_list())
|
groups = len(await bot.get_group_list())
|
||||||
friends = len(await bot.get_friend_list())
|
friends = len(await bot.get_friend_list())
|
||||||
status = await bot.get_status()
|
status = await bot.get_status()
|
||||||
version_info = await bot.get_version_info()
|
version_info = await bot.get_version_info()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
statistics = status.get("stat", {})
|
statistics = status.get("stat", {})
|
||||||
app_name = version_info.get("app_name", "UnknownImplementation")
|
app_name = version_info.get("app_name", "UnknownImplementation")
|
||||||
if app_name in ["Lagrange.OneBot", "LLOneBot", "Shamrock", "NapCat.Onebot"]:
|
if app_name in ["Lagrange.OneBot", "LLOneBot", "Shamrock", "NapCat.Onebot"]:
|
||||||
icon = f"https://q.qlogo.cn/g?b=qq&nk={bot_id}&s=640"
|
icon = f"https://q.qlogo.cn/g?b=qq&nk={bot_id}&s=640"
|
||||||
elif isinstance(bot, satori.Bot):
|
elif isinstance(bot, satori.Bot):
|
||||||
app_name = "Satori"
|
app_name = "Satori"
|
||||||
icon = (await bot.login_get()).user.avatar
|
icon = (await bot.login_get()).user.avatar
|
||||||
else:
|
else:
|
||||||
icon = None
|
icon = None
|
||||||
bot_data = {
|
bot_data = {
|
||||||
"name": bot_name,
|
"name": bot_name,
|
||||||
"icon": icon,
|
"icon": icon,
|
||||||
"id": bot_id,
|
"id": bot_id,
|
||||||
"protocol_name": protocol_names.get(
|
"protocol_name": protocol_names.get(
|
||||||
version_info.get("protocol_name"), "Online"
|
version_info.get("protocol_name"), "Online"
|
||||||
),
|
),
|
||||||
"groups": groups,
|
"groups": groups,
|
||||||
"friends": friends,
|
"friends": friends,
|
||||||
"message_sent": (
|
"message_sent": (
|
||||||
satori_counter.msg_sent
|
satori_counter.msg_sent
|
||||||
if isinstance(bot, satori.Bot)
|
if isinstance(bot, satori.Bot)
|
||||||
else statistics.get("message_sent", 0)
|
else statistics.get("message_sent", 0)
|
||||||
),
|
),
|
||||||
"message_received": (
|
"message_received": (
|
||||||
satori_counter.msg_received
|
satori_counter.msg_received
|
||||||
if isinstance(bot, satori.Bot)
|
if isinstance(bot, satori.Bot)
|
||||||
else statistics.get("message_received", 0)
|
else statistics.get("message_received", 0)
|
||||||
),
|
),
|
||||||
"app_name": app_name,
|
"app_name": app_name,
|
||||||
}
|
}
|
||||||
result["bots"].append(bot_data)
|
result["bots"].append(bot_data)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
async def get_hardware_data() -> dict:
|
async def get_hardware_data() -> dict:
|
||||||
mem = psutil.virtual_memory()
|
mem = psutil.virtual_memory()
|
||||||
all_processes = psutil.Process().children(recursive=True)
|
all_processes = psutil.Process().children(recursive=True)
|
||||||
all_processes.append(psutil.Process())
|
all_processes.append(psutil.Process())
|
||||||
|
|
||||||
mem_used_process = 0
|
mem_used_process = 0
|
||||||
process_mem = {}
|
process_mem = {}
|
||||||
for process in all_processes:
|
for process in all_processes:
|
||||||
try:
|
try:
|
||||||
ps_name = process.name().replace(".exe", "")
|
ps_name = process.name().replace(".exe", "")
|
||||||
if ps_name not in process_mem:
|
if ps_name not in process_mem:
|
||||||
process_mem[ps_name] = 0
|
process_mem[ps_name] = 0
|
||||||
process_mem[ps_name] += process.memory_info().rss
|
process_mem[ps_name] += process.memory_info().rss
|
||||||
mem_used_process += process.memory_info().rss
|
mem_used_process += process.memory_info().rss
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
swap = psutil.swap_memory()
|
swap = psutil.swap_memory()
|
||||||
cpu_brand_raw = cpuinfo.get_cpu_info().get("brand_raw", "Unknown")
|
cpu_brand_raw = cpuinfo.get_cpu_info().get("brand_raw", "Unknown")
|
||||||
if "AMD" in cpu_brand_raw:
|
if "AMD" in cpu_brand_raw:
|
||||||
brand = "AMD"
|
brand = "AMD"
|
||||||
elif "Intel" in cpu_brand_raw:
|
elif "Intel" in cpu_brand_raw:
|
||||||
brand = "Intel"
|
brand = "Intel"
|
||||||
else:
|
else:
|
||||||
brand = "Unknown"
|
brand = "Unknown"
|
||||||
result = {
|
result = {
|
||||||
"cpu": {
|
"cpu": {
|
||||||
"percent": psutil.cpu_percent(),
|
"percent": psutil.cpu_percent(),
|
||||||
"name": f"{brand} {cpuinfo.get_cpu_info().get('arch', 'Unknown')}",
|
"name": f"{brand} {cpuinfo.get_cpu_info().get('arch', 'Unknown')}",
|
||||||
"cores": psutil.cpu_count(logical=False),
|
"cores": psutil.cpu_count(logical=False),
|
||||||
"threads": psutil.cpu_count(logical=True),
|
"threads": psutil.cpu_count(logical=True),
|
||||||
"freq": psutil.cpu_freq().current, # MHz
|
"freq": psutil.cpu_freq().current, # MHz
|
||||||
},
|
},
|
||||||
"memory": {
|
"memory": {
|
||||||
"percent": mem.percent,
|
"percent": mem.percent,
|
||||||
"total": mem.total,
|
"total": mem.total,
|
||||||
"used": mem.used,
|
"used": mem.used,
|
||||||
"free": mem.free,
|
"free": mem.free,
|
||||||
"usedProcess": mem_used_process,
|
"usedProcess": mem_used_process,
|
||||||
},
|
},
|
||||||
"swap": {
|
"swap": {
|
||||||
"percent": swap.percent,
|
"percent": swap.percent,
|
||||||
"total": swap.total,
|
"total": swap.total,
|
||||||
"used": swap.used,
|
"used": swap.used,
|
||||||
"free": swap.free,
|
"free": swap.free,
|
||||||
},
|
},
|
||||||
"disk": [],
|
"disk": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
for disk in psutil.disk_partitions(all=True):
|
for disk in psutil.disk_partitions(all=True):
|
||||||
try:
|
try:
|
||||||
disk_usage = psutil.disk_usage(disk.mountpoint)
|
disk_usage = psutil.disk_usage(disk.mountpoint)
|
||||||
if disk_usage.total == 0:
|
if disk_usage.total == 0:
|
||||||
continue # 虚拟磁盘
|
continue # 虚拟磁盘
|
||||||
result["disk"].append(
|
result["disk"].append(
|
||||||
{
|
{
|
||||||
"name": disk.mountpoint,
|
"name": disk.mountpoint,
|
||||||
"percent": disk_usage.percent,
|
"percent": disk_usage.percent,
|
||||||
"total": disk_usage.total,
|
"total": disk_usage.total,
|
||||||
"used": disk_usage.used,
|
"used": disk_usage.used,
|
||||||
"free": disk_usage.free,
|
"free": disk_usage.free,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
async def get_liteyuki_data() -> dict:
|
async def get_liteyuki_data() -> dict:
|
||||||
temp_data: TempConfig = common_db.where_one(TempConfig(), default=TempConfig())
|
temp_data: TempConfig = common_db.where_one(TempConfig(), default=TempConfig())
|
||||||
result = {
|
result = {
|
||||||
"name": list(get_config("nickname", [__NAME__]))[0],
|
"name": list(get_config("nickname", [__NAME__]))[0],
|
||||||
"version": f"{__VERSION__}{'-' + commit_hash[:7] if (commit_hash and len(commit_hash) > 8) else ''}",
|
"version": f"{__VERSION__}{'-' + commit_hash[:7] if (commit_hash and len(commit_hash) > 8) else ''}",
|
||||||
"plugins": len(nonebot.get_loaded_plugins()),
|
"plugins": len(nonebot.get_loaded_plugins()),
|
||||||
"resources": len(get_loaded_resource_packs()),
|
"resources": len(get_loaded_resource_packs()),
|
||||||
"nonebot": f"{nonebot.__version__}",
|
"nonebot": f"{nonebot.__version__}",
|
||||||
"python": f"{platform.python_implementation()} {platform.python_version()}",
|
"python": f"{platform.python_implementation()} {platform.python_version()}",
|
||||||
"system": f"{platform.system()} {platform.release()}",
|
"system": f"{platform.system()} {platform.release()}",
|
||||||
"runtime": time.time()
|
"runtime": time.time()
|
||||||
- temp_data.data.get("start_time", time.time()), # 运行时间秒数
|
- temp_data.data.get("start_time", time.time()), # 运行时间秒数
|
||||||
"bots": len(nonebot.get_bots()),
|
"bots": len(nonebot.get_bots()),
|
||||||
}
|
}
|
||||||
return result
|
return result
|
@ -1,10 +1,10 @@
|
|||||||
class SatoriCounter:
|
class SatoriCounter:
|
||||||
msg_sent: int
|
msg_sent: int
|
||||||
msg_received: int
|
msg_received: int
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.msg_sent = 0
|
self.msg_sent = 0
|
||||||
self.msg_received = 0
|
self.msg_received = 0
|
||||||
|
|
||||||
|
|
||||||
satori_counter = SatoriCounter()
|
satori_counter = SatoriCounter()
|
@ -1,60 +1,60 @@
|
|||||||
from src.utils import event as event_utils
|
from src.utils import event as event_utils
|
||||||
from src.utils.base.language import get_user_lang
|
from src.utils.base.language import get_user_lang
|
||||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||||
from .api import *
|
from .api import *
|
||||||
|
|
||||||
require("nonebot_plugin_alconna")
|
require("nonebot_plugin_alconna")
|
||||||
from nonebot_plugin_alconna import on_alconna, Alconna, Subcommand, UniMessage
|
from nonebot_plugin_alconna import on_alconna, Alconna, Subcommand, UniMessage
|
||||||
|
|
||||||
status_alc = on_alconna(
|
status_alc = on_alconna(
|
||||||
aliases={"状态"},
|
aliases={"状态"},
|
||||||
command=Alconna(
|
command=Alconna(
|
||||||
"status",
|
"status",
|
||||||
Subcommand(
|
Subcommand(
|
||||||
"memory",
|
"memory",
|
||||||
alias={"mem", "m", "内存"},
|
alias={"mem", "m", "内存"},
|
||||||
),
|
),
|
||||||
Subcommand(
|
Subcommand(
|
||||||
"process",
|
"process",
|
||||||
alias={"proc", "p", "进程"},
|
alias={"proc", "p", "进程"},
|
||||||
),
|
),
|
||||||
Subcommand(
|
Subcommand(
|
||||||
"refresh",
|
"refresh",
|
||||||
alias={"refr", "r", "刷新"},
|
alias={"refr", "r", "刷新"},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
status_card_cache = {} # lang -> bytes
|
status_card_cache = {} # lang -> bytes
|
||||||
|
|
||||||
|
|
||||||
@status_alc.handle()
|
@status_alc.handle()
|
||||||
async def _(event: T_MessageEvent, bot: T_Bot):
|
async def _(event: T_MessageEvent, bot: T_Bot):
|
||||||
ulang = get_user_lang(event_utils.get_user_id(event))
|
ulang = get_user_lang(event_utils.get_user_id(event))
|
||||||
global status_card_cache
|
global status_card_cache
|
||||||
if ulang.lang_code not in status_card_cache.keys() or (
|
if ulang.lang_code not in status_card_cache.keys() or (
|
||||||
ulang.lang_code in status_card_cache.keys()
|
ulang.lang_code in status_card_cache.keys()
|
||||||
and time.time() - status_card_cache[ulang.lang_code][1] > 60
|
and time.time() - status_card_cache[ulang.lang_code][1] > 60
|
||||||
):
|
):
|
||||||
status_card_cache[ulang.lang_code] = (
|
status_card_cache[ulang.lang_code] = (
|
||||||
await generate_status_card(
|
await generate_status_card(
|
||||||
bot=await get_bots_data(),
|
bot=await get_bots_data(),
|
||||||
hardware=await get_hardware_data(),
|
hardware=await get_hardware_data(),
|
||||||
liteyuki=await get_liteyuki_data(),
|
liteyuki=await get_liteyuki_data(),
|
||||||
lang=ulang.lang_code,
|
lang=ulang.lang_code,
|
||||||
bot_id=bot.self_id,
|
bot_id=bot.self_id,
|
||||||
),
|
),
|
||||||
time.time(),
|
time.time(),
|
||||||
)
|
)
|
||||||
image = status_card_cache[ulang.lang_code][0]
|
image = status_card_cache[ulang.lang_code][0]
|
||||||
await status_alc.finish(UniMessage.image(raw=image))
|
await status_alc.finish(UniMessage.image(raw=image))
|
||||||
|
|
||||||
|
|
||||||
@status_alc.assign("memory")
|
@status_alc.assign("memory")
|
||||||
async def _():
|
async def _():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@status_alc.assign("process")
|
@status_alc.assign("process")
|
||||||
async def _():
|
async def _():
|
||||||
pass
|
pass
|
@ -1,17 +1,17 @@
|
|||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
from .api import *
|
from .api import *
|
||||||
|
|
||||||
__author__ = "snowykami"
|
__author__ = "snowykami"
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="联合黑名单(测试中...)",
|
name="联合黑名单(测试中...)",
|
||||||
description="",
|
description="",
|
||||||
usage="",
|
usage="",
|
||||||
type="application",
|
type="application",
|
||||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||||
extra={
|
extra={
|
||||||
"liteyuki": True,
|
"liteyuki": True,
|
||||||
"toggleable" : True,
|
"toggleable" : True,
|
||||||
"default_enable" : True,
|
"default_enable" : True,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -1,59 +1,59 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import httpx
|
import httpx
|
||||||
import nonebot
|
import nonebot
|
||||||
from nonebot import require
|
from nonebot import require
|
||||||
from nonebot.exception import IgnoredException
|
from nonebot.exception import IgnoredException
|
||||||
from nonebot.message import event_preprocessor
|
from nonebot.message import event_preprocessor
|
||||||
from nonebot_plugin_alconna.typings import Event
|
from nonebot_plugin_alconna.typings import Event
|
||||||
|
|
||||||
require("nonebot_plugin_apscheduler")
|
require("nonebot_plugin_apscheduler")
|
||||||
|
|
||||||
from nonebot_plugin_apscheduler import scheduler
|
from nonebot_plugin_apscheduler import scheduler
|
||||||
|
|
||||||
blacklist_data: dict[str, set[str]] = {}
|
blacklist_data: dict[str, set[str]] = {}
|
||||||
blacklist: set[str] = set()
|
blacklist: set[str] = set()
|
||||||
|
|
||||||
|
|
||||||
@scheduler.scheduled_job("interval", minutes=10, next_run_time=datetime.datetime.now())
|
@scheduler.scheduled_job("interval", minutes=10, next_run_time=datetime.datetime.now())
|
||||||
async def update_blacklist():
|
async def update_blacklist():
|
||||||
await request_for_blacklist()
|
await request_for_blacklist()
|
||||||
|
|
||||||
|
|
||||||
async def request_for_blacklist():
|
async def request_for_blacklist():
|
||||||
global blacklist
|
global blacklist
|
||||||
urls = [
|
urls = [
|
||||||
"https://cdn.liteyuki.icu/static/ubl/"
|
"https://cdn.liteyuki.icu/static/ubl/"
|
||||||
]
|
]
|
||||||
|
|
||||||
platforms = [
|
platforms = [
|
||||||
"qq"
|
"qq"
|
||||||
]
|
]
|
||||||
|
|
||||||
for plat in platforms:
|
for plat in platforms:
|
||||||
for url in urls:
|
for url in urls:
|
||||||
url += f"{plat}.txt"
|
url += f"{plat}.txt"
|
||||||
async with aiohttp.ClientSession() as client:
|
async with aiohttp.ClientSession() as client:
|
||||||
resp = await client.get(url)
|
resp = await client.get(url)
|
||||||
blacklist_data[plat] = set((await resp.text()).splitlines())
|
blacklist_data[plat] = set((await resp.text()).splitlines())
|
||||||
blacklist = get_uni_set()
|
blacklist = get_uni_set()
|
||||||
nonebot.logger.info("blacklists updated")
|
nonebot.logger.info("blacklists updated")
|
||||||
|
|
||||||
|
|
||||||
def get_uni_set() -> set:
|
def get_uni_set() -> set:
|
||||||
s = set()
|
s = set()
|
||||||
for new_set in blacklist_data.values():
|
for new_set in blacklist_data.values():
|
||||||
s.update(new_set)
|
s.update(new_set)
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
@event_preprocessor
|
@event_preprocessor
|
||||||
async def pre_handle(event: Event):
|
async def pre_handle(event: Event):
|
||||||
try:
|
try:
|
||||||
user_id = str(event.get_user_id())
|
user_id = str(event.get_user_id())
|
||||||
except:
|
except:
|
||||||
return
|
return
|
||||||
|
|
||||||
if user_id in get_uni_set():
|
if user_id in get_uni_set():
|
||||||
raise IgnoredException("UserId in blacklist")
|
raise IgnoredException("UserId in blacklist")
|
@ -1,16 +1,16 @@
|
|||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
|
|
||||||
from .profile_manager import *
|
from .profile_manager import *
|
||||||
|
|
||||||
__author__ = "snowykami"
|
__author__ = "snowykami"
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="轻雪用户管理",
|
name="轻雪用户管理",
|
||||||
description="用户管理插件",
|
description="用户管理插件",
|
||||||
usage="",
|
usage="",
|
||||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||||
extra={
|
extra={
|
||||||
"liteyuki" : True,
|
"liteyuki" : True,
|
||||||
"toggleable" : False,
|
"toggleable" : False,
|
||||||
"default_enable": True,
|
"default_enable": True,
|
||||||
}
|
}
|
||||||
)
|
)
|
@ -1,23 +1,23 @@
|
|||||||
representative_timezones_list = [
|
representative_timezones_list = [
|
||||||
"Etc/GMT+12", # 国际日期变更线西
|
"Etc/GMT+12", # 国际日期变更线西
|
||||||
"Pacific/Honolulu", # 夏威夷标准时间
|
"Pacific/Honolulu", # 夏威夷标准时间
|
||||||
"America/Anchorage", # 阿拉斯加标准时间
|
"America/Anchorage", # 阿拉斯加标准时间
|
||||||
"America/Los_Angeles", # 美国太平洋标准时间
|
"America/Los_Angeles", # 美国太平洋标准时间
|
||||||
"America/Denver", # 美国山地标准时间
|
"America/Denver", # 美国山地标准时间
|
||||||
"America/Chicago", # 美国中部标准时间
|
"America/Chicago", # 美国中部标准时间
|
||||||
"America/New_York", # 美国东部标准时间
|
"America/New_York", # 美国东部标准时间
|
||||||
"Europe/London", # 英国标准时间
|
"Europe/London", # 英国标准时间
|
||||||
"Europe/Paris", # 中欧标准时间
|
"Europe/Paris", # 中欧标准时间
|
||||||
"Europe/Moscow", # 莫斯科标准时间
|
"Europe/Moscow", # 莫斯科标准时间
|
||||||
"Asia/Dubai", # 阿联酋标准时间
|
"Asia/Dubai", # 阿联酋标准时间
|
||||||
"Asia/Kolkata", # 印度标准时间
|
"Asia/Kolkata", # 印度标准时间
|
||||||
"Asia/Shanghai", # 中国标准时间
|
"Asia/Shanghai", # 中国标准时间
|
||||||
"Asia/Hong_Kong", # 中国香港标准时间
|
"Asia/Hong_Kong", # 中国香港标准时间
|
||||||
"Asia/Chongqing", # 中国重庆标准时间
|
"Asia/Chongqing", # 中国重庆标准时间
|
||||||
"Asia/Macau", # 中国澳门标准时间
|
"Asia/Macau", # 中国澳门标准时间
|
||||||
"Asia/Taipei", # 中国台湾标准时间
|
"Asia/Taipei", # 中国台湾标准时间
|
||||||
"Asia/Tokyo", # 日本标准时间
|
"Asia/Tokyo", # 日本标准时间
|
||||||
"Australia/Sydney", # 澳大利亚东部标准时间
|
"Australia/Sydney", # 澳大利亚东部标准时间
|
||||||
"Pacific/Auckland" # 新西兰标准时间
|
"Pacific/Auckland" # 新西兰标准时间
|
||||||
]
|
]
|
||||||
representative_timezones_list.sort()
|
representative_timezones_list.sort()
|
@ -1,150 +1,150 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
from nonebot import require
|
from nonebot import require
|
||||||
|
|
||||||
from src.utils.base.data import LiteModel, Database
|
from src.utils.base.data import LiteModel, Database
|
||||||
from src.utils.base.data_manager import User, user_db, group_db
|
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.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.base.ly_typing import T_Bot, T_MessageEvent
|
||||||
from src.utils.message.message import MarkdownMessage as md
|
from src.utils.message.message import MarkdownMessage as md
|
||||||
from .const import representative_timezones_list
|
from .const import representative_timezones_list
|
||||||
from src.utils import event as event_utils
|
from src.utils import event as event_utils
|
||||||
|
|
||||||
require("nonebot_plugin_alconna")
|
require("nonebot_plugin_alconna")
|
||||||
from nonebot_plugin_alconna import Alconna, Args, Arparma, Subcommand, on_alconna
|
from nonebot_plugin_alconna import Alconna, Args, Arparma, Subcommand, on_alconna
|
||||||
|
|
||||||
profile_alc = on_alconna(
|
profile_alc = on_alconna(
|
||||||
Alconna(
|
Alconna(
|
||||||
"profile",
|
"profile",
|
||||||
Subcommand(
|
Subcommand(
|
||||||
"set",
|
"set",
|
||||||
Args["key", str]["value", str, None],
|
Args["key", str]["value", str, None],
|
||||||
alias=["s", "设置"],
|
alias=["s", "设置"],
|
||||||
),
|
),
|
||||||
Subcommand(
|
Subcommand(
|
||||||
"get",
|
"get",
|
||||||
Args["key", str],
|
Args["key", str],
|
||||||
alias=["g", "查询"],
|
alias=["g", "查询"],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
aliases={"用户信息"}
|
aliases={"用户信息"}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# json储存
|
# json储存
|
||||||
class Profile(LiteModel):
|
class Profile(LiteModel):
|
||||||
lang: str = "zh-CN"
|
lang: str = "zh-CN"
|
||||||
nickname: str = ""
|
nickname: str = ""
|
||||||
timezone: str = "Asia/Shanghai"
|
timezone: str = "Asia/Shanghai"
|
||||||
location: str = ""
|
location: str = ""
|
||||||
|
|
||||||
|
|
||||||
@profile_alc.handle()
|
@profile_alc.handle()
|
||||||
async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot):
|
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),
|
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))))
|
default=User(user_id=str(event_utils.get_user_id(event))))
|
||||||
ulang = get_user_lang(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.get("set"):
|
||||||
if result.subcommands["set"].args.get("value"):
|
if result.subcommands["set"].args.get("value"):
|
||||||
# 对合法性进行校验后设置
|
# 对合法性进行校验后设置
|
||||||
r = set_profile(result.args["key"], result.args["value"], str(event_utils.get_user_id(event)))
|
r = set_profile(result.args["key"], result.args["value"], str(event_utils.get_user_id(event)))
|
||||||
if r:
|
if r:
|
||||||
user.profile[result.args["key"]] = result.args["value"]
|
user.profile[result.args["key"]] = result.args["value"]
|
||||||
user_db.save(user) # 数据库保存
|
user_db.save(user) # 数据库保存
|
||||||
await profile_alc.finish(
|
await profile_alc.finish(
|
||||||
ulang.get(
|
ulang.get(
|
||||||
"user.profile.set_success",
|
"user.profile.set_success",
|
||||||
ATTR=ulang.get(f"user.profile.{result.args['key']}"),
|
ATTR=ulang.get(f"user.profile.{result.args['key']}"),
|
||||||
VALUE=result.args["value"]
|
VALUE=result.args["value"]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await profile_alc.finish(ulang.get("user.profile.set_failed", ATTR=ulang.get(f"user.profile.{result.args['key']}")))
|
await profile_alc.finish(ulang.get("user.profile.set_failed", ATTR=ulang.get(f"user.profile.{result.args['key']}")))
|
||||||
else:
|
else:
|
||||||
# 未输入值,尝试呼出菜单
|
# 未输入值,尝试呼出菜单
|
||||||
menu = get_profile_menu(result.args["key"], ulang)
|
menu = get_profile_menu(result.args["key"], ulang)
|
||||||
if menu:
|
if menu:
|
||||||
await md.send_md(menu, bot, event=event)
|
await md.send_md(menu, bot, event=event)
|
||||||
else:
|
else:
|
||||||
await profile_alc.finish(ulang.get("user.profile.input_value", ATTR=ulang.get(f"user.profile.{result.args['key']}")))
|
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"]
|
user.profile[result.args["key"]] = result.args["value"]
|
||||||
|
|
||||||
elif result.subcommands.get("get"):
|
elif result.subcommands.get("get"):
|
||||||
if result.args["key"] in user.profile:
|
if result.args["key"] in user.profile:
|
||||||
await profile_alc.finish(user.profile[result.args["key"]])
|
await profile_alc.finish(user.profile[result.args["key"]])
|
||||||
else:
|
else:
|
||||||
await profile_alc.finish("无此键值")
|
await profile_alc.finish("无此键值")
|
||||||
else:
|
else:
|
||||||
profile = Profile(**user.profile)
|
profile = Profile(**user.profile)
|
||||||
|
|
||||||
for k, v in user.profile.items():
|
for k, v in user.profile.items():
|
||||||
profile.__setattr__(k, v)
|
profile.__setattr__(k, v)
|
||||||
|
|
||||||
reply = f"# {ulang.get('user.profile.info')}\n***\n"
|
reply = f"# {ulang.get('user.profile.info')}\n***\n"
|
||||||
|
|
||||||
hidden_attr = ["id", "TABLE_NAME"]
|
hidden_attr = ["id", "TABLE_NAME"]
|
||||||
enter_attr = ["lang", "timezone"]
|
enter_attr = ["lang", "timezone"]
|
||||||
|
|
||||||
for key in sorted(profile.dict().keys()):
|
for key in sorted(profile.dict().keys()):
|
||||||
if key in hidden_attr:
|
if key in hidden_attr:
|
||||||
continue
|
continue
|
||||||
val = profile.dict()[key]
|
val = profile.dict()[key]
|
||||||
key_text = ulang.get(f"user.profile.{key}")
|
key_text = ulang.get(f"user.profile.{key}")
|
||||||
btn_set = md.btn_cmd(ulang.get("user.profile.edit"), f"profile set {key}",
|
btn_set = md.btn_cmd(ulang.get("user.profile.edit"), f"profile set {key}",
|
||||||
enter=True if key in enter_attr else False)
|
enter=True if key in enter_attr else False)
|
||||||
reply += (f"\n**{key_text}** **{val}**\n"
|
reply += (f"\n**{key_text}** **{val}**\n"
|
||||||
f"\n> {ulang.get(f'user.profile.{key}.desc')}"
|
f"\n> {ulang.get(f'user.profile.{key}.desc')}"
|
||||||
f"\n> {btn_set} \n\n***\n")
|
f"\n> {btn_set} \n\n***\n")
|
||||||
await md.send_md(reply, bot, event=event)
|
await md.send_md(reply, bot, event=event)
|
||||||
|
|
||||||
|
|
||||||
def get_profile_menu(key: str, ulang: Language) -> Optional[str]:
|
def get_profile_menu(key: str, ulang: Language) -> Optional[str]:
|
||||||
"""获取属性的markdown菜单
|
"""获取属性的markdown菜单
|
||||||
Args:
|
Args:
|
||||||
ulang: 用户语言
|
ulang: 用户语言
|
||||||
key: 属性键
|
key: 属性键
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
setting_name = ulang.get(f"user.profile.{key}")
|
setting_name = ulang.get(f"user.profile.{key}")
|
||||||
|
|
||||||
no_menu = ["id", "nickname", "location"]
|
no_menu = ["id", "nickname", "location"]
|
||||||
|
|
||||||
if key in no_menu:
|
if key in no_menu:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
reply = f"**{setting_name} {ulang.get('user.profile.settings')}**\n***\n"
|
reply = f"**{setting_name} {ulang.get('user.profile.settings')}**\n***\n"
|
||||||
if key == "lang":
|
if key == "lang":
|
||||||
for lang_code, lang_name in get_all_lang().items():
|
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}")
|
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"
|
reply += f"\n{btn_set_lang}\n***\n"
|
||||||
elif key == "timezone":
|
elif key == "timezone":
|
||||||
for tz in representative_timezones_list:
|
for tz in representative_timezones_list:
|
||||||
btn_set_tz = md.btn_cmd(tz, f"profile set {key} {tz}")
|
btn_set_tz = md.btn_cmd(tz, f"profile set {key} {tz}")
|
||||||
reply += f"{btn_set_tz}\n***\n"
|
reply += f"{btn_set_tz}\n***\n"
|
||||||
return reply
|
return reply
|
||||||
|
|
||||||
|
|
||||||
def set_profile(key: str, value: str, user_id: str) -> bool:
|
def set_profile(key: str, value: str, user_id: str) -> bool:
|
||||||
"""设置属性,使用if分支对每一个合法性进行检查
|
"""设置属性,使用if分支对每一个合法性进行检查
|
||||||
Args:
|
Args:
|
||||||
user_id:
|
user_id:
|
||||||
key:
|
key:
|
||||||
value:
|
value:
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
是否成功设置,输入合法性不通过返回False
|
是否成功设置,输入合法性不通过返回False
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if key == "lang":
|
if key == "lang":
|
||||||
if value in get_all_lang():
|
if value in get_all_lang():
|
||||||
change_user_lang(user_id, value)
|
change_user_lang(user_id, value)
|
||||||
return True
|
return True
|
||||||
elif key == "timezone":
|
elif key == "timezone":
|
||||||
if value in pytz.all_timezones:
|
if value in pytz.all_timezones:
|
||||||
return True
|
return True
|
||||||
elif key == "nickname":
|
elif key == "nickname":
|
||||||
return True
|
return True
|
@ -1,27 +1,27 @@
|
|||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
from nonebot import get_driver
|
from nonebot import get_driver
|
||||||
from .qweather import *
|
from .qweather import *
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="轻雪天气",
|
name="轻雪天气",
|
||||||
description="基于和风天气api的天气插件",
|
description="基于和风天气api的天气插件",
|
||||||
usage="",
|
usage="",
|
||||||
type="application",
|
type="application",
|
||||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||||
extra={
|
extra={
|
||||||
"liteyuki" : True,
|
"liteyuki" : True,
|
||||||
"toggleable" : True,
|
"toggleable" : True,
|
||||||
"default_enable": True,
|
"default_enable": True,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
from ...utils.base.data_manager import set_memory_data
|
from ...utils.base.data_manager import set_memory_data
|
||||||
|
|
||||||
driver = get_driver()
|
driver = get_driver()
|
||||||
|
|
||||||
|
|
||||||
@driver.on_startup
|
@driver.on_startup
|
||||||
async def _():
|
async def _():
|
||||||
# 检查是否为开发者模式
|
# 检查是否为开发者模式
|
||||||
is_dev = await check_key_dev(get_config("weather_key", ""))
|
is_dev = await check_key_dev(get_config("weather_key", ""))
|
||||||
set_memory_data("weather.is_dev", is_dev)
|
set_memory_data("weather.is_dev", is_dev)
|
@ -1,171 +1,171 @@
|
|||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
from .qw_models import *
|
from .qw_models import *
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from ...utils.base.data_manager import get_memory_data
|
from ...utils.base.data_manager import get_memory_data
|
||||||
from ...utils.base.language import Language
|
from ...utils.base.language import Language
|
||||||
|
|
||||||
dev_url = "https://devapi.qweather.com/" # 开发HBa
|
dev_url = "https://devapi.qweather.com/" # 开发HBa
|
||||||
com_url = "https://api.qweather.com/" # 正式环境
|
com_url = "https://api.qweather.com/" # 正式环境
|
||||||
|
|
||||||
|
|
||||||
def get_qw_lang(lang: str) -> str:
|
def get_qw_lang(lang: str) -> str:
|
||||||
if lang in ["zh-HK", "zh-TW"]:
|
if lang in ["zh-HK", "zh-TW"]:
|
||||||
return "zh-hant"
|
return "zh-hant"
|
||||||
elif lang.startswith("zh"):
|
elif lang.startswith("zh"):
|
||||||
return "zh"
|
return "zh"
|
||||||
elif lang.startswith("en"):
|
elif lang.startswith("en"):
|
||||||
return "en"
|
return "en"
|
||||||
else:
|
else:
|
||||||
return lang
|
return lang
|
||||||
|
|
||||||
|
|
||||||
async def check_key_dev(key: str) -> bool:
|
async def check_key_dev(key: str) -> bool:
|
||||||
url = "https://api.qweather.com/v7/weather/now?"
|
url = "https://api.qweather.com/v7/weather/now?"
|
||||||
params = {
|
params = {
|
||||||
"location": "101010100",
|
"location": "101010100",
|
||||||
"key" : key,
|
"key" : key,
|
||||||
}
|
}
|
||||||
async with aiohttp.ClientSession() as client:
|
async with aiohttp.ClientSession() as client:
|
||||||
resp = await client.get(url, params=params)
|
resp = await client.get(url, params=params)
|
||||||
return (await resp.json()).get("code") != "200" # 查询不到付费数据为开发版
|
return (await resp.json()).get("code") != "200" # 查询不到付费数据为开发版
|
||||||
|
|
||||||
|
|
||||||
def get_local_data(ulang_code: str) -> dict:
|
def get_local_data(ulang_code: str) -> dict:
|
||||||
"""
|
"""
|
||||||
获取本地化数据
|
获取本地化数据
|
||||||
Args:
|
Args:
|
||||||
ulang_code:
|
ulang_code:
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
ulang = Language(ulang_code)
|
ulang = Language(ulang_code)
|
||||||
return {
|
return {
|
||||||
"monday" : ulang.get("weather.monday"),
|
"monday" : ulang.get("weather.monday"),
|
||||||
"tuesday" : ulang.get("weather.tuesday"),
|
"tuesday" : ulang.get("weather.tuesday"),
|
||||||
"wednesday": ulang.get("weather.wednesday"),
|
"wednesday": ulang.get("weather.wednesday"),
|
||||||
"thursday" : ulang.get("weather.thursday"),
|
"thursday" : ulang.get("weather.thursday"),
|
||||||
"friday" : ulang.get("weather.friday"),
|
"friday" : ulang.get("weather.friday"),
|
||||||
"saturday" : ulang.get("weather.saturday"),
|
"saturday" : ulang.get("weather.saturday"),
|
||||||
"sunday" : ulang.get("weather.sunday"),
|
"sunday" : ulang.get("weather.sunday"),
|
||||||
"today" : ulang.get("weather.today"),
|
"today" : ulang.get("weather.today"),
|
||||||
"tomorrow" : ulang.get("weather.tomorrow"),
|
"tomorrow" : ulang.get("weather.tomorrow"),
|
||||||
"day" : ulang.get("weather.day"),
|
"day" : ulang.get("weather.day"),
|
||||||
"night" : ulang.get("weather.night"),
|
"night" : ulang.get("weather.night"),
|
||||||
"no_aqi" : ulang.get("weather.no_aqi"),
|
"no_aqi" : ulang.get("weather.no_aqi"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def city_lookup(
|
async def city_lookup(
|
||||||
location: str,
|
location: str,
|
||||||
key: str,
|
key: str,
|
||||||
adm: str = "",
|
adm: str = "",
|
||||||
number: int = 20,
|
number: int = 20,
|
||||||
lang: str = "zh",
|
lang: str = "zh",
|
||||||
) -> CityLookup:
|
) -> CityLookup:
|
||||||
"""
|
"""
|
||||||
通过关键字搜索城市信息
|
通过关键字搜索城市信息
|
||||||
Args:
|
Args:
|
||||||
location:
|
location:
|
||||||
key:
|
key:
|
||||||
adm:
|
adm:
|
||||||
number:
|
number:
|
||||||
lang: 可传入标准i18n语言代码,如zh-CN、en-US等
|
lang: 可传入标准i18n语言代码,如zh-CN、en-US等
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
url = "https://geoapi.qweather.com/v2/city/lookup?"
|
url = "https://geoapi.qweather.com/v2/city/lookup?"
|
||||||
params = {
|
params = {
|
||||||
"location": location,
|
"location": location,
|
||||||
"adm" : adm,
|
"adm" : adm,
|
||||||
"number" : number,
|
"number" : number,
|
||||||
"key" : key,
|
"key" : key,
|
||||||
"lang" : lang,
|
"lang" : lang,
|
||||||
}
|
}
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
resp = await client.get(url, params=params)
|
resp = await client.get(url, params=params)
|
||||||
return CityLookup.parse_obj(resp.json())
|
return CityLookup.parse_obj(resp.json())
|
||||||
|
|
||||||
|
|
||||||
async def get_weather_now(
|
async def get_weather_now(
|
||||||
key: str,
|
key: str,
|
||||||
location: str,
|
location: str,
|
||||||
lang: str = "zh",
|
lang: str = "zh",
|
||||||
unit: str = "m",
|
unit: str = "m",
|
||||||
dev: bool = get_memory_data("is_dev", True),
|
dev: bool = get_memory_data("is_dev", True),
|
||||||
) -> dict:
|
) -> dict:
|
||||||
url_path = "v7/weather/now?"
|
url_path = "v7/weather/now?"
|
||||||
url = dev_url + url_path if dev else com_url + url_path
|
url = dev_url + url_path if dev else com_url + url_path
|
||||||
params = {
|
params = {
|
||||||
"location": location,
|
"location": location,
|
||||||
"key" : key,
|
"key" : key,
|
||||||
"lang" : lang,
|
"lang" : lang,
|
||||||
"unit" : unit,
|
"unit" : unit,
|
||||||
}
|
}
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
resp = await client.get(url, params=params)
|
resp = await client.get(url, params=params)
|
||||||
return resp.json()
|
return resp.json()
|
||||||
|
|
||||||
|
|
||||||
async def get_weather_daily(
|
async def get_weather_daily(
|
||||||
key: str,
|
key: str,
|
||||||
location: str,
|
location: str,
|
||||||
lang: str = "zh",
|
lang: str = "zh",
|
||||||
unit: str = "m",
|
unit: str = "m",
|
||||||
dev: bool = get_memory_data("is_dev", True),
|
dev: bool = get_memory_data("is_dev", True),
|
||||||
) -> dict:
|
) -> dict:
|
||||||
url_path = "v7/weather/%dd?" % (7 if dev else 30)
|
url_path = "v7/weather/%dd?" % (7 if dev else 30)
|
||||||
url = dev_url + url_path if dev else com_url + url_path
|
url = dev_url + url_path if dev else com_url + url_path
|
||||||
params = {
|
params = {
|
||||||
"location": location,
|
"location": location,
|
||||||
"key" : key,
|
"key" : key,
|
||||||
"lang" : lang,
|
"lang" : lang,
|
||||||
"unit" : unit,
|
"unit" : unit,
|
||||||
}
|
}
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
resp = await client.get(url, params=params)
|
resp = await client.get(url, params=params)
|
||||||
return resp.json()
|
return resp.json()
|
||||||
|
|
||||||
|
|
||||||
async def get_weather_hourly(
|
async def get_weather_hourly(
|
||||||
key: str,
|
key: str,
|
||||||
location: str,
|
location: str,
|
||||||
lang: str = "zh",
|
lang: str = "zh",
|
||||||
unit: str = "m",
|
unit: str = "m",
|
||||||
dev: bool = get_memory_data("is_dev", True),
|
dev: bool = get_memory_data("is_dev", True),
|
||||||
) -> dict:
|
) -> dict:
|
||||||
url_path = "v7/weather/%dh?" % (24 if dev else 168)
|
url_path = "v7/weather/%dh?" % (24 if dev else 168)
|
||||||
url = dev_url + url_path if dev else com_url + url_path
|
url = dev_url + url_path if dev else com_url + url_path
|
||||||
params = {
|
params = {
|
||||||
"location": location,
|
"location": location,
|
||||||
"key" : key,
|
"key" : key,
|
||||||
"lang" : lang,
|
"lang" : lang,
|
||||||
"unit" : unit,
|
"unit" : unit,
|
||||||
}
|
}
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
resp = await client.get(url, params=params)
|
resp = await client.get(url, params=params)
|
||||||
return resp.json()
|
return resp.json()
|
||||||
|
|
||||||
|
|
||||||
async def get_airquality(
|
async def get_airquality(
|
||||||
key: str,
|
key: str,
|
||||||
location: str,
|
location: str,
|
||||||
lang: str,
|
lang: str,
|
||||||
pollutant: bool = False,
|
pollutant: bool = False,
|
||||||
station: bool = False,
|
station: bool = False,
|
||||||
dev: bool = get_memory_data("is_dev", True),
|
dev: bool = get_memory_data("is_dev", True),
|
||||||
) -> dict:
|
) -> dict:
|
||||||
url_path = f"airquality/v1/now/{location}?"
|
url_path = f"airquality/v1/now/{location}?"
|
||||||
url = dev_url + url_path if dev else com_url + url_path
|
url = dev_url + url_path if dev else com_url + url_path
|
||||||
params = {
|
params = {
|
||||||
"key" : key,
|
"key" : key,
|
||||||
"lang" : lang,
|
"lang" : lang,
|
||||||
"pollutant": pollutant,
|
"pollutant": pollutant,
|
||||||
"station" : station,
|
"station" : station,
|
||||||
}
|
}
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
resp = await client.get(url, params=params)
|
resp = await client.get(url, params=params)
|
||||||
return resp.json()
|
return resp.json()
|
@ -1,62 +1,62 @@
|
|||||||
from src.utils.base.data import LiteModel
|
from src.utils.base.data import LiteModel
|
||||||
|
|
||||||
|
|
||||||
class Location(LiteModel):
|
class Location(LiteModel):
|
||||||
name: str = ""
|
name: str = ""
|
||||||
id: str = ""
|
id: str = ""
|
||||||
lat: str = ""
|
lat: str = ""
|
||||||
lon: str = ""
|
lon: str = ""
|
||||||
adm2: str = ""
|
adm2: str = ""
|
||||||
adm1: str = ""
|
adm1: str = ""
|
||||||
country: str = ""
|
country: str = ""
|
||||||
tz: str = ""
|
tz: str = ""
|
||||||
utcOffset: str = ""
|
utcOffset: str = ""
|
||||||
isDst: str = ""
|
isDst: str = ""
|
||||||
type: str = ""
|
type: str = ""
|
||||||
rank: str = ""
|
rank: str = ""
|
||||||
fxLink: str = ""
|
fxLink: str = ""
|
||||||
sources: str = ""
|
sources: str = ""
|
||||||
license: str = ""
|
license: str = ""
|
||||||
|
|
||||||
|
|
||||||
class CityLookup(LiteModel):
|
class CityLookup(LiteModel):
|
||||||
code: str = ""
|
code: str = ""
|
||||||
location: list[Location] = [Location()]
|
location: list[Location] = [Location()]
|
||||||
|
|
||||||
|
|
||||||
class Now(LiteModel):
|
class Now(LiteModel):
|
||||||
obsTime: str = ""
|
obsTime: str = ""
|
||||||
temp: str = ""
|
temp: str = ""
|
||||||
feelsLike: str = ""
|
feelsLike: str = ""
|
||||||
icon: str = ""
|
icon: str = ""
|
||||||
text: str = ""
|
text: str = ""
|
||||||
wind360: str = ""
|
wind360: str = ""
|
||||||
windDir: str = ""
|
windDir: str = ""
|
||||||
windScale: str = ""
|
windScale: str = ""
|
||||||
windSpeed: str = ""
|
windSpeed: str = ""
|
||||||
humidity: str = ""
|
humidity: str = ""
|
||||||
precip: str = ""
|
precip: str = ""
|
||||||
pressure: str = ""
|
pressure: str = ""
|
||||||
vis: str = ""
|
vis: str = ""
|
||||||
cloud: str = ""
|
cloud: str = ""
|
||||||
dew: str = ""
|
dew: str = ""
|
||||||
sources: str = ""
|
sources: str = ""
|
||||||
license: str = ""
|
license: str = ""
|
||||||
|
|
||||||
|
|
||||||
class WeatherNow(LiteModel):
|
class WeatherNow(LiteModel):
|
||||||
code: str = ""
|
code: str = ""
|
||||||
updateTime: str = ""
|
updateTime: str = ""
|
||||||
fxLink: str = ""
|
fxLink: str = ""
|
||||||
now: Now = Now()
|
now: Now = Now()
|
||||||
|
|
||||||
|
|
||||||
class Daily(LiteModel):
|
class Daily(LiteModel):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class WeatherDaily(LiteModel):
|
class WeatherDaily(LiteModel):
|
||||||
code: str = ""
|
code: str = ""
|
||||||
updateTime: str = ""
|
updateTime: str = ""
|
||||||
fxLink: str = ""
|
fxLink: str = ""
|
||||||
daily: list[str] = []
|
daily: list[str] = []
|
@ -1,102 +1,102 @@
|
|||||||
from nonebot import require, on_endswith
|
from nonebot import require, on_endswith
|
||||||
from nonebot.adapters import satori
|
from nonebot.adapters import satori
|
||||||
from nonebot.adapters.onebot.v11 import MessageSegment
|
from nonebot.adapters.onebot.v11 import MessageSegment
|
||||||
from nonebot.internal.matcher import Matcher
|
from nonebot.internal.matcher import Matcher
|
||||||
|
|
||||||
from src.utils.base.config import get_config
|
from src.utils.base.config import get_config
|
||||||
from src.utils.base.ly_typing import T_MessageEvent
|
from src.utils.base.ly_typing import T_MessageEvent
|
||||||
|
|
||||||
from .qw_api import *
|
from .qw_api import *
|
||||||
from src.utils.base.data_manager import User, user_db
|
from src.utils.base.data_manager import User, user_db
|
||||||
from src.utils.base.language import Language, get_user_lang
|
from src.utils.base.language import Language, get_user_lang
|
||||||
from src.utils.base.resource import get_path
|
from src.utils.base.resource import get_path
|
||||||
from src.utils.message.html_tool import template2image
|
from src.utils.message.html_tool import template2image
|
||||||
from src.utils import event as event_utils
|
from src.utils import event as event_utils
|
||||||
|
|
||||||
require("nonebot_plugin_alconna")
|
require("nonebot_plugin_alconna")
|
||||||
from nonebot_plugin_alconna import on_alconna, Alconna, Args, MultiVar, Arparma, UniMessage
|
from nonebot_plugin_alconna import on_alconna, Alconna, Args, MultiVar, Arparma, UniMessage
|
||||||
|
|
||||||
wx_alc = on_alconna(
|
wx_alc = on_alconna(
|
||||||
aliases={"天气"},
|
aliases={"天气"},
|
||||||
command=Alconna(
|
command=Alconna(
|
||||||
"weather",
|
"weather",
|
||||||
Args["keywords", MultiVar(str), []],
|
Args["keywords", MultiVar(str), []],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@wx_alc.handle()
|
@wx_alc.handle()
|
||||||
async def _(result: Arparma, event: T_MessageEvent, matcher: Matcher):
|
async def _(result: Arparma, event: T_MessageEvent, matcher: Matcher):
|
||||||
"""await alconna.send("weather", city)"""
|
"""await alconna.send("weather", city)"""
|
||||||
kws = result.main_args.get("keywords")
|
kws = result.main_args.get("keywords")
|
||||||
image = await get_weather_now_card(matcher, event, kws)
|
image = await get_weather_now_card(matcher, event, kws)
|
||||||
await wx_alc.finish(UniMessage.image(raw=image))
|
await wx_alc.finish(UniMessage.image(raw=image))
|
||||||
|
|
||||||
|
|
||||||
@on_endswith(("天气", "weather")).handle()
|
@on_endswith(("天气", "weather")).handle()
|
||||||
async def _(event: T_MessageEvent, matcher: Matcher):
|
async def _(event: T_MessageEvent, matcher: Matcher):
|
||||||
"""await alconna.send("weather", city)"""
|
"""await alconna.send("weather", city)"""
|
||||||
# kws = event.message.extract_plain_text()
|
# kws = event.message.extract_plain_text()
|
||||||
kws = event.get_plaintext()
|
kws = event.get_plaintext()
|
||||||
image = await get_weather_now_card(matcher, event, [kws.replace("天气", "").replace("weather", "")], False)
|
image = await get_weather_now_card(matcher, event, [kws.replace("天气", "").replace("weather", "")], False)
|
||||||
if isinstance(event, satori.event.Event):
|
if isinstance(event, satori.event.Event):
|
||||||
await matcher.finish(satori.MessageSegment.image(raw=image, mime="image/png"))
|
await matcher.finish(satori.MessageSegment.image(raw=image, mime="image/png"))
|
||||||
else:
|
else:
|
||||||
await matcher.finish(MessageSegment.image(image))
|
await matcher.finish(MessageSegment.image(image))
|
||||||
|
|
||||||
|
|
||||||
async def get_weather_now_card(matcher: Matcher, event: T_MessageEvent, keyword: list[str], tip: bool = True):
|
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))
|
ulang = get_user_lang(event_utils.get_user_id(event))
|
||||||
qw_lang = get_qw_lang(ulang.lang_code)
|
qw_lang = get_qw_lang(ulang.lang_code)
|
||||||
key = get_config("weather_key")
|
key = get_config("weather_key")
|
||||||
is_dev = get_memory_data("weather.is_dev", True)
|
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())
|
user: User = user_db.where_one(User(), "user_id = ?", event_utils.get_user_id(event), default=User())
|
||||||
# params
|
# params
|
||||||
unit = user.profile.get("unit", "m")
|
unit = user.profile.get("unit", "m")
|
||||||
stored_location = user.profile.get("location", None)
|
stored_location = user.profile.get("location", None)
|
||||||
|
|
||||||
if not key:
|
if not key:
|
||||||
await matcher.finish(ulang.get("weather.no_key") if tip else None)
|
await matcher.finish(ulang.get("weather.no_key") if tip else None)
|
||||||
|
|
||||||
if keyword:
|
if keyword:
|
||||||
if len(keyword) >= 2:
|
if len(keyword) >= 2:
|
||||||
adm = keyword[0]
|
adm = keyword[0]
|
||||||
city = keyword[-1]
|
city = keyword[-1]
|
||||||
else:
|
else:
|
||||||
adm = ""
|
adm = ""
|
||||||
city = keyword[0]
|
city = keyword[0]
|
||||||
city_info = await city_lookup(city, key, adm=adm, lang=qw_lang)
|
city_info = await city_lookup(city, key, adm=adm, lang=qw_lang)
|
||||||
city_name = " ".join(keyword)
|
city_name = " ".join(keyword)
|
||||||
else:
|
else:
|
||||||
if not stored_location:
|
if not stored_location:
|
||||||
await matcher.finish(ulang.get("liteyuki.invalid_command", TEXT="location") if tip else None)
|
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_info = await city_lookup(stored_location, key, lang=qw_lang)
|
||||||
city_name = stored_location
|
city_name = stored_location
|
||||||
if city_info.code == "200":
|
if city_info.code == "200":
|
||||||
location_data = city_info.location[0]
|
location_data = city_info.location[0]
|
||||||
else:
|
else:
|
||||||
await matcher.finish(ulang.get("weather.city_not_found", CITY=city_name) if tip else None)
|
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_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_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)
|
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)
|
aqi = await get_airquality(key, location_data.id, lang=qw_lang, dev=is_dev)
|
||||||
|
|
||||||
image = await template2image(
|
image = await template2image(
|
||||||
template=get_path("templates/weather_now.html", abs_path=True),
|
template=get_path("templates/weather_now.html", abs_path=True),
|
||||||
templates={
|
templates={
|
||||||
"data": {
|
"data": {
|
||||||
"params" : {
|
"params" : {
|
||||||
"unit": unit,
|
"unit": unit,
|
||||||
"lang": ulang.lang_code,
|
"lang": ulang.lang_code,
|
||||||
},
|
},
|
||||||
"weatherNow" : weather_now,
|
"weatherNow" : weather_now,
|
||||||
"weatherDaily" : weather_daily,
|
"weatherDaily" : weather_daily,
|
||||||
"weatherHourly": weather_hourly,
|
"weatherHourly": weather_hourly,
|
||||||
"aqi" : aqi,
|
"aqi" : aqi,
|
||||||
"location" : location_data.dump(),
|
"location" : location_data.dump(),
|
||||||
"localization" : get_local_data(ulang.lang_code),
|
"localization" : get_local_data(ulang.lang_code),
|
||||||
"is_dev": 1 if is_dev else 0
|
"is_dev": 1 if is_dev else 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return image
|
return image
|
@ -1,7 +1,7 @@
|
|||||||
import threading
|
import threading
|
||||||
|
|
||||||
from nonebot import logger
|
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"):
|
def reload(delay: float = 0.0, receiver: str = "nonebot"):
|
||||||
@ -14,6 +14,13 @@ def reload(delay: float = 0.0, receiver: str = "nonebot"):
|
|||||||
Returns:
|
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)
|
if delay > 0:
|
||||||
logger.info(f"Reloading LiteyukiBot({receiver})...")
|
threading.Timer(delay, chan.send, args=(1,)).start()
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
chan.send(1)
|
||||||
|
Loading…
Reference in New Issue
Block a user