mirror of
https://github.com/LiteyukiStudio/LiteyukiBot.git
synced 2025-10-05 12:16:24 +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
|
name: Publish
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
push:
|
||||||
types: [published]
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pypi-publish:
|
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
|
[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.
|
1. Install [`Git`](https://git-scm.com/download/) and [`Python3.10+`](https://www.python.org/downloads/release/python-31010/) Environment.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Clone the project
|
# Clone Repo
|
||||||
git clone https://github.com/LiteyukiStudio/LiteyukiBot --depth=1
|
git clone https://github.com/LiteyukiStudio/LiteyukiBot --depth=1
|
||||||
# change directory
|
|
||||||
|
# Change directory
|
||||||
cd LiteyukiBot
|
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
|
pip install -r requirements.txt
|
||||||
# start the bot!
|
|
||||||
|
# Run Liteyuki
|
||||||
python main.py
|
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 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
|
> 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**
|
## **Device Requirements**
|
||||||
- Windows system version minimum `Windows10+`/`Windows Server 2019+`
|
- Windows system version minimum `Windows10+`/`Windows Server 2019+`
|
||||||
|
2248
docs/pnpm-lock.yaml
generated
2248
docs/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@ order: 2
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
nonebot:
|
nonebot:
|
||||||
# Nonebot机器人的配置,以前的最外层配置项仍可为Nonebot服务,但是部分内容会被覆盖,请尽快迁移
|
# Nonebot机器人的配置,6.3.10版本后,NoneBot下配置已迁移至nonebot键下,不再使用外层配置,但是部分内容会被覆盖,请尽快迁移
|
||||||
command_start: [ "/", "" ] # 指令前缀,若没有""空命令头,请开启alconna_use_command_start保证alconna解析正常
|
command_start: [ "/", "" ] # 指令前缀,若没有""空命令头,请开启alconna_use_command_start保证alconna解析正常
|
||||||
host: 127.0.0.1 # 监听地址,默认为本机,若要接收外部请求请填写0.0.0.0
|
host: 127.0.0.1 # 监听地址,默认为本机,若要接收外部请求请填写0.0.0.0
|
||||||
port: 20216 # 绑定端口
|
port: 20216 # 绑定端口
|
||||||
|
@@ -2,11 +2,13 @@
|
|||||||
title: 安装
|
title: 安装
|
||||||
order: 1
|
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
|
```bash
|
||||||
# 克隆项目到本地,轻雪使用Git进行版本管理,该步骤为必要项
|
# 克隆项目到本地,轻雪使用Git进行版本管理,该步骤为必要项
|
||||||
@@ -14,15 +16,21 @@ git clone https://github.com/LiteyukiStudio/LiteyukiBot --depth=1 # 若你不能
|
|||||||
|
|
||||||
# 切换到Bot目录下
|
# 切换到Bot目录下
|
||||||
cd LiteyukiBot
|
cd LiteyukiBot
|
||||||
|
|
||||||
|
# 创建虚拟环境
|
||||||
|
python -m venv venv
|
||||||
|
|
||||||
|
# 激活虚拟环境
|
||||||
|
.\venv\Scripts\activate # Windows
|
||||||
|
source venv/bin/activate # Linux
|
||||||
|
|
||||||
# 安装依赖
|
# 安装依赖
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
|
|
||||||
# 启动Bot
|
# 启动Bot
|
||||||
python main.py
|
python main.py
|
||||||
```
|
```
|
||||||
|
|
||||||
> [!tip]
|
|
||||||
> 推荐使用虚拟环境来运行轻雪,以避免依赖冲突,你可以使用`python -m venv .venv`来创建虚拟环境,然后使用`.venv\Scripts\activate`来激活虚拟环境(Linux下使用`source .venv/bin/activate`激活)
|
|
||||||
|
|
||||||
## **使用Docker构建**
|
## **使用Docker构建**
|
||||||
|
|
||||||
1. 安装 [`Docker`](https://docs.docker.com/get-docker/)
|
1. 安装 [`Docker`](https://docs.docker.com/get-docker/)
|
||||||
@@ -35,10 +43,6 @@ python main.py
|
|||||||
> Windows请使用项目绝对目录`/path/to/LiteyukiBot`代替`$(pwd)` <br>
|
> Windows请使用项目绝对目录`/path/to/LiteyukiBot`代替`$(pwd)` <br>
|
||||||
> 若你修改了端口号请将`20216:20216`中的`20216`替换为你的端口号
|
> 若你修改了端口号请将`20216:20216`中的`20216`替换为你的端口号
|
||||||
|
|
||||||
## **使用TRSS Scripts部署**
|
|
||||||
[TRSS_Liteyuki轻雪机器人管理脚本](https://timerainstarsky.github.io/TRSS_Liteyuki/),该功能由TRSS提供支持,不是LiteyukiBot官方提供的功能,推荐使用`Arch Linux`
|
|
||||||
|
|
||||||
|
|
||||||
## **装置要求**
|
## **装置要求**
|
||||||
|
|
||||||
- Windows系统版本最低`Windows10+`/`Windows Server 2019+`
|
- Windows系统版本最低`Windows10+`/`Windows Server 2019+`
|
||||||
@@ -48,7 +52,8 @@ python main.py
|
|||||||
- 硬盘: 至少`1GB`空间
|
- 硬盘: 至少`1GB`空间
|
||||||
|
|
||||||
> [!warning]
|
> [!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]
|
> [!warning]
|
||||||
> 轻雪的更新功能依赖Git,如果你没有安装Git直接下载源代码运行,你将无法使用更新功能
|
> 轻雪的更新功能依赖Git,如果你没有安装Git直接下载源代码运行,你将无法使用更新功能
|
||||||
|
@@ -1,25 +1,28 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import atexit
|
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import signal
|
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from typing import Any, Optional
|
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.comm.channel import get_channel
|
||||||
from liteyuki.core.manager import ProcessManager
|
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.log import init_log, logger
|
||||||
from liteyuki.plugin import load_plugin
|
from liteyuki.plugin import load_plugin
|
||||||
|
from liteyuki.session import message_handler_thread
|
||||||
from liteyuki.utils import IS_MAIN_PROCESS
|
from liteyuki.utils import IS_MAIN_PROCESS
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"LiteyukiBot",
|
"LiteyukiBot",
|
||||||
"get_bot",
|
"get_bot",
|
||||||
"get_config",
|
"get_config",
|
||||||
"get_config_with_compat",
|
"get_config_with_compat",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -30,6 +33,10 @@ class LiteyukiBot:
|
|||||||
Args:
|
Args:
|
||||||
**kwargs: 配置
|
**kwargs: 配置
|
||||||
"""
|
"""
|
||||||
|
"""总通道"""
|
||||||
|
self.i_chan = Chan[Any]() # 外部输入通道
|
||||||
|
self.o_chan = Chan[Any]() # 外部输出通道
|
||||||
|
|
||||||
"""常规操作"""
|
"""常规操作"""
|
||||||
print_logo()
|
print_logo()
|
||||||
global _BOT_INSTANCE
|
global _BOT_INSTANCE
|
||||||
@@ -60,8 +67,9 @@ class LiteyukiBot:
|
|||||||
启动逻辑
|
启动逻辑
|
||||||
"""
|
"""
|
||||||
await self.lifespan.before_start() # 启动前钩子
|
await self.lifespan.before_start() # 启动前钩子
|
||||||
|
sub_process_manager.start_all()
|
||||||
await self.lifespan.after_start() # 启动后钩子
|
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):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
@@ -73,19 +81,7 @@ class LiteyukiBot:
|
|||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
logger.opt(colors=True).info("<y>Liteyuki is stopping...</y>")
|
logger.opt(colors=True).info("<y>Liteyuki is stopping...</y>")
|
||||||
self.stop()
|
self.stop()
|
||||||
logger.opt(colors=True).info("<y>Liteyuki is stopped...</y>")
|
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()
|
|
||||||
|
|
||||||
def restart(self, delay: int = 0):
|
def restart(self, delay: int = 0):
|
||||||
"""
|
"""
|
||||||
@@ -108,7 +104,11 @@ class LiteyukiBot:
|
|||||||
cmd = "nohup"
|
cmd = "nohup"
|
||||||
self.process_manager.terminate_all()
|
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)
|
sys.exit(0)
|
||||||
self.call_restart_count += 1
|
self.call_restart_count += 1
|
||||||
|
|
||||||
@@ -189,7 +189,9 @@ class LiteyukiBot:
|
|||||||
"""
|
"""
|
||||||
return self.lifespan.on_before_process_shutdown(func)
|
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:
|
Args:
|
||||||
@@ -211,7 +213,7 @@ class LiteyukiBot:
|
|||||||
return self.lifespan.on_after_restart(func)
|
return self.lifespan.on_after_restart(func)
|
||||||
|
|
||||||
|
|
||||||
_BOT_INSTANCE: LiteyukiBot
|
_BOT_INSTANCE: LiteyukiBot | None = None
|
||||||
|
|
||||||
|
|
||||||
def get_bot() -> LiteyukiBot:
|
def get_bot() -> LiteyukiBot:
|
||||||
@@ -241,7 +243,9 @@ def get_config(key: str, default: Any = None) -> Any:
|
|||||||
return get_bot().config.get(key, default)
|
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:
|
Args:
|
||||||
@@ -256,14 +260,18 @@ def get_config_with_compat(key: str, compat_keys: tuple[str], default: Any = Non
|
|||||||
return get_bot().config[key]
|
return get_bot().config[key]
|
||||||
for compat_key in compat_keys:
|
for compat_key in compat_keys:
|
||||||
if compat_key in get_bot().config:
|
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 get_bot().config[compat_key]
|
||||||
return default
|
return default
|
||||||
|
|
||||||
|
|
||||||
def print_logo():
|
def print_logo():
|
||||||
"""@litedoc-hide"""
|
"""@litedoc-hide"""
|
||||||
print("\033[34m" + r"""
|
print(
|
||||||
|
"\033[34m"
|
||||||
|
+ r"""
|
||||||
__ ______ ________ ________ __ __ __ __ __ __ ______
|
__ ______ ________ ________ __ __ __ __ __ __ ______
|
||||||
/ | / |/ |/ |/ \ / |/ | / |/ | / |/ |
|
/ | / |/ |/ |/ \ / |/ | / |/ | / |/ |
|
||||||
$$ | $$$$$$/ $$$$$$$$/ $$$$$$$$/ $$ \ /$$/ $$ | $$ |$$ | /$$/ $$$$$$/
|
$$ | $$$$$$/ $$$$$$$$/ $$$$$$$$/ $$ \ /$$/ $$ | $$ |$$ | /$$/ $$$$$$/
|
||||||
@@ -273,4 +281,6 @@ def print_logo():
|
|||||||
$$ |_____ _$$ |_ $$ | $$ |_____ $$ | $$ \__$$ |$$ |$$ \ _$$ |_
|
$$ |_____ _$$ |_ $$ | $$ |_____ $$ | $$ \__$$ |$$ |$$ \ _$$ |_
|
||||||
$$ |/ $$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |/ $$ |
|
$$ |/ $$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |/ $$ |
|
||||||
$$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/
|
$$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/
|
||||||
""" + "\033[0m")
|
"""
|
||||||
|
+ "\033[0m"
|
||||||
|
)
|
||||||
|
@@ -4,20 +4,31 @@
|
|||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
from multiprocessing import Pipe
|
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.log import logger
|
||||||
from liteyuki.utils import IS_MAIN_PROCESS, is_coroutine_callable
|
from liteyuki.utils import IS_MAIN_PROCESS, is_coroutine_callable
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
SYNC_ON_RECEIVE_FUNC: TypeAlias = Callable[[T], Any] # 同步接收函数
|
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[
|
||||||
ON_RECEIVE_FUNC: TypeAlias = SYNC_ON_RECEIVE_FUNC | ASYNC_ON_RECEIVE_FUNC # 接收函数
|
[T], Coroutine[Any, Any, Any]
|
||||||
|
] # 异步接收函数
|
||||||
|
ON_RECEIVE_FUNC: TypeAlias = SYNC_ON_RECEIVE_FUNC | ASYNC_ON_RECEIVE_FUNC # 接收函数
|
||||||
|
|
||||||
SYNC_FILTER_FUNC: TypeAlias = Callable[[T], bool] # 同步过滤函数
|
SYNC_FILTER_FUNC: TypeAlias = Callable[[T], bool] # 同步过滤函数
|
||||||
ASYNC_FILTER_FUNC: TypeAlias = Callable[[T], Coroutine[Any, Any, bool]] # 异步过滤函数
|
ASYNC_FILTER_FUNC: TypeAlias = Callable[[T], Coroutine[Any, Any, bool]] # 异步过滤函数
|
||||||
FILTER_FUNC: TypeAlias = SYNC_FILTER_FUNC | ASYNC_FILTER_FUNC # 过滤函数
|
FILTER_FUNC: TypeAlias = SYNC_FILTER_FUNC | ASYNC_FILTER_FUNC # 过滤函数
|
||||||
|
|
||||||
_func_id: int = 0
|
_func_id: int = 0
|
||||||
_channel: dict[str, "Channel"] = {}
|
_channel: dict[str, "Channel"] = {}
|
||||||
@@ -39,7 +50,9 @@ class Channel(Generic[T]):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
self.conn_send, self.conn_recv = Pipe()
|
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._closed = False
|
||||||
self._on_main_receive_func_ids: list[int] = []
|
self._on_main_receive_func_ids: list[int] = []
|
||||||
self._on_sub_receive_func_ids: list[int] = []
|
self._on_sub_receive_func_ids: list[int] = []
|
||||||
@@ -64,7 +77,9 @@ class Channel(Generic[T]):
|
|||||||
_channel[name] = self
|
_channel[name] = self
|
||||||
logger.debug(f"Channel {name} initialized in main process")
|
logger.debug(f"Channel {name} initialized in main process")
|
||||||
else:
|
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]:
|
def _get_generic_type(self) -> Optional[type]:
|
||||||
"""
|
"""
|
||||||
@@ -72,7 +87,7 @@ class Channel(Generic[T]):
|
|||||||
Returns:
|
Returns:
|
||||||
Optional[type]: 泛型类型
|
Optional[type]: 泛型类型
|
||||||
"""
|
"""
|
||||||
if hasattr(self, '__orig_class__'):
|
if hasattr(self, "__orig_class__"):
|
||||||
return get_args(self.__orig_class__)[0]
|
return get_args(self.__orig_class__)[0]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -98,7 +113,10 @@ class Channel(Generic[T]):
|
|||||||
elif isinstance(structure, dict):
|
elif isinstance(structure, dict):
|
||||||
if not isinstance(data, dict):
|
if not isinstance(data, dict):
|
||||||
return False
|
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
|
return False
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@@ -113,10 +131,12 @@ class Channel(Generic[T]):
|
|||||||
if self.type_check:
|
if self.type_check:
|
||||||
_type = self._get_generic_type()
|
_type = self._get_generic_type()
|
||||||
if _type is not None and not self._validate_structure(data, _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:
|
if self._closed:
|
||||||
raise RuntimeError("Cannot send to a closed channel_")
|
raise RuntimeError("Cannot send to a closed channel")
|
||||||
self.conn_send.send(data)
|
self.conn_send.send(data)
|
||||||
|
|
||||||
def receive(self) -> T:
|
def receive(self) -> T:
|
||||||
@@ -126,7 +146,7 @@ class Channel(Generic[T]):
|
|||||||
T: 数据
|
T: 数据
|
||||||
"""
|
"""
|
||||||
if self._closed:
|
if self._closed:
|
||||||
raise RuntimeError("Cannot receive from a closed channel_")
|
raise RuntimeError("Cannot receive from a closed channel")
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
data = self.conn_recv.recv()
|
data = self.conn_recv.recv()
|
||||||
@@ -142,7 +162,9 @@ class Channel(Generic[T]):
|
|||||||
data = await loop.run_in_executor(None, self.receive)
|
data = await loop.run_in_executor(None, self.receive)
|
||||||
return data
|
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:
|
Args:
|
||||||
@@ -187,37 +209,52 @@ class Channel(Generic[T]):
|
|||||||
data: 数据
|
data: 数据
|
||||||
"""
|
"""
|
||||||
if IS_MAIN_PROCESS:
|
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:
|
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") # 主动通道
|
active_channel: Channel = Channel(name="active_channel") # 主动通道
|
||||||
passive_channel: Channel = Channel(name="passive_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_active_channel: Channel[Channel[Any]] # 主动通道传递通道
|
||||||
channel_deliver_passive_channel: Channel[tuple[str, dict[str, Any]]] # 被动通道传递通道
|
channel_deliver_passive_channel: Channel[tuple[str, dict[str, Any]]] # 被动通道传递通道
|
||||||
|
|
||||||
if IS_MAIN_PROCESS:
|
if IS_MAIN_PROCESS:
|
||||||
channel_deliver_active_channel = Channel(name="channel_deliver_active_channel") # 主动通道传递通道
|
channel_deliver_active_channel = Channel(
|
||||||
channel_deliver_passive_channel = Channel(name="channel_deliver_passive_channel") # 被动通道传递通道
|
name="channel_deliver_active_channel"
|
||||||
|
) # 主动通道传递通道
|
||||||
|
channel_deliver_passive_channel = Channel(
|
||||||
|
name="channel_deliver_passive_channel"
|
||||||
|
) # 被动通道传递通道
|
||||||
|
|
||||||
|
@channel_deliver_passive_channel.on_receive(
|
||||||
@channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == "set_channel")
|
filter_func=lambda data: data[0] == "set_channel"
|
||||||
|
)
|
||||||
def on_set_channel(data: tuple[str, dict[str, Any]]):
|
def on_set_channel(data: tuple[str, dict[str, Any]]):
|
||||||
name, channel = data[1]["name"], data[1]["channel_"]
|
name, channel = data[1]["name"], data[1]["channel_"]
|
||||||
set_channel(name, channel)
|
set_channel(name, channel)
|
||||||
|
|
||||||
|
@channel_deliver_passive_channel.on_receive(
|
||||||
@channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == "get_channel")
|
filter_func=lambda data: data[0] == "get_channel"
|
||||||
|
)
|
||||||
def on_get_channel(data: tuple[str, dict[str, Any]]):
|
def on_get_channel(data: tuple[str, dict[str, Any]]):
|
||||||
name, recv_chan = data[1]["name"], data[1]["recv_chan"]
|
name, recv_chan = data[1]["name"], data[1]["recv_chan"]
|
||||||
recv_chan.send(get_channel(name))
|
recv_chan.send(get_channel(name))
|
||||||
|
|
||||||
|
@channel_deliver_passive_channel.on_receive(
|
||||||
@channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == "get_channels")
|
filter_func=lambda data: data[0] == "get_channels"
|
||||||
|
)
|
||||||
def on_get_channels(data: tuple[str, dict[str, Any]]):
|
def on_get_channels(data: tuple[str, dict[str, Any]]):
|
||||||
recv_chan = data[1]["recv_chan"]
|
recv_chan = data[1]["recv_chan"]
|
||||||
recv_chan.send(get_channels())
|
recv_chan.send(get_channels())
|
||||||
@@ -231,7 +268,9 @@ def set_channel(name: str, channel: "Channel"):
|
|||||||
channel ([`Channel`](#class-channel-generic-t)): 通道实例
|
channel ([`Channel`](#class-channel-generic-t)): 通道实例
|
||||||
"""
|
"""
|
||||||
if not isinstance(channel, Channel):
|
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 IS_MAIN_PROCESS:
|
||||||
if name in _channel:
|
if name in _channel:
|
||||||
@@ -241,10 +280,11 @@ def set_channel(name: str, channel: "Channel"):
|
|||||||
# 请求主进程设置通道
|
# 请求主进程设置通道
|
||||||
channel_deliver_passive_channel.send(
|
channel_deliver_passive_channel.send(
|
||||||
(
|
(
|
||||||
"set_channel", {
|
"set_channel",
|
||||||
"name" : name,
|
{
|
||||||
"channel_": channel,
|
"name": name,
|
||||||
}
|
"channel_": channel,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -273,13 +313,7 @@ def get_channel(name: str) -> "Channel":
|
|||||||
else:
|
else:
|
||||||
recv_chan = Channel[Channel[Any]]("recv_chan")
|
recv_chan = Channel[Channel[Any]]("recv_chan")
|
||||||
channel_deliver_passive_channel.send(
|
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()
|
return recv_chan.receive()
|
||||||
|
|
||||||
@@ -294,12 +328,5 @@ def get_channels() -> dict[str, "Channel"]:
|
|||||||
return _channel
|
return _channel
|
||||||
else:
|
else:
|
||||||
recv_chan = Channel[dict[str, Channel[Any]]]("recv_chan")
|
recv_chan = Channel[dict[str, Channel[Any]]]("recv_chan")
|
||||||
channel_deliver_passive_channel.send(
|
channel_deliver_passive_channel.send(("get_channels", {"recv_chan": recv_chan}))
|
||||||
(
|
|
||||||
"get_channels",
|
|
||||||
{
|
|
||||||
"recv_chan": recv_chan
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return recv_chan.receive()
|
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 multiprocessing import Process
|
||||||
from typing import Any, Callable, TYPE_CHECKING, TypeAlias
|
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.log import logger
|
||||||
from liteyuki.utils import IS_MAIN_PROCESS
|
from liteyuki.utils import IS_MAIN_PROCESS
|
||||||
|
|
||||||
@@ -26,7 +29,10 @@ from liteyuki.comm import Channel
|
|||||||
if IS_MAIN_PROCESS:
|
if IS_MAIN_PROCESS:
|
||||||
from liteyuki.comm.channel import get_channel, publish_channel, get_channels
|
from liteyuki.comm.channel import get_channel, publish_channel, get_channels
|
||||||
from liteyuki.comm.storage import shared_memory
|
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:
|
else:
|
||||||
from liteyuki.comm import channel
|
from liteyuki.comm import channel
|
||||||
from liteyuki.comm import storage
|
from liteyuki.comm import storage
|
||||||
@@ -34,20 +40,18 @@ else:
|
|||||||
TARGET_FUNC: TypeAlias = Callable[..., Any]
|
TARGET_FUNC: TypeAlias = Callable[..., Any]
|
||||||
TIMEOUT = 10
|
TIMEOUT = 10
|
||||||
|
|
||||||
__all__ = [
|
__all__ = ["ProcessManager", "sub_process_manager"]
|
||||||
"ProcessManager"
|
|
||||||
]
|
|
||||||
multiprocessing.set_start_method("spawn", force=True)
|
multiprocessing.set_start_method("spawn", force=True)
|
||||||
|
|
||||||
|
|
||||||
class ChannelDeliver:
|
class ChannelDeliver:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
active: Channel[Any],
|
active: Channel[Any],
|
||||||
passive: Channel[Any],
|
passive: Channel[Any],
|
||||||
channel_deliver_active: Channel[Channel[Any]],
|
channel_deliver_active: Channel[Channel[Any]],
|
||||||
channel_deliver_passive: Channel[tuple[str, dict]],
|
channel_deliver_passive: Channel[tuple[str, dict]],
|
||||||
publish: Channel[tuple[str, Any]],
|
publish: Channel[tuple[str, Any]],
|
||||||
):
|
):
|
||||||
self.active = active
|
self.active = active
|
||||||
self.passive = passive
|
self.passive = passive
|
||||||
@@ -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.active_channel = cd.active # 子进程主动通道
|
||||||
channel.passive_channel = cd.passive # 子进程被动通道
|
channel.passive_channel = cd.passive # 子进程被动通道
|
||||||
channel.channel_deliver_active_channel = cd.channel_deliver_active # 子进程通道传递主动通道
|
channel.channel_deliver_active_channel = (
|
||||||
channel.channel_deliver_passive_channel = cd.channel_deliver_passive # 子进程通道传递被动通道
|
cd.channel_deliver_active
|
||||||
|
) # 子进程通道传递主动通道
|
||||||
|
channel.channel_deliver_passive_channel = (
|
||||||
|
cd.channel_deliver_passive
|
||||||
|
) # 子进程通道传递被动通道
|
||||||
channel.publish_channel = cd.publish # 子进程发布通道
|
channel.publish_channel = cd.publish # 子进程发布通道
|
||||||
|
|
||||||
# 给子进程创建共享内存实例
|
# 给子进程创建共享内存实例
|
||||||
@@ -102,8 +112,12 @@ class ProcessManager:
|
|||||||
chan_active = get_channel(f"{name}-active")
|
chan_active = get_channel(f"{name}-active")
|
||||||
|
|
||||||
def _start_process():
|
def _start_process():
|
||||||
process = Process(target=self.targets[name][0], args=self.targets[name][1],
|
process = Process(
|
||||||
kwargs=self.targets[name][2], daemon=True)
|
target=self.targets[name][0],
|
||||||
|
args=self.targets[name][1],
|
||||||
|
kwargs=self.targets[name][2],
|
||||||
|
daemon=True,
|
||||||
|
)
|
||||||
self.processes[name] = process
|
self.processes[name] = process
|
||||||
process.start()
|
process.start()
|
||||||
|
|
||||||
@@ -133,7 +147,9 @@ class ProcessManager:
|
|||||||
|
|
||||||
for name in self.targets:
|
for name in self.targets:
|
||||||
logger.debug(f"Starting process {name}")
|
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):
|
def add_target(self, name: str, target: TARGET_FUNC, args: tuple = (), kwargs=None):
|
||||||
"""
|
"""
|
||||||
@@ -154,10 +170,14 @@ class ProcessManager:
|
|||||||
passive=chan_passive,
|
passive=chan_passive,
|
||||||
channel_deliver_active=channel_deliver_active_channel,
|
channel_deliver_active=channel_deliver_active_channel,
|
||||||
channel_deliver_passive=channel_deliver_passive_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):
|
def join_all(self):
|
||||||
@@ -199,3 +219,79 @@ class ProcessManager:
|
|||||||
if name not in self.targets:
|
if name not in self.targets:
|
||||||
logger.warning(f"Process {name} not found.")
|
logger.warning(f"Process {name} not found.")
|
||||||
return self.processes[name].is_alive()
|
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
|
f"{metadata.name}({module.__name__.split('.')[-1]})", metadata.type
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
|
||||||
logger.opt(colors=True).warning(
|
logger.opt(colors=True).warning(
|
||||||
f'The metadata of Liteyuki plugin "{module.__name__}" is not specified, use empty.'
|
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
|
@Software: PyCharm
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from liteyuki.message.on import on_startswith
|
from liteyuki.session.on import on_startswith
|
||||||
from liteyuki.message.event import MessageEvent
|
from liteyuki.session.event import MessageEvent
|
||||||
from liteyuki.message.rule import is_su_rule
|
from liteyuki.session.rule import is_su_rule
|
||||||
|
|
||||||
|
|
||||||
@on_startswith(["liteecho"], rule=is_su_rule).handle()
|
@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 typing import Any, Optional
|
||||||
|
|
||||||
from liteyuki import Channel
|
from liteyuki import Channel
|
||||||
from liteyuki.comm.storage import shared_memory
|
|
||||||
|
|
||||||
|
|
||||||
class MessageEvent:
|
class MessageEvent:
|
@@ -11,8 +11,8 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|||||||
import traceback
|
import traceback
|
||||||
from typing import Any, TypeAlias, Callable, Coroutine
|
from typing import Any, TypeAlias, Callable, Coroutine
|
||||||
|
|
||||||
from liteyuki.message.event import MessageEvent
|
from liteyuki.session.event import MessageEvent
|
||||||
from liteyuki.message.rule import Rule
|
from liteyuki.session.rule import Rule
|
||||||
|
|
||||||
EventHandler: TypeAlias = Callable[[MessageEvent], Coroutine[None, None, Any]]
|
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 queue import Queue
|
||||||
|
|
||||||
from liteyuki.comm.storage import shared_memory
|
from liteyuki.session.event import MessageEvent
|
||||||
from liteyuki.log import logger
|
from liteyuki.session.matcher import Matcher
|
||||||
from liteyuki.message.event import MessageEvent
|
from liteyuki.session.rule import Rule, empty_rule
|
||||||
from liteyuki.message.matcher import Matcher
|
|
||||||
from liteyuki.message.rule import Rule, empty_rule
|
|
||||||
|
|
||||||
_matcher_list: list[Matcher] = []
|
_matcher_list: list[Matcher] = []
|
||||||
_queue: Queue = Queue()
|
_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):
|
def add_matcher(matcher: Matcher):
|
||||||
for i, m in enumerate(_matcher_list):
|
for i, m in enumerate(_matcher_list):
|
||||||
if m.priority < matcher.priority:
|
if m.priority < matcher.priority:
|
@@ -11,7 +11,7 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|||||||
import inspect
|
import inspect
|
||||||
from typing import Optional, TypeAlias, Callable, Coroutine
|
from typing import Optional, TypeAlias, Callable, Coroutine
|
||||||
|
|
||||||
from liteyuki.message.event import MessageEvent
|
from liteyuki.session.event import MessageEvent
|
||||||
from liteyuki import get_config
|
from liteyuki import get_config
|
||||||
|
|
||||||
_superusers: list[str] = get_config("liteyuki.superusers", [])
|
_superusers: list[str] = get_config("liteyuki.superusers", [])
|
@@ -10,17 +10,18 @@ readme = "README.md"
|
|||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
authors = [
|
authors = [
|
||||||
{ name = "snowykami", email = "snowykami@outlook.com" },
|
{ name = "snowykami", email = "snowykami@outlook.com" },
|
||||||
{ name = "LiteyukiStudio", email = "studio@liteyuki.icu" },
|
|
||||||
]
|
]
|
||||||
license = { text = "MIT&LSO" }
|
license = { text = "MIT&LSO" }
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"loguru~=0.7.2",
|
"loguru~=0.7.2",
|
||||||
"pydantic==2.8.2",
|
"pydantic>=2.9.2",
|
||||||
"PyYAML==6.0.2",
|
"PyYAML>=6.0.2",
|
||||||
"toml==0.10.2",
|
"toml>=0.10.2",
|
||||||
"watchdog==4.0.1",
|
"watchdog>=4.0.1",
|
||||||
"pdm-backend==2.3.3"
|
"pdm-backend>=2.3.3",
|
||||||
|
"magicoca>=1.0.5",
|
||||||
|
"croterline~=1.0.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
@@ -38,5 +39,14 @@ includes = ["liteyuki/", "LICENSE", "README.md"]
|
|||||||
excludes = ["tests/", "docs/", "src/"]
|
excludes = ["tests/", "docs/", "src/"]
|
||||||
|
|
||||||
[tool.pdm.version]
|
[tool.pdm.version]
|
||||||
source = "file"
|
source = "scm"
|
||||||
path = "liteyuki/__init__.py"
|
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
|
aiohttp>=3.9.3
|
||||||
aiofiles>=23.2.1
|
aiofiles>=23.2.1
|
||||||
colored>=2.2.4
|
colored>=2.2.4
|
||||||
@@ -26,3 +27,11 @@ importlib_metadata>=7.0.2
|
|||||||
watchdog>=4.0.0
|
watchdog>=4.0.0
|
||||||
jieba>=0.42.1
|
jieba>=0.42.1
|
||||||
python-dotenv>=1.0.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
|
@Software: PyCharm
|
||||||
"""
|
"""
|
||||||
from liteyuki.plugin import PluginMetadata, PluginType
|
from liteyuki.plugin import PluginMetadata, PluginType
|
||||||
from liteyuki.message.on import on_message
|
from liteyuki.session.on import on_message
|
||||||
from liteyuki.message.event import MessageEvent
|
from liteyuki.session.event import MessageEvent
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="你好轻雪",
|
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
|
from watchdog.events import FileSystemEvent
|
||||||
|
|
||||||
|
|
||||||
liteyuki = get_bot()
|
bot = get_bot()
|
||||||
|
|
||||||
exclude_extensions = (".pyc", ".pyo")
|
exclude_extensions = (".pyc", ".pyo")
|
||||||
|
|
||||||
|
|
||||||
@observer.on_file_system_event(
|
@observer.on_file_system_event(
|
||||||
directories=("src/nonebot_plugins",),
|
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):
|
def restart_nonebot_process(event: FileSystemEvent):
|
||||||
logger.debug(f"File {event.src_path} changed, reloading nonebot...")
|
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.language import Language
|
||||||
from src.utils.base.resource import get_path
|
from src.utils.base.resource import get_path
|
||||||
from src.utils.message.string_tool import convert_seconds_to_time
|
from src.utils.message.string_tool import convert_seconds_to_time
|
||||||
from ...utils.external.logo import get_group_icon, get_user_icon
|
from src.utils.external.logo import get_group_icon, get_user_icon
|
||||||
|
|
||||||
|
|
||||||
async def count_msg_by_bot_id(bot_id: str) -> int:
|
async def count_msg_by_bot_id(bot_id: str) -> int:
|
@@ -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()
|
driver = get_driver()
|
||||||
|
|
@@ -3,8 +3,8 @@ import aiohttp
|
|||||||
from .qw_models import *
|
from .qw_models import *
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from ...utils.base.data_manager import get_memory_data
|
from src.utils.base.data_manager import get_memory_data
|
||||||
from ...utils.base.language import Language
|
from src.utils.base.language import Language
|
||||||
|
|
||||||
dev_url = "https://devapi.qweather.com/" # 开发HBa
|
dev_url = "https://devapi.qweather.com/" # 开发HBa
|
||||||
com_url = "https://api.qweather.com/" # 正式环境
|
com_url = "https://api.qweather.com/" # 正式环境
|
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())
|
sys_lang = Language(get_default_lang_code())
|
||||||
nonebot.logger.info(sys_lang.get("main.current_language", LANG=sys_lang.get("language.name")))
|
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.base.ly_typing import T_Bot, T_MessageEvent
|
||||||
from src.utils.message.message import MarkdownMessage as md, broadcast_to_superusers
|
from src.utils.message.message import MarkdownMessage as md, broadcast_to_superusers
|
||||||
from .api import update_liteyuki # type: ignore
|
from .api import update_liteyuki # type: ignore
|
||||||
from ..utils.base import reload # type: ignore
|
from src.utils.base import reload # type: ignore
|
||||||
from ..utils.base.ly_function import get_function # type: ignore
|
from src.utils.base.ly_function import get_function # type: ignore
|
||||||
from ..utils.message.html_tool import md_to_pic
|
from src.utils.message.html_tool import md_to_pic
|
||||||
|
|
||||||
require("nonebot_plugin_alconna")
|
require("nonebot_plugin_alconna")
|
||||||
require("nonebot_plugin_apscheduler")
|
require("nonebot_plugin_apscheduler")
|
@@ -1,4 +1,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
import os.path
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import nonebot.plugin
|
import nonebot.plugin
|
||||||
from nonebot import get_driver
|
from nonebot import get_driver
|
||||||
@@ -16,16 +18,19 @@ driver = get_driver()
|
|||||||
|
|
||||||
@driver.on_startup
|
@driver.on_startup
|
||||||
async def load_plugins():
|
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):
|
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:
|
if installed_plugins:
|
||||||
for installed_plugin in installed_plugins:
|
for installed_plugin in installed_plugins:
|
||||||
if not check_for_package(installed_plugin.module_name):
|
if not check_for_package(installed_plugin.module_name):
|
||||||
nonebot.logger.error(
|
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:
|
else:
|
||||||
nonebot.load_plugin(installed_plugin.module_name)
|
nonebot.load_plugin(installed_plugin.module_name)
|
||||||
nonebot.plugin.load_plugins("plugins")
|
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