mirror of
https://github.com/LiteyukiStudio/LiteyukiBot.git
synced 2024-11-14 13:47:24 +08:00
🐛 fix 通道无法在进程内传递消息的问题
This commit is contained in:
parent
c3fc5d429b
commit
37b8d969b1
13
.gitignore
vendored
13
.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
.venv/
|
||||
.idea/
|
||||
.cache/
|
||||
|
||||
node_modules/
|
||||
data/
|
||||
db/
|
||||
@ -11,19 +12,23 @@ __pycache__/
|
||||
*.pyd
|
||||
*.pyw
|
||||
/plugins/
|
||||
|
||||
#config
|
||||
config/
|
||||
!config/default.yml
|
||||
_config.yml
|
||||
config.yml
|
||||
config.example.yml
|
||||
compile.bat
|
||||
src/resources/templates/latest-debug.html
|
||||
|
||||
# vuepress
|
||||
.github
|
||||
|
||||
test.py
|
||||
line_count.py
|
||||
# mupy
|
||||
mypy.ini
|
||||
|
||||
# nuitka
|
||||
compile.bat
|
||||
src/resources/templates/latest-debug.html
|
||||
main.build/
|
||||
main.dist/
|
||||
main.exe
|
||||
|
@ -9,7 +9,7 @@ bgImageDark:
|
||||
bgImageStyle:
|
||||
background-attachment: fixed
|
||||
heroText: LiteyukiBot
|
||||
tagline: LiteyukiBot 轻雪机器人,基于NoneBot2构建的综合应用型聊天机器人
|
||||
tagline: LiteyukiBot 轻雪机器人,综合性的机器人应用及管理框架
|
||||
|
||||
actions:
|
||||
- text: 快速部署
|
||||
@ -30,9 +30,9 @@ highlights:
|
||||
background-repeat: repeat
|
||||
background-size: initial
|
||||
features:
|
||||
- title: 基于NoneBot2
|
||||
- title: 支持多种框架
|
||||
icon: robot
|
||||
details: 拥有良好的生态支持
|
||||
details: 兼容nonebot,melobot等,拥有良好的生态支持
|
||||
link: https://nonebot.dev/
|
||||
|
||||
- title: 便捷管理
|
||||
|
@ -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
|
||||
|
@ -1,6 +1,7 @@
|
||||
from liteyuki.bot import (
|
||||
LiteyukiBot,
|
||||
get_bot
|
||||
get_bot,
|
||||
get_config
|
||||
)
|
||||
|
||||
from liteyuki.comm import (
|
||||
|
@ -4,21 +4,22 @@ import platform
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from typing import Any, Optional
|
||||
from typing import Any, Iterable, Optional
|
||||
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
from watchdog.observers import Observer
|
||||
|
||||
from liteyuki.bot.lifespan import (LIFESPAN_FUNC, Lifespan)
|
||||
from liteyuki.comm import get_channel
|
||||
from liteyuki.core import IS_MAIN_PROCESS
|
||||
from liteyuki.core.manager import ProcessManager
|
||||
from liteyuki.core.spawn_process import mb_run, nb_run
|
||||
from liteyuki.log import init_log, logger
|
||||
from liteyuki.plugin import load_plugins
|
||||
|
||||
__all__ = [
|
||||
"LiteyukiBot",
|
||||
"get_bot"
|
||||
"get_bot",
|
||||
"get_config",
|
||||
]
|
||||
|
||||
|
||||
@ -26,15 +27,18 @@ class LiteyukiBot:
|
||||
def __init__(self, *args, **kwargs):
|
||||
global _BOT_INSTANCE
|
||||
_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.loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(self.loop)
|
||||
self.loop_thread = threading.Thread(target=self.loop.run_forever, daemon=True)
|
||||
self.stop_event = threading.Event()
|
||||
self.call_restart_count = 0
|
||||
|
||||
print("\033[34m" + r"""
|
||||
@ -48,58 +52,22 @@ $$ |_____ _$$ |_ $$ | $$ |_____ $$ | $$ \__$$ |$$ |$$ \ _$$ |_
|
||||
$$ |/ $$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |/ $$ |
|
||||
$$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/
|
||||
""" + "\033[0m")
|
||||
load_plugins("liteyuki/plugins") # 加载轻雪插件
|
||||
logger.info("Liteyuki is initializing...")
|
||||
|
||||
def run(self):
|
||||
load_plugins("liteyuki/plugins") # 加载轻雪插件
|
||||
|
||||
"""
|
||||
启动逻辑
|
||||
"""
|
||||
self.loop_thread.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()) # 启动后钩子
|
||||
|
||||
self.start_watcher() # 启动文件监视器
|
||||
self.start_watcher() # 启动文件监视器,后续准备插件化
|
||||
self.keep_running()
|
||||
|
||||
def start_watcher(self):
|
||||
if self.config.get("debug", False):
|
||||
|
||||
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()
|
||||
pass
|
||||
|
||||
def restart(self, delay: int = 0):
|
||||
"""
|
||||
@ -135,15 +103,22 @@ $$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/
|
||||
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()) # 停止前钩子
|
||||
|
||||
# if name:
|
||||
# self.process_manager.terminate(name)
|
||||
# else:
|
||||
# self.process_manager.terminate_all()
|
||||
if name:
|
||||
self.process_manager.terminate(name)
|
||||
chan_active = get_channel(f"{name}-active")
|
||||
chan_active.send(1)
|
||||
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):
|
||||
"""
|
||||
@ -239,6 +214,9 @@ $$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/
|
||||
"""
|
||||
return self.lifespan.on_after_nonebot_init(func)
|
||||
|
||||
def keep_running(self):
|
||||
self.stop_event.wait()
|
||||
|
||||
|
||||
_BOT_INSTANCE: Optional[LiteyukiBot] = None
|
||||
|
||||
@ -254,3 +232,37 @@ def get_bot() -> Optional[LiteyukiBot]:
|
||||
else:
|
||||
# 从多进程上下文中获取
|
||||
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
|
||||
|
||||
|
@ -35,13 +35,14 @@ _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.main_send_conn, self.sub_receive_conn = Pipe()
|
||||
# self.sub_send_conn, self.main_receive_conn = Pipe()
|
||||
self.conn_send, self.conn_recv = Pipe()
|
||||
self._closed = False
|
||||
self._on_main_receive_funcs: list[str] = []
|
||||
self._on_sub_receive_funcs: list[str] = []
|
||||
@ -61,12 +62,7 @@ class Channel:
|
||||
"""
|
||||
if self._closed:
|
||||
raise RuntimeError("Cannot send to a closed channel")
|
||||
if IS_MAIN_PROCESS:
|
||||
print("主进程发送数据:", data)
|
||||
self.main_send_conn.send(data)
|
||||
else:
|
||||
print("子进程发送数据:", data)
|
||||
self.sub_send_conn.send(data)
|
||||
self.conn_send.send(data)
|
||||
|
||||
def receive(self) -> Any:
|
||||
"""
|
||||
@ -77,14 +73,7 @@ class Channel:
|
||||
raise RuntimeError("Cannot receive from a closed channel")
|
||||
|
||||
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)
|
||||
|
||||
data = self.conn_recv.recv()
|
||||
return data
|
||||
|
||||
def close(self):
|
||||
@ -92,10 +81,8 @@ class Channel:
|
||||
关闭通道
|
||||
"""
|
||||
self._closed = True
|
||||
self.sub_receive_conn.close()
|
||||
self.main_send_conn.close()
|
||||
self.sub_send_conn.close()
|
||||
self.main_receive_conn.close()
|
||||
self.conn_send.close()
|
||||
self.conn_recv.close()
|
||||
|
||||
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
|
||||
while not self._closed:
|
||||
data = self.main_receive_conn.recv()
|
||||
data = self.conn_recv.recv()
|
||||
self._run_on_main_receive_funcs(data)
|
||||
|
||||
def _start_sub_receive_loop(self):
|
||||
@ -167,7 +154,7 @@ class Channel:
|
||||
"""
|
||||
self.is_sub_receive_loop_running = True
|
||||
while not self._closed:
|
||||
data = self.sub_receive_conn.recv()
|
||||
data = self.conn_recv.recv()
|
||||
self._run_on_sub_receive_funcs(data)
|
||||
|
||||
def __iter__(self):
|
||||
@ -188,6 +175,8 @@ def set_channel(name: str, channel: Channel):
|
||||
name: 通道名称
|
||||
channel: 通道实例
|
||||
"""
|
||||
if not isinstance(channel, Channel):
|
||||
raise TypeError(f"channel must be an instance of Channel, {type(channel)} found")
|
||||
_channel[name] = channel
|
||||
|
||||
|
||||
@ -198,7 +187,7 @@ def set_channels(channels: dict[str, Channel]):
|
||||
channels: 通道名称
|
||||
"""
|
||||
for name, channel in channels.items():
|
||||
_channel[name] = channel
|
||||
set_channel(name, channel)
|
||||
|
||||
|
||||
def get_channel(name: str) -> Optional[Channel]:
|
||||
|
@ -1,12 +1,24 @@
|
||||
"""
|
||||
该模块用于常用配置文件的加载
|
||||
多配置文件编写原则:
|
||||
1.尽量不要冲突: 一个键不要多次出现
|
||||
2.分工明确: 每个配置文件给一个或一类服务提供配置
|
||||
3.扁平化编写: 配置文件尽量扁平化,不要出现过多的嵌套
|
||||
4.注意冲突时的优先级: 项目目录下的配置文件优先级高于config目录下的配置文件
|
||||
5.请不要将需要动态加载的内容写入配置文件,你应该使用其他储存方式
|
||||
"""
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
from typing import List
|
||||
from typing import Any, List
|
||||
|
||||
import nonebot
|
||||
import toml
|
||||
import yaml
|
||||
from pydantic import BaseModel
|
||||
|
||||
from liteyuki import logger
|
||||
|
||||
config = {} # 主进程全局配置,确保加载后读取
|
||||
_SUPPORTED_CONFIG_FORMATS = (".yaml", ".yml", ".json", ".toml")
|
||||
|
||||
|
||||
class SatoriNodeConfig(BaseModel):
|
||||
@ -32,18 +44,100 @@ class BasicConfig(BaseModel):
|
||||
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"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)
|
||||
def flat_config(config: dict[str, Any]) -> dict[str, Any]:
|
||||
"""
|
||||
扁平化配置文件
|
||||
|
||||
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"Config file {file} is empty, use default config. please modify it and restart")
|
||||
conf = BasicConfig().dict()
|
||||
return conf
|
||||
{a:{b:{c:1}}} -> {"a.b.c": 1}
|
||||
Args:
|
||||
config: 配置项目
|
||||
|
||||
Returns:
|
||||
扁平化后的配置文件,但也包含原有的键值对
|
||||
"""
|
||||
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
|
||||
|
@ -1,6 +1,5 @@
|
||||
import multiprocessing
|
||||
|
||||
from .spawn_process import *
|
||||
from .manager import *
|
||||
|
||||
__all__ = [
|
||||
|
@ -36,13 +36,6 @@ class ProcessManager:
|
||||
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):
|
||||
"""
|
||||
开启后自动监控进程,并添加到进程字典中
|
||||
@ -61,7 +54,7 @@ class ProcessManager:
|
||||
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]),
|
||||
process = Process(target=self.targets[name][0], args=self.targets[name][1],
|
||||
kwargs=self.targets[name][2])
|
||||
self.processes[name] = process
|
||||
process.start()
|
||||
@ -88,9 +81,30 @@ class ProcessManager:
|
||||
else:
|
||||
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)
|
||||
|
||||
set_channels(
|
||||
{
|
||||
f"{name}-active" : chan_active,
|
||||
f"{name}-passive": chan_passive
|
||||
}
|
||||
)
|
||||
|
||||
def join(self):
|
||||
for name, process in self.targets:
|
||||
process.join()
|
||||
|
@ -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
4
liteyuki/dev/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
该模块用于存放一些开发工具
|
||||
"""
|
5
liteyuki/dev/observer.py
Normal file
5
liteyuki/dev/observer.py
Normal file
@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
|
||||
"""
|
||||
import watchdog
|
@ -44,8 +44,12 @@ def load_plugin(module_path: str | Path) -> Optional[Plugin]:
|
||||
module_name=module_path,
|
||||
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(
|
||||
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__]
|
||||
|
||||
|
21
liteyuki/plugins/code_watchdog/__init__.py
Normal file
21
liteyuki/plugins/code_watchdog/__init__.py
Normal 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")
|
44
liteyuki/plugins/code_watchdog/observer.py
Normal file
44
liteyuki/plugins/code_watchdog/observer.py
Normal 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()
|
56
liteyuki/plugins/nonebot_launcher/__init__.py
Normal file
56
liteyuki/plugins/nonebot_launcher/__init__.py
Normal 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")
|
20
liteyuki/plugins/plugins_loader/__init__.py
Normal file
20
liteyuki/plugins/plugins_loader/__init__.py
Normal 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")
|
@ -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
|
7
main.py
7
main.py
@ -1,6 +1,9 @@
|
||||
"""
|
||||
启动脚本,会执行一些启动的操作,比如加载配置文件,初始化 bot 实例等。
|
||||
"""
|
||||
from liteyuki import LiteyukiBot
|
||||
from liteyuki.config import load_from_yaml
|
||||
from liteyuki.config import load_config_in_default
|
||||
|
||||
if __name__ == "__main__":
|
||||
bot = LiteyukiBot(**load_from_yaml("config.yml"))
|
||||
bot = LiteyukiBot(**load_config_in_default())
|
||||
bot.run()
|
||||
|
@ -1,3 +0,0 @@
|
||||
# 说明
|
||||
|
||||
此目录为**轻雪插件**目录,非其他插件目录。
|
@ -8,20 +8,22 @@
|
||||
# @File : asa.py
|
||||
# @Software: PyCharm
|
||||
import asyncio
|
||||
import multiprocessing
|
||||
|
||||
from liteyuki.plugin import PluginMetadata
|
||||
from liteyuki import get_bot, logger
|
||||
from liteyuki.comm.channel import get_channel
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="lifespan_monitor",
|
||||
name="生命周期日志",
|
||||
)
|
||||
|
||||
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
|
||||
def _():
|
||||
logger.info("生命周期监控器:准备启动")
|
||||
@ -41,15 +43,3 @@ def _():
|
||||
@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)
|
9
src/liteyuki_plugins/process_manager/__init__.py
Normal file
9
src/liteyuki_plugins/process_manager/__init__.py
Normal file
@ -0,0 +1,9 @@
|
||||
from liteyuki.plugin import PluginMetadata
|
||||
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="进程管理器",
|
||||
version="0.1.0",
|
||||
author="snowykami",
|
||||
description="进程管理器,用于管理子进程"
|
||||
)
|
@ -2,9 +2,9 @@
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/8/10 下午5:18
|
||||
@Time : 2024/8/11 下午8:22
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : reloader_monitor.py
|
||||
@File : reloader.py.py
|
||||
@Software: PyCharm
|
||||
"""
|
||||
"""
|
@ -16,12 +16,4 @@ __plugin_meta__ = PluginMetadata(
|
||||
"toggleable" : 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
21
tests/test_config_load.py
Normal 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))
|
@ -1,4 +1,4 @@
|
||||
from src.liteyuki import LiteyukiBot
|
||||
from liteyuki import LiteyukiBot
|
||||
|
||||
if __name__ == "__main__":
|
||||
lyb = LiteyukiBot()
|
||||
|
@ -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")
|
Loading…
Reference in New Issue
Block a user