🐛 fix 通道无法在进程内传递消息的问题

This commit is contained in:
远野千束 2024-08-12 02:40:51 +08:00
parent c3fc5d429b
commit 37b8d969b1
36 changed files with 430 additions and 238 deletions

13
.gitignore vendored
View File

@ -1,6 +1,7 @@
.venv/ .venv/
.idea/ .idea/
.cache/ .cache/
node_modules/ node_modules/
data/ data/
db/ db/
@ -11,19 +12,23 @@ __pycache__/
*.pyd *.pyd
*.pyw *.pyw
/plugins/ /plugins/
#config
config/
!config/default.yml
_config.yml _config.yml
config.yml config.yml
config.example.yml config.example.yml
compile.bat
src/resources/templates/latest-debug.html
# vuepress # vuepress
.github .github
test.py # mupy
line_count.py
mypy.ini mypy.ini
# nuitka # nuitka
compile.bat
src/resources/templates/latest-debug.html
main.build/ main.build/
main.dist/ main.dist/
main.exe main.exe

View File

@ -9,7 +9,7 @@ bgImageDark:
bgImageStyle: bgImageStyle:
background-attachment: fixed background-attachment: fixed
heroText: LiteyukiBot heroText: LiteyukiBot
tagline: LiteyukiBot 轻雪机器人,基于NoneBot2构建的综合应用型聊天机器人 tagline: LiteyukiBot 轻雪机器人,综合性的机器人应用及管理框架
actions: actions:
- text: 快速部署 - text: 快速部署
@ -30,9 +30,9 @@ highlights:
background-repeat: repeat background-repeat: repeat
background-size: initial background-size: initial
features: features:
- title: 基于NoneBot2 - title: 支持多种框架
icon: robot icon: robot
details: 拥有良好的生态支持 details: 兼容nonebotmelobot等拥有良好的生态支持
link: https://nonebot.dev/ link: https://nonebot.dev/
- title: 便捷管理 - title: 便捷管理

View File

@ -8,8 +8,11 @@ tag:
- 部署 - 部署
--- ---
首次运行后生成`config.yml`,你可以修改配置项后重启轻雪,绝大多数情况下,你只需要修改`superusers`及`nickname`字段即可 轻雪支持`yaml`、`json`和`toml`作为配置文件,取决于你个人的喜好
首次运行后生成`config.yml`和`config`目录,你可修改配置项后重启轻雪,绝大多数情况下,你只需要修改`superusers`及`nickname`字段即可
启动时会加载项目目录下`config.yml/yaml/json/toml`和`config`目录下的所有配置文件,你可在`config`目录下创建多个配置文件,轻雪会自动合并这些配置文件
## **基础配置项** ## **基础配置项**
```yaml ```yaml

View File

@ -1,6 +1,7 @@
from liteyuki.bot import ( from liteyuki.bot import (
LiteyukiBot, LiteyukiBot,
get_bot get_bot,
get_config
) )
from liteyuki.comm import ( from liteyuki.comm import (

View File

@ -4,21 +4,22 @@ import platform
import sys import sys
import threading import threading
import time import time
from typing import Any, Optional from typing import Any, Iterable, Optional
from watchdog.events import FileSystemEventHandler from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer from watchdog.observers import Observer
from liteyuki.bot.lifespan import (LIFESPAN_FUNC, Lifespan) from liteyuki.bot.lifespan import (LIFESPAN_FUNC, Lifespan)
from liteyuki.comm import get_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.log import init_log, logger from liteyuki.log import init_log, logger
from liteyuki.plugin import load_plugins from liteyuki.plugin import load_plugins
__all__ = [ __all__ = [
"LiteyukiBot", "LiteyukiBot",
"get_bot" "get_bot",
"get_config",
] ]
@ -26,15 +27,18 @@ class LiteyukiBot:
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
global _BOT_INSTANCE global _BOT_INSTANCE
_BOT_INSTANCE = self # 引用 _BOT_INSTANCE = self # 引用
self.config: dict[str, Any] = kwargs
self.init(**self.config) # 初始化
self.lifespan: Lifespan = Lifespan() self.lifespan = Lifespan()
self.config: dict[str, Any] = kwargs
self.init(**self.config) # 初始化
self.process_manager: ProcessManager = ProcessManager(bot=self) 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.stop_event = threading.Event()
self.call_restart_count = 0 self.call_restart_count = 0
print("\033[34m" + r""" print("\033[34m" + r"""
@ -48,58 +52,22 @@ $$ |_____ _$$ |_ $$ | $$ |_____ $$ | $$ \__$$ |$$ |$$ \ _$$ |_
$$ |/ $$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |/ $$ | $$ |/ $$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |/ $$ |
$$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/ $$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/
""" + "\033[0m") """ + "\033[0m")
load_plugins("liteyuki/plugins") # 加载轻雪插件
logger.info("Liteyuki is initializing...")
def run(self): def run(self):
load_plugins("liteyuki/plugins") # 加载轻雪插件 """
启动逻辑
"""
self.loop_thread.start() # 启动事件循环 self.loop_thread.start() # 启动事件循环
asyncio.run(self.lifespan.before_start()) # 启动前钩子 asyncio.run(self.lifespan.before_start()) # 启动前钩子
self.process_manager.add_target("nonebot", nb_run, **self.config)
self.process_manager.start("nonebot")
self.process_manager.add_target("melobot", mb_run, **self.config)
self.process_manager.start("melobot")
asyncio.run(self.lifespan.after_start()) # 启动后钩子 asyncio.run(self.lifespan.after_start()) # 启动后钩子
self.start_watcher() # 启动文件监视器,后续准备插件化
self.start_watcher() # 启动文件监视器 self.keep_running()
def start_watcher(self): def start_watcher(self):
if self.config.get("debug", False): pass
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): def restart(self, delay: int = 0):
""" """
@ -135,15 +103,22 @@ $$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/
Returns: Returns:
""" """
logger.info("Stopping LiteyukiBot...") logger.info(f"Stopping process {name}...")
self.loop.create_task(self.lifespan.before_shutdown()) # 重启前钩子 self.loop.create_task(self.lifespan.before_shutdown()) # 重启前钩子
self.loop.create_task(self.lifespan.before_shutdown()) # 停止前钩子 self.loop.create_task(self.lifespan.before_shutdown()) # 停止前钩子
# if name:
# self.process_manager.terminate(name)
# else:
# self.process_manager.terminate_all()
if name: if name:
self.process_manager.terminate(name) chan_active = get_channel(f"{name}-active")
chan_active.send(1)
else: else:
self.process_manager.terminate_all() for process_name in self.process_manager.processes:
chan_active = get_channel(f"{process_name}-active")
chan_active.send(1)
def init(self, *args, **kwargs): def init(self, *args, **kwargs):
""" """
@ -239,6 +214,9 @@ $$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/
""" """
return self.lifespan.on_after_nonebot_init(func) return self.lifespan.on_after_nonebot_init(func)
def keep_running(self):
self.stop_event.wait()
_BOT_INSTANCE: Optional[LiteyukiBot] = None _BOT_INSTANCE: Optional[LiteyukiBot] = None
@ -254,3 +232,37 @@ def get_bot() -> Optional[LiteyukiBot]:
else: else:
# 从多进程上下文中获取 # 从多进程上下文中获取
pass pass
def get_config(key: str, default: Any = None) -> Any:
"""
获取配置
Args:
key: 配置键
default: 默认值
Returns:
Any: 配置值
"""
return _BOT_INSTANCE.config.get(key, default)
def get_config_with_compat(key: str, compat_keys: tuple[str], default: Any = None) -> Any:
"""
获取配置兼容旧版本
Args:
key: 配置键
compat_keys: 兼容键
default: 默认值
Returns:
Any: 配置值
"""
if key in _BOT_INSTANCE.config:
return _BOT_INSTANCE.config[key]
for compat_key in compat_keys:
if compat_key in _BOT_INSTANCE.config:
logger.warning(f"Config key {compat_key} will be deprecated, use {key} instead.")
return _BOT_INSTANCE.config[compat_key]
return default

View File

@ -35,13 +35,14 @@ _callback_funcs: dict[str, ON_RECEIVE_FUNC] = {}
class Channel: class Channel:
""" """
通道类用于进程间通信进程内不可用仅限主进程和子进程之间通信 通道类可以在进程间和进程内通信双向但同时只能有一个发送者和一个接收者
有两种接收工作方式但是只能选择一种主动接收和被动接收主动接收使用 `receive` 方法被动接收使用 `on_receive` 装饰器 有两种接收工作方式但是只能选择一种主动接收和被动接收主动接收使用 `receive` 方法被动接收使用 `on_receive` 装饰器
""" """
def __init__(self, _id: str): def __init__(self, _id: str):
self.main_send_conn, self.sub_receive_conn = Pipe() # self.main_send_conn, self.sub_receive_conn = Pipe()
self.sub_send_conn, self.main_receive_conn = Pipe() # self.sub_send_conn, self.main_receive_conn = Pipe()
self.conn_send, self.conn_recv = Pipe()
self._closed = False self._closed = False
self._on_main_receive_funcs: list[str] = [] self._on_main_receive_funcs: list[str] = []
self._on_sub_receive_funcs: list[str] = [] self._on_sub_receive_funcs: list[str] = []
@ -61,12 +62,7 @@ class Channel:
""" """
if self._closed: if self._closed:
raise RuntimeError("Cannot send to a closed channel") raise RuntimeError("Cannot send to a closed channel")
if IS_MAIN_PROCESS: self.conn_send.send(data)
print("主进程发送数据:", data)
self.main_send_conn.send(data)
else:
print("子进程发送数据:", data)
self.sub_send_conn.send(data)
def receive(self) -> Any: def receive(self) -> Any:
""" """
@ -77,14 +73,7 @@ class Channel:
raise RuntimeError("Cannot receive from a closed channel") raise RuntimeError("Cannot receive from a closed channel")
while True: while True:
# 判断receiver是否为None或者receiver是否等于接收者是则接收数据否则不动数据 data = self.conn_recv.recv()
if IS_MAIN_PROCESS:
data = self.main_receive_conn.recv()
print("主进程接收数据:", data)
else:
data = self.sub_receive_conn.recv()
print("子进程接收数据:", data)
return data return data
def close(self): def close(self):
@ -92,10 +81,8 @@ class Channel:
关闭通道 关闭通道
""" """
self._closed = True self._closed = True
self.sub_receive_conn.close() self.conn_send.close()
self.main_send_conn.close() self.conn_recv.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]: def on_receive(self, filter_func: Optional[FILTER_FUNC] = None) -> Callable[[ON_RECEIVE_FUNC], ON_RECEIVE_FUNC]:
""" """
@ -158,7 +145,7 @@ class Channel:
""" """
self.is_main_receive_loop_running = True self.is_main_receive_loop_running = True
while not self._closed: while not self._closed:
data = self.main_receive_conn.recv() data = self.conn_recv.recv()
self._run_on_main_receive_funcs(data) self._run_on_main_receive_funcs(data)
def _start_sub_receive_loop(self): def _start_sub_receive_loop(self):
@ -167,7 +154,7 @@ class Channel:
""" """
self.is_sub_receive_loop_running = True self.is_sub_receive_loop_running = True
while not self._closed: while not self._closed:
data = self.sub_receive_conn.recv() data = self.conn_recv.recv()
self._run_on_sub_receive_funcs(data) self._run_on_sub_receive_funcs(data)
def __iter__(self): def __iter__(self):
@ -188,6 +175,8 @@ def set_channel(name: str, channel: Channel):
name: 通道名称 name: 通道名称
channel: 通道实例 channel: 通道实例
""" """
if not isinstance(channel, Channel):
raise TypeError(f"channel must be an instance of Channel, {type(channel)} found")
_channel[name] = channel _channel[name] = channel
@ -198,7 +187,7 @@ def set_channels(channels: dict[str, Channel]):
channels: 通道名称 channels: 通道名称
""" """
for name, channel in channels.items(): for name, channel in channels.items():
_channel[name] = channel set_channel(name, channel)
def get_channel(name: str) -> Optional[Channel]: def get_channel(name: str) -> Optional[Channel]:

View File

@ -1,12 +1,24 @@
"""
该模块用于常用配置文件的加载
多配置文件编写原则
1.尽量不要冲突: 一个键不要多次出现
2.分工明确: 每个配置文件给一个或一类服务提供配置
3.扁平化编写: 配置文件尽量扁平化不要出现过多的嵌套
4.注意冲突时的优先级: 项目目录下的配置文件优先级高于config目录下的配置文件
5.请不要将需要动态加载的内容写入配置文件你应该使用其他储存方式
"""
import copy
import json
import os import os
from typing import List from typing import Any, List
import nonebot import toml
import yaml import yaml
from pydantic import BaseModel from pydantic import BaseModel
from liteyuki import logger
config = {} # 主进程全局配置,确保加载后读取 _SUPPORTED_CONFIG_FORMATS = (".yaml", ".yml", ".json", ".toml")
class SatoriNodeConfig(BaseModel): class SatoriNodeConfig(BaseModel):
@ -32,18 +44,100 @@ class BasicConfig(BaseModel):
data_path: str = "data/liteyuki" data_path: str = "data/liteyuki"
def load_from_yaml(file: str) -> dict: def flat_config(config: dict[str, Any]) -> dict[str, Any]:
global config """
nonebot.logger.debug("Loading config from %s" % file) 扁平化配置文件
if not os.path.exists(file):
nonebot.logger.warning(f"Config file {file} not found, created default config, please modify it and restart")
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: {a:{b:{c:1}}} -> {"a.b.c": 1}
conf = yaml.load(f, Loader=yaml.FullLoader) Args:
config = conf config: 配置项目
if conf is None:
nonebot.logger.warning(f"Config file {file} is empty, use default config. please modify it and restart") Returns:
conf = BasicConfig().dict() 扁平化后的配置文件但也包含原有的键值对
return conf """
new_config = copy.deepcopy(config)
for key, value in config.items():
if isinstance(value, dict):
for k, v in flat_config(value).items():
new_config[f"{key}.{k}"] = v
return new_config
def load_from_yaml(file: str) -> dict[str, Any]:
"""
Load config from yaml file
"""
logger.debug(f"Loading YAML config from {file}")
config = yaml.safe_load(open(file, "r", encoding="utf-8"))
return flat_config(config if config is not None else {})
def load_from_json(file: str) -> dict[str, Any]:
"""
Load config from json file
"""
logger.debug(f"Loading JSON config from {file}")
config = json.load(open(file, "r", encoding="utf-8"))
return flat_config(config if config is not None else {})
def load_from_toml(file: str) -> dict[str, Any]:
"""
Load config from toml file
"""
logger.debug(f"Loading TOML config from {file}")
config = toml.load(open(file, "r", encoding="utf-8"))
return flat_config(config if config is not None else {})
def load_from_files(*files: str, no_warning: bool = False) -> dict[str, Any]:
"""
从指定文件加载配置项会自动识别文件格式
默认执行扁平化选项
"""
config = {}
for file in files:
if os.path.exists(file):
if file.endswith((".yaml", "yml")):
config.update(load_from_yaml(file))
elif file.endswith(".json"):
config.update(load_from_json(file))
elif file.endswith(".toml"):
config.update(load_from_toml(file))
else:
if not no_warning:
logger.warning(f"Unsupported config file format: {file}")
else:
if not no_warning:
logger.warning(f"Config file not found: {file}")
return config
def load_configs_from_dirs(*directories: str, no_waring: bool = False) -> dict[str, Any]:
"""
从目录下加载配置文件不递归
按照读取文件的优先级反向覆盖
默认执行扁平化选项
"""
config = {}
for directory in directories:
if not os.path.exists(directory):
if not no_waring:
logger.warning(f"Directory not found: {directory}")
continue
for file in os.listdir(directory):
if file.endswith(_SUPPORTED_CONFIG_FORMATS):
config.update(load_from_files(os.path.join(directory, file), no_warning=no_waring))
return config
def load_config_in_default(no_waring: bool = False) -> dict[str, Any]:
"""
从一个标准的轻雪项目加载配置文件
项目目录下的config.*和config目录下的所有配置文件
项目目录下的配置文件优先
"""
config = load_configs_from_dirs("config", no_waring=no_waring)
config.update(load_from_files("config.yaml", "config.toml", "config.json", "config.yml", no_warning=no_waring))
return config

View File

@ -1,6 +1,5 @@
import multiprocessing import multiprocessing
from .spawn_process import *
from .manager import * from .manager import *
__all__ = [ __all__ = [

View File

@ -36,13 +36,6 @@ class ProcessManager:
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):
""" """
开启后自动监控进程并添加到进程字典中 开启后自动监控进程并添加到进程字典中
@ -61,7 +54,7 @@ class ProcessManager:
while not should_exit: while not should_exit:
chan_active = get_channel(f"{name}-active") chan_active = get_channel(f"{name}-active")
chan_passive = get_channel(f"{name}-passive") chan_passive = get_channel(f"{name}-passive")
process = Process(target=self.targets[name][0], args=(chan_active, chan_passive, *self.targets[name][1]), process = Process(target=self.targets[name][0], args=self.targets[name][1],
kwargs=self.targets[name][2]) kwargs=self.targets[name][2])
self.processes[name] = process self.processes[name] = process
process.start() process.start()
@ -88,9 +81,30 @@ class ProcessManager:
else: else:
threading.Thread(target=_start).start() threading.Thread(target=_start).start()
def add_target(self, name: str, target, *args, **kwargs): def add_target(self, name: str, target, args: tuple = (), kwargs=None):
"""
添加进程
Args:
name: 进程名用于获取和唯一标识
target: 进程函数
args: 进程函数参数
kwargs: 进程函数关键字参数通常会默认传入chan_active和chan_passive
"""
if kwargs is None:
kwargs = {}
chan_active = Channel(_id=f"{name}-active")
chan_passive = Channel(_id=f"{name}-passive")
kwargs["chan_active"] = chan_active
kwargs["chan_passive"] = chan_passive
self.targets[name] = (target, args, kwargs) self.targets[name] = (target, args, kwargs)
set_channels(
{
f"{name}-active" : chan_active,
f"{name}-passive": chan_passive
}
)
def join(self): def join(self):
for name, process in self.targets: for name, process in self.targets:
process.join() process.join()

View File

@ -1,56 +0,0 @@
from typing import Optional, TYPE_CHECKING
import nonebot
from liteyuki.core.nb import adapter_manager, driver_manager
from liteyuki.comm.channel import set_channel
if TYPE_CHECKING:
from liteyuki.comm.channel import Channel
timeout_limit: int = 20
"""导出对象用于主进程与nonebot通信"""
_channels = {}
def nb_run(chan_active: "Channel", chan_passive: "Channel", *args, **kwargs):
"""
初始化NoneBot并运行在子进程
Args:
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 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()

4
liteyuki/dev/__init__.py Normal file
View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
"""
该模块用于存放一些开发工具
"""

5
liteyuki/dev/observer.py Normal file
View File

@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
"""
"""
import watchdog

View File

@ -44,8 +44,12 @@ def load_plugin(module_path: str | Path) -> Optional[Plugin]:
module_name=module_path, module_name=module_path,
metadata=module.__dict__.get("__plugin_metadata__", None) metadata=module.__dict__.get("__plugin_metadata__", None)
) )
display_name = module.__name__.split(".")[-1]
if module.__dict__.get("__plugin_meta__"):
metadata: "PluginMetadata" = module.__dict__["__plugin_meta__"]
display_name = f"{metadata.name}({module.__name__.split('.')[-1]})"
logger.opt(colors=True).success( logger.opt(colors=True).success(
f'Succeeded to load liteyuki plugin "<y>{module.__name__.split(".")[-1]}</y>"' f'Succeeded to load liteyuki plugin "<y>{display_name}</y>"'
) )
return _plugins[module.__name__] return _plugins[module.__name__]

View File

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
@Time : 2024/8/11 下午8:50
@Author : snowykami
@Email : snowykami@outlook.com
@File : __init__.py.py
@Software: PyCharm
"""
from liteyuki.core import IS_MAIN_PROCESS
from liteyuki.plugin import PluginMetadata
from .observer import *
__plugin_meta__ = PluginMetadata(
name="代码热重载监视",
)
if IS_MAIN_PROCESS:
config = get_config("liteyuki.reload")

View File

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
@Time : 2024/8/11 下午10:01
@Author : snowykami
@Email : snowykami@outlook.com
@File : observer.py
@Software: PyCharm
"""
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
from liteyuki import get_config, logger, get_bot
liteyuki_bot = get_bot()
if get_config("debug", False):
src_directories = (
"src/nonebot_plugins",
"src/utils",
)
src_excludes_extensions = (
"pyc",
)
logger.debug("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
logger.info(f"{event.src_path} modified, reloading bot...")
liteyuki_bot.restart_process("nonebot")
code_modified_handler = CodeModifiedHandler()
observer = Observer()
for directory in src_directories:
observer.schedule(code_modified_handler, directory, recursive=True)
observer.start()

View File

@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
@Time : 2024/8/11 下午5:24
@Author : snowykami
@Email : snowykami@outlook.com
@File : __init__.py.py
@Software: PyCharm
"""
import nonebot
from liteyuki.plugin import PluginMetadata
from liteyuki import get_bot
from liteyuki.comm import Channel, set_channel
from liteyuki.core import IS_MAIN_PROCESS
from .nb_utils import adapter_manager, driver_manager
__plugin_meta__ = PluginMetadata(
name="NoneBot2启动器",
)
liteyuki = get_bot()
def nb_run(chan_active: "Channel", chan_passive: "Channel", **kwargs):
"""
初始化NoneBot并运行在子进程
Args:
chan_active:
chan_passive:
**kwargs:
Returns:
"""
# 给子进程传递通道对象
set_channel("nonebot-active", chan_active)
set_channel("nonebot-passive", chan_passive)
kwargs.update(kwargs.get("nonebot", {})) # nonebot配置优先
nonebot.init(**kwargs)
driver_manager.init(config=kwargs)
adapter_manager.init(kwargs)
adapter_manager.register()
nonebot.load_plugin("src.liteyuki_main")
nonebot.run()
if IS_MAIN_PROCESS:
@liteyuki.on_after_start
def start_run_nonebot():
liteyuki.process_manager.add_target(name="nonebot", target=nb_run, args=(), kwargs=liteyuki.config)
liteyuki.process_manager.start("nonebot")

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
@Time : 2024/8/11 下午10:02
@Author : snowykami
@Email : snowykami@outlook.com
@File : __init__.py.py
@Software: PyCharm
"""
from liteyuki.plugin import PluginMetadata, load_plugins
__plugin_meta__ = PluginMetadata(
name="外部轻雪插件加载器",
version="0.1.0",
author="snowykami",
description="插件加载器,用于加载轻雪原生插件"
)
load_plugins("src/liteyuki_plugins")

View File

@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
@Time : 2024/7/25 上午2:28
@Author : snowykami
@Email : snowykami@outlook.com
@File : __init__.py
@Software: PyCharm
"""
# -*- 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

View File

@ -1,6 +1,9 @@
"""
启动脚本会执行一些启动的操作比如加载配置文件初始化 bot 实例等
"""
from liteyuki import LiteyukiBot from liteyuki import LiteyukiBot
from liteyuki.config import load_from_yaml from liteyuki.config import load_config_in_default
if __name__ == "__main__": if __name__ == "__main__":
bot = LiteyukiBot(**load_from_yaml("config.yml")) bot = LiteyukiBot(**load_config_in_default())
bot.run() bot.run()

View File

@ -1,3 +0,0 @@
# 说明
此目录为**轻雪插件**目录,非其他插件目录。

View File

@ -8,20 +8,22 @@
# @File : asa.py # @File : asa.py
# @Software: PyCharm # @Software: PyCharm
import asyncio import asyncio
import multiprocessing
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 from liteyuki.comm.channel import get_channel
__plugin_meta__ = PluginMetadata( __plugin_meta__ = PluginMetadata(
name="lifespan_monitor", name="生命周期日志",
) )
bot = get_bot() bot = get_bot()
nbp_chan = get_channel("nonebot-passive")
mbp_chan = get_channel("melobot-passive")
# nbp_chan = get_channel("nonebot-passive")
# mbp_chan = get_channel("melobot-passive")
@bot.on_before_start @bot.on_before_start
def _(): def _():
logger.info("生命周期监控器:准备启动") logger.info("生命周期监控器:准备启动")
@ -41,15 +43,3 @@ def _():
@bot.on_after_start @bot.on_after_start
def _(): def _():
logger.info("生命周期监控器:启动完成") logger.info("生命周期监控器:启动完成")
@bot.on_after_start
async def _():
logger.info("生命周期监控器:启动完成")
# @mbp_chan.on_receive()
# @nbp_chan.on_receive()
# async def _(data):
# print("主进程收到数据", data)

View File

@ -0,0 +1,9 @@
from liteyuki.plugin import PluginMetadata
__plugin_meta__ = PluginMetadata(
name="进程管理器",
version="0.1.0",
author="snowykami",
description="进程管理器,用于管理子进程"
)

View File

@ -2,9 +2,9 @@
""" """
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
@Time : 2024/8/10 下午5:18 @Time : 2024/8/11 下午8:22
@Author : snowykami @Author : snowykami
@Email : snowykami@outlook.com @Email : snowykami@outlook.com
@File : reloader_monitor.py @File : reloader.py.py
@Software: PyCharm @Software: PyCharm
""" """

View File

@ -17,11 +17,3 @@ __plugin_meta__ = PluginMetadata(
"default_enable": True, "default_enable": True,
} }
) )
# chan = get_channel("nonebot-passive")
#
#
# @chan.on_receive()
# async def _(d):
# print("CRT子进程接收到数据", d)
# chan.send("CRT子进程已接收到数据")

21
tests/test_config_load.py Normal file
View File

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
@Time : 2024/8/11 下午11:07
@Author : snowykami
@Email : snowykami@outlook.com
@File : test_config_load.py
@Software: PyCharm
"""
import json
import os
import sys
sys.path.insert(0, os.getcwd())
from liteyuki.config import load_config_in_default
def test_default_load():
config = load_config_in_default()
print(json.dumps(config, indent=4, ensure_ascii=False))

View File

@ -1,4 +1,4 @@
from src.liteyuki import LiteyukiBot from liteyuki import LiteyukiBot
if __name__ == "__main__": if __name__ == "__main__":
lyb = LiteyukiBot() lyb = LiteyukiBot()

View File

@ -1,16 +0,0 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
@Time : 2024/8/7 下午11:44
@Author : snowykami
@Email : snowykami@outlook.com
@File : test_dll.py
@Software: PyCharm
"""
from src.utils.extension import load_lib
a = load_lib("src/libs/ly_api")
a.Register("sss", "sss", 64, "sss", "sss")