forked from bot/app
Merge remote-tracking branch 'origin/main'
# Conflicts: # .github/workflows/deploy-docs.yml # docs/dev/api/README.md # docs/dev/api/bot/README.md # docs/dev/api/bot/lifespan.md # docs/dev/api/comm/README.md # docs/dev/api/comm/channel.md # docs/dev/api/comm/event.md # docs/dev/api/comm/storage.md # docs/dev/api/config.md # docs/dev/api/core/README.md # docs/dev/api/core/manager.md # docs/dev/api/dev/README.md # docs/dev/api/dev/observer.md # docs/dev/api/dev/plugin.md # docs/dev/api/exception.md # docs/dev/api/log.md # docs/dev/api/message/README.md # docs/dev/api/message/event.md # docs/dev/api/message/matcher.md # docs/dev/api/message/on.md # docs/dev/api/message/rule.md # docs/dev/api/message/session.md # docs/dev/api/mkdoc.md # docs/dev/api/plugin/README.md # docs/dev/api/plugin/load.md # docs/dev/api/plugin/manager.md # docs/dev/api/plugin/model.md # docs/dev/api/utils.md # docs/en/dev/api/README.md # docs/en/dev/api/bot/README.md # docs/en/dev/api/bot/lifespan.md # docs/en/dev/api/comm/README.md # docs/en/dev/api/comm/channel.md # docs/en/dev/api/comm/event.md # docs/en/dev/api/comm/storage.md # docs/en/dev/api/config.md # docs/en/dev/api/core/README.md # docs/en/dev/api/core/manager.md # docs/en/dev/api/dev/README.md # docs/en/dev/api/dev/observer.md # docs/en/dev/api/dev/plugin.md # docs/en/dev/api/exception.md # docs/en/dev/api/log.md # docs/en/dev/api/message/README.md # docs/en/dev/api/message/event.md # docs/en/dev/api/message/matcher.md # docs/en/dev/api/message/on.md # docs/en/dev/api/message/rule.md # docs/en/dev/api/message/session.md # docs/en/dev/api/mkdoc.md # docs/en/dev/api/plugin/README.md # docs/en/dev/api/plugin/load.md # docs/en/dev/api/plugin/manager.md # docs/en/dev/api/plugin/model.md # docs/en/dev/api/utils.md # litedoc/__main__.py # litedoc/docstring/docstring.py # litedoc/output.py # litedoc/style/markdown.py # litedoc/syntax/astparser.py
This commit is contained in:
commit
4910de74fd
@ -1,8 +0,0 @@
|
|||||||
---
|
|
||||||
title: 开发及贡献
|
|
||||||
index: false
|
|
||||||
icon: laptop-code
|
|
||||||
category: 开发
|
|
||||||
---
|
|
||||||
|
|
||||||
<Catalog />
|
|
@ -1,99 +0,0 @@
|
|||||||
---
|
|
||||||
title: 进程通信
|
|
||||||
icon: exchange-alt
|
|
||||||
order: 4
|
|
||||||
category: 开发
|
|
||||||
---
|
|
||||||
|
|
||||||
## **通道通信**
|
|
||||||
|
|
||||||
### 简介
|
|
||||||
|
|
||||||
轻雪运行在主进程 MainProcess 里,其他插件框架进程是伴随的子进程,因此无法通过内存共享和直接对象传递的方式进行通信,轻雪提供了一个通道`Channel`用于跨进程通信,你可以通过`Channel`发送消息给其他进程,也可以监听其他进程的消息。
|
|
||||||
|
|
||||||
例如子进程接收到用户信息需要重启机器人,这时可以通过通道对主进程发送消息,主进程接收到消息后重启对应子进程。
|
|
||||||
|
|
||||||
### 示例
|
|
||||||
|
|
||||||
通道是全双工的,有两种接收模式,但一个通道只能使用一种,即被动模式和主动模式,被动模式由`chan.on_receive()`装饰回调函数实现,主动模式需调用`chan.receive()`实现
|
|
||||||
|
|
||||||
- 创建子进程的同时会初始化一个被动通道和一个主动通道,且通道标识为`{process_name}-active`和`{process_name}-passive`,
|
|
||||||
- 主进程中通过`get_channel`函数获取通道对象
|
|
||||||
- 子进程中导入单例`active_channel`及`passive_channel`即可
|
|
||||||
|
|
||||||
> 在轻雪插件中(主进程中)
|
|
||||||
|
|
||||||
```python
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
from liteyuki.comm import get_channel, Channel
|
|
||||||
from liteyuki import get_bot
|
|
||||||
|
|
||||||
# get_channel函数获取通道对象,参数为调用set_channel时的通道标识
|
|
||||||
channel_passive = get_channel("nonebot-passive") # 获取被动通道
|
|
||||||
channel_active = get_channel("nonebot-active") # 获取主动通道
|
|
||||||
liteyuki_bot = get_bot()
|
|
||||||
|
|
||||||
|
|
||||||
# 注册一个函数在轻雪启动后运行
|
|
||||||
@liteyuki_bot.on_after_start
|
|
||||||
async def send_data():
|
|
||||||
while True:
|
|
||||||
channel_passive.send("I am liteyuki main process passive")
|
|
||||||
channel_active.send("I am liteyuki main process active")
|
|
||||||
await asyncio.sleep(3) # 每3秒发送一次消息
|
|
||||||
```
|
|
||||||
|
|
||||||
> 在子进程中(例如NoneBot插件中)
|
|
||||||
|
|
||||||
```python
|
|
||||||
from nonebot import get_driver
|
|
||||||
from liteyuki.comm import active_channel, passive_channel # 子进程中获取通道直接导入进程全局单例即可
|
|
||||||
from liteyuki.log import logger
|
|
||||||
|
|
||||||
driver = get_driver()
|
|
||||||
|
|
||||||
|
|
||||||
# 被动模式,通过装饰器注册一个函数在接收到消息时运行,每次接收到字符串数据时都会运行
|
|
||||||
@passive_channel.on_receive(filter_func=lambda data: isinstance(data, str))
|
|
||||||
async def on_passive_receive(data):
|
|
||||||
logger.info(f"Passive receive: {data}")
|
|
||||||
|
|
||||||
|
|
||||||
# 注册一个函数在NoneBot启动后运行
|
|
||||||
@driver.on_startup
|
|
||||||
def on_startup():
|
|
||||||
while True:
|
|
||||||
data = active_channel.receive()
|
|
||||||
logger.info(f"Active receive: {data}")
|
|
||||||
```
|
|
||||||
|
|
||||||
> 启动后控制台输出
|
|
||||||
|
|
||||||
```log
|
|
||||||
0000-00-00 00:00:00 [ℹ️信息] Passive receive: I am liteyuki main process passive
|
|
||||||
0000-00-00 00:00:00 [ℹ️信息] Active receive: I am liteyuki main process active
|
|
||||||
0000-00-00 00:00:03 [ℹ️信息] Passive receive: I am liteyuki main process passive
|
|
||||||
0000-00-00 00:00:03 [ℹ️信息] Active receive: I am liteyuki main process active
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
## **共享内存通信**
|
|
||||||
|
|
||||||
### 简介
|
|
||||||
|
|
||||||
- 相比于普通进程通信,内存共享使得代码编写更加简洁,轻雪框架提供了一个内存共享通信的接口,你可以通过`storage`模块实现内存共享通信,该模块封装通道实现
|
|
||||||
- 内存共享是线程安全的,你可以在多个线程中读写共享内存,线程锁会自动保护共享内存的读写操作
|
|
||||||
|
|
||||||
### 示例
|
|
||||||
|
|
||||||
> 在任意进程中均可使用
|
|
||||||
|
|
||||||
```python
|
|
||||||
from liteyuki.comm.storage import shared_memory
|
|
||||||
|
|
||||||
shared_memory.set("key", "value") # 设置共享内存
|
|
||||||
value = shared_memory.get("key") # 获取共享内存
|
|
||||||
```
|
|
||||||
|
|
||||||
源代码:[liteyuki/comm/storage.py](https://github.com/LiteyukiStudio/LiteyukiBot/blob/main/liteyuki/comm/storage.py)
|
|
@ -1,74 +0,0 @@
|
|||||||
---
|
|
||||||
title: 轻雪函数
|
|
||||||
icon: code
|
|
||||||
order: 2
|
|
||||||
category: 开发
|
|
||||||
---
|
|
||||||
|
|
||||||
## **轻雪函数**
|
|
||||||
|
|
||||||
轻雪函数 Liteyuki Function 是轻雪的一个功能,它允许你在轻雪中运行一些自定义的由数据驱动的命令,类似于Minecraft的mcfunction,属于资源包的一部分,但需单独起篇幅.
|
|
||||||
|
|
||||||
### **函数文件**
|
|
||||||
|
|
||||||
函数文件放在资源包的`functions`目录下,文件名以`.mcfunction` `.lyfunction` `.lyf`结尾,例如`test.mcfunction`,文件内容为一系列的命令,每行一个命令,支持单行注释`#`(编辑时的语法高亮可采取`shell`格式),例如:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
# 在发信器输出"hello world"
|
|
||||||
cmd echo hello world
|
|
||||||
|
|
||||||
# 如果你想同时输出多行内容可以尝试换行符(Python格式)
|
|
||||||
cmd echo hello world\nLiteyuki bot
|
|
||||||
```
|
|
||||||
|
|
||||||
也支持句末注释,例如:
|
|
||||||
```shell
|
|
||||||
cmd echo hello world # 输出"hello world"
|
|
||||||
```
|
|
||||||
|
|
||||||
### **命令文档**
|
|
||||||
|
|
||||||
```shell
|
|
||||||
var <var1=value1> [var2=value2] ... # 定义变量
|
|
||||||
cmd <command> # 在设备上执行命令
|
|
||||||
api <api_name> [var=value...] # 调用Bot API
|
|
||||||
function <func_name> # 调用函数,可递归
|
|
||||||
sleep <time> # 异步等待,单位s
|
|
||||||
nohup <command> # 使用新的task执行命令,即不等待
|
|
||||||
end # 结束函数关键字,包括子task
|
|
||||||
await # 等待所有异步任务结束,若函数中启动了其他task,需要在最后调用,否则task对象会被销毁
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
#### **示例**
|
|
||||||
|
|
||||||
```shell
|
|
||||||
# 疯狂戳好友
|
|
||||||
# 使用 /function poke user_id=123456 执行
|
|
||||||
# 每隔0.2s戳两次,无限戳,会触发最大递归深度限制
|
|
||||||
# 若要戳20s后停止,则需要删除await,添加sleep 20和end
|
|
||||||
api friend_poke user_id=user_id
|
|
||||||
api friend_poke user_id=user_id
|
|
||||||
sleep 0.2
|
|
||||||
nohup function poke
|
|
||||||
await
|
|
||||||
```
|
|
||||||
|
|
||||||
### **API**
|
|
||||||
|
|
||||||
理论上所有基于onebotv11的api都可调用,不同Adapter api也有差别.
|
|
||||||
|
|
||||||
[Onebot v11 API文档](https://283375.github.io/onebot_v11_vitepress/api/index.html)
|
|
||||||
|
|
||||||
### **结束关键字**
|
|
||||||
|
|
||||||
由于LiteyukiBot基于异步运行, 所以在编写lyfunction时也要注意异步的调用,避免出现"单线程走到底"的情况是效率提升的关键.
|
|
||||||
|
|
||||||
`await` 异步任务结束关键字,用于结束当前已完成function的执行
|
|
||||||
|
|
||||||
> [!warning]
|
|
||||||
> 但若出现非单function的情况,有一个task任务没有完成而await被执行了,那么当前所有函数包的task都会被截停销毁
|
|
||||||
|
|
||||||
|
|
||||||
> [!tip]
|
|
||||||
> 编写轻雪函数推荐你使用VS Code插件[Liteyuki Function](https://github.com/LiteyukiStudio/lyfunctionTextmate)实现语法高亮
|
|
@ -1,82 +0,0 @@
|
|||||||
---
|
|
||||||
title: 轻雪插件开发
|
|
||||||
icon: laptop-code
|
|
||||||
order: 3
|
|
||||||
category: 开发
|
|
||||||
---
|
|
||||||
|
|
||||||
## 简介
|
|
||||||
|
|
||||||
轻雪插件是轻雪内置的一部分功能,运行在主进程中,可以很高程度地扩展轻雪的功能
|
|
||||||
|
|
||||||
## 开始
|
|
||||||
|
|
||||||
### 创建插件
|
|
||||||
|
|
||||||
一个`.py`文件或一个包含`__init__.py`的文件夹即可被识别为插件
|
|
||||||
|
|
||||||
首先创建一个文件夹,例如`watchdog_plugin`,并在其中创建一个`__init__.py`文件,即可创建一个插件
|
|
||||||
|
|
||||||
`__init__.py`
|
|
||||||
```python
|
|
||||||
from liteyuki.plugin import PluginMetadata, PluginType
|
|
||||||
from .watch_dog import * # 导入逻辑部分
|
|
||||||
|
|
||||||
# 定义插件元数据
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
|
||||||
name="NoneDog", # 插件名称
|
|
||||||
version="1.0.0", # 插件版本
|
|
||||||
description="A simple plugin for nonebot developer", # 插件描述
|
|
||||||
type=PluginType.SERVICE # 插件类型
|
|
||||||
)
|
|
||||||
|
|
||||||
# 你的插件代码
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
### 编写逻辑部分
|
|
||||||
|
|
||||||
轻雪主进程不涉及聊天部分,因此插件主要是一些后台任务或者与聊天机器人的通信
|
|
||||||
以下我们会编写一个简单的插件,用于开发NoneBot时进行文件系统变更重载
|
|
||||||
`watch_dog.py`
|
|
||||||
```python
|
|
||||||
import os
|
|
||||||
from liteyuki.dev import observer # 导入文件系统观察器
|
|
||||||
from liteyuki import get_bot, logger # 导入轻雪Bot和日志
|
|
||||||
from watchdog.events import FileSystemEvent # 导入文件系统事件
|
|
||||||
|
|
||||||
liteyuki = get_bot() # 获取唯一的轻雪Bot实例
|
|
||||||
|
|
||||||
exclude_extensions = (".pyc", ".pyo") # 排除的文件扩展名
|
|
||||||
|
|
||||||
|
|
||||||
# 用observer的on_file_system_event装饰器监听文件系统事件
|
|
||||||
@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)
|
|
||||||
)
|
|
||||||
def restart_nonebot_process(event: FileSystemEvent):
|
|
||||||
logger.debug(f"File {event.src_path} changed, reloading nonebot...")
|
|
||||||
liteyuki.restart_process("nonebot") # 调用重启进程方法
|
|
||||||
```
|
|
||||||
|
|
||||||
### 加载插件
|
|
||||||
|
|
||||||
#### 方法1
|
|
||||||
|
|
||||||
- 在配置文件中的`liteyuki.plugins`中添加你的插件路径,例如`watchdog_plugin`,重启轻雪即可加载插件。
|
|
||||||
|
|
||||||
#### 方法2
|
|
||||||
|
|
||||||
- 使用开发工具快速运行插件,无需手动创建实例
|
|
||||||
- 创建入口文件,例如`main.py`,并在其中写入以下代码
|
|
||||||
|
|
||||||
```python
|
|
||||||
from liteyuki.dev.plugin import run_plugins
|
|
||||||
|
|
||||||
run_plugins("watchdog_plugin")
|
|
||||||
```
|
|
||||||
|
|
||||||
然后运行`python main.py`即可启动插件
|
|
||||||
|
|
||||||
启用插件后,我们在src/nonebot_plugins下创建一个文件,例如`test.py`,并在其中写入一些代码,保存后轻雪会自动重载NoneBot进程
|
|
@ -1,53 +0,0 @@
|
|||||||
---
|
|
||||||
title: 资源包开发
|
|
||||||
icon: box
|
|
||||||
order: 1
|
|
||||||
category: 开发
|
|
||||||
---
|
|
||||||
|
|
||||||
## 简介
|
|
||||||
|
|
||||||
资源包,亦可根据用途称为主题包、字体包、语言包等,它允许你一定程度上自定义轻雪的外观,并且不用修改源代码
|
|
||||||
|
|
||||||
- [资源/主题商店](/store/)提供了一些资源包供你选择,你也可以自己制作资源包
|
|
||||||
- 资源包的制作很简单,如果你接触过`Minecraft`的资源包,那么你能够很快就上手,仅需按照原有路径进行文件替换即可,讲起打包成一个新的资源包。
|
|
||||||
- 部分内容制作需要一点点前端基础,例如`html`,`css`
|
|
||||||
- 轻雪原版资源包请查看`LiteyukiBot/liteyuki/resources`,可以在此基础上进行修改
|
|
||||||
- 欢迎各位投稿资源包到轻雪资源商店
|
|
||||||
|
|
||||||
请注意,主题包中的html渲染使用Js来规定数据的渲染位置,请确保您所编写的html代码能被Bot解析,否则会导致渲染失败或渲染结果不理想/异常/错位等无法预料的事情发生。推荐在编写html时同时更改对应Js代码,以避免出现无法预料的问题。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 加载资源包
|
|
||||||
|
|
||||||
- 资源包通常是以`.zip`格式压缩的,只需要将其解压到根目录`resources`目录下即可,注意不要嵌套文件夹,正常的路径应该是这样的
|
|
||||||
|
|
||||||
```shell
|
|
||||||
main.py
|
|
||||||
resources
|
|
||||||
└─resource_pack_1
|
|
||||||
├─metadata.yml
|
|
||||||
├─templates
|
|
||||||
└───...
|
|
||||||
└─resource_pack_2
|
|
||||||
├─metadata.yml
|
|
||||||
└─...
|
|
||||||
```
|
|
||||||
|
|
||||||
- 你自己制作的资源包也应该遵循这个规则,并且应该在`metadata.yml`中填写一些信息
|
|
||||||
- 若没有`metadata.yml`文件,则该文件夹不会被识别为资源包
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
name: "资源包名称"
|
|
||||||
version: "1.0.0"
|
|
||||||
description: "资源包描述"
|
|
||||||
# 你可以自定义一些信息,但请保证以上三个字段
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
- 资源包加载遵循一个优先级,即后加载的资源包会覆盖前面的资源包,例如,你在A包中定义了一个`index.html`文件,B包也定义了一个`index.html`文件,那么加载B包后,A包中的`index.html`文件会被覆盖
|
|
||||||
- 对于不同资源包的不同文件,是可以相对引用的,例如你在A中定义了`templates/index.html`,在B中定义了`templates/style.css`,可以在A的`index.html`中用`./style.css`相对路径引用B中的css
|
|
||||||
|
|
||||||
> [!tip]
|
|
||||||
> 资源包的结构会随着轻雪的更新而有变动,第三方资源包开发者需要注意版本兼容性,同时用户也应该自行选择可用的资源包
|
|
@ -1,8 +0,0 @@
|
|||||||
---
|
|
||||||
title: Contribute
|
|
||||||
index: false
|
|
||||||
icon: laptop-code
|
|
||||||
category: 开发
|
|
||||||
---
|
|
||||||
|
|
||||||
<Catalog />
|
|
@ -1,99 +0,0 @@
|
|||||||
---
|
|
||||||
title: Communication
|
|
||||||
icon: exchange-alt
|
|
||||||
order: 4
|
|
||||||
category: development
|
|
||||||
---
|
|
||||||
|
|
||||||
## **通道通信**
|
|
||||||
|
|
||||||
### 简介
|
|
||||||
|
|
||||||
轻雪运行在主进程 MainProcess 里,其他插件框架进程是伴随的子进程,因此无法通过内存共享和直接对象传递的方式进行通信,轻雪提供了一个通道`Channel`用于跨进程通信,你可以通过`Channel`发送消息给其他进程,也可以监听其他进程的消息。
|
|
||||||
|
|
||||||
例如子进程接收到用户信息需要重启机器人,这时可以通过通道对主进程发送消息,主进程接收到消息后重启对应子进程。
|
|
||||||
|
|
||||||
### 示例
|
|
||||||
|
|
||||||
通道是全双工的,有两种接收模式,但一个通道只能使用一种,即被动模式和主动模式,被动模式由`chan.on_receive()`装饰回调函数实现,主动模式需调用`chan.receive()`实现
|
|
||||||
|
|
||||||
- 创建子进程的同时会初始化一个被动通道和一个主动通道,且通道标识为`{process_name}-active`和`{process_name}-passive`,
|
|
||||||
- 主进程中通过`get_channel`函数获取通道对象
|
|
||||||
- 子进程中导入单例`active_channel`及`passive_channel`即可
|
|
||||||
|
|
||||||
> 在轻雪插件中(主进程中)
|
|
||||||
|
|
||||||
```python
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
from liteyuki.comm import get_channel, Channel
|
|
||||||
from liteyuki import get_bot
|
|
||||||
|
|
||||||
# get_channel函数获取通道对象,参数为调用set_channel时的通道标识
|
|
||||||
channel_passive = get_channel("nonebot-passive") # 获取被动通道
|
|
||||||
channel_active = get_channel("nonebot-active") # 获取主动通道
|
|
||||||
liteyuki_bot = get_bot()
|
|
||||||
|
|
||||||
|
|
||||||
# 注册一个函数在轻雪启动后运行
|
|
||||||
@liteyuki_bot.on_after_start
|
|
||||||
async def send_data():
|
|
||||||
while True:
|
|
||||||
channel_passive.send("I am liteyuki main process passive")
|
|
||||||
channel_active.send("I am liteyuki main process active")
|
|
||||||
await asyncio.sleep(3) # 每3秒发送一次消息
|
|
||||||
```
|
|
||||||
|
|
||||||
> 在子进程中(例如NoneBot插件中)
|
|
||||||
|
|
||||||
```python
|
|
||||||
from nonebot import get_driver
|
|
||||||
from liteyuki.comm import active_channel, passive_channel # 子进程中获取通道直接导入进程全局单例即可
|
|
||||||
from liteyuki.log import logger
|
|
||||||
|
|
||||||
driver = get_driver()
|
|
||||||
|
|
||||||
|
|
||||||
# 被动模式,通过装饰器注册一个函数在接收到消息时运行,每次接收到字符串数据时都会运行
|
|
||||||
@passive_channel.on_receive(filter_func=lambda data: isinstance(data, str))
|
|
||||||
async def on_passive_receive(data):
|
|
||||||
logger.info(f"Passive receive: {data}")
|
|
||||||
|
|
||||||
|
|
||||||
# 注册一个函数在NoneBot启动后运行
|
|
||||||
@driver.on_startup
|
|
||||||
def on_startup():
|
|
||||||
while True:
|
|
||||||
data = active_channel.receive()
|
|
||||||
logger.info(f"Active receive: {data}")
|
|
||||||
```
|
|
||||||
|
|
||||||
> 启动后控制台输出
|
|
||||||
|
|
||||||
```log
|
|
||||||
0000-00-00 00:00:00 [ℹ️信息] Passive receive: I am liteyuki main process passive
|
|
||||||
0000-00-00 00:00:00 [ℹ️信息] Active receive: I am liteyuki main process active
|
|
||||||
0000-00-00 00:00:03 [ℹ️信息] Passive receive: I am liteyuki main process passive
|
|
||||||
0000-00-00 00:00:03 [ℹ️信息] Active receive: I am liteyuki main process active
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
## **共享内存通信**
|
|
||||||
|
|
||||||
### 简介
|
|
||||||
|
|
||||||
- 相比于普通进程通信,内存共享使得代码编写更加简洁,轻雪框架提供了一个内存共享通信的接口,你可以通过`storage`模块实现内存共享通信,该模块封装通道实现
|
|
||||||
- 内存共享是线程安全的,你可以在多个线程中读写共享内存,线程锁会自动保护共享内存的读写操作
|
|
||||||
|
|
||||||
### 示例
|
|
||||||
|
|
||||||
> 在任意进程中均可使用
|
|
||||||
|
|
||||||
```python
|
|
||||||
from liteyuki.comm.storage import shared_memory
|
|
||||||
|
|
||||||
shared_memory.set("key", "value") # 设置共享内存
|
|
||||||
value = shared_memory.get("key") # 获取共享内存
|
|
||||||
```
|
|
||||||
|
|
||||||
- 源代码:[liteyuki/comm/storage.py](https://github.com/LiteyukiStudio/LiteyukiBot/blob/main/liteyuki/comm/storage.py)
|
|
@ -1,74 +0,0 @@
|
|||||||
---
|
|
||||||
title: Liteyuki Function
|
|
||||||
icon: code
|
|
||||||
order: 2
|
|
||||||
category: development
|
|
||||||
---
|
|
||||||
|
|
||||||
## **轻雪函数**
|
|
||||||
|
|
||||||
轻雪函数 Liteyuki Function 是轻雪的一个功能,它允许你在轻雪中运行一些自定义的由数据驱动的命令,类似于Minecraft的mcfunction,属于资源包的一部分,但需单独起篇幅.
|
|
||||||
|
|
||||||
### **函数文件**
|
|
||||||
|
|
||||||
函数文件放在资源包的`functions`目录下,文件名以`.mcfunction` `.lyfunction` `.lyf`结尾,例如`test.mcfunction`,文件内容为一系列的命令,每行一个命令,支持单行注释`#`(编辑时的语法高亮可采取`shell`格式),例如:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
# 在发信器输出"hello world"
|
|
||||||
cmd echo hello world
|
|
||||||
|
|
||||||
# 如果你想同时输出多行内容可以尝试换行符(Python格式)
|
|
||||||
cmd echo hello world\nLiteyuki bot
|
|
||||||
```
|
|
||||||
|
|
||||||
也支持句末注释,例如:
|
|
||||||
```shell
|
|
||||||
cmd echo hello world # 输出"hello world"
|
|
||||||
```
|
|
||||||
|
|
||||||
### **命令文档**
|
|
||||||
|
|
||||||
```shell
|
|
||||||
var <var1=value1> [var2=value2] ... # 定义变量
|
|
||||||
cmd <command> # 在设备上执行命令
|
|
||||||
api <api_name> [var=value...] # 调用Bot API
|
|
||||||
function <func_name> # 调用函数,可递归
|
|
||||||
sleep <time> # 异步等待,单位s
|
|
||||||
nohup <command> # 使用新的task执行命令,即不等待
|
|
||||||
end # 结束函数关键字,包括子task
|
|
||||||
await # 等待所有异步任务结束,若函数中启动了其他task,需要在最后调用,否则task对象会被销毁
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
#### **示例**
|
|
||||||
|
|
||||||
```shell
|
|
||||||
# 疯狂戳好友
|
|
||||||
# 使用 /function poke user_id=123456 执行
|
|
||||||
# 每隔0.2s戳两次,无限戳,会触发最大递归深度限制
|
|
||||||
# 若要戳20s后停止,则需要删除await,添加sleep 20和end
|
|
||||||
api friend_poke user_id=user_id
|
|
||||||
api friend_poke user_id=user_id
|
|
||||||
sleep 0.2
|
|
||||||
nohup function poke
|
|
||||||
await
|
|
||||||
```
|
|
||||||
|
|
||||||
### **API**
|
|
||||||
|
|
||||||
理论上所有基于onebotv11的api都可调用,不同Adapter api也有差别.
|
|
||||||
|
|
||||||
[Onebot v11 API文档](https://283375.github.io/onebot_v11_vitepress/api/index.html)
|
|
||||||
|
|
||||||
### **结束关键字**
|
|
||||||
|
|
||||||
由于LiteyukiBot基于异步运行, 所以在编写lyfunction时也要注意异步的调用,避免出现"单线程走到底"的情况是效率提升的关键.
|
|
||||||
|
|
||||||
`await` 异步任务结束关键字,用于结束当前已完成function的执行
|
|
||||||
|
|
||||||
> [!warning]
|
|
||||||
> 但若出现非单function的情况,有一个task任务没有完成而await被执行了,那么当前所有函数包的task都会被截停销毁
|
|
||||||
|
|
||||||
|
|
||||||
> [!tip]
|
|
||||||
> 编写轻雪函数推荐你使用VS Code插件[Liteyuki Function](https://github.com/LiteyukiStudio/lyfunctionTextmate)实现语法高亮
|
|
@ -1,63 +0,0 @@
|
|||||||
---
|
|
||||||
title: Liteyuki Plugin
|
|
||||||
icon: laptop-code
|
|
||||||
order: 3
|
|
||||||
category: development
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
## 简介
|
|
||||||
|
|
||||||
轻雪插件是轻雪内置的一部分功能,运行在主进程中,可以很高程度地扩展轻雪的功能
|
|
||||||
|
|
||||||
## 开始
|
|
||||||
|
|
||||||
### 创建插件
|
|
||||||
|
|
||||||
在标准项目中,位于liteyuki/plugins和src/liteyuki_plugins下的Python modules均会被当作插件加载,你可自行添加配置文件以指定插件的加载路径
|
|
||||||
一个`.py`文件或一个包含`__init__.py`的文件夹即可被识别为插件
|
|
||||||
创建一个文件夹,例如`watchdog_plugin`,并在其中创建一个`__init__.py`文件,即可创建一个插件
|
|
||||||
|
|
||||||
```python
|
|
||||||
from liteyuki.plugin import PluginMetadata
|
|
||||||
|
|
||||||
# 定义插件元数据,推荐填写
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
|
||||||
name="NoneDog", # 插件名称
|
|
||||||
version="1.0.0", # 插件版本
|
|
||||||
description="A simple plugin for nonebot developer" # 插件描述
|
|
||||||
)
|
|
||||||
|
|
||||||
# 你的插件代码
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
### 编写逻辑部分
|
|
||||||
|
|
||||||
轻雪主进程不涉及聊天部分,因此插件主要是一些后台任务或者与聊天机器人的通信
|
|
||||||
以下我们会编写一个简单的插件,用于开发NoneBot时进行文件系统变更重载
|
|
||||||
|
|
||||||
```python
|
|
||||||
import os
|
|
||||||
from liteyuki.dev import observer # 导入文件系统观察器
|
|
||||||
from liteyuki import get_bot, logger # 导入轻雪Bot和日志
|
|
||||||
from watchdog.events import FileSystemEvent # 导入文件系统事件
|
|
||||||
|
|
||||||
liteyuki = get_bot() # 获取唯一的轻雪Bot实例
|
|
||||||
|
|
||||||
exclude_extensions = (".pyc", ".pyo") # 排除的文件扩展名
|
|
||||||
|
|
||||||
|
|
||||||
# 用observer的on_file_system_event装饰器监听文件系统事件
|
|
||||||
@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)
|
|
||||||
)
|
|
||||||
def restart_nonebot_process(event: FileSystemEvent):
|
|
||||||
logger.debug(f"File {event.src_path} changed, reloading nonebot...")
|
|
||||||
liteyuki.restart_process("nonebot") # 调用重启进程方法
|
|
||||||
```
|
|
||||||
|
|
||||||
### 加载插件
|
|
||||||
|
|
||||||
在配置文件中的`liteyuki.plugins`中添加你的插件路径,例如`watchdog_plugin`,重启轻雪即可加载插件。然后我们在src/nonebot_plugins下创建一个文件,例如`test.py`,并在其中写入一些代码,保存后轻雪会自动重载NoneBot进程
|
|
@ -1,53 +0,0 @@
|
|||||||
---
|
|
||||||
title: Resource Pack
|
|
||||||
icon: box
|
|
||||||
order: 1
|
|
||||||
category: development
|
|
||||||
---
|
|
||||||
|
|
||||||
## 简介
|
|
||||||
|
|
||||||
资源包,亦可根据用途称为主题包、字体包、语言包等,它允许你一定程度上自定义轻雪的外观,并且不用修改源代码
|
|
||||||
|
|
||||||
- [资源/主题商店](/store/)提供了一些资源包供你选择,你也可以自己制作资源包
|
|
||||||
- 资源包的制作很简单,如果你接触过`Minecraft`的资源包,那么你能够很快就上手,仅需按照原有路径进行文件替换即可,讲起打包成一个新的资源包。
|
|
||||||
- 部分内容制作需要一点点前端基础,例如`html`,`css`
|
|
||||||
- 轻雪原版资源包请查看`LiteyukiBot/liteyuki/resources`,可以在此基础上进行修改
|
|
||||||
- 欢迎各位投稿资源包到轻雪资源商店
|
|
||||||
|
|
||||||
请注意,主题包中的html渲染使用Js来规定数据的渲染位置,请确保您所编写的html代码能被Bot解析,否则会导致渲染失败或渲染结果不理想/异常/错位等无法预料的事情发生。推荐在编写html时同时更改对应Js代码,以避免出现无法预料的问题。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 加载资源包
|
|
||||||
|
|
||||||
- 资源包通常是以`.zip`格式压缩的,只需要将其解压到根目录`resources`目录下即可,注意不要嵌套文件夹,正常的路径应该是这样的
|
|
||||||
|
|
||||||
```shell
|
|
||||||
main.py
|
|
||||||
resources
|
|
||||||
└─resource_pack_1
|
|
||||||
├─metadata.yml
|
|
||||||
├─templates
|
|
||||||
└───...
|
|
||||||
└─resource_pack_2
|
|
||||||
├─metadata.yml
|
|
||||||
└─...
|
|
||||||
```
|
|
||||||
|
|
||||||
- 你自己制作的资源包也应该遵循这个规则,并且应该在`metadata.yml`中填写一些信息
|
|
||||||
- 若没有`metadata.yml`文件,则该文件夹不会被识别为资源包
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
name: "资源包名称"
|
|
||||||
version: "1.0.0"
|
|
||||||
description: "资源包描述"
|
|
||||||
# 你可以自定义一些信息,但请保证以上三个字段
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
- 资源包加载遵循一个优先级,即后加载的资源包会覆盖前面的资源包,例如,你在A包中定义了一个`index.html`文件,B包也定义了一个`index.html`文件,那么加载B包后,A包中的`index.html`文件会被覆盖
|
|
||||||
- 对于不同资源包的不同文件,是可以相对引用的,例如你在A中定义了`templates/index.html`,在B中定义了`templates/style.css`,可以在A的`index.html`中用`./style.css`相对路径引用B中的css
|
|
||||||
|
|
||||||
> [!tip]
|
|
||||||
> 资源包的结构会随着轻雪的更新而有变动,第三方资源包开发者需要注意版本兼容性,同时用户也应该自行选择可用的资源包
|
|
47
litedoc/translator.py
Normal file
47
litedoc/translator.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||||
|
|
||||||
|
@Time : 2024/8/29 下午12:02
|
||||||
|
@Author : snowykami
|
||||||
|
@Email : snowykami@outlook.com
|
||||||
|
@File : translator.py
|
||||||
|
@Software: PyCharm
|
||||||
|
"""
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from translate import Translator # type: ignore
|
||||||
|
|
||||||
|
# 特殊映射语言
|
||||||
|
i18n_lang2googletrans_lang = {
|
||||||
|
"zh-Hans": "zh-cn",
|
||||||
|
"zh-Hant": "zh-tw",
|
||||||
|
"en" : "en",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_google_lang(lang: str) -> str:
|
||||||
|
"""
|
||||||
|
Get google translate language
|
||||||
|
Args:
|
||||||
|
lang: language
|
||||||
|
Returns:
|
||||||
|
google translate language
|
||||||
|
"""
|
||||||
|
return i18n_lang2googletrans_lang.get(lang, lang)
|
||||||
|
|
||||||
|
|
||||||
|
def translate(text: str, lang: str, source_lang: str) -> str:
|
||||||
|
"""
|
||||||
|
Translate text to target language
|
||||||
|
Args:
|
||||||
|
source_lang:
|
||||||
|
text: text
|
||||||
|
lang: target language
|
||||||
|
Returns:
|
||||||
|
translated text
|
||||||
|
"""
|
||||||
|
if lang == source_lang:
|
||||||
|
return text
|
||||||
|
google_lang = get_google_lang(lang)
|
||||||
|
return Translator(to_lang=google_lang, from_lang=source_lang).translate(text)
|
@ -10,7 +10,6 @@ from typing import Any, Optional
|
|||||||
|
|
||||||
from liteyuki.bot.lifespan import (LIFESPAN_FUNC, Lifespan)
|
from liteyuki.bot.lifespan import (LIFESPAN_FUNC, Lifespan)
|
||||||
from liteyuki.comm.channel import get_channel
|
from liteyuki.comm.channel import get_channel
|
||||||
from liteyuki.comm.storage import shared_memory
|
|
||||||
from liteyuki.core.manager import ProcessManager
|
from liteyuki.core.manager import ProcessManager
|
||||||
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
|
||||||
@ -63,16 +62,25 @@ class LiteyukiBot:
|
|||||||
signal.signal(signal.SIGTERM, self._handle_exit)
|
signal.signal(signal.SIGTERM, self._handle_exit)
|
||||||
atexit.register(self.process_manager.terminate_all) # 注册退出时的函数
|
atexit.register(self.process_manager.terminate_all) # 注册退出时的函数
|
||||||
|
|
||||||
def run(self):
|
async def _run(self):
|
||||||
"""
|
"""
|
||||||
启动逻辑
|
启动逻辑
|
||||||
"""
|
"""
|
||||||
self.lifespan.before_start() # 启动前钩子
|
await self.lifespan.before_start() # 启动前钩子
|
||||||
self.process_manager.start_all()
|
await self.process_manager.start_all()
|
||||||
self.lifespan.after_start() # 启动后钩子
|
await self.lifespan.after_start() # 启动后钩子
|
||||||
self.keep_alive()
|
await self.keep_alive()
|
||||||
|
|
||||||
def keep_alive(self):
|
def run(self):
|
||||||
|
"""
|
||||||
|
外部启动接口
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
asyncio.run(self._run())
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.info("Liteyuki is stopping...")
|
||||||
|
|
||||||
|
async def keep_alive(self):
|
||||||
"""
|
"""
|
||||||
保持轻雪运行
|
保持轻雪运行
|
||||||
Returns:
|
Returns:
|
||||||
@ -131,9 +139,6 @@ class LiteyukiBot:
|
|||||||
name: 进程名称, 默认为None, 所有进程
|
name: 进程名称, 默认为None, 所有进程
|
||||||
Returns:
|
Returns:
|
||||||
"""
|
"""
|
||||||
self.lifespan.before_process_shutdown() # 重启前钩子
|
|
||||||
self.lifespan.before_process_shutdown() # 停止前钩子
|
|
||||||
|
|
||||||
if name is not None:
|
if name is not None:
|
||||||
chan_active = get_channel(f"{name}-active")
|
chan_active = get_channel(f"{name}-active")
|
||||||
chan_active.send(1)
|
chan_active.send(1)
|
||||||
@ -230,17 +235,6 @@ class LiteyukiBot:
|
|||||||
"""
|
"""
|
||||||
return self.lifespan.on_after_restart(func)
|
return self.lifespan.on_after_restart(func)
|
||||||
|
|
||||||
def on_after_nonebot_init(self, func: LIFESPAN_FUNC):
|
|
||||||
"""
|
|
||||||
注册nonebot初始化后的函数
|
|
||||||
Args:
|
|
||||||
func:
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
return self.lifespan.on_after_nonebot_init(func)
|
|
||||||
|
|
||||||
|
|
||||||
_BOT_INSTANCE: LiteyukiBot
|
_BOT_INSTANCE: LiteyukiBot
|
||||||
|
|
||||||
|
@ -39,29 +39,17 @@ class Lifespan:
|
|||||||
self._before_process_restart_funcs: list[LIFESPAN_FUNC] = []
|
self._before_process_restart_funcs: list[LIFESPAN_FUNC] = []
|
||||||
self._after_restart_funcs: list[LIFESPAN_FUNC] = []
|
self._after_restart_funcs: list[LIFESPAN_FUNC] = []
|
||||||
|
|
||||||
self._after_nonebot_init_funcs: list[LIFESPAN_FUNC] = []
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def run_funcs(funcs: list[LIFESPAN_FUNC | PROCESS_LIFESPAN_FUNC], *args, **kwargs) -> None:
|
async def run_funcs(funcs: list[ASYNC_LIFESPAN_FUNC | PROCESS_LIFESPAN_FUNC], *args, **kwargs) -> None:
|
||||||
"""
|
"""
|
||||||
运行函数
|
并发运行异步函数
|
||||||
Args:
|
Args:
|
||||||
funcs:
|
funcs:
|
||||||
Returns:
|
Returns:
|
||||||
"""
|
"""
|
||||||
try:
|
loop = asyncio.get_running_loop()
|
||||||
loop = asyncio.get_event_loop()
|
tasks = [func(*args, **kwargs) if is_coroutine_callable(func) else async_wrapper(func)(*args, **kwargs) for func in funcs]
|
||||||
except RuntimeError:
|
await asyncio.gather(*tasks)
|
||||||
loop = asyncio.new_event_loop()
|
|
||||||
asyncio.set_event_loop(loop)
|
|
||||||
|
|
||||||
tasks = []
|
|
||||||
for func in funcs:
|
|
||||||
if is_coroutine_callable(func):
|
|
||||||
tasks.append(func(*args, **kwargs))
|
|
||||||
else:
|
|
||||||
tasks.append(async_wrapper(func)(*args, **kwargs))
|
|
||||||
loop.run_until_complete(asyncio.gather(*tasks))
|
|
||||||
|
|
||||||
def on_before_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
def on_before_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||||
"""
|
"""
|
||||||
@ -131,63 +119,51 @@ class Lifespan:
|
|||||||
self._after_restart_funcs.append(func)
|
self._after_restart_funcs.append(func)
|
||||||
return func
|
return func
|
||||||
|
|
||||||
def on_after_nonebot_init(self, func):
|
async def before_start(self) -> None:
|
||||||
"""
|
|
||||||
注册 NoneBot 初始化后的函数
|
|
||||||
Args:
|
|
||||||
func:
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
self._after_nonebot_init_funcs.append(func)
|
|
||||||
return func
|
|
||||||
|
|
||||||
def before_start(self) -> None:
|
|
||||||
"""
|
"""
|
||||||
启动前
|
启动前
|
||||||
Returns:
|
Returns:
|
||||||
"""
|
"""
|
||||||
logger.debug("Running before_start functions")
|
logger.debug("Running before_start functions")
|
||||||
self.run_funcs(self._before_start_funcs)
|
await self.run_funcs(self._before_start_funcs)
|
||||||
|
|
||||||
def after_start(self) -> None:
|
async def after_start(self) -> None:
|
||||||
"""
|
"""
|
||||||
启动后
|
启动后
|
||||||
Returns:
|
Returns:
|
||||||
"""
|
"""
|
||||||
logger.debug("Running after_start functions")
|
logger.debug("Running after_start functions")
|
||||||
self.run_funcs(self._after_start_funcs)
|
await self.run_funcs(self._after_start_funcs)
|
||||||
|
|
||||||
def before_process_shutdown(self) -> None:
|
async def before_process_shutdown(self) -> None:
|
||||||
"""
|
"""
|
||||||
停止前
|
停止前
|
||||||
Returns:
|
Returns:
|
||||||
"""
|
"""
|
||||||
logger.debug("Running before_shutdown functions")
|
logger.debug("Running before_shutdown functions")
|
||||||
self.run_funcs(self._before_process_shutdown_funcs)
|
await self.run_funcs(self._before_process_shutdown_funcs)
|
||||||
|
|
||||||
def after_shutdown(self) -> None:
|
async def after_shutdown(self) -> None:
|
||||||
"""
|
"""
|
||||||
停止后
|
停止后
|
||||||
Returns:
|
Returns:
|
||||||
"""
|
"""
|
||||||
logger.debug("Running after_shutdown functions")
|
logger.debug("Running after_shutdown functions")
|
||||||
self.run_funcs(self._after_shutdown_funcs)
|
await self.run_funcs(self._after_shutdown_funcs)
|
||||||
|
|
||||||
def before_process_restart(self) -> None:
|
async def before_process_restart(self) -> None:
|
||||||
"""
|
"""
|
||||||
重启前
|
重启前
|
||||||
Returns:
|
Returns:
|
||||||
"""
|
"""
|
||||||
logger.debug("Running before_restart functions")
|
logger.debug("Running before_restart functions")
|
||||||
self.run_funcs(self._before_process_restart_funcs)
|
await self.run_funcs(self._before_process_restart_funcs)
|
||||||
|
|
||||||
def after_restart(self) -> None:
|
async def after_restart(self) -> None:
|
||||||
"""
|
"""
|
||||||
重启后
|
重启后
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
logger.debug("Running after_restart functions")
|
logger.debug("Running after_restart functions")
|
||||||
self.run_funcs(self._after_restart_funcs)
|
await self.run_funcs(self._after_restart_funcs)
|
||||||
|
@ -10,12 +10,12 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|||||||
|
|
||||||
本模块定义了一个通用的通道类,用于进程间通信
|
本模块定义了一个通用的通道类,用于进程间通信
|
||||||
"""
|
"""
|
||||||
import threading
|
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.utils import IS_MAIN_PROCESS, is_coroutine_callable, run_coroutine
|
|
||||||
from liteyuki.log import logger
|
from liteyuki.log import logger
|
||||||
|
from liteyuki.utils import IS_MAIN_PROCESS, is_coroutine_callable
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
@ -38,21 +38,22 @@ class Channel(Generic[T]):
|
|||||||
有两种接收工作方式,但是只能选择一种,主动接收和被动接收,主动接收使用 `receive` 方法,被动接收使用 `on_receive` 装饰器
|
有两种接收工作方式,但是只能选择一种,主动接收和被动接收,主动接收使用 `receive` 方法,被动接收使用 `on_receive` 装饰器
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, _id: str = "", type_check: Optional[bool] = None):
|
def __init__(self, name: str, type_check: Optional[bool] = None):
|
||||||
"""
|
"""
|
||||||
初始化通道
|
初始化通道
|
||||||
Args:
|
Args:
|
||||||
_id: 通道ID
|
name: 通道ID
|
||||||
type_check: 是否开启类型检查, 若为空,则传入泛型默认开启,否则默认关闭
|
type_check: 是否开启类型检查, 若为空,则传入泛型默认开启,否则默认关闭
|
||||||
"""
|
"""
|
||||||
self.conn_send, self.conn_recv = Pipe()
|
|
||||||
self._closed = False
|
|
||||||
self._on_main_receive_funcs: list[int] = []
|
|
||||||
self._on_sub_receive_funcs: list[int] = []
|
|
||||||
self.name: str = _id
|
|
||||||
|
|
||||||
self.is_main_receive_loop_running = False
|
self.conn_send, self.conn_recv = Pipe()
|
||||||
self.is_sub_receive_loop_running = False
|
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] = []
|
||||||
|
self.name: str = name
|
||||||
|
|
||||||
|
self.is_receive_loop_running = False
|
||||||
|
|
||||||
if type_check is None:
|
if type_check is None:
|
||||||
# 若传入泛型则默认开启类型检查
|
# 若传入泛型则默认开启类型检查
|
||||||
@ -62,6 +63,16 @@ class Channel(Generic[T]):
|
|||||||
if self._get_generic_type() is None:
|
if self._get_generic_type() is None:
|
||||||
raise TypeError("Type hint is required for enforcing type check.")
|
raise TypeError("Type hint is required for enforcing type check.")
|
||||||
self.type_check = type_check
|
self.type_check = type_check
|
||||||
|
if name in _channel:
|
||||||
|
raise ValueError(f"Channel {name} already exists")
|
||||||
|
|
||||||
|
if IS_MAIN_PROCESS:
|
||||||
|
if name in _channel:
|
||||||
|
raise ValueError(f"Channel {name} already exists")
|
||||||
|
_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")
|
||||||
|
|
||||||
def _get_generic_type(self) -> Optional[type]:
|
def _get_generic_type(self) -> Optional[type]:
|
||||||
"""
|
"""
|
||||||
@ -105,7 +116,7 @@ class Channel(Generic[T]):
|
|||||||
|
|
||||||
def send(self, data: T):
|
def send(self, data: T):
|
||||||
"""
|
"""
|
||||||
发送数据
|
发送数据,发送函数为同步函数,没有异步的必要
|
||||||
Args:
|
Args:
|
||||||
data: 数据
|
data: 数据
|
||||||
"""
|
"""
|
||||||
@ -120,7 +131,7 @@ class Channel(Generic[T]):
|
|||||||
|
|
||||||
def receive(self) -> T:
|
def receive(self) -> T:
|
||||||
"""
|
"""
|
||||||
接收数据
|
同步接收数据,会阻塞线程
|
||||||
Args:
|
Args:
|
||||||
"""
|
"""
|
||||||
if self._closed:
|
if self._closed:
|
||||||
@ -130,13 +141,15 @@ class Channel(Generic[T]):
|
|||||||
data = self.conn_recv.recv()
|
data = self.conn_recv.recv()
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def close(self):
|
async def async_receive(self) -> T:
|
||||||
"""
|
"""
|
||||||
关闭通道
|
异步接收数据,会挂起等待
|
||||||
"""
|
"""
|
||||||
self._closed = True
|
print("等待接收数据")
|
||||||
self.conn_send.close()
|
loop = asyncio.get_running_loop()
|
||||||
self.conn_recv.close()
|
data = await loop.run_in_executor(None, self.receive)
|
||||||
|
print("接收到数据")
|
||||||
|
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]]:
|
||||||
"""
|
"""
|
||||||
@ -146,11 +159,8 @@ class Channel(Generic[T]):
|
|||||||
Returns:
|
Returns:
|
||||||
装饰器,装饰一个函数在接收到数据后执行
|
装饰器,装饰一个函数在接收到数据后执行
|
||||||
"""
|
"""
|
||||||
if (not self.is_sub_receive_loop_running) and not IS_MAIN_PROCESS:
|
if not IS_MAIN_PROCESS:
|
||||||
threading.Thread(target=self._start_sub_receive_loop, daemon=True).start()
|
raise RuntimeError("on_receive can only be used in main process")
|
||||||
|
|
||||||
if (not self.is_main_receive_loop_running) and IS_MAIN_PROCESS:
|
|
||||||
threading.Thread(target=self._start_main_receive_loop, daemon=True).start()
|
|
||||||
|
|
||||||
def decorator(func: Callable[[T], Any]) -> Callable[[T], Any]:
|
def decorator(func: Callable[[T], Any]) -> Callable[[T], Any]:
|
||||||
global _func_id
|
global _func_id
|
||||||
@ -171,65 +181,52 @@ class Channel(Generic[T]):
|
|||||||
|
|
||||||
_callback_funcs[_func_id] = wrapper
|
_callback_funcs[_func_id] = wrapper
|
||||||
if IS_MAIN_PROCESS:
|
if IS_MAIN_PROCESS:
|
||||||
self._on_main_receive_funcs.append(_func_id)
|
self._on_main_receive_func_ids.append(_func_id)
|
||||||
else:
|
else:
|
||||||
self._on_sub_receive_funcs.append(_func_id)
|
self._on_sub_receive_func_ids.append(_func_id)
|
||||||
_func_id += 1
|
_func_id += 1
|
||||||
return func
|
return func
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
def _run_on_main_receive_funcs(self, data: Any):
|
async def _run_on_receive_funcs(self, data: Any):
|
||||||
"""
|
"""
|
||||||
运行接收函数
|
运行接收函数
|
||||||
Args:
|
Args:
|
||||||
data: 数据
|
data: 数据
|
||||||
"""
|
"""
|
||||||
for func_id in self._on_main_receive_funcs:
|
if IS_MAIN_PROCESS:
|
||||||
func = _callback_funcs[func_id]
|
[asyncio.create_task(_callback_funcs[func_id](data)) for func_id in self._on_main_receive_func_ids]
|
||||||
run_coroutine(func(data))
|
else:
|
||||||
|
[asyncio.create_task(_callback_funcs[func_id](data)) for func_id in self._on_sub_receive_func_ids]
|
||||||
|
|
||||||
def _run_on_sub_receive_funcs(self, data: Any):
|
async def start_receive_loop(self):
|
||||||
"""
|
|
||||||
运行接收函数
|
|
||||||
Args:
|
|
||||||
data: 数据
|
|
||||||
"""
|
|
||||||
for func_id in self._on_sub_receive_funcs:
|
|
||||||
func = _callback_funcs[func_id]
|
|
||||||
run_coroutine(func(data))
|
|
||||||
|
|
||||||
def _start_main_receive_loop(self):
|
|
||||||
"""
|
"""
|
||||||
开始接收数据
|
开始接收数据
|
||||||
|
会自动判断主进程和子进程,需要在对应进程都调度一次
|
||||||
"""
|
"""
|
||||||
self.is_main_receive_loop_running = True
|
if len(self._on_main_receive_func_ids) == 0:
|
||||||
while not self._closed:
|
logger.warning(f"No on_receive function registered for {self.name}")
|
||||||
data = self.conn_recv.recv()
|
return
|
||||||
self._run_on_main_receive_funcs(data)
|
|
||||||
|
|
||||||
def _start_sub_receive_loop(self):
|
self.is_receive_loop_running = True
|
||||||
"""
|
logger.debug(f"Starting receive loop for {self.name}")
|
||||||
开始接收数据
|
|
||||||
"""
|
|
||||||
self.is_sub_receive_loop_running = True
|
|
||||||
while not self._closed:
|
while not self._closed:
|
||||||
data = self.conn_recv.recv()
|
data = await self.async_receive()
|
||||||
self._run_on_sub_receive_funcs(data)
|
await self._run_on_receive_funcs(data)
|
||||||
|
|
||||||
|
|
||||||
"""子进程可用的主动和被动通道"""
|
"""子进程可用的主动和被动通道"""
|
||||||
active_channel: Optional["Channel"] = None
|
active_channel: Channel = Channel(name="active_channel")
|
||||||
passive_channel: Optional["Channel"] = None
|
passive_channel: Channel = Channel(name="passive_channel")
|
||||||
publish_channel: Channel[tuple[str, dict[str, Any]]] = Channel(_id="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(_id="channel_deliver_active_channel")
|
channel_deliver_active_channel = Channel(name="channel_deliver_active_channel")
|
||||||
channel_deliver_passive_channel = Channel(_id="channel_deliver_passive_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")
|
||||||
@ -250,7 +247,7 @@ if IS_MAIN_PROCESS:
|
|||||||
recv_chan.send(get_channels())
|
recv_chan.send(get_channels())
|
||||||
|
|
||||||
|
|
||||||
def set_channel(name: str, channel: Channel):
|
def set_channel(name: str, channel: "Channel"):
|
||||||
"""
|
"""
|
||||||
设置通道实例
|
设置通道实例
|
||||||
Args:
|
Args:
|
||||||
@ -261,6 +258,8 @@ def set_channel(name: str, 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:
|
||||||
|
raise ValueError(f"Channel {name} already exists")
|
||||||
_channel[name] = channel
|
_channel[name] = channel
|
||||||
else:
|
else:
|
||||||
# 请求主进程设置通道
|
# 请求主进程设置通道
|
||||||
@ -274,7 +273,7 @@ def set_channel(name: str, channel: Channel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def set_channels(channels: dict[str, Channel]):
|
def set_channels(channels: dict[str, "Channel"]):
|
||||||
"""
|
"""
|
||||||
设置通道实例
|
设置通道实例
|
||||||
Args:
|
Args:
|
||||||
@ -284,7 +283,7 @@ def set_channels(channels: dict[str, Channel]):
|
|||||||
set_channel(name, channel)
|
set_channel(name, channel)
|
||||||
|
|
||||||
|
|
||||||
def get_channel(name: str) -> Channel:
|
def get_channel(name: str) -> "Channel":
|
||||||
"""
|
"""
|
||||||
获取通道实例
|
获取通道实例
|
||||||
Args:
|
Args:
|
||||||
@ -308,7 +307,7 @@ def get_channel(name: str) -> Channel:
|
|||||||
return recv_chan.receive()
|
return recv_chan.receive()
|
||||||
|
|
||||||
|
|
||||||
def get_channels() -> dict[str, Channel]:
|
def get_channels() -> dict[str, "Channel"]:
|
||||||
"""
|
"""
|
||||||
获取通道实例
|
获取通道实例
|
||||||
Returns:
|
Returns:
|
||||||
|
26
liteyuki/comm/rpc.py
Normal file
26
liteyuki/comm/rpc.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# -*- 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)
|
54
liteyuki/comm/socks_channel.py
Normal file
54
liteyuki/comm/socks_channel.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||||
|
|
||||||
|
@Time : 2024/8/25 下午3:54
|
||||||
|
@Author : snowykami
|
||||||
|
@Email : snowykami@outlook.com
|
||||||
|
@File : channelv2.py
|
||||||
|
@Software: PyCharm
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
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
|
@ -2,13 +2,13 @@
|
|||||||
"""
|
"""
|
||||||
共享内存模块。类似于redis,但是更加轻量级并且线程安全
|
共享内存模块。类似于redis,但是更加轻量级并且线程安全
|
||||||
"""
|
"""
|
||||||
|
import asyncio
|
||||||
import threading
|
import threading
|
||||||
from typing import Any, Coroutine, Optional, TypeAlias, Callable
|
from typing import Any, Callable, Optional
|
||||||
|
|
||||||
from liteyuki.comm import channel
|
from liteyuki.comm import channel
|
||||||
from liteyuki.comm.channel import Channel, ON_RECEIVE_FUNC, ASYNC_ON_RECEIVE_FUNC
|
from liteyuki.comm.channel import ASYNC_ON_RECEIVE_FUNC, Channel, ON_RECEIVE_FUNC
|
||||||
from liteyuki.utils import IS_MAIN_PROCESS, is_coroutine_callable, run_coroutine, run_coroutine_in_thread
|
from liteyuki.utils import IS_MAIN_PROCESS, is_coroutine_callable, run_coroutine_in_thread
|
||||||
|
|
||||||
if IS_MAIN_PROCESS:
|
if IS_MAIN_PROCESS:
|
||||||
_locks = {}
|
_locks = {}
|
||||||
@ -31,24 +31,13 @@ def _get_lock(key) -> threading.Lock:
|
|||||||
raise RuntimeError("Cannot get lock in sub process.")
|
raise RuntimeError("Cannot get lock in sub process.")
|
||||||
|
|
||||||
|
|
||||||
class Subscriber:
|
|
||||||
def __init__(self):
|
|
||||||
self._subscribers = {}
|
|
||||||
|
|
||||||
def receive(self) -> Any:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def unsubscribe(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class KeyValueStore:
|
class KeyValueStore:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._store = {}
|
self._store = {}
|
||||||
self.active_chan = Channel[tuple[str, Optional[dict[str, Any]]]](_id="shared_memory-active")
|
self.active_chan = Channel[tuple[str, Optional[dict[str, Any]]]](name="shared_memory-active")
|
||||||
self.passive_chan = Channel[tuple[str, Optional[dict[str, Any]]]](_id="shared_memory-passive")
|
self.passive_chan = Channel[tuple[str, Optional[dict[str, Any]]]](name="shared_memory-passive")
|
||||||
|
|
||||||
self.publish_channel = Channel[tuple[str, Any]](_id="shared_memory-publish")
|
self.publish_channel = Channel[tuple[str, Any]](name="shared_memory-publish")
|
||||||
|
|
||||||
self.is_main_receive_loop_running = False
|
self.is_main_receive_loop_running = False
|
||||||
self.is_sub_receive_loop_running = False
|
self.is_sub_receive_loop_running = False
|
||||||
@ -184,12 +173,8 @@ class KeyValueStore:
|
|||||||
Returns:
|
Returns:
|
||||||
装饰器
|
装饰器
|
||||||
"""
|
"""
|
||||||
if IS_MAIN_PROCESS and not self.is_main_receive_loop_running:
|
if not IS_MAIN_PROCESS:
|
||||||
threading.Thread(target=self._start_receive_loop, daemon=True).start()
|
raise RuntimeError("Cannot subscribe in sub process.")
|
||||||
shared_memory.is_main_receive_loop_running = True
|
|
||||||
elif not IS_MAIN_PROCESS and not self.is_sub_receive_loop_running:
|
|
||||||
threading.Thread(target=self._start_receive_loop, daemon=True).start()
|
|
||||||
shared_memory.is_sub_receive_loop_running = True
|
|
||||||
|
|
||||||
def decorator(func: ON_RECEIVE_FUNC) -> ON_RECEIVE_FUNC:
|
def decorator(func: ON_RECEIVE_FUNC) -> ON_RECEIVE_FUNC:
|
||||||
async def wrapper(data: Any):
|
async def wrapper(data: Any):
|
||||||
@ -211,38 +196,29 @@ class KeyValueStore:
|
|||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def run_subscriber_receive_funcs(channel_: str, data: Any):
|
async def run_subscriber_receive_funcs(channel_: str, data: Any):
|
||||||
"""
|
"""
|
||||||
运行订阅者接收函数
|
运行订阅者接收函数
|
||||||
Args:
|
Args:
|
||||||
channel_: 频道
|
channel_: 频道
|
||||||
data: 数据
|
data: 数据
|
||||||
"""
|
"""
|
||||||
if IS_MAIN_PROCESS:
|
[asyncio.create_task(func(data)) for func in _on_main_subscriber_receive_funcs[channel_]]
|
||||||
if channel_ in _on_main_subscriber_receive_funcs and _on_main_subscriber_receive_funcs[channel_]:
|
|
||||||
run_coroutine_in_thread(*[func(data) for func in _on_main_subscriber_receive_funcs[channel_]])
|
|
||||||
else:
|
|
||||||
if channel_ in _on_sub_subscriber_receive_funcs and _on_sub_subscriber_receive_funcs[channel_]:
|
|
||||||
run_coroutine_in_thread(*[func(data) for func in _on_sub_subscriber_receive_funcs[channel_]])
|
|
||||||
|
|
||||||
def _start_receive_loop(self):
|
async def start_receive_loop(self):
|
||||||
"""
|
"""
|
||||||
启动发布订阅接收器循环,在主进程中运行,若有子进程订阅则推送给子进程
|
启动发布订阅接收器循环,在主进程中运行,若有子进程订阅则推送给子进程
|
||||||
"""
|
"""
|
||||||
if IS_MAIN_PROCESS:
|
|
||||||
|
if not IS_MAIN_PROCESS:
|
||||||
|
raise RuntimeError("Cannot start receive loop in sub process.")
|
||||||
while True:
|
while True:
|
||||||
data = self.active_chan.receive()
|
data = await self.active_chan.async_receive()
|
||||||
if data[0] == "publish":
|
if data[0] == "publish":
|
||||||
# 运行主进程订阅函数
|
# 运行主进程订阅函数
|
||||||
self.run_subscriber_receive_funcs(data[1]["channel"], data[1]["data"])
|
await self.run_subscriber_receive_funcs(data[1]["channel"], data[1]["data"])
|
||||||
# 推送给子进程
|
# 推送给子进程
|
||||||
self.publish_channel.send(data)
|
self.publish_channel.send(data)
|
||||||
else:
|
|
||||||
while True:
|
|
||||||
data = self.publish_channel.receive()
|
|
||||||
if data[0] == "publish":
|
|
||||||
# 运行子进程订阅函数
|
|
||||||
self.run_subscriber_receive_funcs(data[1]["channel"], data[1]["data"])
|
|
||||||
|
|
||||||
|
|
||||||
class GlobalKeyValueStore:
|
class GlobalKeyValueStore:
|
||||||
@ -262,7 +238,6 @@ shared_memory: KeyValueStore = GlobalKeyValueStore.get_instance()
|
|||||||
|
|
||||||
# 全局单例访问点
|
# 全局单例访问点
|
||||||
if IS_MAIN_PROCESS:
|
if IS_MAIN_PROCESS:
|
||||||
|
|
||||||
@shared_memory.passive_chan.on_receive(lambda d: d[0] == "get")
|
@shared_memory.passive_chan.on_receive(lambda d: d[0] == "get")
|
||||||
def on_get(data: tuple[str, dict[str, Any]]):
|
def on_get(data: tuple[str, dict[str, Any]]):
|
||||||
key = data[1]["key"]
|
key = data[1]["key"]
|
||||||
@ -289,14 +264,6 @@ if IS_MAIN_PROCESS:
|
|||||||
recv_chan = data[1]["recv_chan"]
|
recv_chan = data[1]["recv_chan"]
|
||||||
recv_chan.send(shared_memory.get_all())
|
recv_chan.send(shared_memory.get_all())
|
||||||
|
|
||||||
|
|
||||||
else:
|
|
||||||
# 子进程在入口函数中对shared_memory进行初始化
|
|
||||||
@channel.publish_channel.on_receive()
|
|
||||||
def on_publish(data: tuple[str, Any]):
|
|
||||||
channel_, data = data
|
|
||||||
shared_memory.run_subscriber_receive_funcs(channel_, data)
|
|
||||||
|
|
||||||
_ref_count = 0 # import 引用计数, 防止获取空指针
|
_ref_count = 0 # import 引用计数, 防止获取空指针
|
||||||
if not IS_MAIN_PROCESS:
|
if not IS_MAIN_PROCESS:
|
||||||
if (shared_memory is None) and _ref_count > 1:
|
if (shared_memory is None) and _ref_count > 1:
|
||||||
|
@ -1,4 +1,2 @@
|
|||||||
import multiprocessing
|
|
||||||
|
|
||||||
from .manager import *
|
from .manager import *
|
||||||
|
|
||||||
|
@ -8,13 +8,11 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|||||||
@File : manager.py
|
@File : manager.py
|
||||||
@Software: PyCharm
|
@Software: PyCharm
|
||||||
"""
|
"""
|
||||||
|
import asyncio
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
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 liteyuki.comm.channel import Channel, get_channel, set_channels, publish_channel
|
|
||||||
from liteyuki.comm.storage import shared_memory
|
|
||||||
from liteyuki.log import logger
|
from liteyuki.log import logger
|
||||||
from liteyuki.utils import IS_MAIN_PROCESS
|
from liteyuki.utils import IS_MAIN_PROCESS
|
||||||
|
|
||||||
@ -22,10 +20,15 @@ if TYPE_CHECKING:
|
|||||||
from liteyuki.bot.lifespan import Lifespan
|
from liteyuki.bot.lifespan import Lifespan
|
||||||
from liteyuki.comm.storage import KeyValueStore
|
from liteyuki.comm.storage import KeyValueStore
|
||||||
|
|
||||||
|
|
||||||
|
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.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
|
||||||
|
|
||||||
TARGET_FUNC: TypeAlias = Callable[..., Any]
|
TARGET_FUNC: TypeAlias = Callable[..., Any]
|
||||||
TIMEOUT = 10
|
TIMEOUT = 10
|
||||||
@ -69,7 +72,7 @@ def _delivery_channel_wrapper(func: TARGET_FUNC, cd: ChannelDeliver, sm: "KeyVal
|
|||||||
channel.publish_channel = cd.publish # 子进程发布通道
|
channel.publish_channel = cd.publish # 子进程发布通道
|
||||||
|
|
||||||
# 给子进程创建共享内存实例
|
# 给子进程创建共享内存实例
|
||||||
from liteyuki.comm import storage
|
|
||||||
storage.shared_memory = sm
|
storage.shared_memory = sm
|
||||||
|
|
||||||
func(*args, **kwargs)
|
func(*args, **kwargs)
|
||||||
@ -85,13 +88,12 @@ class ProcessManager:
|
|||||||
self.targets: dict[str, tuple[Callable, tuple, dict]] = {}
|
self.targets: dict[str, tuple[Callable, tuple, dict]] = {}
|
||||||
self.processes: dict[str, Process] = {}
|
self.processes: dict[str, Process] = {}
|
||||||
|
|
||||||
def start(self, name: str):
|
async def _run_process(self, name: str):
|
||||||
"""
|
"""
|
||||||
开启后自动监控进程,并添加到进程字典中
|
开启后自动监控进程,并添加到进程字典中,会阻塞,请创建task
|
||||||
Args:
|
Args:
|
||||||
name:
|
name:
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if name not in self.targets:
|
if name not in self.targets:
|
||||||
raise KeyError(f"Process {name} not found.")
|
raise KeyError(f"Process {name} not found.")
|
||||||
@ -108,30 +110,31 @@ class ProcessManager:
|
|||||||
_start_process()
|
_start_process()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
data = chan_active.receive()
|
data = await chan_active.async_receive()
|
||||||
if data == 0:
|
if data == 0:
|
||||||
# 停止
|
# 停止
|
||||||
logger.info(f"Stopping process {name}")
|
logger.info(f"Stopping process {name}")
|
||||||
self.lifespan.before_process_shutdown()
|
await self.lifespan.before_process_shutdown()
|
||||||
self.terminate(name)
|
self.terminate(name)
|
||||||
break
|
break
|
||||||
elif data == 1:
|
elif data == 1:
|
||||||
# 重启
|
# 重启
|
||||||
logger.info(f"Restarting process {name}")
|
logger.info(f"Restarting process {name}")
|
||||||
self.lifespan.before_process_shutdown()
|
await self.lifespan.before_process_shutdown()
|
||||||
self.lifespan.before_process_restart()
|
await self.lifespan.before_process_restart()
|
||||||
self.terminate(name)
|
self.terminate(name)
|
||||||
_start_process()
|
_start_process()
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
logger.warning("Unknown data received, ignored.")
|
logger.warning("Unknown data received, ignored.")
|
||||||
|
|
||||||
def start_all(self):
|
async def start_all(self):
|
||||||
"""
|
"""
|
||||||
启动所有进程
|
对外启动方法,启动所有进程,创建asyncio task
|
||||||
"""
|
"""
|
||||||
for name in self.targets:
|
[asyncio.create_task(chan.start_receive_loop()) for chan in get_channels().values()]
|
||||||
threading.Thread(target=self.start, args=(name,), daemon=True).start()
|
[asyncio.create_task(sm.start_receive_loop()) for sm in [shared_memory]]
|
||||||
|
[asyncio.create_task(self._run_process(name)) for name in self.targets]
|
||||||
|
|
||||||
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):
|
||||||
"""
|
"""
|
||||||
@ -144,8 +147,8 @@ class ProcessManager:
|
|||||||
"""
|
"""
|
||||||
if kwargs is None:
|
if kwargs is None:
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
chan_active: Channel = Channel(_id=f"{name}-active")
|
chan_active: Channel = Channel(name=f"{name}-active")
|
||||||
chan_passive: Channel = Channel(_id=f"{name}-passive")
|
chan_passive: Channel = Channel(name=f"{name}-passive")
|
||||||
|
|
||||||
channel_deliver = ChannelDeliver(
|
channel_deliver = ChannelDeliver(
|
||||||
active=chan_active,
|
active=chan_active,
|
||||||
@ -157,12 +160,6 @@ class ProcessManager:
|
|||||||
|
|
||||||
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)
|
||||||
# 主进程通道
|
# 主进程通道
|
||||||
set_channels(
|
|
||||||
{
|
|
||||||
f"{name}-active" : chan_active,
|
|
||||||
f"{name}-passive": chan_passive
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def join_all(self):
|
def join_all(self):
|
||||||
for name, process in self.targets:
|
for name, process in self.targets:
|
||||||
|
@ -10,6 +10,7 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|||||||
"""
|
"""
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
from liteyuki import Channel
|
||||||
from liteyuki.comm.storage import shared_memory
|
from liteyuki.comm.storage import shared_memory
|
||||||
|
|
||||||
|
|
||||||
@ -24,7 +25,7 @@ class MessageEvent:
|
|||||||
session_id: str,
|
session_id: str,
|
||||||
user_id: str,
|
user_id: str,
|
||||||
session_type: str,
|
session_type: str,
|
||||||
receive_channel: str,
|
receive_channel: Optional[Channel["MessageEvent"]] = None,
|
||||||
data: Optional[dict[str, Any]] = None,
|
data: Optional[dict[str, Any]] = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@ -78,7 +79,10 @@ class MessageEvent:
|
|||||||
},
|
},
|
||||||
bot_id=self.bot_id,
|
bot_id=self.bot_id,
|
||||||
session_id=self.session_id,
|
session_id=self.session_id,
|
||||||
|
user_id=self.user_id,
|
||||||
session_type=self.session_type,
|
session_type=self.session_type,
|
||||||
receive_channel="_"
|
receive_channel=None
|
||||||
)
|
)
|
||||||
shared_memory.publish(self.receive_channel, reply_event)
|
# shared_memory.publish(self.receive_channel, reply_event)
|
||||||
|
if self.receive_channel:
|
||||||
|
self.receive_channel.send(reply_event)
|
||||||
|
@ -23,6 +23,7 @@ _queue: Queue = Queue()
|
|||||||
|
|
||||||
@shared_memory.on_subscriber_receive("event_to_liteyuki")
|
@shared_memory.on_subscriber_receive("event_to_liteyuki")
|
||||||
async def _(event: MessageEvent):
|
async def _(event: MessageEvent):
|
||||||
|
print("AA")
|
||||||
current_priority = -1
|
current_priority = -1
|
||||||
for i, matcher in enumerate(_matcher_list):
|
for i, matcher in enumerate(_matcher_list):
|
||||||
logger.info(f"Running matcher {matcher} for event: {event}")
|
logger.info(f"Running matcher {matcher} for event: {event}")
|
||||||
@ -32,17 +33,24 @@ async def _(event: MessageEvent):
|
|||||||
current_priority = matcher.priority
|
current_priority = matcher.priority
|
||||||
if matcher.block:
|
if matcher.block:
|
||||||
break
|
break
|
||||||
|
else:
|
||||||
|
logger.info(f"No matcher matched for event: {event}")
|
||||||
|
print("BB")
|
||||||
|
|
||||||
|
|
||||||
def on_message(rule: Rule = empty_rule, priority: int = 0, block: bool = False) -> Matcher:
|
def add_matcher(matcher: Matcher):
|
||||||
matcher = Matcher(rule, priority, block)
|
|
||||||
# 按照优先级插入
|
|
||||||
for i, m in enumerate(_matcher_list):
|
for i, m in enumerate(_matcher_list):
|
||||||
if m.priority < matcher.priority:
|
if m.priority < matcher.priority:
|
||||||
_matcher_list.insert(i, matcher)
|
_matcher_list.insert(i, matcher)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
_matcher_list.append(matcher)
|
_matcher_list.append(matcher)
|
||||||
|
|
||||||
|
|
||||||
|
def on_message(rule: Rule = empty_rule, priority: int = 0, block: bool = False) -> Matcher:
|
||||||
|
matcher = Matcher(rule, priority, block)
|
||||||
|
# 按照优先级插入
|
||||||
|
add_matcher(matcher)
|
||||||
return matcher
|
return matcher
|
||||||
|
|
||||||
|
|
||||||
@ -50,4 +58,5 @@ def on_keywords(keywords: list[str], rule=empty_rule, priority: int = 0, block:
|
|||||||
@Rule
|
@Rule
|
||||||
async def on_keywords_rule(event: MessageEvent):
|
async def on_keywords_rule(event: MessageEvent):
|
||||||
return any(keyword in event.raw_message for keyword in keywords)
|
return any(keyword in event.raw_message for keyword in keywords)
|
||||||
|
|
||||||
return on_message(on_keywords_rule & rule, priority, block)
|
return on_message(on_keywords_rule & rule, priority, block)
|
||||||
|
@ -43,7 +43,7 @@ def run_coroutine(*coro: Coroutine):
|
|||||||
# 检测是否有现有的事件循环
|
# 检测是否有现有的事件循环
|
||||||
|
|
||||||
try:
|
try:
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_running_loop()
|
||||||
if loop.is_running():
|
if loop.is_running():
|
||||||
# 如果事件循环正在运行,创建任务
|
# 如果事件循环正在运行,创建任务
|
||||||
for c in coro:
|
for c in coro:
|
||||||
@ -62,6 +62,7 @@ def run_coroutine(*coro: Coroutine):
|
|||||||
# 捕获其他异常,防止协程被重复等待
|
# 捕获其他异常,防止协程被重复等待
|
||||||
logger.error(f"Exception occurred: {e}")
|
logger.error(f"Exception occurred: {e}")
|
||||||
|
|
||||||
|
|
||||||
def run_coroutine_in_thread(*coro: Coroutine):
|
def run_coroutine_in_thread(*coro: Coroutine):
|
||||||
"""
|
"""
|
||||||
在新线程中运行协程
|
在新线程中运行协程
|
||||||
@ -73,6 +74,7 @@ def run_coroutine_in_thread(*coro: Coroutine):
|
|||||||
"""
|
"""
|
||||||
threading.Thread(target=run_coroutine, args=coro, daemon=True).start()
|
threading.Thread(target=run_coroutine, args=coro, daemon=True).start()
|
||||||
|
|
||||||
|
|
||||||
def path_to_module_name(path: Path) -> str:
|
def path_to_module_name(path: Path) -> str:
|
||||||
"""
|
"""
|
||||||
转换路径为模块名
|
转换路径为模块名
|
||||||
|
1
main.py
1
main.py
@ -4,6 +4,7 @@
|
|||||||
from liteyuki import LiteyukiBot
|
from liteyuki import LiteyukiBot
|
||||||
from liteyuki.config import load_config_in_default
|
from liteyuki.config import load_config_in_default
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
bot = LiteyukiBot(**load_config_in_default(no_waring=True))
|
bot = LiteyukiBot(**load_config_in_default(no_waring=True))
|
||||||
bot.run()
|
bot.run()
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|
||||||
|
|
||||||
@Time : 2024/8/22 上午9:06
|
|
||||||
@Author : snowykami
|
|
||||||
@Email : snowykami@outlook.com
|
|
||||||
@File : anti_dislink.py
|
|
||||||
@Software: PyCharm
|
|
||||||
"""
|
|
||||||
import random
|
|
||||||
from liteyuki.plugin import PluginMetadata, PluginType
|
|
||||||
|
|
||||||
from liteyuki.message.on import on_keywords
|
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
|
||||||
name="严禁断联化",
|
|
||||||
type=PluginType.APPLICATION
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@on_keywords(["看看你的", "看看j", "给我看看"]).handle()
|
|
||||||
async def _(event):
|
|
||||||
event.reply(random.choice(["No dislink", "严禁断联化"]))
|
|
@ -1,26 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|
||||||
|
|
||||||
@Time : 2024/8/22 上午8:37
|
|
||||||
@Author : snowykami
|
|
||||||
@Email : snowykami@outlook.com
|
|
||||||
@File : ts_chan_main.py
|
|
||||||
@Software: PyCharm
|
|
||||||
"""
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
from liteyuki.comm import Channel, set_channel, get_channel
|
|
||||||
from liteyuki import get_bot
|
|
||||||
|
|
||||||
set_channel("chan-main", Channel("chan-main"))
|
|
||||||
set_channel("chan-sub", Channel("chan-sub"))
|
|
||||||
|
|
||||||
chan_main = get_channel("chan-main")
|
|
||||||
|
|
||||||
|
|
||||||
# @get_bot().on_after_start
|
|
||||||
# async def _():
|
|
||||||
# while True:
|
|
||||||
# chan_main.send("Hello, World!")
|
|
||||||
# await asyncio.sleep(5)
|
|
@ -10,18 +10,23 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from nonebot import Bot, get_bot, on_message
|
from nonebot import Bot, get_bot, on_message, get_driver
|
||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
from nonebot.adapters.onebot.v11 import MessageEvent, Bot
|
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.comm.storage import shared_memory
|
||||||
from liteyuki.message.event import MessageEvent as LiteyukiMessageEvent
|
from liteyuki.message.event import MessageEvent as LiteyukiMessageEvent
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="轻雪物流",
|
name="轻雪push",
|
||||||
description="把消息事件传递给轻雪框架进行处理",
|
description="把消息事件传递给轻雪框架进行处理",
|
||||||
usage="用户无需使用",
|
usage="用户无需使用",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
recv_channel = Channel[LiteyukiMessageEvent](name="event_to_nonebot")
|
||||||
|
|
||||||
|
|
||||||
@on_message().handle()
|
@on_message().handle()
|
||||||
async def _(bot: Bot, event: MessageEvent):
|
async def _(bot: Bot, event: MessageEvent):
|
||||||
@ -34,14 +39,16 @@ async def _(bot: Bot, event: MessageEvent):
|
|||||||
user_id=str(event.user_id),
|
user_id=str(event.user_id),
|
||||||
session_id=str(event.user_id if event.message_type == "private" else event.group_id),
|
session_id=str(event.user_id if event.message_type == "private" else event.group_id),
|
||||||
session_type=event.message_type,
|
session_type=event.message_type,
|
||||||
receive_channel="event_to_nonebot"
|
receive_channel=recv_channel,
|
||||||
)
|
)
|
||||||
shared_memory.publish("event_to_liteyuki", liteyuki_event)
|
shared_memory.publish("event_to_liteyuki", liteyuki_event)
|
||||||
|
|
||||||
|
|
||||||
@shared_memory.on_subscriber_receive("event_to_nonebot")
|
@get_driver().on_bot_connect
|
||||||
async def _(event: LiteyukiMessageEvent):
|
async def _():
|
||||||
bot: Bot = get_bot(event.bot_id)
|
while True:
|
||||||
|
event = await recv_channel.async_receive()
|
||||||
|
bot: Bot = get_bot(event.bot_id) # type: ignore
|
||||||
if event.message_type == "private":
|
if event.message_type == "private":
|
||||||
await bot.send_private_msg(user_id=int(event.session_id), message=event.data["message"])
|
await bot.send_private_msg(user_id=int(event.session_id), message=event.data["message"])
|
||||||
elif event.message_type == "group":
|
elif event.message_type == "group":
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|
||||||
|
|
||||||
@Time : 2024/8/22 上午8:39
|
|
||||||
@Author : snowykami
|
|
||||||
@Email : snowykami@outlook.com
|
|
||||||
@File : ts_chan_sub.py
|
|
||||||
@Software: PyCharm
|
|
||||||
"""
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
from liteyuki.comm import Channel, get_channel
|
|
||||||
from nonebot import get_bot
|
|
||||||
from nonebot.adapters.onebot.v11 import Bot
|
|
||||||
chan_main = get_channel("chan-main")
|
|
||||||
|
|
||||||
|
|
||||||
# @chan_main.on_receive()
|
|
||||||
# async def _(data: str):
|
|
||||||
# print("Received data from chan-main:", data)
|
|
||||||
# try:
|
|
||||||
# bot: Bot = get_bot("2443429204") # type: ignore
|
|
||||||
#
|
|
||||||
# def send_msg():
|
|
||||||
#
|
|
||||||
# bot.send_msg(message_type="private", user_id=2443429204, message=data)
|
|
||||||
#
|
|
||||||
# print("tsA")
|
|
||||||
# print("tsA1")
|
|
||||||
# await asyncio.ensure_future(c)
|
|
||||||
# print("tsB")
|
|
||||||
# except Exception as e:
|
|
||||||
# print(e)
|
|
||||||
# pass
|
|
Loading…
Reference in New Issue
Block a user