mirror of
https://github.com/TriM-Organization/LiteyukiBot-TriM.git
synced 2025-09-07 12:46:23 +00:00
Ⓜ️手动从旧梦 81a191f merge
This commit is contained in:
@ -3,10 +3,19 @@ from liteyuki.bot import (
|
||||
get_bot
|
||||
)
|
||||
|
||||
# def get_bot_instance() -> LiteyukiBot | None:
|
||||
# """
|
||||
# 获取轻雪实例
|
||||
# Returns:
|
||||
# LiteyukiBot: 当前的轻雪实例
|
||||
# """
|
||||
# return _BOT_INSTANCE
|
||||
from liteyuki.comm import (
|
||||
Channel,
|
||||
chan,
|
||||
Event
|
||||
)
|
||||
|
||||
from liteyuki.plugin import (
|
||||
load_plugin,
|
||||
load_plugins
|
||||
)
|
||||
|
||||
from liteyuki.log import (
|
||||
logger,
|
||||
init_log
|
||||
|
||||
)
|
||||
|
@ -1,147 +1,137 @@
|
||||
import asyncio
|
||||
import multiprocessing
|
||||
from typing import Any, Coroutine, Optional
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from typing import Any, Optional
|
||||
|
||||
import nonebot
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
from watchdog.observers import Observer
|
||||
|
||||
from liteyuki.plugin.load import load_plugin, load_plugins
|
||||
from src.utils import (
|
||||
adapter_manager,
|
||||
driver_manager,
|
||||
)
|
||||
from src.utils.base.log import logger
|
||||
from liteyuki.bot.lifespan import (
|
||||
Lifespan,
|
||||
LIFESPAN_FUNC,
|
||||
)
|
||||
from liteyuki.core.spawn_process import nb_run, ProcessingManager
|
||||
from liteyuki.bot.lifespan import LIFESPAN_FUNC, Lifespan
|
||||
from liteyuki.core import IS_MAIN_PROCESS
|
||||
from liteyuki.core.manager import ProcessManager
|
||||
from liteyuki.core.spawn_process import mb_run, nb_run
|
||||
from liteyuki.log import init_log, logger
|
||||
from liteyuki.plugin import load_plugins
|
||||
|
||||
__all__ = [
|
||||
"LiteyukiBot",
|
||||
"get_bot"
|
||||
]
|
||||
|
||||
_MAIN_PROCESS = multiprocessing.current_process().name == "MainProcess"
|
||||
__all__ = ["LiteyukiBot", "get_bot"]
|
||||
|
||||
|
||||
class LiteyukiBot:
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
global _BOT_INSTANCE
|
||||
_BOT_INSTANCE = self # 引用
|
||||
self.running = False
|
||||
self.config: dict[str, Any] = kwargs
|
||||
self.lifespan: Lifespan = Lifespan()
|
||||
self.init(**self.config) # 初始化
|
||||
|
||||
def run(self, *args, **kwargs):
|
||||
self.lifespan: Lifespan = Lifespan()
|
||||
|
||||
if _MAIN_PROCESS:
|
||||
load_plugins("liteyuki/plugins")
|
||||
asyncio.run(self.lifespan.before_start())
|
||||
self._run_nb_in_spawn_process(*args, **kwargs)
|
||||
else:
|
||||
# 子进程启动
|
||||
self.process_manager: ProcessManager = ProcessManager(bot=self)
|
||||
self.loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(self.loop)
|
||||
self.loop_thread = threading.Thread(target=self.loop.run_forever, daemon=True)
|
||||
self.call_restart_count = 0
|
||||
|
||||
driver_manager.init(config=self.config)
|
||||
adapter_manager.init(self.config)
|
||||
adapter_manager.register()
|
||||
nonebot.load_plugin("src.liteyuki_main")
|
||||
def run(self):
|
||||
load_plugins("liteyuki/plugins") # 加载轻雪插件
|
||||
|
||||
def _run_nb_in_spawn_process(self, *args, **kwargs):
|
||||
"""
|
||||
在新的进程中运行nonebot.run方法
|
||||
Args:
|
||||
*args:
|
||||
**kwargs:
|
||||
self.loop_thread.start() # 启动事件循环
|
||||
asyncio.run(self.lifespan.before_start()) # 启动前钩子
|
||||
|
||||
Returns:
|
||||
"""
|
||||
self.process_manager.add_target("nonebot", nb_run, **self.config)
|
||||
self.process_manager.start("nonebot")
|
||||
|
||||
timeout_limit: int = 20
|
||||
should_exit = False
|
||||
self.process_manager.add_target("melobot", mb_run, **self.config)
|
||||
self.process_manager.start("melobot")
|
||||
|
||||
while not should_exit:
|
||||
ctx = multiprocessing.get_context("spawn")
|
||||
event = ctx.Event()
|
||||
ProcessingManager.event = event
|
||||
process = ctx.Process(
|
||||
target=nb_run,
|
||||
args=(event,) + args,
|
||||
kwargs=kwargs,
|
||||
asyncio.run(self.lifespan.after_start()) # 启动后钩子
|
||||
|
||||
self.start_watcher() # 启动文件监视器
|
||||
|
||||
def start_watcher(self):
|
||||
if self.config.get("debug", False):
|
||||
|
||||
src_directories = (
|
||||
"liteyuki",
|
||||
"src/liteyuki_main",
|
||||
"src/liteyuki_plugins",
|
||||
"src/nonebot_plugins",
|
||||
"src/utils",
|
||||
)
|
||||
process.start() # 启动进程
|
||||
src_excludes_extensions = ("pyc",)
|
||||
|
||||
asyncio.run(self.lifespan.after_start())
|
||||
logger.debug("轻雪重载 已启用,正在加载文件修改监测……")
|
||||
restart = self.restart_process
|
||||
|
||||
while not should_exit:
|
||||
if ProcessingManager.event.wait(1):
|
||||
logger.info("接收到重启活动信息")
|
||||
process.terminate()
|
||||
process.join(timeout_limit)
|
||||
if process.is_alive():
|
||||
logger.warning(
|
||||
f"进程 {process.pid} 在 {timeout_limit} 秒后依旧存在,强制清灭。"
|
||||
)
|
||||
process.kill()
|
||||
break
|
||||
elif process.is_alive():
|
||||
continue
|
||||
else:
|
||||
should_exit = True
|
||||
class CodeModifiedHandler(FileSystemEventHandler):
|
||||
"""
|
||||
Handler for code file changes
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _run_coroutine(*coro: Coroutine):
|
||||
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} 已修改,机器人自动重启……")
|
||||
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):
|
||||
"""
|
||||
运行协程
|
||||
Args:
|
||||
coro:
|
||||
|
||||
重启轻雪本体
|
||||
Returns:
|
||||
|
||||
"""
|
||||
# 检测是否有现有的事件循环
|
||||
new_loop = False
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
except RuntimeError:
|
||||
new_loop = True
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
if new_loop:
|
||||
for c in coro:
|
||||
loop.run_until_complete(c)
|
||||
loop.close()
|
||||
if self.call_restart_count < 1:
|
||||
executable = sys.executable
|
||||
args = sys.argv
|
||||
logger.info("正在重启 尹灵温...")
|
||||
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()
|
||||
# 进程退出后重启
|
||||
threading.Thread(
|
||||
target=os.system, args=(f"{cmd} {executable} {' '.join(args)}",)
|
||||
).start()
|
||||
sys.exit(0)
|
||||
self.call_restart_count += 1
|
||||
|
||||
else:
|
||||
for c in coro:
|
||||
loop.create_task(c)
|
||||
|
||||
@property
|
||||
def status(self) -> int:
|
||||
"""
|
||||
获取轻雪状态
|
||||
Returns:
|
||||
int: 0:未启动 1:运行中
|
||||
"""
|
||||
return 1 if self.running else 0
|
||||
|
||||
def restart(self):
|
||||
def restart_process(self, name: Optional[str] = None):
|
||||
"""
|
||||
停止轻雪
|
||||
Args:
|
||||
name: 进程名称, 默认为None, 所有进程
|
||||
Returns:
|
||||
|
||||
"""
|
||||
logger.info("正在停止灵温活动…")
|
||||
logger.info("Stopping LiteyukiBot...")
|
||||
|
||||
logger.debug("正在启动 before_restart 的函数…")
|
||||
self._run_coroutine(self.lifespan.before_restart())
|
||||
logger.debug("正在启动 before_shutdown 的函数…")
|
||||
self._run_coroutine(self.lifespan.before_shutdown())
|
||||
self.loop.create_task(self.lifespan.before_shutdown()) # 重启前钩子
|
||||
self.loop.create_task(self.lifespan.before_shutdown()) # 停止前钩子
|
||||
|
||||
ProcessingManager.restart()
|
||||
self.running = False
|
||||
if name:
|
||||
self.process_manager.terminate(name)
|
||||
else:
|
||||
self.process_manager.terminate_all()
|
||||
|
||||
def init(self, *args, **kwargs):
|
||||
"""
|
||||
@ -151,20 +141,14 @@ class LiteyukiBot:
|
||||
"""
|
||||
self.init_config()
|
||||
self.init_logger()
|
||||
if not _MAIN_PROCESS:
|
||||
nonebot.init(**kwargs)
|
||||
asyncio.run(self.lifespan.after_nonebot_init())
|
||||
|
||||
def init_logger(self):
|
||||
from src.utils.base.log import init_log
|
||||
init_log()
|
||||
# 修改nonebot的日志配置
|
||||
init_log(config=self.config)
|
||||
|
||||
def init_config(self):
|
||||
pass
|
||||
|
||||
def register_adapters(self, *args):
|
||||
pass
|
||||
|
||||
def on_before_start(self, func: LIFESPAN_FUNC):
|
||||
"""
|
||||
注册启动前的函数
|
||||
@ -189,7 +173,7 @@ class LiteyukiBot:
|
||||
|
||||
def on_before_shutdown(self, func: LIFESPAN_FUNC):
|
||||
"""
|
||||
注册停止前的函数
|
||||
注册停止前的函数,为子进程停止时调用
|
||||
Args:
|
||||
func:
|
||||
|
||||
@ -211,7 +195,7 @@ class LiteyukiBot:
|
||||
|
||||
def on_before_restart(self, func: LIFESPAN_FUNC):
|
||||
"""
|
||||
注册重启前的函数
|
||||
注册重启前的函数,为子进程重启时调用
|
||||
Args:
|
||||
func:
|
||||
|
||||
@ -253,4 +237,8 @@ def get_bot() -> Optional[LiteyukiBot]:
|
||||
Returns:
|
||||
LiteyukiBot: 当前的轻雪实例
|
||||
"""
|
||||
return _BOT_INSTANCE
|
||||
if IS_MAIN_PROCESS:
|
||||
return _BOT_INSTANCE
|
||||
else:
|
||||
# 从多进程上下文中获取
|
||||
pass
|
||||
|
@ -1,9 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All rights reserved
|
||||
|
||||
版权所有 © 2020-2024 神羽SnowyKami & 金羿Eilles with LiteyukiStudio & TriM Org.
|
||||
保留所有权利
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/7/23 下午8:24
|
||||
@Author : snowykami
|
||||
@ -13,6 +10,7 @@ Copyright (C) 2020-2024 LiteyukiStudio. All rights reserved
|
||||
"""
|
||||
from typing import Any, Awaitable, Callable, TypeAlias
|
||||
|
||||
from liteyuki.log import logger
|
||||
from liteyuki.utils import is_coroutine_callable
|
||||
|
||||
SYNC_LIFESPAN_FUNC: TypeAlias = Callable[[], Any]
|
||||
@ -138,6 +136,7 @@ class Lifespan:
|
||||
启动前
|
||||
Returns:
|
||||
"""
|
||||
logger.debug("正在运行 before_start 之函数")
|
||||
await self._run_funcs(self._before_start_funcs)
|
||||
|
||||
async def after_start(self) -> None:
|
||||
@ -145,6 +144,7 @@ class Lifespan:
|
||||
启动后
|
||||
Returns:
|
||||
"""
|
||||
logger.debug("正在运行 after_start 之函数")
|
||||
await self._run_funcs(self._after_start_funcs)
|
||||
|
||||
async def before_shutdown(self) -> None:
|
||||
@ -152,6 +152,7 @@ class Lifespan:
|
||||
停止前
|
||||
Returns:
|
||||
"""
|
||||
logger.debug("正在运行 before_shutdown 之函数")
|
||||
await self._run_funcs(self._before_shutdown_funcs)
|
||||
|
||||
async def after_shutdown(self) -> None:
|
||||
@ -159,6 +160,7 @@ class Lifespan:
|
||||
停止后
|
||||
Returns:
|
||||
"""
|
||||
logger.debug("正在运行 after_shutdown 之函数")
|
||||
await self._run_funcs(self._after_shutdown_funcs)
|
||||
|
||||
async def before_restart(self) -> None:
|
||||
@ -166,6 +168,7 @@ class Lifespan:
|
||||
重启前
|
||||
Returns:
|
||||
"""
|
||||
logger.debug("正在运行 before_restart 之函数")
|
||||
await self._run_funcs(self._before_restart_funcs)
|
||||
|
||||
async def after_restart(self) -> None:
|
||||
@ -174,6 +177,7 @@ class Lifespan:
|
||||
Returns:
|
||||
|
||||
"""
|
||||
logger.debug("正在运行 after_restart 之函数")
|
||||
await self._run_funcs(self._after_restart_funcs)
|
||||
|
||||
async def after_nonebot_init(self) -> None:
|
||||
@ -181,4 +185,5 @@ class Lifespan:
|
||||
NoneBot 初始化后
|
||||
Returns:
|
||||
"""
|
||||
logger.debug("正在运行 after_nonebot_init 之函数")
|
||||
await self._run_funcs(self._after_nonebot_init_funcs)
|
||||
|
30
liteyuki/comm/__init__.py
Normal file
30
liteyuki/comm/__init__.py
Normal file
@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/7/26 下午10:36
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : __init__.py
|
||||
@Software: PyCharm
|
||||
该模块用于轻雪主进程和Nonebot子进程之间的通信
|
||||
"""
|
||||
from liteyuki.comm.channel import (
|
||||
Channel,
|
||||
chan,
|
||||
get_channel,
|
||||
set_channel,
|
||||
set_channels,
|
||||
get_channels
|
||||
)
|
||||
from liteyuki.comm.event import Event
|
||||
|
||||
__all__ = [
|
||||
"Channel",
|
||||
"chan",
|
||||
"Event",
|
||||
"get_channel",
|
||||
"set_channel",
|
||||
"set_channels",
|
||||
"get_channels"
|
||||
]
|
219
liteyuki/comm/channel.py
Normal file
219
liteyuki/comm/channel.py
Normal file
@ -0,0 +1,219 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/7/26 下午11:21
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : channel.py
|
||||
@Software: PyCharm
|
||||
|
||||
本模块定义了一个通用的通道类,用于进程间通信
|
||||
"""
|
||||
import functools
|
||||
import multiprocessing
|
||||
import threading
|
||||
from multiprocessing import Pipe
|
||||
from typing import Any, Optional, Callable, Awaitable, List, TypeAlias
|
||||
from uuid import uuid4
|
||||
|
||||
from liteyuki.utils import is_coroutine_callable, run_coroutine
|
||||
|
||||
SYNC_ON_RECEIVE_FUNC: TypeAlias = Callable[[Any], Any]
|
||||
ASYNC_ON_RECEIVE_FUNC: TypeAlias = Callable[[Any], Awaitable[Any]]
|
||||
ON_RECEIVE_FUNC: TypeAlias = SYNC_ON_RECEIVE_FUNC | ASYNC_ON_RECEIVE_FUNC
|
||||
|
||||
SYNC_FILTER_FUNC: TypeAlias = Callable[[Any], bool]
|
||||
ASYNC_FILTER_FUNC: TypeAlias = Callable[[Any], Awaitable[bool]]
|
||||
FILTER_FUNC: TypeAlias = SYNC_FILTER_FUNC | ASYNC_FILTER_FUNC
|
||||
|
||||
IS_MAIN_PROCESS = multiprocessing.current_process().name == "MainProcess"
|
||||
|
||||
_channel: dict[str, "Channel"] = {}
|
||||
_callback_funcs: dict[str, ON_RECEIVE_FUNC] = {}
|
||||
|
||||
|
||||
class Channel:
|
||||
"""
|
||||
通道类,用于进程间通信,进程内不可用,仅限主进程和子进程之间通信
|
||||
有两种接收工作方式,但是只能选择一种,主动接收和被动接收,主动接收使用 `receive` 方法,被动接收使用 `on_receive` 装饰器
|
||||
"""
|
||||
|
||||
def __init__(self, _id: str):
|
||||
self.main_send_conn, self.sub_receive_conn = Pipe()
|
||||
self.sub_send_conn, self.main_receive_conn = Pipe()
|
||||
self._closed = False
|
||||
self._on_main_receive_funcs: list[str] = []
|
||||
self._on_sub_receive_funcs: list[str] = []
|
||||
self.name: str = _id
|
||||
|
||||
self.is_main_receive_loop_running = False
|
||||
self.is_sub_receive_loop_running = False
|
||||
|
||||
def __str__(self):
|
||||
return f"Channel({self.name})"
|
||||
|
||||
def send(self, data: Any):
|
||||
"""
|
||||
发送数据
|
||||
Args:
|
||||
data: 数据
|
||||
"""
|
||||
if self._closed:
|
||||
raise RuntimeError("无法发送至已关闭的通道中")
|
||||
if IS_MAIN_PROCESS:
|
||||
print("主进程发送数据:", data)
|
||||
self.main_send_conn.send(data)
|
||||
else:
|
||||
print("子进程发送数据:", data)
|
||||
self.sub_send_conn.send(data)
|
||||
|
||||
def receive(self) -> Any:
|
||||
"""
|
||||
接收数据
|
||||
Args:
|
||||
"""
|
||||
if self._closed:
|
||||
raise RuntimeError("无法从已关闭的通道中接收")
|
||||
|
||||
while True:
|
||||
# 判断receiver是否为None或者receiver是否等于接收者,是则接收数据,否则不动数据
|
||||
if IS_MAIN_PROCESS:
|
||||
data = self.main_receive_conn.recv()
|
||||
print("主进程接收数据:", data)
|
||||
else:
|
||||
data = self.sub_receive_conn.recv()
|
||||
print("子进程接收数据:", data)
|
||||
|
||||
return data
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
关闭通道
|
||||
"""
|
||||
self._closed = True
|
||||
self.sub_receive_conn.close()
|
||||
self.main_send_conn.close()
|
||||
self.sub_send_conn.close()
|
||||
self.main_receive_conn.close()
|
||||
|
||||
def on_receive(self, filter_func: Optional[FILTER_FUNC] = None) -> Callable[[ON_RECEIVE_FUNC], ON_RECEIVE_FUNC]:
|
||||
"""
|
||||
接收数据并执行函数
|
||||
Args:
|
||||
filter_func: 过滤函数,为None则不过滤
|
||||
Returns:
|
||||
装饰器,装饰一个函数在接收到数据后执行
|
||||
"""
|
||||
if (not self.is_sub_receive_loop_running) and not IS_MAIN_PROCESS:
|
||||
threading.Thread(target=self._start_sub_receive_loop).start()
|
||||
|
||||
if (not self.is_main_receive_loop_running) and IS_MAIN_PROCESS:
|
||||
threading.Thread(target=self._start_main_receive_loop).start()
|
||||
|
||||
def decorator(func: ON_RECEIVE_FUNC) -> ON_RECEIVE_FUNC:
|
||||
async def wrapper(data: Any) -> Any:
|
||||
if filter_func is not None:
|
||||
if is_coroutine_callable(filter_func):
|
||||
if not await filter_func(data):
|
||||
return
|
||||
else:
|
||||
if not filter_func(data):
|
||||
return
|
||||
return await func(data)
|
||||
|
||||
function_id = str(uuid4())
|
||||
_callback_funcs[function_id] = wrapper
|
||||
if IS_MAIN_PROCESS:
|
||||
self._on_main_receive_funcs.append(function_id)
|
||||
else:
|
||||
self._on_sub_receive_funcs.append(function_id)
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
def _run_on_main_receive_funcs(self, data: Any):
|
||||
"""
|
||||
运行接收函数
|
||||
Args:
|
||||
data: 数据
|
||||
"""
|
||||
for func_id in self._on_main_receive_funcs:
|
||||
func = _callback_funcs[func_id]
|
||||
run_coroutine(func(data))
|
||||
|
||||
def _run_on_sub_receive_funcs(self, data: Any):
|
||||
"""
|
||||
运行接收函数
|
||||
Args:
|
||||
data: 数据
|
||||
"""
|
||||
for func_id in self._on_sub_receive_funcs:
|
||||
func = _callback_funcs[func_id]
|
||||
run_coroutine(func(data))
|
||||
|
||||
def _start_main_receive_loop(self):
|
||||
"""
|
||||
开始接收数据
|
||||
"""
|
||||
self.is_main_receive_loop_running = True
|
||||
while not self._closed:
|
||||
data = self.main_receive_conn.recv()
|
||||
self._run_on_main_receive_funcs(data)
|
||||
|
||||
def _start_sub_receive_loop(self):
|
||||
"""
|
||||
开始接收数据
|
||||
"""
|
||||
self.is_sub_receive_loop_running = True
|
||||
while not self._closed:
|
||||
data = self.sub_receive_conn.recv()
|
||||
self._run_on_sub_receive_funcs(data)
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self) -> Any:
|
||||
return self.receive()
|
||||
|
||||
|
||||
"""默认通道实例,可直接从模块导入使用"""
|
||||
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
|
21
liteyuki/comm/event.py
Normal file
21
liteyuki/comm/event.py
Normal file
@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/7/26 下午10:47
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : event.py
|
||||
@Software: PyCharm
|
||||
"""
|
||||
from typing import Any
|
||||
|
||||
|
||||
class Event:
|
||||
"""
|
||||
事件类
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, data: dict[str, Any]):
|
||||
self.name = name
|
||||
self.data = data
|
@ -0,0 +1,49 @@
|
||||
import os
|
||||
from typing import List
|
||||
|
||||
import nonebot
|
||||
import yaml
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
config = {} # 主进程全局配置,确保加载后读取
|
||||
|
||||
|
||||
class SatoriNodeConfig(BaseModel):
|
||||
host: str = ""
|
||||
port: str = "5500"
|
||||
path: str = ""
|
||||
token: str = ""
|
||||
|
||||
|
||||
class SatoriConfig(BaseModel):
|
||||
comment: str = "此皆正处于开发之中,切勿在生产环境中启用。"
|
||||
enable: bool = False
|
||||
hosts: List[SatoriNodeConfig] = [SatoriNodeConfig()]
|
||||
|
||||
|
||||
class BasicConfig(BaseModel):
|
||||
host: str = "127.0.0.1"
|
||||
port: int = 20247
|
||||
superusers: list[str] = []
|
||||
command_start: list[str] = ["/", ""]
|
||||
nickname: list[str] = [f"灵温"]
|
||||
satori: SatoriConfig = SatoriConfig()
|
||||
data_path: str = "data/liteyuki"
|
||||
|
||||
|
||||
def load_from_yaml(file: str) -> dict:
|
||||
global config
|
||||
nonebot.logger.debug("Loading config from %s" % file)
|
||||
if not os.path.exists(file):
|
||||
nonebot.logger.warning(f"未找到配置文件 {file} ,已创建默认配置,请修改后重启。")
|
||||
with open(file, "w", encoding="utf-8") as f:
|
||||
yaml.dump(BasicConfig().dict(), f, default_flow_style=False)
|
||||
|
||||
with open(file, "r", encoding="utf-8") as f:
|
||||
conf = yaml.load(f, Loader=yaml.FullLoader)
|
||||
config = conf
|
||||
if conf is None:
|
||||
nonebot.logger.warning(f"配置文件 {file} 为空,已创建默认配置,请修改后重启。")
|
||||
conf = BasicConfig().dict()
|
||||
return conf
|
||||
|
@ -1,3 +1,11 @@
|
||||
import multiprocessing
|
||||
|
||||
from .spawn_process import *
|
||||
from .manager import *
|
||||
|
||||
__all__ = [
|
||||
"IS_MAIN_PROCESS"
|
||||
]
|
||||
|
||||
IS_MAIN_PROCESS = multiprocessing.current_process().name == "MainProcess"
|
||||
|
||||
|
120
liteyuki/core/manager.py
Normal file
120
liteyuki/core/manager.py
Normal file
@ -0,0 +1,120 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/7/27 上午11:12
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : manager.py
|
||||
@Software: PyCharm
|
||||
"""
|
||||
import asyncio
|
||||
import threading
|
||||
from multiprocessing import Process
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from liteyuki.comm import Channel, get_channel, set_channels
|
||||
from liteyuki.log import logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from liteyuki.bot import LiteyukiBot
|
||||
|
||||
TIMEOUT = 10
|
||||
|
||||
__all__ = ["ProcessManager"]
|
||||
|
||||
|
||||
class ProcessManager:
|
||||
"""
|
||||
在主进程中被调用
|
||||
"""
|
||||
|
||||
def __init__(self, bot: "LiteyukiBot"):
|
||||
self.bot = bot
|
||||
self.targets: dict[str, tuple[callable, tuple, dict]] = {}
|
||||
self.processes: dict[str, Process] = {}
|
||||
|
||||
set_channels(
|
||||
{
|
||||
"nonebot-active": Channel(_id="nonebot-active"),
|
||||
"melobot-active": Channel(_id="melobot-active"),
|
||||
"nonebot-passive": Channel(_id="nonebot-passive"),
|
||||
"melobot-passive": Channel(_id="melobot-passive"),
|
||||
}
|
||||
)
|
||||
|
||||
def start(self, name: str, delay: int = 0):
|
||||
"""
|
||||
开启后自动监控进程,并添加到进程字典中
|
||||
Args:
|
||||
name:
|
||||
delay:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if name not in self.targets:
|
||||
raise KeyError(f"未有 Process {name} 之存在")
|
||||
|
||||
def _start():
|
||||
should_exit = False
|
||||
while not should_exit:
|
||||
chan_active = get_channel(f"{name}-active")
|
||||
chan_passive = get_channel(f"{name}-passive")
|
||||
process = Process(
|
||||
target=self.targets[name][0],
|
||||
args=(chan_active, chan_passive, *self.targets[name][1]),
|
||||
kwargs=self.targets[name][2],
|
||||
)
|
||||
self.processes[name] = process
|
||||
process.start()
|
||||
while not should_exit:
|
||||
# 0退出 1重启
|
||||
data = chan_active.receive()
|
||||
if data == 1:
|
||||
logger.info(f"重启 {name} 进程")
|
||||
asyncio.run(self.bot.lifespan.before_shutdown())
|
||||
asyncio.run(self.bot.lifespan.before_restart())
|
||||
self.terminate(name)
|
||||
break
|
||||
|
||||
elif data == 0:
|
||||
logger.info(f"关停 {name} 进程")
|
||||
asyncio.run(self.bot.lifespan.before_shutdown())
|
||||
should_exit = True
|
||||
self.terminate(name)
|
||||
else:
|
||||
logger.warning("数据未知,省略:{}".format(data))
|
||||
|
||||
if delay:
|
||||
threading.Timer(delay, _start).start()
|
||||
else:
|
||||
threading.Thread(target=_start).start()
|
||||
|
||||
def add_target(self, name: str, target, *args, **kwargs):
|
||||
self.targets[name] = (target, args, kwargs)
|
||||
|
||||
def join(self):
|
||||
for name, process in self.targets:
|
||||
process.join()
|
||||
|
||||
def terminate(self, name: str):
|
||||
"""
|
||||
终止进程并从进程字典中删除
|
||||
Args:
|
||||
name:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if name not in self.targets:
|
||||
raise logger.warning(f"未有 Process {name} 之存在")
|
||||
process = self.processes[name]
|
||||
process.terminate()
|
||||
process.join(TIMEOUT)
|
||||
if process.is_alive():
|
||||
process.kill()
|
||||
|
||||
def terminate_all(self):
|
||||
for name in self.targets:
|
||||
self.terminate(name)
|
14
liteyuki/core/nb/adapter_manager/__init__.py
Normal file
14
liteyuki/core/nb/adapter_manager/__init__.py
Normal file
@ -0,0 +1,14 @@
|
||||
from . import (
|
||||
satori,
|
||||
onebot
|
||||
)
|
||||
|
||||
|
||||
def init(config: dict):
|
||||
onebot.init()
|
||||
satori.init(config)
|
||||
|
||||
|
||||
def register():
|
||||
onebot.register()
|
||||
satori.register()
|
12
liteyuki/core/nb/adapter_manager/onebot.py
Normal file
12
liteyuki/core/nb/adapter_manager/onebot.py
Normal file
@ -0,0 +1,12 @@
|
||||
import nonebot
|
||||
from nonebot.adapters.onebot import v11, v12
|
||||
|
||||
|
||||
def init():
|
||||
pass
|
||||
|
||||
|
||||
def register():
|
||||
driver = nonebot.get_driver()
|
||||
driver.register_adapter(v11.Adapter)
|
||||
driver.register_adapter(v12.Adapter)
|
26
liteyuki/core/nb/adapter_manager/satori.py
Normal file
26
liteyuki/core/nb/adapter_manager/satori.py
Normal file
@ -0,0 +1,26 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
import nonebot
|
||||
from nonebot.adapters import satori
|
||||
|
||||
|
||||
def init(config: dict):
|
||||
if config.get("satori", None) is None:
|
||||
nonebot.logger.info("未查见 Satori 的配置文档,将跳过 Satori 初始化")
|
||||
return None
|
||||
satori_config = config.get("satori")
|
||||
if not satori_config.get("enable", False):
|
||||
nonebot.logger.info("未启用 Satori ,将跳过 Satori 初始化")
|
||||
return None
|
||||
if os.getenv("SATORI_CLIENTS", None) is not None:
|
||||
nonebot.logger.info("Satori 客户端已设入环境变量,跳过此步。")
|
||||
os.environ["SATORI_CLIENTS"] = json.dumps(satori_config.get("hosts", []), ensure_ascii=False)
|
||||
config['satori_clients'] = satori_config.get("hosts", [])
|
||||
return
|
||||
|
||||
|
||||
def register():
|
||||
if os.getenv("SATORI_CLIENTS", None) is not None:
|
||||
driver = nonebot.get_driver()
|
||||
driver.register_adapter(satori.Adapter)
|
6
liteyuki/core/nb/driver_manager/__init__.py
Normal file
6
liteyuki/core/nb/driver_manager/__init__.py
Normal file
@ -0,0 +1,6 @@
|
||||
from .auto_set_env import auto_set_env
|
||||
|
||||
|
||||
def init(config: dict):
|
||||
auto_set_env(config)
|
||||
return
|
20
liteyuki/core/nb/driver_manager/auto_set_env.py
Normal file
20
liteyuki/core/nb/driver_manager/auto_set_env.py
Normal file
@ -0,0 +1,20 @@
|
||||
import os
|
||||
|
||||
import dotenv
|
||||
import nonebot
|
||||
|
||||
from .defines import *
|
||||
|
||||
|
||||
def auto_set_env(config: dict):
|
||||
dotenv.load_dotenv(".env")
|
||||
if os.getenv("DRIVER", None) is not None:
|
||||
nonebot.logger.info("Driver 已设入环境变量中,将跳过自动配置环节。")
|
||||
return
|
||||
if config.get("satori", {'enable': False}).get("enable", False):
|
||||
os.environ["DRIVER"] = get_driver_string(ASGI_DRIVER, HTTPX_DRIVER, WEBSOCKETS_DRIVER)
|
||||
nonebot.logger.info("已启用 Satori,将 driver 设为 ASGI+HTTPX+WEBSOCKETS")
|
||||
else:
|
||||
os.environ["DRIVER"] = get_driver_string(ASGI_DRIVER)
|
||||
nonebot.logger.info("已禁用 Satori,将 driver 设为 ASGI")
|
||||
return
|
17
liteyuki/core/nb/driver_manager/defines.py
Normal file
17
liteyuki/core/nb/driver_manager/defines.py
Normal file
@ -0,0 +1,17 @@
|
||||
ASGI_DRIVER = "~fastapi"
|
||||
HTTPX_DRIVER = "~httpx"
|
||||
WEBSOCKETS_DRIVER = "~websockets"
|
||||
|
||||
|
||||
def get_driver_string(*argv):
|
||||
output_string = ""
|
||||
if ASGI_DRIVER in argv:
|
||||
output_string += ASGI_DRIVER
|
||||
for arg in argv:
|
||||
if arg != ASGI_DRIVER:
|
||||
output_string = f"{output_string}+{arg}"
|
||||
return output_string
|
||||
|
||||
|
||||
def get_driver_full_string(*argv):
|
||||
return f"DRIVER={get_driver_string(argv)}"
|
@ -1,37 +1,56 @@
|
||||
import threading
|
||||
from multiprocessing import get_context, Event
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
import nonebot
|
||||
from nonebot import logger
|
||||
|
||||
from liteyuki.plugin.load import load_plugins
|
||||
from liteyuki.core.nb import adapter_manager, driver_manager
|
||||
from liteyuki.comm.channel import set_channel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from liteyuki.comm.channel import Channel
|
||||
|
||||
timeout_limit: int = 20
|
||||
__all__ = [
|
||||
"ProcessingManager",
|
||||
"nb_run",
|
||||
]
|
||||
|
||||
"""导出对象,用于主进程与nonebot通信"""
|
||||
_channels = {}
|
||||
|
||||
|
||||
class ProcessingManager:
|
||||
event: Event = None
|
||||
def nb_run(chan_active: "Channel", chan_passive: "Channel", *args, **kwargs):
|
||||
"""
|
||||
初始化NoneBot并运行在子进程
|
||||
Args:
|
||||
|
||||
@classmethod
|
||||
def restart(cls, delay: int = 0):
|
||||
"""
|
||||
发送终止信号
|
||||
Args:
|
||||
delay: 延迟时间,默认为0,单位秒
|
||||
Returns:
|
||||
"""
|
||||
if cls.event is None:
|
||||
raise RuntimeError("ProcessingManager 未初始化。")
|
||||
if delay > 0:
|
||||
threading.Timer(delay, function=cls.event.set).start()
|
||||
return
|
||||
cls.event.set()
|
||||
chan_active:
|
||||
chan_passive:
|
||||
**kwargs:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
set_channel("nonebot-active", chan_active)
|
||||
set_channel("nonebot-passive", chan_passive)
|
||||
nonebot.init(**kwargs)
|
||||
driver_manager.init(config=kwargs)
|
||||
adapter_manager.init(kwargs)
|
||||
adapter_manager.register()
|
||||
nonebot.load_plugin("src.liteyuki_main")
|
||||
nonebot.run()
|
||||
|
||||
|
||||
def nb_run(event, *args, **kwargs):
|
||||
ProcessingManager.event = event
|
||||
nonebot.run(*args, **kwargs)
|
||||
def mb_run(chan_active: "Channel", chan_passive: "Channel", *args, **kwargs):
|
||||
"""
|
||||
初始化MeloBot并运行在子进程
|
||||
Args:
|
||||
chan_active
|
||||
chan_passive
|
||||
*args:
|
||||
**kwargs:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
set_channel("melobot-active", chan_active)
|
||||
set_channel("melobot-passive", chan_passive)
|
||||
|
||||
# bot = MeloBot(__name__)
|
||||
# bot.init(AbstractConnector(cd_time=0))
|
||||
# bot.run()
|
||||
|
84
liteyuki/log.py
Normal file
84
liteyuki/log.py
Normal file
@ -0,0 +1,84 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/7/27 上午9:12
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : log.py
|
||||
@Software: PyCharm
|
||||
"""
|
||||
import sys
|
||||
import loguru
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
logger = loguru.logger
|
||||
if TYPE_CHECKING:
|
||||
# avoid sphinx autodoc resolve annotation failed
|
||||
# because loguru module do not have `Logger` class actually
|
||||
from loguru import Record
|
||||
|
||||
|
||||
def default_filter(record: "Record"):
|
||||
"""默认的日志过滤器,根据 `config.log_level` 配置改变日志等级。"""
|
||||
log_level = record["extra"].get("nonebot_log_level", "INFO")
|
||||
levelno = logger.level(log_level).no if isinstance(log_level, str) else log_level
|
||||
return record["level"].no >= levelno
|
||||
|
||||
|
||||
# DEBUG日志格式
|
||||
debug_format: str = (
|
||||
"<c>{time:YYYY-MM-DD HH:mm:ss}</c> "
|
||||
"<lvl>[{level.icon}]</lvl> "
|
||||
"<c><{name}.{module}.{function}:{line}></c> "
|
||||
"{message}"
|
||||
)
|
||||
|
||||
# 默认日志格式
|
||||
default_format: str = (
|
||||
"<c>{time:MM-DD HH:mm:ss}</c> "
|
||||
"<lvl>[{level.icon}]</lvl> "
|
||||
"<c><{name}></c> "
|
||||
"{message}"
|
||||
)
|
||||
|
||||
|
||||
def get_format(level: str) -> str:
|
||||
if level == "DEBUG":
|
||||
return debug_format
|
||||
else:
|
||||
return default_format
|
||||
|
||||
|
||||
logger = loguru.logger.bind()
|
||||
|
||||
|
||||
def init_log(config: dict):
|
||||
"""
|
||||
在语言加载完成后执行
|
||||
Returns:
|
||||
|
||||
"""
|
||||
global logger
|
||||
|
||||
logger.remove()
|
||||
logger.add(
|
||||
sys.stdout,
|
||||
level=0,
|
||||
diagnose=False,
|
||||
filter=default_filter,
|
||||
format=get_format(config.get("log_level", "INFO")),
|
||||
)
|
||||
show_icon = config.get("log_icon", True)
|
||||
|
||||
# debug = lang.get("log.debug", default="==DEBUG")
|
||||
# info = lang.get("log.info", default="===INFO")
|
||||
# success = lang.get("log.success", default="SUCCESS")
|
||||
# warning = lang.get("log.warning", default="WARNING")
|
||||
# error = lang.get("log.error", default="==ERROR")
|
||||
#
|
||||
# logger.level("DEBUG", color="<blue>", icon=f"{'🐛' if show_icon else ''}{debug}")
|
||||
# logger.level("INFO", color="<normal>", icon=f"{'ℹ️' if show_icon else ''}{info}")
|
||||
# logger.level("SUCCESS", color="<green>", icon=f"{'✅' if show_icon else ''}{success}")
|
||||
# logger.level("WARNING", color="<yellow>", icon=f"{'⚠️' if show_icon else ''}{warning}")
|
||||
# logger.level("ERROR", color="<red>", icon=f"{'⭕' if show_icon else ''}{error}")
|
@ -1,10 +1,11 @@
|
||||
from liteyuki.plugin.model import Plugin, PluginMetadata
|
||||
from liteyuki.plugin.load import load_plugin, _plugins
|
||||
from liteyuki.plugin.load import load_plugin, load_plugins, _plugins
|
||||
|
||||
__all__ = [
|
||||
"PluginMetadata",
|
||||
"Plugin",
|
||||
"load_plugin",
|
||||
"load_plugins",
|
||||
]
|
||||
|
||||
|
||||
|
@ -1,9 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All rights reserved
|
||||
|
||||
版权所有 © 2020-2024 神羽SnowyKami & 金羿Eilles with LiteyukiStudio & TriM Org.
|
||||
保留所有权利
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/7/23 下午11:59
|
||||
@Author : snowykami
|
||||
@ -25,6 +22,11 @@ from liteyuki.utils import path_to_module_name
|
||||
|
||||
_plugins: dict[str, Plugin] = {}
|
||||
|
||||
__all__ = [
|
||||
"load_plugin",
|
||||
"load_plugins",
|
||||
]
|
||||
|
||||
|
||||
def load_plugin(module_path: str | Path) -> Optional[Plugin]:
|
||||
"""加载单个插件,可以是本地插件或是通过 `pip` 安装的插件。
|
||||
|
@ -1,9 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All rights reserved
|
||||
|
||||
版权所有 © 2020-2024 神羽SnowyKami & 金羿Eilles with LiteyukiStudio & TriM Org.
|
||||
保留所有权利
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/7/24 上午12:02
|
||||
@Author : snowykami
|
||||
@ -19,10 +16,10 @@ from pydantic import BaseModel
|
||||
|
||||
class PluginMetadata(BaseModel):
|
||||
"""
|
||||
轻雪插件元数据,由插件编写者提供
|
||||
轻雪插件元数据,由插件编写者提供,name为必填项
|
||||
"""
|
||||
name: str
|
||||
description: str
|
||||
description: str = ""
|
||||
usage: str = ""
|
||||
type: str = ""
|
||||
homepage: str = ""
|
||||
|
55
liteyuki/plugins/lifespan_monitor.py
Normal file
55
liteyuki/plugins/lifespan_monitor.py
Normal file
@ -0,0 +1,55 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
#
|
||||
# @Time : 2024/7/22 上午11:25
|
||||
# @Author : snowykami
|
||||
# @Email : snowykami@outlook.com
|
||||
# @File : asa.py
|
||||
# @Software: PyCharm
|
||||
import asyncio
|
||||
|
||||
from liteyuki.plugin import PluginMetadata
|
||||
from liteyuki import get_bot, logger
|
||||
from liteyuki.comm.channel import get_channel
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="lifespan_monitor",
|
||||
)
|
||||
|
||||
bot = get_bot()
|
||||
nbp_chan = get_channel("nonebot-passive")
|
||||
mbp_chan = get_channel("melobot-passive")
|
||||
|
||||
|
||||
@bot.on_before_start
|
||||
def _():
|
||||
logger.info("生命周期监控器:启动前")
|
||||
|
||||
|
||||
@bot.on_before_shutdown
|
||||
def _():
|
||||
print(get_channel("main"))
|
||||
logger.info("生命周期监控器:停止前")
|
||||
|
||||
|
||||
@bot.on_before_restart
|
||||
def _():
|
||||
logger.info("生命周期监控器:重启前")
|
||||
|
||||
|
||||
@bot.on_after_start
|
||||
def _():
|
||||
logger.info("生命周期监控器:启动后")
|
||||
|
||||
|
||||
@bot.on_after_start
|
||||
async def _():
|
||||
logger.info("生命周期监控器:启动后")
|
||||
|
||||
|
||||
|
||||
# @mbp_chan.on_receive()
|
||||
# @nbp_chan.on_receive()
|
||||
# async def _(data):
|
||||
# print("主进程收到数据", data)
|
@ -1,41 +0,0 @@
|
||||
import multiprocessing
|
||||
import time
|
||||
|
||||
import nonebot
|
||||
from nonebot import get_driver
|
||||
|
||||
from liteyuki.plugin import PluginMetadata
|
||||
from liteyuki import get_bot
|
||||
|
||||
__plugin_metadata__ = PluginMetadata(
|
||||
name="plugin_loader",
|
||||
description="轻雪插件加载器",
|
||||
usage="",
|
||||
type="",
|
||||
homepage=""
|
||||
)
|
||||
|
||||
from src.utils import TempConfig, common_db
|
||||
|
||||
liteyuki = get_bot()
|
||||
|
||||
|
||||
@liteyuki.on_after_start
|
||||
def _():
|
||||
temp_data = common_db.where_one(TempConfig(), default=TempConfig())
|
||||
# 储存重启计时信息
|
||||
if temp_data.data.get("reload", False):
|
||||
delta_time = time.time() - temp_data.data.get("reload_time", 0)
|
||||
temp_data.data["delta_time"] = delta_time
|
||||
common_db.save(temp_data) # 更新数据
|
||||
|
||||
|
||||
@liteyuki.on_before_start
|
||||
def _():
|
||||
print("灵温正在启动")
|
||||
|
||||
|
||||
@liteyuki.on_after_nonebot_init
|
||||
async def _():
|
||||
print("NoneBot初始化完成")
|
||||
nonebot.load_plugin("src.liteyuki_main")
|
@ -1,13 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All rights reserved
|
||||
|
||||
版权所有 © 2020-2024 神羽SnowyKami & 金羿Eilles with LiteyukiStudio & TriM Org.
|
||||
保留所有权利
|
||||
|
||||
@Time : 2024/7/23 下午11:21
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : data_source.py
|
||||
@Software: PyCharm
|
||||
"""
|
57
liteyuki/plugins/register_service.py
Normal file
57
liteyuki/plugins/register_service.py
Normal file
@ -0,0 +1,57 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/8/10 下午11:25
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : register_service.py
|
||||
@Software: PyCharm
|
||||
"""
|
||||
import json
|
||||
import os.path
|
||||
import platform
|
||||
|
||||
import requests
|
||||
from git import Repo
|
||||
from liteyuki.plugin import PluginMetadata
|
||||
from liteyuki import get_bot, logger
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="注册服务",
|
||||
)
|
||||
|
||||
liteyuki = get_bot()
|
||||
commit_hash = Repo(".").head.commit.hexsha
|
||||
|
||||
|
||||
def register_bot():
|
||||
url = "https://api.liteyuki.icu/register"
|
||||
data = {
|
||||
"name" : "尹灵温|轻雪-睿乐",
|
||||
"version" : "即时更新",
|
||||
"hash" : commit_hash,
|
||||
"version_i": 99,
|
||||
"python" : f"{platform.python_implementation()} {platform.python_version()}",
|
||||
"os" : f"{platform.system()} {platform.version()} {platform.machine()}"
|
||||
}
|
||||
try:
|
||||
logger.info("正在等待 Liteyuki 注册服务器……")
|
||||
resp = requests.post(url, json=data, timeout=(10, 15))
|
||||
if resp.status_code == 200:
|
||||
data = resp.json()
|
||||
if liteyuki_id := data.get("liteyuki_id"):
|
||||
with open("data/liteyuki/liteyuki.json", "wb") as f:
|
||||
f.write(json.dumps(data).encode("utf-8"))
|
||||
logger.success("成功将 {} 注册到 Liteyuki 服务器".format(liteyuki_id))
|
||||
else:
|
||||
raise ValueError(f"无法向 Liteyuki 服务器注册:{data}")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"虽然向 Liteyuki 服务器注册失败,但无关紧要:{e}")
|
||||
|
||||
|
||||
@liteyuki.on_before_start
|
||||
async def _():
|
||||
if not os.path.exists("data/liteyuki/liteyuki.json"):
|
||||
register_bot()
|
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
|
||||
"""
|
@ -2,9 +2,12 @@
|
||||
"""
|
||||
一些常用的工具类,部分来源于 nonebot 并遵循其许可进行修改
|
||||
"""
|
||||
import asyncio
|
||||
import inspect
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable
|
||||
from typing import Any, Callable, Coroutine
|
||||
|
||||
from liteyuki.log import logger
|
||||
|
||||
|
||||
def is_coroutine_callable(call: Callable[..., Any]) -> bool:
|
||||
@ -23,6 +26,39 @@ def is_coroutine_callable(call: Callable[..., Any]) -> bool:
|
||||
return inspect.iscoroutinefunction(func_)
|
||||
|
||||
|
||||
def run_coroutine(*coro: Coroutine):
|
||||
"""
|
||||
运行协程
|
||||
Args:
|
||||
coro:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
|
||||
# 检测是否有现有的事件循环
|
||||
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
if loop.is_running():
|
||||
# 如果事件循环正在运行,创建任务
|
||||
for c in coro:
|
||||
asyncio.ensure_future(c)
|
||||
else:
|
||||
# 如果事件循环未运行,运行直到完成
|
||||
for c in coro:
|
||||
loop.run_until_complete(c)
|
||||
except RuntimeError:
|
||||
# 如果没有找到事件循环,创建一个新的
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
loop.run_until_complete(asyncio.gather(*coro))
|
||||
loop.close()
|
||||
except Exception as e:
|
||||
# 捕获其他异常,防止协程被重复等待
|
||||
logger.error(f"协程异常:{e}")
|
||||
|
||||
|
||||
def path_to_module_name(path: Path) -> str:
|
||||
"""
|
||||
转换路径为模块名
|
||||
|
Reference in New Issue
Block a user