mirror of
https://github.com/LiteyukiStudio/LiteyukiBot.git
synced 2025-10-04 22:16:23 +00:00
Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
6b64a0c379 | |||
f117da7ff3 | |||
f548a07230 | |||
e2e53c21fa | |||
3eaf23a56b | |||
4a5dd1f727 | |||
c2cb416b4e | |||
5cd528d5e9 | |||
980fca650b | |||
9c525141f6 | |||
3d218a0e8d | |||
db385f597b | |||
98a9d6413a |
5
.github/workflows/pypi-publish.yml
vendored
5
.github/workflows/pypi-publish.yml
vendored
@@ -1,8 +1,9 @@
|
||||
name: Publish
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
pypi-publish:
|
||||
|
@@ -25,6 +25,10 @@
|
||||
### 感谢
|
||||
- 所有贡献者们
|
||||
|
||||
### 参考及鸣谢
|
||||
- [nonebot-plugin-uninfo](https://github.com/RF-Tar-Railt/nonebot-plugin-uninfo)为会话部分用户信息提供了参考
|
||||
- [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna/)为消息部分提供了参考
|
||||
|
||||
|
||||
[OneBot]: https://img.shields.io/badge/OneBot-11/12-blue?style=for-the-badge
|
||||
|
||||
|
@@ -9,13 +9,23 @@ order: 1
|
||||
1. Install [`Git`](https://git-scm.com/download/) and [`Python3.10+`](https://www.python.org/downloads/release/python-31010/) Environment.
|
||||
|
||||
```bash
|
||||
# Clone the project
|
||||
# Clone Repo
|
||||
git clone https://github.com/LiteyukiStudio/LiteyukiBot --depth=1
|
||||
# change directory
|
||||
|
||||
# Change directory
|
||||
cd LiteyukiBot
|
||||
# install dependencies
|
||||
|
||||
# Create virtual environment
|
||||
python -m venv venv
|
||||
|
||||
# Activate virtual environment
|
||||
.\venv\Scripts\activate # Windows
|
||||
source venv/bin/activate # Linux
|
||||
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
# start the bot!
|
||||
|
||||
# Run Liteyuki
|
||||
python main.py
|
||||
```
|
||||
|
||||
@@ -37,9 +47,6 @@ python main.py
|
||||
> If you are using Windows, please use the absolute project directory `/path/to/LiteyukiBot` instead of `$(pwd)` <br>
|
||||
> If you have modified the port number, please replace `20216:20216` with your port number
|
||||
|
||||
## **Use TRSS Script**
|
||||
[TRSS_Liteyuki Management Script](https://timerainstarsky.github.io/TRSS_Liteyuki/), which provides a more convenient way to manage LiteyukiBot, recommended to use `Arch Linux`
|
||||
|
||||
|
||||
## **Device Requirements**
|
||||
- Windows system version minimum `Windows10+`/`Windows Server 2019+`
|
||||
|
2202
docs/pnpm-lock.yaml
generated
2202
docs/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@ order: 2
|
||||
|
||||
```yaml
|
||||
nonebot:
|
||||
# Nonebot机器人的配置,以前的最外层配置项仍可为Nonebot服务,但是部分内容会被覆盖,请尽快迁移
|
||||
# Nonebot机器人的配置,6.3.10版本后,NoneBot下配置已迁移至nonebot键下,不再使用外层配置,但是部分内容会被覆盖,请尽快迁移
|
||||
command_start: [ "/", "" ] # 指令前缀,若没有""空命令头,请开启alconna_use_command_start保证alconna解析正常
|
||||
host: 127.0.0.1 # 监听地址,默认为本机,若要接收外部请求请填写0.0.0.0
|
||||
port: 20216 # 绑定端口
|
||||
|
@@ -2,11 +2,13 @@
|
||||
title: 安装
|
||||
order: 1
|
||||
---
|
||||
|
||||
# 安装
|
||||
|
||||
## **常规部署**
|
||||
|
||||
1. 安装 [`Git`](https://git-scm.com/download/) 和 [`Python3.10+`](https://www.python.org/downloads/release/python-31010/) 环境
|
||||
1. 安装 [`Git`](https://git-scm.com/download/) 和 [
|
||||
`Python3.10+`](https://www.python.org/downloads/release/python-31010/) 环境
|
||||
|
||||
```bash
|
||||
# 克隆项目到本地,轻雪使用Git进行版本管理,该步骤为必要项
|
||||
@@ -14,15 +16,21 @@ git clone https://github.com/LiteyukiStudio/LiteyukiBot --depth=1 # 若你不能
|
||||
|
||||
# 切换到Bot目录下
|
||||
cd LiteyukiBot
|
||||
|
||||
# 创建虚拟环境
|
||||
python -m venv venv
|
||||
|
||||
# 激活虚拟环境
|
||||
.\venv\Scripts\activate # Windows
|
||||
source venv/bin/activate # Linux
|
||||
|
||||
# 安装依赖
|
||||
pip install -r requirements.txt
|
||||
|
||||
# 启动Bot
|
||||
python main.py
|
||||
```
|
||||
|
||||
> [!tip]
|
||||
> 推荐使用虚拟环境来运行轻雪,以避免依赖冲突,你可以使用`python -m venv .venv`来创建虚拟环境,然后使用`.venv\Scripts\activate`来激活虚拟环境(Linux下使用`source .venv/bin/activate`激活)
|
||||
|
||||
## **使用Docker构建**
|
||||
|
||||
1. 安装 [`Docker`](https://docs.docker.com/get-docker/)
|
||||
@@ -35,10 +43,6 @@ python main.py
|
||||
> Windows请使用项目绝对目录`/path/to/LiteyukiBot`代替`$(pwd)` <br>
|
||||
> 若你修改了端口号请将`20216:20216`中的`20216`替换为你的端口号
|
||||
|
||||
## **使用TRSS Scripts部署**
|
||||
[TRSS_Liteyuki轻雪机器人管理脚本](https://timerainstarsky.github.io/TRSS_Liteyuki/),该功能由TRSS提供支持,不是LiteyukiBot官方提供的功能,推荐使用`Arch Linux`
|
||||
|
||||
|
||||
## **装置要求**
|
||||
|
||||
- Windows系统版本最低`Windows10+`/`Windows Server 2019+`
|
||||
@@ -48,7 +52,8 @@ python main.py
|
||||
- 硬盘: 至少`1GB`空间
|
||||
|
||||
> [!warning]
|
||||
> 如果装置上有多个环境,请使用`path/to/python -m pip install -r requirements.txt`来安装依赖,`path/to/python`为你的Python可执行文件路径
|
||||
> 如果装置上有多个环境,请使用`path/to/python -m pip install -r requirements.txt`来安装依赖,`path/to/python`
|
||||
> 为你的Python可执行文件路径
|
||||
|
||||
> [!warning]
|
||||
> 轻雪的更新功能依赖Git,如果你没有安装Git直接下载源代码运行,你将无法使用更新功能
|
||||
|
@@ -1,18 +1,21 @@
|
||||
import asyncio
|
||||
import atexit
|
||||
import os
|
||||
import platform
|
||||
import signal
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from typing import Any, Optional
|
||||
|
||||
from liteyuki.bot.lifespan import (LIFESPAN_FUNC, Lifespan, PROCESS_LIFESPAN_FUNC)
|
||||
from magicoca import Chan
|
||||
|
||||
from liteyuki.bot.lifespan import LIFESPAN_FUNC, Lifespan, PROCESS_LIFESPAN_FUNC
|
||||
from liteyuki.comm.channel import get_channel
|
||||
from liteyuki.core.manager import ProcessManager
|
||||
# new version
|
||||
from liteyuki.core.manager import sub_process_manager
|
||||
from liteyuki.log import init_log, logger
|
||||
from liteyuki.plugin import load_plugin
|
||||
from liteyuki.session import message_handler_thread
|
||||
from liteyuki.utils import IS_MAIN_PROCESS
|
||||
|
||||
__all__ = [
|
||||
@@ -30,6 +33,10 @@ class LiteyukiBot:
|
||||
Args:
|
||||
**kwargs: 配置
|
||||
"""
|
||||
"""总通道"""
|
||||
self.i_chan = Chan[Any]() # 外部输入通道
|
||||
self.o_chan = Chan[Any]() # 外部输出通道
|
||||
|
||||
"""常规操作"""
|
||||
print_logo()
|
||||
global _BOT_INSTANCE
|
||||
@@ -60,8 +67,9 @@ class LiteyukiBot:
|
||||
启动逻辑
|
||||
"""
|
||||
await self.lifespan.before_start() # 启动前钩子
|
||||
sub_process_manager.start_all()
|
||||
await self.lifespan.after_start() # 启动后钩子
|
||||
await self.keep_alive()
|
||||
message_handler_thread([_.ctx.sub_chan for _ in sub_process_manager.processes.values()])
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
@@ -73,19 +81,7 @@ class LiteyukiBot:
|
||||
except KeyboardInterrupt:
|
||||
logger.opt(colors=True).info("<y>Liteyuki is stopping...</y>")
|
||||
self.stop()
|
||||
logger.opt(colors=True).info("<y>Liteyuki is stopped...</y>")
|
||||
|
||||
async def keep_alive(self):
|
||||
"""
|
||||
保持轻雪运行
|
||||
"""
|
||||
logger.info("Liteyuki is keeping alive...")
|
||||
try:
|
||||
while not self.stop_event.is_set():
|
||||
await asyncio.sleep(0.1)
|
||||
except Exception:
|
||||
logger.info("Liteyuki is exiting...")
|
||||
self.stop()
|
||||
logger.opt(colors=True).info("<y>Liteyuki is stopped !</y>")
|
||||
|
||||
def restart(self, delay: int = 0):
|
||||
"""
|
||||
@@ -108,7 +104,11 @@ class LiteyukiBot:
|
||||
cmd = "nohup"
|
||||
self.process_manager.terminate_all()
|
||||
# 进程退出后重启
|
||||
threading.Thread(target=os.system, args=(f"{cmd} {executable} {' '.join(args)}",), daemon=True).start()
|
||||
threading.Thread(
|
||||
target=os.system,
|
||||
args=(f"{cmd} {executable} {' '.join(args)}",),
|
||||
daemon=True,
|
||||
).start()
|
||||
sys.exit(0)
|
||||
self.call_restart_count += 1
|
||||
|
||||
@@ -189,7 +189,9 @@ class LiteyukiBot:
|
||||
"""
|
||||
return self.lifespan.on_before_process_shutdown(func)
|
||||
|
||||
def on_before_process_restart(self, func: PROCESS_LIFESPAN_FUNC) -> PROCESS_LIFESPAN_FUNC:
|
||||
def on_before_process_restart(
|
||||
self, func: PROCESS_LIFESPAN_FUNC
|
||||
) -> PROCESS_LIFESPAN_FUNC:
|
||||
"""
|
||||
注册进程重启前的函数,为子进程重启时调用
|
||||
Args:
|
||||
@@ -211,7 +213,7 @@ class LiteyukiBot:
|
||||
return self.lifespan.on_after_restart(func)
|
||||
|
||||
|
||||
_BOT_INSTANCE: LiteyukiBot
|
||||
_BOT_INSTANCE: LiteyukiBot | None = None
|
||||
|
||||
|
||||
def get_bot() -> LiteyukiBot:
|
||||
@@ -241,7 +243,9 @@ def get_config(key: str, default: Any = None) -> Any:
|
||||
return get_bot().config.get(key, default)
|
||||
|
||||
|
||||
def get_config_with_compat(key: str, compat_keys: tuple[str], default: Any = None) -> Any:
|
||||
def get_config_with_compat(
|
||||
key: str, compat_keys: tuple[str], default: Any = None
|
||||
) -> Any:
|
||||
"""
|
||||
获取配置,兼容旧版本
|
||||
Args:
|
||||
@@ -256,14 +260,18 @@ def get_config_with_compat(key: str, compat_keys: tuple[str], default: Any = Non
|
||||
return get_bot().config[key]
|
||||
for compat_key in compat_keys:
|
||||
if compat_key in get_bot().config:
|
||||
logger.warning(f"Config key \"{compat_key}\" will be deprecated, use \"{key}\" instead.")
|
||||
logger.warning(
|
||||
f'Config key "{compat_key}" will be deprecated, use "{key}" instead.'
|
||||
)
|
||||
return get_bot().config[compat_key]
|
||||
return default
|
||||
|
||||
|
||||
def print_logo():
|
||||
"""@litedoc-hide"""
|
||||
print("\033[34m" + r"""
|
||||
print(
|
||||
"\033[34m"
|
||||
+ r"""
|
||||
__ ______ ________ ________ __ __ __ __ __ __ ______
|
||||
/ | / |/ |/ |/ \ / |/ | / |/ | / |/ |
|
||||
$$ | $$$$$$/ $$$$$$$$/ $$$$$$$$/ $$ \ /$$/ $$ | $$ |$$ | /$$/ $$$$$$/
|
||||
@@ -273,4 +281,6 @@ def print_logo():
|
||||
$$ |_____ _$$ |_ $$ | $$ |_____ $$ | $$ \__$$ |$$ |$$ \ _$$ |_
|
||||
$$ |/ $$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |/ $$ |
|
||||
$$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/
|
||||
""" + "\033[0m")
|
||||
"""
|
||||
+ "\033[0m"
|
||||
)
|
||||
|
@@ -4,7 +4,16 @@
|
||||
"""
|
||||
import asyncio
|
||||
from multiprocessing import Pipe
|
||||
from typing import Any, Callable, Coroutine, Generic, Optional, TypeAlias, TypeVar, get_args
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Coroutine,
|
||||
Generic,
|
||||
Optional,
|
||||
TypeAlias,
|
||||
TypeVar,
|
||||
get_args,
|
||||
)
|
||||
|
||||
from liteyuki.log import logger
|
||||
from liteyuki.utils import IS_MAIN_PROCESS, is_coroutine_callable
|
||||
@@ -12,7 +21,9 @@ from liteyuki.utils import IS_MAIN_PROCESS, is_coroutine_callable
|
||||
T = TypeVar("T")
|
||||
|
||||
SYNC_ON_RECEIVE_FUNC: TypeAlias = Callable[[T], Any] # 同步接收函数
|
||||
ASYNC_ON_RECEIVE_FUNC: TypeAlias = Callable[[T], Coroutine[Any, Any, Any]] # 异步接收函数
|
||||
ASYNC_ON_RECEIVE_FUNC: TypeAlias = Callable[
|
||||
[T], Coroutine[Any, Any, Any]
|
||||
] # 异步接收函数
|
||||
ON_RECEIVE_FUNC: TypeAlias = SYNC_ON_RECEIVE_FUNC | ASYNC_ON_RECEIVE_FUNC # 接收函数
|
||||
|
||||
SYNC_FILTER_FUNC: TypeAlias = Callable[[T], bool] # 同步过滤函数
|
||||
@@ -39,7 +50,9 @@ class Channel(Generic[T]):
|
||||
"""
|
||||
|
||||
self.conn_send, self.conn_recv = Pipe()
|
||||
self._conn_send_inner, self._conn_recv_inner = Pipe() # 内部通道,用于子进程通信
|
||||
self._conn_send_inner, self._conn_recv_inner = (
|
||||
Pipe()
|
||||
) # 内部通道,用于子进程通信
|
||||
self._closed = False
|
||||
self._on_main_receive_func_ids: list[int] = []
|
||||
self._on_sub_receive_func_ids: list[int] = []
|
||||
@@ -64,7 +77,9 @@ class Channel(Generic[T]):
|
||||
_channel[name] = self
|
||||
logger.debug(f"Channel {name} initialized in main process")
|
||||
else:
|
||||
logger.debug(f"Channel {name} initialized in sub process, should manually set in main process")
|
||||
logger.debug(
|
||||
f"Channel {name} initialized in sub process, should manually set in main process"
|
||||
)
|
||||
|
||||
def _get_generic_type(self) -> Optional[type]:
|
||||
"""
|
||||
@@ -72,7 +87,7 @@ class Channel(Generic[T]):
|
||||
Returns:
|
||||
Optional[type]: 泛型类型
|
||||
"""
|
||||
if hasattr(self, '__orig_class__'):
|
||||
if hasattr(self, "__orig_class__"):
|
||||
return get_args(self.__orig_class__)[0]
|
||||
return None
|
||||
|
||||
@@ -98,7 +113,10 @@ class Channel(Generic[T]):
|
||||
elif isinstance(structure, dict):
|
||||
if not isinstance(data, dict):
|
||||
return False
|
||||
return all(k in data and self._validate_structure(data[k], structure[k]) for k in structure)
|
||||
return all(
|
||||
k in data and self._validate_structure(data[k], structure[k])
|
||||
for k in structure
|
||||
)
|
||||
return False
|
||||
|
||||
def __str__(self):
|
||||
@@ -113,10 +131,12 @@ class Channel(Generic[T]):
|
||||
if self.type_check:
|
||||
_type = self._get_generic_type()
|
||||
if _type is not None and not self._validate_structure(data, _type):
|
||||
raise TypeError(f"Data must be an instance of {_type}, {type(data)} found")
|
||||
raise TypeError(
|
||||
f"Data must be an instance of {_type}, {type(data)} found"
|
||||
)
|
||||
|
||||
if self._closed:
|
||||
raise RuntimeError("Cannot send to a closed channel_")
|
||||
raise RuntimeError("Cannot send to a closed channel")
|
||||
self.conn_send.send(data)
|
||||
|
||||
def receive(self) -> T:
|
||||
@@ -126,7 +146,7 @@ class Channel(Generic[T]):
|
||||
T: 数据
|
||||
"""
|
||||
if self._closed:
|
||||
raise RuntimeError("Cannot receive from a closed channel_")
|
||||
raise RuntimeError("Cannot receive from a closed channel")
|
||||
|
||||
while True:
|
||||
data = self.conn_recv.recv()
|
||||
@@ -142,7 +162,9 @@ class Channel(Generic[T]):
|
||||
data = await loop.run_in_executor(None, self.receive)
|
||||
return data
|
||||
|
||||
def on_receive(self, filter_func: Optional[FILTER_FUNC] = None) -> Callable[[Callable[[T], Any]], Callable[[T], Any]]:
|
||||
def on_receive(
|
||||
self, filter_func: Optional[FILTER_FUNC] = None
|
||||
) -> Callable[[Callable[[T], Any]], Callable[[T], Any]]:
|
||||
"""
|
||||
接收数据并执行函数
|
||||
Args:
|
||||
@@ -187,37 +209,52 @@ class Channel(Generic[T]):
|
||||
data: 数据
|
||||
"""
|
||||
if IS_MAIN_PROCESS:
|
||||
[asyncio.create_task(_callback_funcs[func_id](data)) for func_id in self._on_main_receive_func_ids]
|
||||
[
|
||||
asyncio.create_task(_callback_funcs[func_id](data))
|
||||
for func_id in self._on_main_receive_func_ids
|
||||
]
|
||||
else:
|
||||
[asyncio.create_task(_callback_funcs[func_id](data)) for func_id in self._on_sub_receive_func_ids]
|
||||
[
|
||||
asyncio.create_task(_callback_funcs[func_id](data))
|
||||
for func_id in self._on_sub_receive_func_ids
|
||||
]
|
||||
|
||||
|
||||
"""子进程可用的主动和被动通道"""
|
||||
active_channel: Channel = Channel(name="active_channel") # 主动通道
|
||||
passive_channel: Channel = Channel(name="passive_channel") # 被动通道
|
||||
publish_channel: Channel[tuple[str, dict[str, Any]]] = Channel(name="publish_channel") # 发布通道
|
||||
publish_channel: Channel[tuple[str, dict[str, Any]]] = Channel(
|
||||
name="publish_channel"
|
||||
) # 发布通道
|
||||
"""通道传递通道,主进程创建单例,子进程初始化时实例化"""
|
||||
channel_deliver_active_channel: Channel[Channel[Any]] # 主动通道传递通道
|
||||
channel_deliver_passive_channel: Channel[tuple[str, dict[str, Any]]] # 被动通道传递通道
|
||||
|
||||
if IS_MAIN_PROCESS:
|
||||
channel_deliver_active_channel = Channel(name="channel_deliver_active_channel") # 主动通道传递通道
|
||||
channel_deliver_passive_channel = Channel(name="channel_deliver_passive_channel") # 被动通道传递通道
|
||||
channel_deliver_active_channel = Channel(
|
||||
name="channel_deliver_active_channel"
|
||||
) # 主动通道传递通道
|
||||
channel_deliver_passive_channel = Channel(
|
||||
name="channel_deliver_passive_channel"
|
||||
) # 被动通道传递通道
|
||||
|
||||
|
||||
@channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == "set_channel")
|
||||
@channel_deliver_passive_channel.on_receive(
|
||||
filter_func=lambda data: data[0] == "set_channel"
|
||||
)
|
||||
def on_set_channel(data: tuple[str, dict[str, Any]]):
|
||||
name, channel = data[1]["name"], data[1]["channel_"]
|
||||
set_channel(name, channel)
|
||||
|
||||
|
||||
@channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == "get_channel")
|
||||
@channel_deliver_passive_channel.on_receive(
|
||||
filter_func=lambda data: data[0] == "get_channel"
|
||||
)
|
||||
def on_get_channel(data: tuple[str, dict[str, Any]]):
|
||||
name, recv_chan = data[1]["name"], data[1]["recv_chan"]
|
||||
recv_chan.send(get_channel(name))
|
||||
|
||||
|
||||
@channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == "get_channels")
|
||||
@channel_deliver_passive_channel.on_receive(
|
||||
filter_func=lambda data: data[0] == "get_channels"
|
||||
)
|
||||
def on_get_channels(data: tuple[str, dict[str, Any]]):
|
||||
recv_chan = data[1]["recv_chan"]
|
||||
recv_chan.send(get_channels())
|
||||
@@ -231,7 +268,9 @@ def set_channel(name: str, channel: "Channel"):
|
||||
channel ([`Channel`](#class-channel-generic-t)): 通道实例
|
||||
"""
|
||||
if not isinstance(channel, Channel):
|
||||
raise TypeError(f"channel_ must be an instance of Channel, {type(channel)} found")
|
||||
raise TypeError(
|
||||
f"channel_ must be an instance of Channel, {type(channel)} found"
|
||||
)
|
||||
|
||||
if IS_MAIN_PROCESS:
|
||||
if name in _channel:
|
||||
@@ -241,10 +280,11 @@ def set_channel(name: str, channel: "Channel"):
|
||||
# 请求主进程设置通道
|
||||
channel_deliver_passive_channel.send(
|
||||
(
|
||||
"set_channel", {
|
||||
"name" : name,
|
||||
"set_channel",
|
||||
{
|
||||
"name": name,
|
||||
"channel_": channel,
|
||||
}
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
@@ -273,13 +313,7 @@ def get_channel(name: str) -> "Channel":
|
||||
else:
|
||||
recv_chan = Channel[Channel[Any]]("recv_chan")
|
||||
channel_deliver_passive_channel.send(
|
||||
(
|
||||
"get_channel",
|
||||
{
|
||||
"name" : name,
|
||||
"recv_chan": recv_chan
|
||||
}
|
||||
)
|
||||
("get_channel", {"name": name, "recv_chan": recv_chan})
|
||||
)
|
||||
return recv_chan.receive()
|
||||
|
||||
@@ -294,12 +328,5 @@ def get_channels() -> dict[str, "Channel"]:
|
||||
return _channel
|
||||
else:
|
||||
recv_chan = Channel[dict[str, Channel[Any]]]("recv_chan")
|
||||
channel_deliver_passive_channel.send(
|
||||
(
|
||||
"get_channels",
|
||||
{
|
||||
"recv_chan": recv_chan
|
||||
}
|
||||
)
|
||||
)
|
||||
channel_deliver_passive_channel.send(("get_channels", {"recv_chan": recv_chan}))
|
||||
return recv_chan.receive()
|
||||
|
@@ -1,26 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
本模块用于实现RPC(基于IPC)通信
|
||||
"""
|
||||
|
||||
from typing import TypeAlias, Callable, Any
|
||||
|
||||
from liteyuki.comm.channel import Channel
|
||||
|
||||
ON_CALLING_FUNC: TypeAlias = Callable[[tuple, dict], Any]
|
||||
|
||||
|
||||
class RPC:
|
||||
"""
|
||||
RPC类
|
||||
"""
|
||||
|
||||
def __init__(self, on_calling: ON_CALLING_FUNC) -> None:
|
||||
self.on_calling = on_calling
|
||||
|
||||
def call(self, args: tuple, kwargs: dict) -> Any:
|
||||
"""
|
||||
调用
|
||||
"""
|
||||
# 获取self.calling函数名
|
||||
return self.on_calling(args, kwargs)
|
@@ -1,48 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
基于socket的通道
|
||||
"""
|
||||
|
||||
|
||||
class SocksChannel:
|
||||
"""
|
||||
通道类,可以在进程间和进程内通信,双向但同时只能有一个发送者和一个接收者
|
||||
有两种接收工作方式,但是只能选择一种,主动接收和被动接收,主动接收使用 `receive` 方法,被动接收使用 `on_receive` 装饰器
|
||||
"""
|
||||
|
||||
def __init__(self, name: str):
|
||||
"""
|
||||
初始化通道
|
||||
Args:
|
||||
name: 通道ID
|
||||
"""
|
||||
|
||||
self._name = name
|
||||
self._conn_send = None
|
||||
self._conn_recv = None
|
||||
self._closed = False
|
||||
|
||||
def send(self, data):
|
||||
"""
|
||||
发送数据
|
||||
Args:
|
||||
data: 数据
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
def receive(self):
|
||||
"""
|
||||
接收数据
|
||||
Returns:
|
||||
data: 数据
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
关闭通道
|
||||
"""
|
||||
|
||||
pass
|
@@ -14,6 +14,9 @@ import threading
|
||||
from multiprocessing import Process
|
||||
from typing import Any, Callable, TYPE_CHECKING, TypeAlias
|
||||
|
||||
from croterline.context import Context
|
||||
from croterline.process import SubProcess, ProcessFuncType
|
||||
|
||||
from liteyuki.log import logger
|
||||
from liteyuki.utils import IS_MAIN_PROCESS
|
||||
|
||||
@@ -26,7 +29,10 @@ from liteyuki.comm import Channel
|
||||
if IS_MAIN_PROCESS:
|
||||
from liteyuki.comm.channel import get_channel, publish_channel, get_channels
|
||||
from liteyuki.comm.storage import shared_memory
|
||||
from liteyuki.comm.channel import channel_deliver_active_channel, channel_deliver_passive_channel
|
||||
from liteyuki.comm.channel import (
|
||||
channel_deliver_active_channel,
|
||||
channel_deliver_passive_channel,
|
||||
)
|
||||
else:
|
||||
from liteyuki.comm import channel
|
||||
from liteyuki.comm import storage
|
||||
@@ -34,9 +40,7 @@ else:
|
||||
TARGET_FUNC: TypeAlias = Callable[..., Any]
|
||||
TIMEOUT = 10
|
||||
|
||||
__all__ = [
|
||||
"ProcessManager"
|
||||
]
|
||||
__all__ = ["ProcessManager", "sub_process_manager"]
|
||||
multiprocessing.set_start_method("spawn", force=True)
|
||||
|
||||
|
||||
@@ -57,7 +61,9 @@ class ChannelDeliver:
|
||||
|
||||
|
||||
# 函数处理一些跨进程通道的
|
||||
def _delivery_channel_wrapper(func: TARGET_FUNC, cd: ChannelDeliver, sm: "KeyValueStore", *args, **kwargs):
|
||||
def _delivery_channel_wrapper(
|
||||
func: TARGET_FUNC, cd: ChannelDeliver, sm: "KeyValueStore", *args, **kwargs
|
||||
):
|
||||
"""
|
||||
子进程入口函数
|
||||
处理一些操作
|
||||
@@ -68,8 +74,12 @@ def _delivery_channel_wrapper(func: TARGET_FUNC, cd: ChannelDeliver, sm: "KeyVal
|
||||
|
||||
channel.active_channel = cd.active # 子进程主动通道
|
||||
channel.passive_channel = cd.passive # 子进程被动通道
|
||||
channel.channel_deliver_active_channel = cd.channel_deliver_active # 子进程通道传递主动通道
|
||||
channel.channel_deliver_passive_channel = cd.channel_deliver_passive # 子进程通道传递被动通道
|
||||
channel.channel_deliver_active_channel = (
|
||||
cd.channel_deliver_active
|
||||
) # 子进程通道传递主动通道
|
||||
channel.channel_deliver_passive_channel = (
|
||||
cd.channel_deliver_passive
|
||||
) # 子进程通道传递被动通道
|
||||
channel.publish_channel = cd.publish # 子进程发布通道
|
||||
|
||||
# 给子进程创建共享内存实例
|
||||
@@ -102,8 +112,12 @@ class ProcessManager:
|
||||
chan_active = get_channel(f"{name}-active")
|
||||
|
||||
def _start_process():
|
||||
process = Process(target=self.targets[name][0], args=self.targets[name][1],
|
||||
kwargs=self.targets[name][2], daemon=True)
|
||||
process = Process(
|
||||
target=self.targets[name][0],
|
||||
args=self.targets[name][1],
|
||||
kwargs=self.targets[name][2],
|
||||
daemon=True,
|
||||
)
|
||||
self.processes[name] = process
|
||||
process.start()
|
||||
|
||||
@@ -133,7 +147,9 @@ class ProcessManager:
|
||||
|
||||
for name in self.targets:
|
||||
logger.debug(f"Starting process {name}")
|
||||
threading.Thread(target=self._run_process, args=(name, ), daemon=True).start()
|
||||
threading.Thread(
|
||||
target=self._run_process, args=(name,), daemon=True
|
||||
).start()
|
||||
|
||||
def add_target(self, name: str, target: TARGET_FUNC, args: tuple = (), kwargs=None):
|
||||
"""
|
||||
@@ -154,10 +170,14 @@ class ProcessManager:
|
||||
passive=chan_passive,
|
||||
channel_deliver_active=channel_deliver_active_channel,
|
||||
channel_deliver_passive=channel_deliver_passive_channel,
|
||||
publish=publish_channel
|
||||
publish=publish_channel,
|
||||
)
|
||||
|
||||
self.targets[name] = (_delivery_channel_wrapper, (target, channel_deliver, shared_memory, *args), kwargs)
|
||||
self.targets[name] = (
|
||||
_delivery_channel_wrapper,
|
||||
(target, channel_deliver, shared_memory, *args),
|
||||
kwargs,
|
||||
)
|
||||
# 主进程通道
|
||||
|
||||
def join_all(self):
|
||||
@@ -199,3 +219,79 @@ class ProcessManager:
|
||||
if name not in self.targets:
|
||||
logger.warning(f"Process {name} not found.")
|
||||
return self.processes[name].is_alive()
|
||||
|
||||
|
||||
# new version
|
||||
|
||||
|
||||
class _SubProcessManager:
|
||||
"""
|
||||
子进程管理器
|
||||
若要子进程间通信,请先在子进程A中发送通信事件给主进程,包含当前进程信息及上下文信息,主进程再将信息发送给子进程B,子进程B再根据信息进行操作
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.processes: dict[str, SubProcess] = {}
|
||||
|
||||
|
||||
def add(self, name: str, func: ProcessFuncType, *args, **kwargs):
|
||||
"""
|
||||
添加子进程
|
||||
Args:
|
||||
func: 子进程函数
|
||||
name: 子进程名称
|
||||
args: 子进程函数参数
|
||||
kwargs: 子进程函数关键字参数
|
||||
Returns:
|
||||
"""
|
||||
self.processes[name] = SubProcess(name, func, *args, **kwargs)
|
||||
|
||||
def start(self, name: str):
|
||||
"""
|
||||
启动指定子进程
|
||||
Args:
|
||||
name: 子进程名称
|
||||
Returns:
|
||||
"""
|
||||
if name not in self.processes:
|
||||
raise KeyError(f"Process {name} not found.")
|
||||
self.processes[name].start()
|
||||
|
||||
def start_all(self):
|
||||
"""
|
||||
启动所有子进程
|
||||
"""
|
||||
for name, process in self.processes.items():
|
||||
process.start()
|
||||
logger.debug(f"Starting process {name}")
|
||||
|
||||
def terminate(self, name: str):
|
||||
"""
|
||||
终止指定子进程
|
||||
Args:
|
||||
name: 子进程名称
|
||||
Returns:
|
||||
"""
|
||||
if name not in self.processes:
|
||||
raise KeyError(f"Process {name} not found.")
|
||||
self.processes[name].terminate()
|
||||
|
||||
def terminate_all(self):
|
||||
"""
|
||||
终止所有子进程
|
||||
"""
|
||||
for name, process in self.processes.items():
|
||||
process.terminate()
|
||||
logger.debug(f"Terminating process {name}")
|
||||
|
||||
def get_process(self, name: str) -> SubProcess | None:
|
||||
"""
|
||||
获取指定子进程
|
||||
Args:
|
||||
name: 子进程名称
|
||||
Returns:
|
||||
"""
|
||||
return self.processes.get(name, None)
|
||||
|
||||
|
||||
sub_process_manager = _SubProcessManager()
|
||||
|
@@ -1,10 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/8/19 下午10:44
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : __init__.py.py
|
||||
@Software: PyCharm
|
||||
"""
|
@@ -1,10 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/8/19 下午10:47
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : session.py
|
||||
@Software: PyCharm
|
||||
"""
|
@@ -60,7 +60,6 @@ def load_plugin(module_path: str | Path) -> Optional[Plugin]:
|
||||
f"{metadata.name}({module.__name__.split('.')[-1]})", metadata.type
|
||||
)
|
||||
else:
|
||||
|
||||
logger.opt(colors=True).warning(
|
||||
f'The metadata of Liteyuki plugin "{module.__name__}" is not specified, use empty.'
|
||||
)
|
||||
|
@@ -9,9 +9,9 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
@Software: PyCharm
|
||||
"""
|
||||
|
||||
from liteyuki.message.on import on_startswith
|
||||
from liteyuki.message.event import MessageEvent
|
||||
from liteyuki.message.rule import is_su_rule
|
||||
from liteyuki.session.on import on_startswith
|
||||
from liteyuki.session.event import MessageEvent
|
||||
from liteyuki.session.rule import is_su_rule
|
||||
|
||||
|
||||
@on_startswith(["liteecho"], rule=is_su_rule).handle()
|
||||
|
19
liteyuki/session/__init__.py
Normal file
19
liteyuki/session/__init__.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
该模块参考并引用了nonebot-plugin-alconna的消息段定义
|
||||
"""
|
||||
from typing import Any
|
||||
|
||||
from magicoca import Chan, select
|
||||
from mypy.server.objgraph import Iterable
|
||||
from six import Iterator
|
||||
|
||||
|
||||
def message_handler_thread(i_chans: Iterable[Chan[Any]]):
|
||||
"""
|
||||
Args:
|
||||
i_chans: 多路输入管道组
|
||||
Returns:
|
||||
"""
|
||||
for msg in select(*i_chans):
|
||||
print("Recv from anybot", msg)
|
@@ -11,7 +11,6 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
from typing import Any, Optional
|
||||
|
||||
from liteyuki import Channel
|
||||
from liteyuki.comm.storage import shared_memory
|
||||
|
||||
|
||||
class MessageEvent:
|
@@ -11,8 +11,8 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
import traceback
|
||||
from typing import Any, TypeAlias, Callable, Coroutine
|
||||
|
||||
from liteyuki.message.event import MessageEvent
|
||||
from liteyuki.message.rule import Rule
|
||||
from liteyuki.session.event import MessageEvent
|
||||
from liteyuki.session.rule import Rule
|
||||
|
||||
EventHandler: TypeAlias = Callable[[MessageEvent], Coroutine[None, None, Any]]
|
||||
|
51
liteyuki/session/models.py
Normal file
51
liteyuki/session/models.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
class User(BaseModel):
|
||||
"""
|
||||
用户信息
|
||||
Attributes:
|
||||
id: 用户ID
|
||||
name: 用户名
|
||||
nick: 用户昵称
|
||||
avatar: 用户头像图链接
|
||||
"""
|
||||
id: str
|
||||
name: str | None
|
||||
nick: str | None
|
||||
avatar: str | None
|
||||
|
||||
class Scene(BaseModel):
|
||||
"""
|
||||
场景信息
|
||||
Attributes:
|
||||
id: 场景ID
|
||||
type: 场景类型
|
||||
name: 场景名
|
||||
avatar: 场景头像图链接
|
||||
parent: 父场景
|
||||
"""
|
||||
id: str
|
||||
type: str
|
||||
name: str | None
|
||||
avatar: str | None
|
||||
parent: "Scene | None"
|
||||
|
||||
class Session(BaseModel):
|
||||
"""
|
||||
会话信息
|
||||
Attributes:
|
||||
self_id: 机器人ID
|
||||
adapter: 适配器ID
|
||||
scope: 会话范围
|
||||
scene: 场景信息
|
||||
user: 用户信息
|
||||
member: 成员信息,仅频道及群聊有效
|
||||
operator: 操作者信息,仅频道及群聊有效
|
||||
"""
|
||||
self_id: str
|
||||
adapter: str
|
||||
scope: str
|
||||
scene: Scene
|
||||
user: User
|
||||
member: "Member | None"
|
||||
operator: "Member | None"
|
@@ -11,33 +11,13 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
from queue import Queue
|
||||
|
||||
from liteyuki.comm.storage import shared_memory
|
||||
from liteyuki.log import logger
|
||||
from liteyuki.message.event import MessageEvent
|
||||
from liteyuki.message.matcher import Matcher
|
||||
from liteyuki.message.rule import Rule, empty_rule
|
||||
from liteyuki.session.event import MessageEvent
|
||||
from liteyuki.session.matcher import Matcher
|
||||
from liteyuki.session.rule import Rule, empty_rule
|
||||
|
||||
_matcher_list: list[Matcher] = []
|
||||
_queue: Queue = Queue()
|
||||
|
||||
|
||||
@shared_memory.on_subscriber_receive("event_to_liteyuki")
|
||||
async def _(event: MessageEvent):
|
||||
print("AA")
|
||||
current_priority = -1
|
||||
for i, matcher in enumerate(_matcher_list):
|
||||
logger.info(f"Running matcher {matcher} for event: {event}")
|
||||
await matcher.run(event)
|
||||
# 同优先级不阻断,不同优先级阻断
|
||||
if current_priority != matcher.priority:
|
||||
current_priority = matcher.priority
|
||||
if matcher.block:
|
||||
break
|
||||
else:
|
||||
logger.info(f"No matcher matched for event: {event}")
|
||||
print("BB")
|
||||
|
||||
|
||||
def add_matcher(matcher: Matcher):
|
||||
for i, m in enumerate(_matcher_list):
|
||||
if m.priority < matcher.priority:
|
@@ -11,7 +11,7 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
import inspect
|
||||
from typing import Optional, TypeAlias, Callable, Coroutine
|
||||
|
||||
from liteyuki.message.event import MessageEvent
|
||||
from liteyuki.session.event import MessageEvent
|
||||
from liteyuki import get_config
|
||||
|
||||
_superusers: list[str] = get_config("liteyuki.superusers", [])
|
@@ -10,17 +10,18 @@ readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
authors = [
|
||||
{ name = "snowykami", email = "snowykami@outlook.com" },
|
||||
{ name = "LiteyukiStudio", email = "studio@liteyuki.icu" },
|
||||
]
|
||||
license = { text = "MIT&LSO" }
|
||||
|
||||
dependencies = [
|
||||
"loguru~=0.7.2",
|
||||
"pydantic==2.8.2",
|
||||
"PyYAML==6.0.2",
|
||||
"toml==0.10.2",
|
||||
"watchdog==4.0.1",
|
||||
"pdm-backend==2.3.3"
|
||||
"pydantic>=2.9.2",
|
||||
"PyYAML>=6.0.2",
|
||||
"toml>=0.10.2",
|
||||
"watchdog>=4.0.1",
|
||||
"pdm-backend>=2.3.3",
|
||||
"magicoca>=1.0.5",
|
||||
"croterline~=1.0.5",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
@@ -38,5 +39,14 @@ includes = ["liteyuki/", "LICENSE", "README.md"]
|
||||
excludes = ["tests/", "docs/", "src/"]
|
||||
|
||||
[tool.pdm.version]
|
||||
source = "file"
|
||||
path = "liteyuki/__init__.py"
|
||||
source = "scm"
|
||||
tag_filter = "v*"
|
||||
tag_regex = '^v(?:\D*)?(?P<version>([1-9][0-9]*!)?(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))*((a|b|c|rc)(0|[1-9][0-9]*))?(\.post(0|[1-9][0-9]*))?(\.dev(0|[1-9][0-9]*))?$)$'
|
||||
|
||||
[tool.pdm.dev-dependencies]
|
||||
dev = [
|
||||
"pytest>=8.3.3",
|
||||
"black>=24.10.0",
|
||||
"uv>=0.4.20",
|
||||
"mypy>=1.11.2",
|
||||
]
|
||||
|
@@ -1,3 +1,4 @@
|
||||
# app dependencies
|
||||
aiohttp>=3.9.3
|
||||
aiofiles>=23.2.1
|
||||
colored>=2.2.4
|
||||
@@ -26,3 +27,11 @@ importlib_metadata>=7.0.2
|
||||
watchdog>=4.0.0
|
||||
jieba>=0.42.1
|
||||
python-dotenv>=1.0.1
|
||||
loguru~=0.7.2
|
||||
pydantic~=2.9.2
|
||||
pip~=23.2.1
|
||||
fastapi~=0.115.0
|
||||
|
||||
# liteyuki dependencies
|
||||
croterline>=1.0.5
|
||||
magicoca>=1.0.5
|
@@ -9,8 +9,8 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
@Software: PyCharm
|
||||
"""
|
||||
from liteyuki.plugin import PluginMetadata, PluginType
|
||||
from liteyuki.message.on import on_message
|
||||
from liteyuki.message.event import MessageEvent
|
||||
from liteyuki.session.on import on_message
|
||||
from liteyuki.session.event import MessageEvent
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="你好轻雪",
|
||||
|
@@ -1,53 +0,0 @@
|
||||
# -*- 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.utils import IS_MAIN_PROCESS
|
||||
from liteyuki.plugin import PluginMetadata, PluginType
|
||||
from .nb_utils import adapter_manager, driver_manager # type: ignore
|
||||
from liteyuki.log import logger
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="NoneBot2启动器",
|
||||
type=PluginType.APPLICATION,
|
||||
)
|
||||
|
||||
|
||||
def nb_run(*args, **kwargs):
|
||||
"""
|
||||
初始化NoneBot并运行在子进程
|
||||
Args:
|
||||
**kwargs:
|
||||
|
||||
Returns:
|
||||
"""
|
||||
# 给子进程传递通道对象
|
||||
kwargs.update(kwargs.get("nonebot", {})) # nonebot配置优先
|
||||
nonebot.init(**kwargs)
|
||||
|
||||
driver_manager.init(config=kwargs)
|
||||
adapter_manager.init(kwargs)
|
||||
adapter_manager.register()
|
||||
|
||||
try:
|
||||
# nonebot.load_plugin("nonebot-plugin-lnpm") # 尝试加载轻雪NoneBot插件加载器(Nonebot插件)
|
||||
nonebot.load_plugin("src.liteyuki_main") # 尝试加载轻雪主插件(Nonebot插件)
|
||||
except Exception as e:
|
||||
pass
|
||||
nonebot.run()
|
||||
|
||||
|
||||
if IS_MAIN_PROCESS:
|
||||
from liteyuki import get_bot
|
||||
from .dev_reloader import *
|
||||
|
||||
liteyuki = get_bot()
|
||||
liteyuki.process_manager.add_target(name="nonebot", target=nb_run, args=(), kwargs=liteyuki.config)
|
33
src/liteyuki_plugins/nonebot/__init__.py
Normal file
33
src/liteyuki_plugins/nonebot/__init__.py
Normal file
@@ -0,0 +1,33 @@
|
||||
import os.path
|
||||
from pathlib import Path
|
||||
|
||||
import nonebot
|
||||
from croterline.utils import IsMainProcess
|
||||
|
||||
from liteyuki.core import sub_process_manager
|
||||
from liteyuki.plugin import PluginMetadata, PluginType
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="NoneBot2启动器",
|
||||
type=PluginType.APPLICATION,
|
||||
)
|
||||
|
||||
|
||||
def nb_run(*args, **kwargs):
|
||||
nonebot.init(**kwargs)
|
||||
|
||||
from .nb_utils import driver_manager, adapter_manager
|
||||
driver_manager.init(config=kwargs)
|
||||
adapter_manager.init(kwargs)
|
||||
adapter_manager.register()
|
||||
nonebot.load_plugin(Path(os.path.dirname(__file__)) / "np_main")
|
||||
nonebot.run()
|
||||
|
||||
|
||||
if IsMainProcess:
|
||||
from .dev_reloader import *
|
||||
bot = get_bot()
|
||||
|
||||
sub_process_manager.add(
|
||||
name="nonebot", func=nb_run, **bot.config.get("nonebot", {})
|
||||
)
|
@@ -10,15 +10,17 @@ from liteyuki.utils import IS_MAIN_PROCESS
|
||||
from watchdog.events import FileSystemEvent
|
||||
|
||||
|
||||
liteyuki = get_bot()
|
||||
bot = get_bot()
|
||||
|
||||
exclude_extensions = (".pyc", ".pyo")
|
||||
|
||||
|
||||
@observer.on_file_system_event(
|
||||
directories=("src/nonebot_plugins",),
|
||||
event_filter=lambda event: not event.src_path.endswith(exclude_extensions) and ("__pycache__" not in event.src_path ) and os.path.isfile(event.src_path)
|
||||
event_filter=lambda event: not event.src_path.endswith(exclude_extensions)
|
||||
and ("__pycache__" not in event.src_path)
|
||||
and os.path.isfile(event.src_path),
|
||||
)
|
||||
def restart_nonebot_process(event: FileSystemEvent):
|
||||
logger.debug(f"File {event.src_path} changed, reloading nonebot...")
|
||||
liteyuki.restart_process("nonebot")
|
||||
bot.restart_process("nonebot")
|
@@ -10,7 +10,7 @@ from .common import MessageEventModel, msg_db
|
||||
from src.utils.base.language import Language
|
||||
from src.utils.base.resource import get_path
|
||||
from src.utils.message.string_tool import convert_seconds_to_time
|
||||
from ...utils.external.logo import get_group_icon, get_user_icon
|
||||
from src.utils.external.logo import get_group_icon, get_user_icon
|
||||
|
||||
|
||||
async def count_msg_by_bot_id(bot_id: str) -> int:
|
@@ -15,7 +15,7 @@ __plugin_meta__ = PluginMetadata(
|
||||
}
|
||||
)
|
||||
|
||||
from ...utils.base.data_manager import set_memory_data
|
||||
from src.utils.base.data_manager import set_memory_data
|
||||
|
||||
driver = get_driver()
|
||||
|
@@ -3,8 +3,8 @@ import aiohttp
|
||||
from .qw_models import *
|
||||
import httpx
|
||||
|
||||
from ...utils.base.data_manager import get_memory_data
|
||||
from ...utils.base.language import Language
|
||||
from src.utils.base.data_manager import get_memory_data
|
||||
from src.utils.base.language import Language
|
||||
|
||||
dev_url = "https://devapi.qweather.com/" # 开发HBa
|
||||
com_url = "https://api.qweather.com/" # 正式环境
|
30
src/liteyuki_plugins/nonebot/nonebot_plugins/to_liteyuki.py
Normal file
30
src/liteyuki_plugins/nonebot/nonebot_plugins/to_liteyuki.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/8/20 上午5:10
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : to_liteyuki.py
|
||||
@Software: PyCharm
|
||||
"""
|
||||
|
||||
from croterline.process import get_ctx
|
||||
from nonebot.adapters.onebot.v11 import MessageEvent
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot import on_message
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪push",
|
||||
description="把消息事件传递给轻雪框架进行处理",
|
||||
usage="用户无需使用",
|
||||
)
|
||||
|
||||
ctx = get_ctx()
|
||||
|
||||
@on_message().handle()
|
||||
async def _(event: MessageEvent):
|
||||
print("Push message to Liteyuki")
|
||||
|
||||
ctx.sub_chan << event.raw_message
|
||||
|
@@ -14,7 +14,7 @@ __plugin_meta__ = PluginMetadata(
|
||||
}
|
||||
)
|
||||
|
||||
from ..utils.base.language import Language, get_default_lang_code
|
||||
from src.utils.base.language import Language, get_default_lang_code
|
||||
|
||||
sys_lang = Language(get_default_lang_code())
|
||||
nonebot.logger.info(sys_lang.get("main.current_language", LANG=sys_lang.get("language.name")))
|
@@ -20,9 +20,9 @@ from src.utils.base.language import get_user_lang
|
||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
from src.utils.message.message import MarkdownMessage as md, broadcast_to_superusers
|
||||
from .api import update_liteyuki # type: ignore
|
||||
from ..utils.base import reload # type: ignore
|
||||
from ..utils.base.ly_function import get_function # type: ignore
|
||||
from ..utils.message.html_tool import md_to_pic
|
||||
from src.utils.base import reload # type: ignore
|
||||
from src.utils.base.ly_function import get_function # type: ignore
|
||||
from src.utils.message.html_tool import md_to_pic
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
require("nonebot_plugin_apscheduler")
|
@@ -1,4 +1,6 @@
|
||||
import asyncio
|
||||
import os.path
|
||||
from pathlib import Path
|
||||
|
||||
import nonebot.plugin
|
||||
from nonebot import get_driver
|
||||
@@ -16,16 +18,19 @@ driver = get_driver()
|
||||
|
||||
@driver.on_startup
|
||||
async def load_plugins():
|
||||
nonebot.plugin.load_plugins("src/nonebot_plugins")
|
||||
nonebot.plugin.load_plugins(os.path.abspath(os.path.join(os.path.dirname(__file__), "../nonebot_plugins")))
|
||||
# 从数据库读取已安装的插件
|
||||
if not get_config("safe_mode", False):
|
||||
# 安全模式下,不加载插件
|
||||
installed_plugins: list[InstalledPlugin] = plugin_db.where_all(InstalledPlugin())
|
||||
installed_plugins: list[InstalledPlugin] = plugin_db.where_all(
|
||||
InstalledPlugin()
|
||||
)
|
||||
if installed_plugins:
|
||||
for installed_plugin in installed_plugins:
|
||||
if not check_for_package(installed_plugin.module_name):
|
||||
nonebot.logger.error(
|
||||
f"{installed_plugin.module_name} not installed, but still in loader index.")
|
||||
f"{installed_plugin.module_name} not installed, but still in loader index."
|
||||
)
|
||||
else:
|
||||
nonebot.load_plugin(installed_plugin.module_name)
|
||||
nonebot.plugin.load_plugins("plugins")
|
0
src/liteyuki_plugins/nonebot/np_main/uitls.py
Normal file
0
src/liteyuki_plugins/nonebot/np_main/uitls.py
Normal file
@@ -1,16 +0,0 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .auto_update import *
|
||||
|
||||
__author__ = "expliyh"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="Satori 用户数据自动更新(临时措施)",
|
||||
description="",
|
||||
usage="",
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
"toggleable" : True,
|
||||
"default_enable" : True,
|
||||
}
|
||||
)
|
@@ -1,20 +0,0 @@
|
||||
import nonebot
|
||||
|
||||
from nonebot.message import event_preprocessor
|
||||
from src.utils.base.ly_typing import T_MessageEvent
|
||||
from src.utils import satori_utils
|
||||
from nonebot.adapters import satori
|
||||
from nonebot_plugin_alconna.typings import Event
|
||||
from src.nonebot_plugins.liteyuki_status.counter_for_satori import satori_counter
|
||||
|
||||
|
||||
@event_preprocessor
|
||||
async def pre_handle(event: Event):
|
||||
if isinstance(event, satori.MessageEvent):
|
||||
if event.user.id == event.self_id:
|
||||
satori_counter.msg_sent += 1
|
||||
else:
|
||||
satori_counter.msg_received += 1
|
||||
if event.user.name is not None:
|
||||
if await satori_utils.user_infos.put(event.user):
|
||||
nonebot.logger.info(f"Satori user {event.user.name}<{event.user.id}> updated")
|
@@ -1,17 +0,0 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .api import *
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="联合黑名单(测试中...)",
|
||||
description="",
|
||||
usage="",
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
"toggleable" : True,
|
||||
"default_enable" : True,
|
||||
}
|
||||
)
|
||||
|
@@ -1,58 +0,0 @@
|
||||
import datetime
|
||||
|
||||
import aiohttp
|
||||
import nonebot
|
||||
from nonebot import require
|
||||
from nonebot.exception import IgnoredException
|
||||
from nonebot.message import event_preprocessor
|
||||
from nonebot_plugin_alconna.typings import Event
|
||||
|
||||
require("nonebot_plugin_apscheduler")
|
||||
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
|
||||
blacklist_data: dict[str, set[str]] = {}
|
||||
blacklist: set[str] = set()
|
||||
|
||||
|
||||
@scheduler.scheduled_job("interval", minutes=10, next_run_time=datetime.datetime.now())
|
||||
async def update_blacklist():
|
||||
await request_for_blacklist()
|
||||
|
||||
|
||||
async def request_for_blacklist():
|
||||
global blacklist
|
||||
urls = [
|
||||
"https://cdn.liteyuki.icu/static/ubl/"
|
||||
]
|
||||
|
||||
platforms = [
|
||||
"qq"
|
||||
]
|
||||
|
||||
for plat in platforms:
|
||||
for url in urls:
|
||||
url += f"{plat}.txt"
|
||||
async with aiohttp.ClientSession() as client:
|
||||
resp = await client.get(url)
|
||||
blacklist_data[plat] = set((await resp.text()).splitlines())
|
||||
blacklist = get_uni_set()
|
||||
nonebot.logger.info("blacklists updated")
|
||||
|
||||
|
||||
def get_uni_set() -> set:
|
||||
s = set()
|
||||
for new_set in blacklist_data.values():
|
||||
s.update(new_set)
|
||||
return s
|
||||
|
||||
|
||||
@event_preprocessor
|
||||
async def pre_handle(event: Event):
|
||||
try:
|
||||
user_id = str(event.get_user_id())
|
||||
except:
|
||||
return
|
||||
|
||||
if user_id in get_uni_set():
|
||||
raise IgnoredException("UserId in blacklist")
|
@@ -1,55 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/8/20 上午5:10
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : to_liteyuki.py
|
||||
@Software: PyCharm
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
from nonebot import Bot, get_bot, on_message, get_driver
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot.adapters.onebot.v11 import MessageEvent, Bot
|
||||
|
||||
from liteyuki import Channel
|
||||
from liteyuki.comm import get_channel
|
||||
from liteyuki.comm.storage import shared_memory
|
||||
from liteyuki.message.event import MessageEvent as LiteyukiMessageEvent
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪push",
|
||||
description="把消息事件传递给轻雪框架进行处理",
|
||||
usage="用户无需使用",
|
||||
)
|
||||
|
||||
recv_channel = Channel[LiteyukiMessageEvent](name="event_to_nonebot")
|
||||
|
||||
|
||||
# @on_message().handle()
|
||||
# async def _(bot: Bot, event: MessageEvent):
|
||||
# liteyuki_event = LiteyukiMessageEvent(
|
||||
# message_type=event.message_type,
|
||||
# message=event.dict()["message"],
|
||||
# raw_message=event.raw_message,
|
||||
# data=event.dict(),
|
||||
# bot_id=bot.self_id,
|
||||
# user_id=str(event.user_id),
|
||||
# session_id=str(event.user_id if event.message_type == "private" else event.group_id),
|
||||
# session_type=event.message_type,
|
||||
# receive_channel=recv_channel,
|
||||
# )
|
||||
# shared_memory.publish("event_to_liteyuki", liteyuki_event)
|
||||
|
||||
|
||||
# @get_driver().on_bot_connect
|
||||
# async def _():
|
||||
# while True:
|
||||
# event = await recv_channel.async_receive()
|
||||
# bot: Bot = get_bot(event.bot_id) # type: ignore
|
||||
# if event.message_type == "private":
|
||||
# await bot.send_private_msg(user_id=int(event.session_id), message=event.data["message"])
|
||||
# elif event.message_type == "group":
|
||||
# await bot.send_group_msg(group_id=int(event.session_id), message=event.data["message"])
|
22
tests/test_ipc.py
Normal file
22
tests/test_ipc.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from liteyuki.comm import Channel as Chan
|
||||
from multiprocessing import Process
|
||||
|
||||
|
||||
def p1(chan: Chan):
|
||||
for i in range(10):
|
||||
chan.send(i)
|
||||
|
||||
|
||||
def p2(chan: Chan):
|
||||
while True:
|
||||
print(chan.recv())
|
||||
|
||||
|
||||
def test_ipc():
|
||||
chan = Chan("Name")
|
||||
|
||||
p1_proc = Process(target=p1, args=(chan,))
|
||||
p2_proc = Process(target=p2, args=(chan,))
|
||||
|
||||
p1_proc.start()
|
||||
p2_proc.start()
|
Reference in New Issue
Block a user