Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
287ab63091 | |||
0c942d9806 | |||
237789e0d4 | |||
e656fa6a48 | |||
6dcb085b53 | |||
55a427e344 | |||
43eef20b71 | |||
b8fdb4146e | |||
cdbede7135 | |||
85a3a9ad52 | |||
943e0c2665 |
12
.github/workflows/deploy-docs.yml
vendored
12
.github/workflows/deploy-docs.yml
vendored
@ -33,6 +33,18 @@ jobs:
|
||||
cd docs
|
||||
pnpm install
|
||||
|
||||
- name: 设置Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
|
||||
- name: 生成API markdown
|
||||
run: |-
|
||||
python -m pip install pydantic
|
||||
python liteyuki/mkdoc.py
|
||||
|
||||
|
||||
- name: 构建文档
|
||||
env:
|
||||
NODE_OPTIONS: --max_old_space_size=8192
|
||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -49,4 +49,8 @@ prompt.txt
|
||||
# pdm
|
||||
.pdm-python
|
||||
.pdm-build
|
||||
dist
|
||||
dist
|
||||
|
||||
doc
|
||||
|
||||
mkdoc2.py
|
@ -1,31 +1,10 @@
|
||||
import {sidebar} from "vuepress-theme-hope";
|
||||
|
||||
export const enSidebarConfig = sidebar({
|
||||
"/en/": [
|
||||
"",
|
||||
{
|
||||
text: "Install & Deploy",
|
||||
icon: "laptop-code",
|
||||
prefix: "deploy/",
|
||||
children: "structure",
|
||||
},
|
||||
{
|
||||
text: "Usage & Features",
|
||||
icon: "book",
|
||||
prefix: "usage/",
|
||||
children: "structure",
|
||||
},
|
||||
{
|
||||
text: "Resources & Plugins",
|
||||
icon: "store",
|
||||
prefix: "store/",
|
||||
children: "structure",
|
||||
},
|
||||
{
|
||||
text: "Development & Contribution",
|
||||
icon: "pen-nib",
|
||||
prefix: "dev/",
|
||||
children: "structure",
|
||||
}
|
||||
],
|
||||
});
|
||||
export const enSidebarConfig = sidebar(
|
||||
{
|
||||
"/en/deploy/": "structure",
|
||||
"/en/usage/": "structure",
|
||||
"/en/store/": "structure",
|
||||
"/en/dev/": "structure",
|
||||
}
|
||||
)
|
||||
|
@ -1,31 +1,11 @@
|
||||
import {sidebar} from "vuepress-theme-hope";
|
||||
|
||||
export const zhSidebarConfig = sidebar({
|
||||
"/": [
|
||||
"",
|
||||
{
|
||||
text: "安装及部署",
|
||||
icon: "laptop-code",
|
||||
prefix: "deploy/",
|
||||
children: "structure",
|
||||
},
|
||||
{
|
||||
text: "使用及功能",
|
||||
icon: "book",
|
||||
prefix: "usage/",
|
||||
children: "structure",
|
||||
},
|
||||
{
|
||||
text: "资源及插件",
|
||||
icon: "store",
|
||||
prefix: "store/",
|
||||
children: "structure",
|
||||
},
|
||||
{
|
||||
text: "开发及贡献",
|
||||
icon: "pen-nib",
|
||||
prefix: "dev/",
|
||||
children: "structure",
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
export const zhSidebarConfig = sidebar(
|
||||
{
|
||||
"/deploy/": "structure",
|
||||
"/usage/": "structure",
|
||||
"/store/": "structure",
|
||||
"/dev/": "structure",
|
||||
}
|
||||
)
|
@ -5,6 +5,7 @@ import {enNavbarConfig, zhNavbarConfig} from "./navbar/index.js";
|
||||
export default hopeTheme({
|
||||
|
||||
hostname: "https://vuepress-theme-hope-docs-demo.netlify.app",
|
||||
hotReload: true,
|
||||
|
||||
locales: {
|
||||
"/": {
|
||||
|
7
docs/dev/api/README.md
Normal file
7
docs/dev/api/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
title: liteyuki
|
||||
index: true
|
||||
icon: laptop-code
|
||||
category: API
|
||||
---
|
||||
|
227
docs/dev/api/bot/README.md
Normal file
227
docs/dev/api/bot/README.md
Normal file
@ -0,0 +1,227 @@
|
||||
---
|
||||
title: liteyuki.bot
|
||||
index: true
|
||||
icon: laptop-code
|
||||
category: API
|
||||
---
|
||||
|
||||
### ***def*** `get_bot() -> LiteyukiBot`
|
||||
|
||||
获取轻雪实例
|
||||
|
||||
|
||||
|
||||
Returns:
|
||||
|
||||
LiteyukiBot: 当前的轻雪实例
|
||||
|
||||
### ***def*** `get_config(key: str, default: Any) -> Any`
|
||||
|
||||
获取配置
|
||||
|
||||
Args:
|
||||
|
||||
key: 配置键
|
||||
|
||||
default: 默认值
|
||||
|
||||
|
||||
|
||||
Returns:
|
||||
|
||||
Any: 配置值
|
||||
|
||||
### ***def*** `get_config_with_compat(key: str, compat_keys: tuple[str], default: Any) -> Any`
|
||||
|
||||
获取配置,兼容旧版本
|
||||
|
||||
Args:
|
||||
|
||||
key: 配置键
|
||||
|
||||
compat_keys: 兼容键
|
||||
|
||||
default: 默认值
|
||||
|
||||
|
||||
|
||||
Returns:
|
||||
|
||||
Any: 配置值
|
||||
|
||||
### ***def*** `print_logo() -> None`
|
||||
|
||||
|
||||
|
||||
### ***class*** `LiteyukiBot`
|
||||
|
||||
|
||||
|
||||
###   ***def*** `__init__(self) -> None`
|
||||
|
||||
 初始化轻雪实例
|
||||
|
||||
Args:
|
||||
|
||||
*args:
|
||||
|
||||
**kwargs: 配置
|
||||
|
||||
###   ***def*** `run(self) -> None`
|
||||
|
||||
 启动逻辑
|
||||
|
||||
###   ***def*** `keep_alive(self) -> None`
|
||||
|
||||
 保持轻雪运行
|
||||
|
||||
Returns:
|
||||
|
||||
###   ***def*** `restart(self, delay: int) -> None`
|
||||
|
||||
 重启轻雪本体
|
||||
|
||||
Returns:
|
||||
|
||||
###   ***def*** `restart_process(self, name: Optional[str]) -> None`
|
||||
|
||||
 停止轻雪
|
||||
|
||||
Args:
|
||||
|
||||
name: 进程名称, 默认为None, 所有进程
|
||||
|
||||
Returns:
|
||||
|
||||
###   ***def*** `init(self) -> None`
|
||||
|
||||
 初始化轻雪, 自动调用
|
||||
|
||||
Returns:
|
||||
|
||||
###   ***def*** `init_logger(self) -> None`
|
||||
|
||||
 
|
||||
|
||||
###   ***def*** `stop(self) -> None`
|
||||
|
||||
 停止轻雪
|
||||
|
||||
Returns:
|
||||
|
||||
###   ***def*** `on_before_start(self, func: LIFESPAN_FUNC) -> None`
|
||||
|
||||
 注册启动前的函数
|
||||
|
||||
Args:
|
||||
|
||||
func:
|
||||
|
||||
|
||||
|
||||
Returns:
|
||||
|
||||
###   ***def*** `on_after_start(self, func: LIFESPAN_FUNC) -> None`
|
||||
|
||||
 注册启动后的函数
|
||||
|
||||
Args:
|
||||
|
||||
func:
|
||||
|
||||
|
||||
|
||||
Returns:
|
||||
|
||||
###   ***def*** `on_after_shutdown(self, func: LIFESPAN_FUNC) -> None`
|
||||
|
||||
 注册停止后的函数:未实现
|
||||
|
||||
Args:
|
||||
|
||||
func:
|
||||
|
||||
|
||||
|
||||
Returns:
|
||||
|
||||
###   ***def*** `on_before_process_shutdown(self, func: LIFESPAN_FUNC) -> None`
|
||||
|
||||
 注册进程停止前的函数,为子进程停止时调用
|
||||
|
||||
Args:
|
||||
|
||||
func:
|
||||
|
||||
|
||||
|
||||
Returns:
|
||||
|
||||
###   ***def*** `on_before_process_restart(self, func: LIFESPAN_FUNC) -> None`
|
||||
|
||||
 注册进程重启前的函数,为子进程重启时调用
|
||||
|
||||
Args:
|
||||
|
||||
func:
|
||||
|
||||
|
||||
|
||||
Returns:
|
||||
|
||||
###   ***def*** `on_after_restart(self, func: LIFESPAN_FUNC) -> None`
|
||||
|
||||
 注册重启后的函数:未实现
|
||||
|
||||
Args:
|
||||
|
||||
func:
|
||||
|
||||
|
||||
|
||||
Returns:
|
||||
|
||||
###   ***def*** `on_after_nonebot_init(self, func: LIFESPAN_FUNC) -> None`
|
||||
|
||||
 注册nonebot初始化后的函数
|
||||
|
||||
Args:
|
||||
|
||||
func:
|
||||
|
||||
|
||||
|
||||
Returns:
|
||||
|
||||
### ***var*** `executable = sys.executable`
|
||||
|
||||
|
||||
|
||||
### ***var*** `args = sys.argv`
|
||||
|
||||
|
||||
|
||||
### ***var*** `chan_active = get_channel(f'{name}-active')`
|
||||
|
||||
|
||||
|
||||
### ***var*** `cmd = 'start'`
|
||||
|
||||
|
||||
|
||||
### ***var*** `chan_active = get_channel(f'{process_name}-active')`
|
||||
|
||||
|
||||
|
||||
### ***var*** `cmd = 'nohup'`
|
||||
|
||||
|
||||
|
||||
### ***var*** `cmd = 'open'`
|
||||
|
||||
|
||||
|
||||
### ***var*** `cmd = 'nohup'`
|
||||
|
||||
|
||||
|
170
docs/dev/api/bot/lifespan.md
Normal file
170
docs/dev/api/bot/lifespan.md
Normal file
@ -0,0 +1,170 @@
|
||||
---
|
||||
title: liteyuki.bot.lifespan
|
||||
order: 1
|
||||
icon: laptop-code
|
||||
category: API
|
||||
---
|
||||
|
||||
### ***def*** `run_funcs(funcs: list[LIFESPAN_FUNC | PROCESS_LIFESPAN_FUNC]) -> None`
|
||||
|
||||
运行函数
|
||||
|
||||
Args:
|
||||
|
||||
funcs:
|
||||
|
||||
Returns:
|
||||
|
||||
### ***class*** `Lifespan`
|
||||
|
||||
|
||||
|
||||
###   ***def*** `__init__(self) -> None`
|
||||
|
||||
 轻雪生命周期管理,启动、停止、重启
|
||||
|
||||
###   ***@staticmethod***
|
||||
###   ***def*** `run_funcs(funcs: list[LIFESPAN_FUNC | PROCESS_LIFESPAN_FUNC]) -> None`
|
||||
|
||||
 运行函数
|
||||
|
||||
Args:
|
||||
|
||||
funcs:
|
||||
|
||||
Returns:
|
||||
|
||||
###   ***def*** `on_before_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
||||
|
||||
 注册启动时的函数
|
||||
|
||||
Args:
|
||||
|
||||
func:
|
||||
|
||||
Returns:
|
||||
|
||||
LIFESPAN_FUNC:
|
||||
|
||||
###   ***def*** `on_after_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
||||
|
||||
 注册启动时的函数
|
||||
|
||||
Args:
|
||||
|
||||
func:
|
||||
|
||||
Returns:
|
||||
|
||||
LIFESPAN_FUNC:
|
||||
|
||||
###   ***def*** `on_before_process_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
||||
|
||||
 注册停止前的函数
|
||||
|
||||
Args:
|
||||
|
||||
func:
|
||||
|
||||
Returns:
|
||||
|
||||
LIFESPAN_FUNC:
|
||||
|
||||
###   ***def*** `on_after_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
||||
|
||||
 注册停止后的函数
|
||||
|
||||
Args:
|
||||
|
||||
func:
|
||||
|
||||
|
||||
|
||||
Returns:
|
||||
|
||||
LIFESPAN_FUNC:
|
||||
|
||||
###   ***def*** `on_before_process_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
||||
|
||||
 注册重启时的函数
|
||||
|
||||
Args:
|
||||
|
||||
func:
|
||||
|
||||
Returns:
|
||||
|
||||
LIFESPAN_FUNC:
|
||||
|
||||
###   ***def*** `on_after_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
||||
|
||||
 注册重启后的函数
|
||||
|
||||
Args:
|
||||
|
||||
func:
|
||||
|
||||
Returns:
|
||||
|
||||
LIFESPAN_FUNC:
|
||||
|
||||
###   ***def*** `on_after_nonebot_init(self, func: Any) -> None`
|
||||
|
||||
 注册 NoneBot 初始化后的函数
|
||||
|
||||
Args:
|
||||
|
||||
func:
|
||||
|
||||
|
||||
|
||||
Returns:
|
||||
|
||||
###   ***def*** `before_start(self) -> None`
|
||||
|
||||
 启动前
|
||||
|
||||
Returns:
|
||||
|
||||
###   ***def*** `after_start(self) -> None`
|
||||
|
||||
 启动后
|
||||
|
||||
Returns:
|
||||
|
||||
###   ***def*** `before_process_shutdown(self) -> None`
|
||||
|
||||
 停止前
|
||||
|
||||
Returns:
|
||||
|
||||
###   ***def*** `after_shutdown(self) -> None`
|
||||
|
||||
 停止后
|
||||
|
||||
Returns:
|
||||
|
||||
###   ***def*** `before_process_restart(self) -> None`
|
||||
|
||||
 重启前
|
||||
|
||||
Returns:
|
||||
|
||||
###   ***def*** `after_restart(self) -> None`
|
||||
|
||||
 重启后
|
||||
|
||||
Returns:
|
||||
|
||||
### ***var*** `tasks = []`
|
||||
|
||||
|
||||
|
||||
### ***var*** `loop = asyncio.get_event_loop()`
|
||||
|
||||
|
||||
|
||||
### ***var*** `loop = asyncio.new_event_loop()`
|
||||
|
||||
|
||||
|
7
docs/dev/api/comm/README.md
Normal file
7
docs/dev/api/comm/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
title: liteyuki.comm
|
||||
index: true
|
||||
icon: laptop-code
|
||||
category: API
|
||||
---
|
||||
|
149
docs/dev/api/comm/channel.md
Normal file
149
docs/dev/api/comm/channel.md
Normal file
@ -0,0 +1,149 @@
|
||||
---
|
||||
title: liteyuki.comm.channel_
|
||||
order: 1
|
||||
icon: laptop-code
|
||||
category: API
|
||||
---
|
||||
|
||||
### ***def*** `set_channel(name: str, channel: Channel) -> None`
|
||||
|
||||
设置通道实例
|
||||
|
||||
Args:
|
||||
|
||||
name: 通道名称
|
||||
|
||||
channel: 通道实例
|
||||
|
||||
### ***def*** `set_channels(channels: dict[str, Channel]) -> None`
|
||||
|
||||
设置通道实例
|
||||
|
||||
Args:
|
||||
|
||||
channels: 通道名称
|
||||
|
||||
### ***def*** `get_channel(name: str) -> Channel`
|
||||
|
||||
获取通道实例
|
||||
|
||||
Args:
|
||||
|
||||
name: 通道名称
|
||||
|
||||
Returns:
|
||||
|
||||
### ***def*** `get_channels() -> dict[str, Channel]`
|
||||
|
||||
获取通道实例
|
||||
|
||||
Returns:
|
||||
|
||||
### ***def*** `on_set_channel(data: tuple[str, dict[str, Any]]) -> None`
|
||||
|
||||
|
||||
|
||||
### ***def*** `on_get_channel(data: tuple[str, dict[str, Any]]) -> None`
|
||||
|
||||
|
||||
|
||||
### ***def*** `on_get_channels(data: tuple[str, dict[str, Any]]) -> None`
|
||||
|
||||
|
||||
|
||||
### ***def*** `decorator(func: Callable[[T], Any]) -> Callable[[T], Any]`
|
||||
|
||||
|
||||
|
||||
### ***async def*** `wrapper(data: T) -> Any`
|
||||
|
||||
|
||||
|
||||
### ***class*** `Channel(Generic[T])`
|
||||
|
||||
通道类,可以在进程间和进程内通信,双向但同时只能有一个发送者和一个接收者
|
||||
|
||||
有两种接收工作方式,但是只能选择一种,主动接收和被动接收,主动接收使用 `receive` 方法,被动接收使用 `on_receive` 装饰器
|
||||
|
||||
###   ***def*** `__init__(self, _id: str, type_check: bool) -> None`
|
||||
|
||||
 初始化通道
|
||||
|
||||
Args:
|
||||
|
||||
_id: 通道ID
|
||||
|
||||
###   ***def*** `send(self, data: T) -> None`
|
||||
|
||||
 发送数据
|
||||
|
||||
Args:
|
||||
|
||||
data: 数据
|
||||
|
||||
###   ***def*** `receive(self) -> T`
|
||||
|
||||
 接收数据
|
||||
|
||||
Args:
|
||||
|
||||
###   ***def*** `close(self) -> None`
|
||||
|
||||
 关闭通道
|
||||
|
||||
###   ***def*** `on_receive(self, filter_func: Optional[FILTER_FUNC]) -> Callable[[Callable[[T], Any]], Callable[[T], Any]]`
|
||||
|
||||
 接收数据并执行函数
|
||||
|
||||
Args:
|
||||
|
||||
filter_func: 过滤函数,为None则不过滤
|
||||
|
||||
Returns:
|
||||
|
||||
装饰器,装饰一个函数在接收到数据后执行
|
||||
|
||||
### ***var*** `T = TypeVar('T')`
|
||||
|
||||
|
||||
|
||||
### ***var*** `channel_deliver_active_channel = Channel(_id='channel_deliver_active_channel')`
|
||||
|
||||
|
||||
|
||||
### ***var*** `channel_deliver_passive_channel = Channel(_id='channel_deliver_passive_channel')`
|
||||
|
||||
|
||||
|
||||
### ***var*** `recv_chan = data[1]['recv_chan']`
|
||||
|
||||
|
||||
|
||||
### ***var*** `recv_chan = Channel[Channel[Any]]('recv_chan')`
|
||||
|
||||
|
||||
|
||||
### ***var*** `recv_chan = Channel[dict[str, Channel[Any]]]('recv_chan')`
|
||||
|
||||
|
||||
|
||||
### ***var*** `data = self.conn_recv.recv()`
|
||||
|
||||
|
||||
|
||||
### ***var*** `func = _callback_funcs[func_id]`
|
||||
|
||||
|
||||
|
||||
### ***var*** `func = _callback_funcs[func_id]`
|
||||
|
||||
|
||||
|
||||
### ***var*** `data = self.conn_recv.recv()`
|
||||
|
||||
|
||||
|
||||
### ***var*** `data = self.conn_recv.recv()`
|
||||
|
||||
|
||||
|
15
docs/dev/api/comm/event.md
Normal file
15
docs/dev/api/comm/event.md
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
title: liteyuki.comm.event
|
||||
order: 1
|
||||
icon: laptop-code
|
||||
category: API
|
||||
---
|
||||
|
||||
### ***class*** `Event`
|
||||
|
||||
事件类
|
||||
|
||||
###   ***def*** `__init__(self, name: str, data: dict[str, Any]) -> None`
|
||||
|
||||
 
|
||||
|
140
docs/dev/api/comm/storage.md
Normal file
140
docs/dev/api/comm/storage.md
Normal file
@ -0,0 +1,140 @@
|
||||
---
|
||||
title: liteyuki.comm.storage
|
||||
order: 1
|
||||
icon: laptop-code
|
||||
category: API
|
||||
---
|
||||
|
||||
### ***def*** `on_get(data: tuple[str, dict[str, Any]]) -> None`
|
||||
|
||||
|
||||
|
||||
### ***def*** `on_set(data: tuple[str, dict[str, Any]]) -> None`
|
||||
|
||||
|
||||
|
||||
### ***def*** `on_delete(data: tuple[str, dict[str, Any]]) -> None`
|
||||
|
||||
|
||||
|
||||
### ***def*** `on_get_all(data: tuple[str, dict[str, Any]]) -> None`
|
||||
|
||||
|
||||
|
||||
### ***class*** `KeyValueStore`
|
||||
|
||||
|
||||
|
||||
###   ***def*** `__init__(self) -> None`
|
||||
|
||||
 
|
||||
|
||||
###   ***def*** `set(self, key: str, value: Any) -> None`
|
||||
|
||||
 设置键值对
|
||||
|
||||
Args:
|
||||
|
||||
key: 键
|
||||
|
||||
value: 值
|
||||
|
||||
###   ***def*** `get(self, key: str, default: Optional[Any]) -> Optional[Any]`
|
||||
|
||||
 获取键值对
|
||||
|
||||
Args:
|
||||
|
||||
key: 键
|
||||
|
||||
default: 默认值
|
||||
|
||||
|
||||
|
||||
Returns:
|
||||
|
||||
Any: 值
|
||||
|
||||
###   ***def*** `delete(self, key: str, ignore_key_error: bool) -> None`
|
||||
|
||||
 删除键值对
|
||||
|
||||
Args:
|
||||
|
||||
key: 键
|
||||
|
||||
ignore_key_error: 是否忽略键不存在的错误
|
||||
|
||||
|
||||
|
||||
Returns:
|
||||
|
||||
###   ***def*** `get_all(self) -> dict[str, Any]`
|
||||
|
||||
 获取所有键值对
|
||||
|
||||
Returns:
|
||||
|
||||
dict[str, Any]: 键值对
|
||||
|
||||
### ***class*** `GlobalKeyValueStore`
|
||||
|
||||
|
||||
|
||||
###   ***@classmethod***
|
||||
###   ***def*** `get_instance(cls: Any) -> None`
|
||||
|
||||
 
|
||||
|
||||
###   ***attr*** `_instance: None`
|
||||
|
||||
###   ***attr*** `_lock: threading.Lock()`
|
||||
|
||||
### ***var*** `key = data[1]['key']`
|
||||
|
||||
|
||||
|
||||
### ***var*** `default = data[1]['default']`
|
||||
|
||||
|
||||
|
||||
### ***var*** `recv_chan = data[1]['recv_chan']`
|
||||
|
||||
|
||||
|
||||
### ***var*** `key = data[1]['key']`
|
||||
|
||||
|
||||
|
||||
### ***var*** `value = data[1]['value']`
|
||||
|
||||
|
||||
|
||||
### ***var*** `key = data[1]['key']`
|
||||
|
||||
|
||||
|
||||
### ***var*** `recv_chan = data[1]['recv_chan']`
|
||||
|
||||
|
||||
|
||||
### ***var*** `lock = _get_lock(key)`
|
||||
|
||||
|
||||
|
||||
### ***var*** `lock = _get_lock(key)`
|
||||
|
||||
|
||||
|
||||
### ***var*** `recv_chan = Channel[Optional[Any]]('recv_chan')`
|
||||
|
||||
|
||||
|
||||
### ***var*** `lock = _get_lock(key)`
|
||||
|
||||
|
||||
|
||||
### ***var*** `recv_chan = Channel[dict[str, Any]]('recv_chan')`
|
||||
|
||||
|
||||
|
99
docs/dev/api/config.md
Normal file
99
docs/dev/api/config.md
Normal file
@ -0,0 +1,99 @@
|
||||
---
|
||||
title: liteyuki.config
|
||||
order: 1
|
||||
icon: laptop-code
|
||||
category: API
|
||||
---
|
||||
|
||||
### ***def*** `flat_config(config: dict[str, Any]) -> dict[str, Any]`
|
||||
|
||||
扁平化配置文件
|
||||
|
||||
|
||||
|
||||
{a:{b:{c:1}}} -> {"a.b.c": 1}
|
||||
|
||||
Args:
|
||||
|
||||
config: 配置项目
|
||||
|
||||
|
||||
|
||||
Returns:
|
||||
|
||||
扁平化后的配置文件,但也包含原有的键值对
|
||||
|
||||
### ***def*** `load_from_yaml(file: str) -> dict[str, Any]`
|
||||
|
||||
Load config from yaml file
|
||||
|
||||
### ***def*** `load_from_json(file: str) -> dict[str, Any]`
|
||||
|
||||
Load config from json file
|
||||
|
||||
### ***def*** `load_from_toml(file: str) -> dict[str, Any]`
|
||||
|
||||
Load config from toml file
|
||||
|
||||
### ***def*** `load_from_files() -> dict[str, Any]`
|
||||
|
||||
从指定文件加载配置项,会自动识别文件格式
|
||||
|
||||
默认执行扁平化选项
|
||||
|
||||
### ***def*** `load_configs_from_dirs() -> dict[str, Any]`
|
||||
|
||||
从目录下加载配置文件,不递归
|
||||
|
||||
按照读取文件的优先级反向覆盖
|
||||
|
||||
默认执行扁平化选项
|
||||
|
||||
### ***def*** `load_config_in_default(no_waring: bool) -> dict[str, Any]`
|
||||
|
||||
从一个标准的轻雪项目加载配置文件
|
||||
|
||||
项目目录下的config.*和config目录下的所有配置文件
|
||||
|
||||
项目目录下的配置文件优先
|
||||
|
||||
### ***class*** `SatoriNodeConfig(BaseModel)`
|
||||
|
||||
|
||||
|
||||
### ***class*** `SatoriConfig(BaseModel)`
|
||||
|
||||
|
||||
|
||||
### ***class*** `BasicConfig(BaseModel)`
|
||||
|
||||
|
||||
|
||||
### ***var*** `new_config = copy.deepcopy(config)`
|
||||
|
||||
|
||||
|
||||
### ***var*** `config = yaml.safe_load(open(file, 'r', encoding='utf-8'))`
|
||||
|
||||
|
||||
|
||||
### ***var*** `config = json.load(open(file, 'r', encoding='utf-8'))`
|
||||
|
||||
|
||||
|
||||
### ***var*** `config = toml.load(open(file, 'r', encoding='utf-8'))`
|
||||
|
||||
|
||||
|
||||
### ***var*** `config = {}`
|
||||
|
||||
|
||||
|
||||
### ***var*** `config = {}`
|
||||
|
||||
|
||||
|
||||
### ***var*** `config = load_configs_from_dirs('config', no_waring=no_waring)`
|
||||
|
||||
|
||||
|
7
docs/dev/api/core/README.md
Normal file
7
docs/dev/api/core/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
title: liteyuki.core
|
||||
index: true
|
||||
icon: laptop-code
|
||||
category: API
|
||||
---
|
||||
|
111
docs/dev/api/core/manager.md
Normal file
111
docs/dev/api/core/manager.md
Normal file
@ -0,0 +1,111 @@
|
||||
---
|
||||
title: liteyuki.core.manager
|
||||
order: 1
|
||||
icon: laptop-code
|
||||
category: API
|
||||
---
|
||||
|
||||
### ***class*** `ChannelDeliver`
|
||||
|
||||
|
||||
|
||||
###   ***def*** `__init__(self, active: Channel[Any], passive: Channel[Any], channel_deliver_active: Channel[Channel[Any]], channel_deliver_passive: Channel[tuple[str, dict]]) -> None`
|
||||
|
||||
 
|
||||
|
||||
### ***class*** `ProcessManager`
|
||||
|
||||
进程管理器
|
||||
|
||||
###   ***def*** `__init__(self, lifespan: 'Lifespan') -> None`
|
||||
|
||||
 
|
||||
|
||||
###   ***def*** `start(self, name: str) -> None`
|
||||
|
||||
 开启后自动监控进程,并添加到进程字典中
|
||||
|
||||
Args:
|
||||
|
||||
name:
|
||||
|
||||
Returns:
|
||||
|
||||
###   ***def*** `start_all(self) -> None`
|
||||
|
||||
 启动所有进程
|
||||
|
||||
###   ***def*** `add_target(self, name: str, target: TARGET_FUNC, args: tuple, kwargs: Any) -> None`
|
||||
|
||||
 添加进程
|
||||
|
||||
Args:
|
||||
|
||||
name: 进程名,用于获取和唯一标识
|
||||
|
||||
target: 进程函数
|
||||
|
||||
args: 进程函数参数
|
||||
|
||||
kwargs: 进程函数关键字参数,通常会默认传入chan_active和chan_passive
|
||||
|
||||
###   ***def*** `join_all(self) -> None`
|
||||
|
||||
 
|
||||
|
||||
###   ***def*** `terminate(self, name: str) -> None`
|
||||
|
||||
 终止进程并从进程字典中删除
|
||||
|
||||
Args:
|
||||
|
||||
name:
|
||||
|
||||
|
||||
|
||||
Returns:
|
||||
|
||||
###   ***def*** `terminate_all(self) -> None`
|
||||
|
||||
 
|
||||
|
||||
###   ***def*** `is_process_alive(self, name: str) -> bool`
|
||||
|
||||
 检查进程是否存活
|
||||
|
||||
Args:
|
||||
|
||||
name:
|
||||
|
||||
|
||||
|
||||
Returns:
|
||||
|
||||
### ***var*** `TIMEOUT = 10`
|
||||
|
||||
|
||||
|
||||
### ***var*** `chan_active = get_channel(f'{name}-active')`
|
||||
|
||||
|
||||
|
||||
### ***var*** `channel_deliver = ChannelDeliver(active=chan_active, passive=chan_passive, channel_deliver_active=channel_deliver_active_channel, channel_deliver_passive=channel_deliver_passive_channel)`
|
||||
|
||||
|
||||
|
||||
### ***var*** `process = self.processes[name]`
|
||||
|
||||
|
||||
|
||||
### ***var*** `process = Process(target=self.targets[name][0], args=self.targets[name][1], kwargs=self.targets[name][2], daemon=True)`
|
||||
|
||||
|
||||
|
||||
### ***var*** `data = chan_active.receive()`
|
||||
|
||||
|
||||
|
||||
### ***var*** `kwargs = {}`
|
||||
|
||||
|
||||
|
7
docs/dev/api/dev/README.md
Normal file
7
docs/dev/api/dev/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
title: liteyuki.dev
|
||||
index: true
|
||||
icon: laptop-code
|
||||
category: API
|
||||
---
|
||||
|
91
docs/dev/api/dev/observer.md
Normal file
91
docs/dev/api/dev/observer.md
Normal file
@ -0,0 +1,91 @@
|
||||
---
|
||||
title: liteyuki.dev.observer
|
||||
order: 1
|
||||
icon: laptop-code
|
||||
category: API
|
||||
---
|
||||
|
||||
### ***def*** `debounce(wait: Any) -> None`
|
||||
|
||||
防抖函数
|
||||
|
||||
### ***def*** `on_file_system_event(directories: tuple[str], recursive: bool, event_filter: FILTER_FUNC) -> Callable[[CALLBACK_FUNC], CALLBACK_FUNC]`
|
||||
|
||||
注册文件系统变化监听器
|
||||
|
||||
Args:
|
||||
|
||||
directories: 监听目录们
|
||||
|
||||
recursive: 是否递归监听子目录
|
||||
|
||||
event_filter: 事件过滤器, 返回True则执行回调函数
|
||||
|
||||
Returns:
|
||||
|
||||
装饰器,装饰一个函数在接收到数据后执行
|
||||
|
||||
### ***def*** `decorator(func: Any) -> None`
|
||||
|
||||
|
||||
|
||||
### ***def*** `decorator(func: CALLBACK_FUNC) -> CALLBACK_FUNC`
|
||||
|
||||
|
||||
|
||||
### ***def*** `wrapper() -> None`
|
||||
|
||||
|
||||
|
||||
### ***def*** `wrapper(event: FileSystemEvent) -> None`
|
||||
|
||||
|
||||
|
||||
### ***class*** `CodeModifiedHandler(FileSystemEventHandler)`
|
||||
|
||||
Handler for code file changes
|
||||
|
||||
###   ***def*** `on_modified(self, event: Any) -> None`
|
||||
|
||||
 
|
||||
|
||||
###   ***def*** `on_created(self, event: Any) -> None`
|
||||
|
||||
 
|
||||
|
||||
###   ***def*** `on_deleted(self, event: Any) -> None`
|
||||
|
||||
 
|
||||
|
||||
###   ***def*** `on_moved(self, event: Any) -> None`
|
||||
|
||||
 
|
||||
|
||||
###   ***def*** `on_any_event(self, event: Any) -> None`
|
||||
|
||||
 
|
||||
|
||||
### ***var*** `liteyuki_bot = get_bot()`
|
||||
|
||||
|
||||
|
||||
### ***var*** `observer = Observer()`
|
||||
|
||||
|
||||
|
||||
### ***var*** `last_call_time = None`
|
||||
|
||||
|
||||
|
||||
### ***var*** `code_modified_handler = CodeModifiedHandler()`
|
||||
|
||||
|
||||
|
||||
### ***var*** `current_time = time.time()`
|
||||
|
||||
|
||||
|
||||
### ***var*** `last_call_time = current_time`
|
||||
|
||||
|
||||
|
27
docs/dev/api/dev/plugin.md
Normal file
27
docs/dev/api/dev/plugin.md
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: liteyuki.dev.plugin
|
||||
order: 1
|
||||
icon: laptop-code
|
||||
category: API
|
||||
---
|
||||
|
||||
### ***def*** `run_plugins() -> None`
|
||||
|
||||
运行插件,无需手动初始化bot
|
||||
|
||||
Args:
|
||||
|
||||
module_path: 插件路径,参考`liteyuki.load_plugin`的函数签名
|
||||
|
||||
### ***var*** `cfg = load_config_in_default()`
|
||||
|
||||
|
||||
|
||||
### ***var*** `plugins = cfg.get('liteyuki.plugins', [])`
|
||||
|
||||
|
||||
|
||||
### ***var*** `bot = LiteyukiBot(**cfg)`
|
||||
|
||||
|
||||
|
11
docs/dev/api/exception.md
Normal file
11
docs/dev/api/exception.md
Normal file
@ -0,0 +1,11 @@
|
||||
---
|
||||
title: liteyuki.exception
|
||||
order: 1
|
||||
icon: laptop-code
|
||||
category: API
|
||||
---
|
||||
|
||||
### ***class*** `LiteyukiException(BaseException)`
|
||||
|
||||
Liteyuki的异常基类。
|
||||
|
21
docs/dev/api/log.md
Normal file
21
docs/dev/api/log.md
Normal file
@ -0,0 +1,21 @@
|
||||
---
|
||||
title: liteyuki.log
|
||||
order: 1
|
||||
icon: laptop-code
|
||||
category: API
|
||||
---
|
||||
|
||||
### ***def*** `get_format(level: str) -> str`
|
||||
|
||||
|
||||
|
||||
### ***def*** `init_log(config: dict) -> None`
|
||||
|
||||
在语言加载完成后执行
|
||||
|
||||
Returns:
|
||||
|
||||
### ***var*** `show_icon = config.get('log_icon', True)`
|
||||
|
||||
|
||||
|
271
docs/dev/api/mkdoc.md
Normal file
271
docs/dev/api/mkdoc.md
Normal file
@ -0,0 +1,271 @@
|
||||
---
|
||||
title: liteyuki.mkdoc
|
||||
order: 1
|
||||
icon: laptop-code
|
||||
category: API
|
||||
---
|
||||
|
||||
### ***def*** `get_relative_path(base_path: str, target_path: str) -> str`
|
||||
|
||||
获取相对路径
|
||||
|
||||
Args:
|
||||
|
||||
base_path: 基础路径
|
||||
|
||||
target_path: 目标路径
|
||||
|
||||
### ***def*** `write_to_files(file_data: dict[str, str]) -> None`
|
||||
|
||||
输出文件
|
||||
|
||||
Args:
|
||||
|
||||
file_data: 文件数据 相对路径
|
||||
|
||||
### ***def*** `get_file_list(module_folder: str) -> None`
|
||||
|
||||
|
||||
|
||||
### ***def*** `get_module_info_normal(file_path: str, ignore_private: bool) -> ModuleInfo`
|
||||
|
||||
获取函数和类
|
||||
|
||||
Args:
|
||||
|
||||
file_path: Python 文件路径
|
||||
|
||||
ignore_private: 忽略私有函数和类
|
||||
|
||||
Returns:
|
||||
|
||||
模块信息
|
||||
|
||||
### ***def*** `generate_markdown(module_info: ModuleInfo, front_matter: Any) -> str`
|
||||
|
||||
生成模块的Markdown
|
||||
|
||||
你可在此自定义生成的Markdown格式
|
||||
|
||||
Args:
|
||||
|
||||
module_info: 模块信息
|
||||
|
||||
front_matter: 自定义选项title, index, icon, category
|
||||
|
||||
Returns:
|
||||
|
||||
Markdown 字符串
|
||||
|
||||
### ***def*** `generate_docs(module_folder: str, output_dir: str, with_top: bool, ignored_paths: Any) -> None`
|
||||
|
||||
生成文档
|
||||
|
||||
Args:
|
||||
|
||||
module_folder: 模块文件夹
|
||||
|
||||
output_dir: 输出文件夹
|
||||
|
||||
with_top: 是否包含顶层文件夹 False时例如docs/api/module_a, docs/api/module_b, True时例如docs/api/module/module_a.md, docs/api/module/module_b.md
|
||||
|
||||
ignored_paths: 忽略的路径
|
||||
|
||||
### ***class*** `DefType(Enum)`
|
||||
|
||||
|
||||
|
||||
###   ***attr*** `FUNCTION: 'function'`
|
||||
|
||||
###   ***attr*** `METHOD: 'method'`
|
||||
|
||||
###   ***attr*** `STATIC_METHOD: 'staticmethod'`
|
||||
|
||||
###   ***attr*** `CLASS_METHOD: 'classmethod'`
|
||||
|
||||
###   ***attr*** `PROPERTY: 'property'`
|
||||
|
||||
### ***class*** `FunctionInfo(BaseModel)`
|
||||
|
||||
|
||||
|
||||
### ***class*** `AttributeInfo(BaseModel)`
|
||||
|
||||
|
||||
|
||||
### ***class*** `ClassInfo(BaseModel)`
|
||||
|
||||
|
||||
|
||||
### ***class*** `ModuleInfo(BaseModel)`
|
||||
|
||||
|
||||
|
||||
### ***var*** `NO_TYPE_ANY = 'Any'`
|
||||
|
||||
|
||||
|
||||
### ***var*** `NO_TYPE_HINT = 'NoTypeHint'`
|
||||
|
||||
|
||||
|
||||
### ***var*** `FUNCTION = 'function'`
|
||||
|
||||
|
||||
|
||||
### ***var*** `METHOD = 'method'`
|
||||
|
||||
|
||||
|
||||
### ***var*** `STATIC_METHOD = 'staticmethod'`
|
||||
|
||||
|
||||
|
||||
### ***var*** `CLASS_METHOD = 'classmethod'`
|
||||
|
||||
|
||||
|
||||
### ***var*** `PROPERTY = 'property'`
|
||||
|
||||
|
||||
|
||||
### ***var*** `file_list = []`
|
||||
|
||||
|
||||
|
||||
### ***var*** `dot_sep_module_path = file_path.replace(os.sep, '.').replace('.py', '').replace('.pyi', '')`
|
||||
|
||||
|
||||
|
||||
### ***var*** `module_docstring = ast.get_docstring(tree)`
|
||||
|
||||
|
||||
|
||||
### ***var*** `module_info = ModuleInfo(module_path=dot_sep_module_path, functions=[], classes=[], attributes=[], docstring=module_docstring if module_docstring else '')`
|
||||
|
||||
|
||||
|
||||
### ***var*** `content = ''`
|
||||
|
||||
|
||||
|
||||
### ***var*** `front_matter = '---\n' + '\n'.join([f'{k}: {v}' for k, v in front_matter.items()]) + '\n---\n\n'`
|
||||
|
||||
|
||||
|
||||
### ***var*** `file_list = get_file_list(module_folder)`
|
||||
|
||||
|
||||
|
||||
### ***var*** `replace_data = {'__init__': 'README', '.py': '.md'}`
|
||||
|
||||
|
||||
|
||||
### ***var*** `file_content = file.read()`
|
||||
|
||||
|
||||
|
||||
### ***var*** `tree = ast.parse(file_content)`
|
||||
|
||||
|
||||
|
||||
### ***var*** `args_with_type = [f'{arg[0]}: {arg[1]}' if arg[1] else arg[0] for arg in func.args]`
|
||||
|
||||
|
||||
|
||||
### ***var*** `ignored_paths = []`
|
||||
|
||||
|
||||
|
||||
### ***var*** `no_module_name_pyfile_path = get_relative_path(module_folder, pyfile_path)`
|
||||
|
||||
|
||||
|
||||
### ***var*** `rel_md_path = pyfile_path if with_top else no_module_name_pyfile_path`
|
||||
|
||||
|
||||
|
||||
### ***var*** `abs_md_path = os.path.join(output_dir, rel_md_path)`
|
||||
|
||||
|
||||
|
||||
### ***var*** `module_info = get_module_info_normal(pyfile_path)`
|
||||
|
||||
|
||||
|
||||
### ***var*** `md_content = generate_markdown(module_info, front_matter)`
|
||||
|
||||
|
||||
|
||||
### ***var*** `inherit = f"({', '.join(cls.inherit)})" if cls.inherit else ''`
|
||||
|
||||
|
||||
|
||||
### ***var*** `rel_md_path = rel_md_path.replace(rk, rv)`
|
||||
|
||||
|
||||
|
||||
### ***var*** `front_matter = {'title': module_info.module_path.replace('.__init__', '').replace('_', '\\n'), 'index': 'true', 'icon': 'laptop-code', 'category': 'API'}`
|
||||
|
||||
|
||||
|
||||
### ***var*** `front_matter = {'title': module_info.module_path.replace('_', '\\n'), 'order': '1', 'icon': 'laptop-code', 'category': 'API'}`
|
||||
|
||||
|
||||
|
||||
### ***var*** `function_docstring = ast.get_docstring(node)`
|
||||
|
||||
|
||||
|
||||
### ***var*** `func_info = FunctionInfo(name=node.name, args=[(arg.arg, ast.unparse(arg.annotation) if arg.annotation else NO_TYPE_ANY) for arg in node.args.args], return_type=ast.unparse(node.returns) if node.returns else 'None', docstring=function_docstring if function_docstring else '', type=DefType.FUNCTION, is_async=isinstance(node, ast.AsyncFunctionDef))`
|
||||
|
||||
|
||||
|
||||
### ***var*** `class_docstring = ast.get_docstring(node)`
|
||||
|
||||
|
||||
|
||||
### ***var*** `class_info = ClassInfo(name=node.name, docstring=class_docstring if class_docstring else '', methods=[], attributes=[], inherit=[ast.unparse(base) for base in node.bases])`
|
||||
|
||||
|
||||
|
||||
### ***var*** `args_with_type = [f'{arg[0]}: {arg[1]}' if arg[1] else arg[0] for arg in method.args]`
|
||||
|
||||
|
||||
|
||||
### ***var*** `args_with_type = [f'{arg[0]}: {arg[1]}' if arg[1] and arg[0] != 'self' else arg[0] for arg in method.args]`
|
||||
|
||||
|
||||
|
||||
### ***var*** `first_arg = node.args.args[0]`
|
||||
|
||||
|
||||
|
||||
### ***var*** `method_docstring = ast.get_docstring(class_node)`
|
||||
|
||||
|
||||
|
||||
### ***var*** `def_type = DefType.METHOD`
|
||||
|
||||
|
||||
|
||||
### ***var*** `def_type = DefType.STATIC_METHOD`
|
||||
|
||||
|
||||
|
||||
### ***var*** `attr_type = NO_TYPE_HINT`
|
||||
|
||||
|
||||
|
||||
### ***var*** `def_type = DefType.CLASS_METHOD`
|
||||
|
||||
|
||||
|
||||
### ***var*** `attr_type = ast.unparse(node.value.annotation)`
|
||||
|
||||
|
||||
|
||||
### ***var*** `def_type = DefType.PROPERTY`
|
||||
|
||||
|
||||
|
15
docs/dev/api/plugin/README.md
Normal file
15
docs/dev/api/plugin/README.md
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
title: liteyuki.plugin
|
||||
index: true
|
||||
icon: laptop-code
|
||||
category: API
|
||||
---
|
||||
|
||||
### ***def*** `get_loaded_plugins() -> dict[str, Plugin]`
|
||||
|
||||
获取已加载的插件
|
||||
|
||||
Returns:
|
||||
|
||||
dict[str, Plugin]: 插件字典
|
||||
|
103
docs/dev/api/plugin/load.md
Normal file
103
docs/dev/api/plugin/load.md
Normal file
@ -0,0 +1,103 @@
|
||||
---
|
||||
title: liteyuki.plugin.load
|
||||
order: 1
|
||||
icon: laptop-code
|
||||
category: API
|
||||
---
|
||||
|
||||
### ***def*** `load_plugin(module_path: str | Path) -> Optional[Plugin]`
|
||||
|
||||
加载单个插件,可以是本地插件或是通过 `pip` 安装的插件。
|
||||
|
||||
|
||||
|
||||
参数:
|
||||
|
||||
module_path: 插件名称 `path.to.your.plugin`
|
||||
|
||||
或插件路径 `pathlib.Path(path/to/your/plugin)`
|
||||
|
||||
### ***def*** `load_plugins() -> set[Plugin]`
|
||||
|
||||
导入文件夹下多个插件
|
||||
|
||||
|
||||
|
||||
参数:
|
||||
|
||||
plugin_dir: 文件夹路径
|
||||
|
||||
ignore_warning: 是否忽略警告,通常是目录不存在或目录为空
|
||||
|
||||
### ***def*** `format_display_name(display_name: str, plugin_type: PluginType) -> str`
|
||||
|
||||
设置插件名称颜色,根据不同类型插件设置颜色
|
||||
|
||||
Args:
|
||||
|
||||
display_name: 插件名称
|
||||
|
||||
plugin_type: 插件类型
|
||||
|
||||
|
||||
|
||||
Returns:
|
||||
|
||||
str: 设置后的插件名称 <y>name</y>
|
||||
|
||||
### ***var*** `module_path = path_to_module_name(Path(module_path)) if isinstance(module_path, Path) else module_path`
|
||||
|
||||
|
||||
|
||||
### ***var*** `plugins = set()`
|
||||
|
||||
|
||||
|
||||
### ***var*** `color = 'y'`
|
||||
|
||||
|
||||
|
||||
### ***var*** `module = import_module(module_path)`
|
||||
|
||||
|
||||
|
||||
### ***var*** `display_name = module.__name__.split('.')[-1]`
|
||||
|
||||
|
||||
|
||||
### ***var*** `display_name = format_display_name(f"{metadata.name}({module.__name__.split('.')[-1]})", metadata.type)`
|
||||
|
||||
|
||||
|
||||
### ***var*** `path = Path(os.path.join(dir_path, f))`
|
||||
|
||||
|
||||
|
||||
### ***var*** `module_name = None`
|
||||
|
||||
|
||||
|
||||
### ***var*** `color = 'm'`
|
||||
|
||||
|
||||
|
||||
### ***var*** `color = 'g'`
|
||||
|
||||
|
||||
|
||||
### ***var*** `color = 'e'`
|
||||
|
||||
|
||||
|
||||
### ***var*** `color = 'c'`
|
||||
|
||||
|
||||
|
||||
### ***var*** `module_name = f'{path_to_module_name(Path(dir_path))}.{f[:-3]}'`
|
||||
|
||||
|
||||
|
||||
### ***var*** `module_name = path_to_module_name(path)`
|
||||
|
||||
|
||||
|
7
docs/dev/api/plugin/manager.md
Normal file
7
docs/dev/api/plugin/manager.md
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
title: liteyuki.plugin.manager
|
||||
order: 1
|
||||
icon: laptop-code
|
||||
category: API
|
||||
---
|
||||
|
89
docs/dev/api/plugin/model.md
Normal file
89
docs/dev/api/plugin/model.md
Normal file
@ -0,0 +1,89 @@
|
||||
---
|
||||
title: liteyuki.plugin.model
|
||||
order: 1
|
||||
icon: laptop-code
|
||||
category: API
|
||||
---
|
||||
|
||||
### ***class*** `PluginType(Enum)`
|
||||
|
||||
插件类型枚举值
|
||||
|
||||
###   ***attr*** `APPLICATION: 'application'`
|
||||
|
||||
###   ***attr*** `SERVICE: 'service'`
|
||||
|
||||
###   ***attr*** `IMPLEMENTATION: 'implementation'`
|
||||
|
||||
###   ***attr*** `MODULE: 'module'`
|
||||
|
||||
###   ***attr*** `UNCLASSIFIED: 'unclassified'`
|
||||
|
||||
### ***class*** `PluginMetadata(BaseModel)`
|
||||
|
||||
轻雪插件元数据,由插件编写者提供,name为必填项
|
||||
|
||||
Attributes:
|
||||
|
||||
----------
|
||||
|
||||
|
||||
|
||||
name: str
|
||||
|
||||
插件名称
|
||||
|
||||
description: str
|
||||
|
||||
插件描述
|
||||
|
||||
usage: str
|
||||
|
||||
插件使用方法
|
||||
|
||||
type: str
|
||||
|
||||
插件类型
|
||||
|
||||
author: str
|
||||
|
||||
插件作者
|
||||
|
||||
homepage: str
|
||||
|
||||
插件主页
|
||||
|
||||
extra: dict[str, Any]
|
||||
|
||||
额外信息
|
||||
|
||||
### ***class*** `Plugin(BaseModel)`
|
||||
|
||||
存储插件信息
|
||||
|
||||
###   ***attr*** `model_config: {'arbitrary_types_allowed': True}`
|
||||
|
||||
### ***var*** `APPLICATION = 'application'`
|
||||
|
||||
|
||||
|
||||
### ***var*** `SERVICE = 'service'`
|
||||
|
||||
|
||||
|
||||
### ***var*** `IMPLEMENTATION = 'implementation'`
|
||||
|
||||
|
||||
|
||||
### ***var*** `MODULE = 'module'`
|
||||
|
||||
|
||||
|
||||
### ***var*** `UNCLASSIFIED = 'unclassified'`
|
||||
|
||||
|
||||
|
||||
### ***var*** `model_config = {'arbitrary_types_allowed': True}`
|
||||
|
||||
|
||||
|
79
docs/dev/api/utils.md
Normal file
79
docs/dev/api/utils.md
Normal file
@ -0,0 +1,79 @@
|
||||
---
|
||||
title: liteyuki.utils
|
||||
order: 1
|
||||
icon: laptop-code
|
||||
category: API
|
||||
---
|
||||
|
||||
### ***def*** `is_coroutine_callable(call: Callable[..., Any]) -> bool`
|
||||
|
||||
判断是否为协程可调用对象
|
||||
|
||||
Args:
|
||||
|
||||
call: 可调用对象
|
||||
|
||||
Returns:
|
||||
|
||||
bool: 是否为协程可调用对象
|
||||
|
||||
### ***def*** `run_coroutine() -> None`
|
||||
|
||||
运行协程
|
||||
|
||||
Args:
|
||||
|
||||
coro:
|
||||
|
||||
|
||||
|
||||
Returns:
|
||||
|
||||
### ***def*** `path_to_module_name(path: Path) -> str`
|
||||
|
||||
转换路径为模块名
|
||||
|
||||
Args:
|
||||
|
||||
path: 路径a/b/c/d -> a.b.c.d
|
||||
|
||||
Returns:
|
||||
|
||||
str: 模块名
|
||||
|
||||
### ***def*** `async_wrapper(func: Callable[..., Any]) -> Callable[..., Coroutine]`
|
||||
|
||||
异步包装器
|
||||
|
||||
Args:
|
||||
|
||||
func: Sync Callable
|
||||
|
||||
Returns:
|
||||
|
||||
Coroutine: Asynchronous Callable
|
||||
|
||||
### ***async def*** `wrapper() -> None`
|
||||
|
||||
|
||||
|
||||
### ***var*** `IS_MAIN_PROCESS = multiprocessing.current_process().name == 'MainProcess'`
|
||||
|
||||
|
||||
|
||||
### ***var*** `func_ = getattr(call, '__call__', None)`
|
||||
|
||||
|
||||
|
||||
### ***var*** `rel_path = path.resolve().relative_to(Path.cwd().resolve())`
|
||||
|
||||
|
||||
|
||||
### ***var*** `loop = asyncio.get_event_loop()`
|
||||
|
||||
|
||||
|
||||
### ***var*** `loop = asyncio.new_event_loop()`
|
||||
|
||||
|
||||
|
@ -20,7 +20,6 @@ from liteyuki.log import (
|
||||
logger
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"LiteyukiBot",
|
||||
"get_bot",
|
||||
@ -34,6 +33,8 @@ __all__ = [
|
||||
"logger",
|
||||
]
|
||||
|
||||
__version__ = "6.3.7" # 测试版本号
|
||||
|
||||
__version__ = "6.3.8" # 测试版本号
|
||||
|
||||
# 6.3.8
|
||||
# 1. 初步添加对聊天的支持
|
||||
# 2. 优化了通道的性能
|
||||
|
@ -10,6 +10,7 @@ from typing import Any, Optional
|
||||
|
||||
from liteyuki.bot.lifespan import (LIFESPAN_FUNC, Lifespan)
|
||||
from liteyuki.comm.channel import get_channel
|
||||
from liteyuki.comm.storage import shared_memory
|
||||
from liteyuki.core.manager import ProcessManager
|
||||
from liteyuki.log import init_log, logger
|
||||
from liteyuki.plugin import load_plugin
|
||||
|
@ -42,7 +42,7 @@ class Lifespan:
|
||||
self._after_nonebot_init_funcs: list[LIFESPAN_FUNC] = []
|
||||
|
||||
@staticmethod
|
||||
def _run_funcs(funcs: list[LIFESPAN_FUNC | PROCESS_LIFESPAN_FUNC], *args, **kwargs) -> None:
|
||||
def run_funcs(funcs: list[LIFESPAN_FUNC | PROCESS_LIFESPAN_FUNC], *args, **kwargs) -> None:
|
||||
"""
|
||||
运行函数
|
||||
Args:
|
||||
@ -149,7 +149,7 @@ class Lifespan:
|
||||
Returns:
|
||||
"""
|
||||
logger.debug("Running before_start functions")
|
||||
self._run_funcs(self._before_start_funcs)
|
||||
self.run_funcs(self._before_start_funcs)
|
||||
|
||||
def after_start(self) -> None:
|
||||
"""
|
||||
@ -157,7 +157,7 @@ class Lifespan:
|
||||
Returns:
|
||||
"""
|
||||
logger.debug("Running after_start functions")
|
||||
self._run_funcs(self._after_start_funcs)
|
||||
self.run_funcs(self._after_start_funcs)
|
||||
|
||||
def before_process_shutdown(self) -> None:
|
||||
"""
|
||||
@ -165,7 +165,7 @@ class Lifespan:
|
||||
Returns:
|
||||
"""
|
||||
logger.debug("Running before_shutdown functions")
|
||||
self._run_funcs(self._before_process_shutdown_funcs)
|
||||
self.run_funcs(self._before_process_shutdown_funcs)
|
||||
|
||||
def after_shutdown(self) -> None:
|
||||
"""
|
||||
@ -173,7 +173,7 @@ class Lifespan:
|
||||
Returns:
|
||||
"""
|
||||
logger.debug("Running after_shutdown functions")
|
||||
self._run_funcs(self._after_shutdown_funcs)
|
||||
self.run_funcs(self._after_shutdown_funcs)
|
||||
|
||||
def before_process_restart(self) -> None:
|
||||
"""
|
||||
@ -181,7 +181,7 @@ class Lifespan:
|
||||
Returns:
|
||||
"""
|
||||
logger.debug("Running before_restart functions")
|
||||
self._run_funcs(self._before_process_restart_funcs)
|
||||
self.run_funcs(self._before_process_restart_funcs)
|
||||
|
||||
def after_restart(self) -> None:
|
||||
"""
|
||||
@ -190,4 +190,4 @@ class Lifespan:
|
||||
|
||||
"""
|
||||
logger.debug("Running after_restart functions")
|
||||
self._run_funcs(self._after_restart_funcs)
|
||||
self.run_funcs(self._after_restart_funcs)
|
||||
|
@ -3,8 +3,8 @@
|
||||
该模块用于轻雪主进程和Nonebot子进程之间的通信
|
||||
依赖关系
|
||||
event -> _
|
||||
storage -> channel
|
||||
rpc -> channel, storage
|
||||
storage -> channel_
|
||||
rpc -> channel_, storage
|
||||
"""
|
||||
from liteyuki.comm.channel import (
|
||||
Channel,
|
||||
|
@ -5,7 +5,7 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
@Time : 2024/7/26 下午11:21
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : channel.py
|
||||
@File : channel_.py
|
||||
@Software: PyCharm
|
||||
|
||||
本模块定义了一个通用的通道类,用于进程间通信
|
||||
@ -38,11 +38,12 @@ class Channel(Generic[T]):
|
||||
有两种接收工作方式,但是只能选择一种,主动接收和被动接收,主动接收使用 `receive` 方法,被动接收使用 `on_receive` 装饰器
|
||||
"""
|
||||
|
||||
def __init__(self, _id: str, type_check: bool = False):
|
||||
def __init__(self, _id: str, type_check: Optional[bool] = None):
|
||||
"""
|
||||
初始化通道
|
||||
Args:
|
||||
_id: 通道ID
|
||||
type_check: 是否开启类型检查, 若为空,则传入泛型默认开启,否则默认关闭
|
||||
"""
|
||||
self.conn_send, self.conn_recv = Pipe()
|
||||
self._closed = False
|
||||
@ -53,7 +54,11 @@ class Channel(Generic[T]):
|
||||
self.is_main_receive_loop_running = False
|
||||
self.is_sub_receive_loop_running = False
|
||||
|
||||
if type_check:
|
||||
if type_check is None:
|
||||
# 若传入泛型则默认开启类型检查
|
||||
type_check = self._get_generic_type() is not None
|
||||
|
||||
elif type_check:
|
||||
if self._get_generic_type() is None:
|
||||
raise TypeError("Type hint is required for enforcing type check.")
|
||||
self.type_check = type_check
|
||||
@ -110,7 +115,7 @@ class Channel(Generic[T]):
|
||||
raise TypeError(f"Data must be an instance of {_type}, {type(data)} found")
|
||||
|
||||
if self._closed:
|
||||
raise RuntimeError("Cannot send to a closed channel")
|
||||
raise RuntimeError("Cannot send to a closed channel_")
|
||||
self.conn_send.send(data)
|
||||
|
||||
def receive(self) -> T:
|
||||
@ -119,7 +124,7 @@ class Channel(Generic[T]):
|
||||
Args:
|
||||
"""
|
||||
if self._closed:
|
||||
raise RuntimeError("Cannot receive from a closed channel")
|
||||
raise RuntimeError("Cannot receive from a closed channel_")
|
||||
|
||||
while True:
|
||||
data = self.conn_recv.recv()
|
||||
@ -226,10 +231,12 @@ class Channel(Generic[T]):
|
||||
"""子进程可用的主动和被动通道"""
|
||||
active_channel: Optional["Channel"] = None
|
||||
passive_channel: Optional["Channel"] = None
|
||||
publish_channel: Channel[tuple[str, dict[str, Any]]] = Channel(_id="publish_channel")
|
||||
|
||||
"""通道传递通道,主进程创建单例,子进程初始化时实例化"""
|
||||
channel_deliver_active_channel: Channel[Channel[Any]]
|
||||
channel_deliver_passive_channel: Channel[tuple[str, dict[str, Any]]]
|
||||
|
||||
if IS_MAIN_PROCESS:
|
||||
channel_deliver_active_channel = Channel(_id="channel_deliver_active_channel")
|
||||
channel_deliver_passive_channel = Channel(_id="channel_deliver_passive_channel")
|
||||
@ -237,7 +244,7 @@ if IS_MAIN_PROCESS:
|
||||
|
||||
@channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == "set_channel")
|
||||
def on_set_channel(data: tuple[str, dict[str, Any]]):
|
||||
name, channel = data[1]["name"], data[1]["channel"]
|
||||
name, channel = data[1]["name"], data[1]["channel_"]
|
||||
set_channel(name, channel)
|
||||
|
||||
|
||||
@ -261,7 +268,7 @@ def set_channel(name: str, channel: 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:
|
||||
_channel[name] = channel
|
||||
@ -271,7 +278,7 @@ def set_channel(name: str, channel: Channel):
|
||||
(
|
||||
"set_channel", {
|
||||
"name" : name,
|
||||
"channel": channel,
|
||||
"channel_": channel,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
@ -4,14 +4,20 @@
|
||||
"""
|
||||
|
||||
import threading
|
||||
from typing import Any, Optional
|
||||
from typing import Any, Coroutine, Optional, TypeAlias, Callable
|
||||
|
||||
from liteyuki.comm.channel import Channel
|
||||
from liteyuki.utils import IS_MAIN_PROCESS
|
||||
from liteyuki.comm import channel
|
||||
from liteyuki.comm.channel import Channel, ON_RECEIVE_FUNC, ASYNC_ON_RECEIVE_FUNC
|
||||
from liteyuki.utils import IS_MAIN_PROCESS, is_coroutine_callable, run_coroutine
|
||||
|
||||
if IS_MAIN_PROCESS:
|
||||
_locks = {}
|
||||
|
||||
_on_main_subscriber_receive_funcs: dict[str, list[ASYNC_ON_RECEIVE_FUNC]] = {} # type: ignore
|
||||
"""主进程订阅者接收函数"""
|
||||
_on_sub_subscriber_receive_funcs: dict[str, list[ASYNC_ON_RECEIVE_FUNC]] = {} # type: ignore
|
||||
"""子进程订阅者接收函数"""
|
||||
|
||||
|
||||
def _get_lock(key) -> threading.Lock:
|
||||
"""
|
||||
@ -25,12 +31,28 @@ def _get_lock(key) -> threading.Lock:
|
||||
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:
|
||||
def __init__(self):
|
||||
self._store = {}
|
||||
self.active_chan = Channel[tuple[str, Optional[dict[str, Any]]]](_id="shared_memory-active")
|
||||
self.passive_chan = Channel[tuple[str, Optional[dict[str, Any]]]](_id="shared_memory-passive")
|
||||
|
||||
self.publish_channel = Channel[tuple[str, Any]](_id="shared_memory-publish")
|
||||
|
||||
self.is_main_receive_loop_running = False
|
||||
self.is_sub_receive_loop_running = False
|
||||
|
||||
def set(self, key: str, value: Any) -> None:
|
||||
"""
|
||||
设置键值对
|
||||
@ -134,6 +156,94 @@ class KeyValueStore:
|
||||
)
|
||||
return recv_chan.receive()
|
||||
|
||||
def publish(self, channel_: str, data: Any) -> None:
|
||||
"""
|
||||
发布消息
|
||||
Args:
|
||||
channel_: 频道
|
||||
data: 数据
|
||||
|
||||
Returns:
|
||||
"""
|
||||
self.active_chan.send(
|
||||
(
|
||||
"publish",
|
||||
{
|
||||
"channel": channel_,
|
||||
"data" : data
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
def on_subscriber_receive(self, channel_: str) -> Callable[[ON_RECEIVE_FUNC], ON_RECEIVE_FUNC]:
|
||||
"""
|
||||
订阅者接收消息时的回调
|
||||
Args:
|
||||
channel_: 频道
|
||||
|
||||
Returns:
|
||||
装饰器
|
||||
"""
|
||||
if IS_MAIN_PROCESS and not self.is_main_receive_loop_running:
|
||||
threading.Thread(target=self._start_receive_loop, daemon=True).start()
|
||||
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:
|
||||
async def wrapper(data: Any):
|
||||
if is_coroutine_callable(func):
|
||||
await func(data)
|
||||
else:
|
||||
func(data)
|
||||
|
||||
if IS_MAIN_PROCESS:
|
||||
if channel_ not in _on_main_subscriber_receive_funcs:
|
||||
_on_main_subscriber_receive_funcs[channel_] = []
|
||||
_on_main_subscriber_receive_funcs[channel_].append(wrapper)
|
||||
else:
|
||||
if channel_ not in _on_sub_subscriber_receive_funcs:
|
||||
_on_sub_subscriber_receive_funcs[channel_] = []
|
||||
_on_sub_subscriber_receive_funcs[channel_].append(wrapper)
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
@staticmethod
|
||||
def run_subscriber_receive_funcs(channel_: str, data: Any):
|
||||
"""
|
||||
运行订阅者接收函数
|
||||
Args:
|
||||
channel_: 频道
|
||||
data: 数据
|
||||
"""
|
||||
if IS_MAIN_PROCESS:
|
||||
if channel_ in _on_main_subscriber_receive_funcs and _on_main_subscriber_receive_funcs[channel_]:
|
||||
run_coroutine(*[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(*[func(data) for func in _on_sub_subscriber_receive_funcs[channel_]])
|
||||
|
||||
def _start_receive_loop(self):
|
||||
"""
|
||||
启动发布订阅接收器循环,在主进程中运行,若有子进程订阅则推送给子进程
|
||||
"""
|
||||
if IS_MAIN_PROCESS:
|
||||
while True:
|
||||
data = self.active_chan.receive()
|
||||
if data[0] == "publish":
|
||||
# 运行主进程订阅函数
|
||||
self.run_subscriber_receive_funcs(data[1]["channel"], data[1]["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:
|
||||
_instance = None
|
||||
@ -141,20 +251,17 @@ class GlobalKeyValueStore:
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls):
|
||||
if IS_MAIN_PROCESS:
|
||||
if cls._instance is None:
|
||||
with cls._lock:
|
||||
if cls._instance is None:
|
||||
cls._instance = KeyValueStore()
|
||||
return cls._instance
|
||||
else:
|
||||
raise RuntimeError("Cannot get instance in sub process.")
|
||||
if cls._instance is None:
|
||||
with cls._lock:
|
||||
if cls._instance is None:
|
||||
cls._instance = KeyValueStore()
|
||||
return cls._instance
|
||||
|
||||
|
||||
shared_memory: KeyValueStore = GlobalKeyValueStore.get_instance()
|
||||
|
||||
# 全局单例访问点
|
||||
if IS_MAIN_PROCESS:
|
||||
shared_memory: KeyValueStore = GlobalKeyValueStore.get_instance()
|
||||
|
||||
|
||||
@shared_memory.passive_chan.on_receive(lambda d: d[0] == "get")
|
||||
def on_get(data: tuple[str, dict[str, Any]]):
|
||||
@ -182,9 +289,13 @@ if IS_MAIN_PROCESS:
|
||||
recv_chan = data[1]["recv_chan"]
|
||||
recv_chan.send(shared_memory.get_all())
|
||||
|
||||
|
||||
else:
|
||||
# 子进程在入口函数中对shared_memory进行初始化
|
||||
shared_memory: Optional[KeyValueStore] = None # type: ignore
|
||||
@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 引用计数, 防止获取空指针
|
||||
if not IS_MAIN_PROCESS:
|
||||
|
@ -13,7 +13,7 @@ import threading
|
||||
from multiprocessing import Process
|
||||
from typing import Any, Callable, TYPE_CHECKING, TypeAlias
|
||||
|
||||
from liteyuki.comm.channel import Channel, get_channel, set_channels
|
||||
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.utils import IS_MAIN_PROCESS
|
||||
@ -42,12 +42,14 @@ class ChannelDeliver:
|
||||
active: Channel[Any],
|
||||
passive: 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]],
|
||||
):
|
||||
self.active = active
|
||||
self.passive = passive
|
||||
self.channel_deliver_active = channel_deliver_active
|
||||
self.channel_deliver_passive = channel_deliver_passive
|
||||
self.publish = publish
|
||||
|
||||
|
||||
# 函数处理一些跨进程通道的
|
||||
@ -64,6 +66,7 @@ def _delivery_channel_wrapper(func: TARGET_FUNC, cd: ChannelDeliver, sm: "KeyVal
|
||||
channel.passive_channel = cd.passive # 子进程被动通道
|
||||
channel.channel_deliver_active_channel = cd.channel_deliver_active # 子进程通道传递主动通道
|
||||
channel.channel_deliver_passive_channel = cd.channel_deliver_passive # 子进程通道传递被动通道
|
||||
channel.publish_channel = cd.publish # 子进程发布通道
|
||||
|
||||
# 给子进程创建共享内存实例
|
||||
from liteyuki.comm import storage
|
||||
@ -148,7 +151,8 @@ class ProcessManager:
|
||||
active=chan_active,
|
||||
passive=chan_passive,
|
||||
channel_deliver_active=channel_deliver_active_channel,
|
||||
channel_deliver_passive=channel_deliver_passive_channel
|
||||
channel_deliver_passive=channel_deliver_passive_channel,
|
||||
publish=publish_channel
|
||||
)
|
||||
|
||||
self.targets[name] = (_delivery_channel_wrapper, (target, channel_deliver, shared_memory, *args), kwargs)
|
||||
|
@ -10,7 +10,9 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
"""
|
||||
import sys
|
||||
|
||||
from loguru import logger
|
||||
import loguru
|
||||
|
||||
logger = loguru.logger
|
||||
|
||||
# DEBUG日志格式
|
||||
debug_format: str = (
|
||||
|
10
liteyuki/message/__init__.py
Normal file
10
liteyuki/message/__init__.py
Normal file
@ -0,0 +1,10 @@
|
||||
# -*- 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
|
||||
"""
|
56
liteyuki/message/event.py
Normal file
56
liteyuki/message/event.py
Normal file
@ -0,0 +1,56 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/8/19 下午10:47
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : event.py
|
||||
@Software: PyCharm
|
||||
"""
|
||||
from typing import Any
|
||||
|
||||
from liteyuki.comm.storage import shared_memory
|
||||
|
||||
|
||||
class Event:
|
||||
def __init__(self, type: str, data: dict[str, Any], bot_id: str, session_id: str, session_type: str, receive_channel: str = "event_to_nonebot"):
|
||||
"""
|
||||
事件
|
||||
Args:
|
||||
type: 类型
|
||||
data: 数据
|
||||
bot_id: 机器人ID
|
||||
session_id: 会话ID
|
||||
session_type: 会话类型
|
||||
receive_channel: 接收频道
|
||||
"""
|
||||
self.type = type
|
||||
self.data = data
|
||||
self.bot_id = bot_id
|
||||
self.session_id = session_id
|
||||
self.session_type = session_type
|
||||
self.receive_channel = receive_channel
|
||||
|
||||
def __str__(self):
|
||||
return f"Event(type={self.type}, data={self.data}, bot_id={self.bot_id}, session_id={self.session_id}, session_type={self.session_type})"
|
||||
|
||||
def reply(self, message: str | dict[str, Any]):
|
||||
"""
|
||||
回复消息
|
||||
Args:
|
||||
message:
|
||||
Returns:
|
||||
"""
|
||||
to_nonebot_event = Event(
|
||||
type=self.session_type,
|
||||
data={
|
||||
"message": message
|
||||
},
|
||||
bot_id=self.bot_id,
|
||||
session_id=self.session_id,
|
||||
session_type=self.session_type,
|
||||
receive_channel="_"
|
||||
)
|
||||
print(to_nonebot_event)
|
||||
shared_memory.publish(self.receive_channel, to_nonebot_event)
|
62
liteyuki/message/matcher.py
Normal file
62
liteyuki/message/matcher.py
Normal file
@ -0,0 +1,62 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/8/19 下午10:51
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : matcher.py
|
||||
@Software: PyCharm
|
||||
"""
|
||||
import traceback
|
||||
from typing import Any, TypeAlias, Callable, Coroutine
|
||||
|
||||
from liteyuki import Event
|
||||
from liteyuki.message.rule import Rule
|
||||
|
||||
EventHandler: TypeAlias = Callable[[Event], Coroutine[None, None, Any]]
|
||||
|
||||
|
||||
class Matcher:
|
||||
def __init__(self, rule: Rule, priority: int, block: bool):
|
||||
"""
|
||||
匹配器
|
||||
Args:
|
||||
rule: 规则
|
||||
priority: 优先级 >= 0
|
||||
block: 是否阻断后续优先级更低的匹配器
|
||||
"""
|
||||
self.rule = rule
|
||||
self.priority = priority
|
||||
self.block = block
|
||||
self.handlers: list[EventHandler] = []
|
||||
|
||||
def __str__(self):
|
||||
return f"Matcher(rule={self.rule}, priority={self.priority}, block={self.block})"
|
||||
|
||||
def handle(self, handler: EventHandler) -> EventHandler:
|
||||
"""
|
||||
添加处理函数,装饰器
|
||||
Args:
|
||||
handler:
|
||||
Returns:
|
||||
EventHandler
|
||||
"""
|
||||
self.handlers.append(handler)
|
||||
return handler
|
||||
|
||||
async def run(self, event: Event) -> None:
|
||||
"""
|
||||
运行处理函数
|
||||
Args:
|
||||
event:
|
||||
Returns:
|
||||
"""
|
||||
if not await self.rule(event):
|
||||
return
|
||||
|
||||
for handler in self.handlers:
|
||||
try:
|
||||
await handler(event)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
47
liteyuki/message/on.py
Normal file
47
liteyuki/message/on.py
Normal file
@ -0,0 +1,47 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/8/19 下午10:52
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : on.py
|
||||
@Software: PyCharm
|
||||
"""
|
||||
import threading
|
||||
|
||||
from queue import Queue
|
||||
|
||||
from liteyuki.comm.storage import shared_memory
|
||||
from liteyuki.log import logger
|
||||
from liteyuki.message.event import Event
|
||||
from liteyuki.message.matcher import Matcher
|
||||
from liteyuki.message.rule import Rule
|
||||
|
||||
_matcher_list: list[Matcher] = []
|
||||
_queue: Queue = Queue()
|
||||
|
||||
|
||||
@shared_memory.on_subscriber_receive("event_to_liteyuki")
|
||||
async def _(event: Event):
|
||||
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
|
||||
|
||||
|
||||
def on_message(rule: Rule = Rule(), priority: int = 0, block: bool = True) -> Matcher:
|
||||
matcher = Matcher(rule, priority, block)
|
||||
# 按照优先级插入
|
||||
for i, m in enumerate(_matcher_list):
|
||||
if m.priority < matcher.priority:
|
||||
_matcher_list.insert(i, matcher)
|
||||
break
|
||||
else:
|
||||
_matcher_list.append(matcher)
|
||||
return matcher
|
33
liteyuki/message/rule.py
Normal file
33
liteyuki/message/rule.py
Normal file
@ -0,0 +1,33 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/8/19 下午10:55
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : rule.py
|
||||
@Software: PyCharm
|
||||
"""
|
||||
|
||||
from typing import Optional, TypeAlias, Callable, Coroutine
|
||||
|
||||
from liteyuki.message.event import Event
|
||||
|
||||
RuleHandler: TypeAlias = Callable[[Event], Coroutine[None, None, bool]]
|
||||
"""规则函数签名"""
|
||||
|
||||
|
||||
class Rule:
|
||||
def __init__(self, handler: Optional[RuleHandler] = None):
|
||||
self.handler = handler
|
||||
|
||||
def __or__(self, other: "Rule") -> "Rule":
|
||||
return Rule(lambda event: self.handler(event) or other.handler(event))
|
||||
|
||||
def __and__(self, other: "Rule") -> "Rule":
|
||||
return Rule(lambda event: self.handler(event) and other.handler(event))
|
||||
|
||||
async def __call__(self, event: Event) -> bool:
|
||||
if self.handler is None:
|
||||
return True
|
||||
return await self.handler(event)
|
10
liteyuki/message/session.py
Normal file
10
liteyuki/message/session.py
Normal file
@ -0,0 +1,10 @@
|
||||
# -*- 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
|
||||
"""
|
343
liteyuki/mkdoc.py
Normal file
343
liteyuki/mkdoc.py
Normal file
@ -0,0 +1,343 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/8/19 上午6:23
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : mkdoc.py
|
||||
@Software: PyCharm
|
||||
"""
|
||||
|
||||
import ast
|
||||
import os
|
||||
import shutil
|
||||
from typing import Any
|
||||
from enum import Enum
|
||||
from pydantic import BaseModel
|
||||
|
||||
NO_TYPE_ANY = "Any"
|
||||
NO_TYPE_HINT = "NoTypeHint"
|
||||
|
||||
|
||||
class DefType(Enum):
|
||||
FUNCTION = "function"
|
||||
METHOD = "method"
|
||||
STATIC_METHOD = "staticmethod"
|
||||
CLASS_METHOD = "classmethod"
|
||||
PROPERTY = "property"
|
||||
|
||||
|
||||
class FunctionInfo(BaseModel):
|
||||
name: str
|
||||
args: list[tuple[str, str]]
|
||||
return_type: str
|
||||
docstring: str
|
||||
|
||||
type: DefType
|
||||
"""若为类中def,则有"""
|
||||
is_async: bool
|
||||
|
||||
|
||||
class AttributeInfo(BaseModel):
|
||||
name: str
|
||||
type: str
|
||||
value: Any = None
|
||||
docstring: str = ""
|
||||
|
||||
|
||||
class ClassInfo(BaseModel):
|
||||
name: str
|
||||
docstring: str
|
||||
methods: list[FunctionInfo]
|
||||
attributes: list[AttributeInfo]
|
||||
inherit: list[str]
|
||||
|
||||
|
||||
class ModuleInfo(BaseModel):
|
||||
module_path: str
|
||||
"""点分割模块路径 例如 liteyuki.bot"""
|
||||
|
||||
functions: list[FunctionInfo]
|
||||
classes: list[ClassInfo]
|
||||
attributes: list[AttributeInfo]
|
||||
docstring: str
|
||||
|
||||
|
||||
def get_relative_path(base_path: str, target_path: str) -> str:
|
||||
"""
|
||||
获取相对路径
|
||||
Args:
|
||||
base_path: 基础路径
|
||||
target_path: 目标路径
|
||||
"""
|
||||
return os.path.relpath(target_path, base_path)
|
||||
|
||||
|
||||
def write_to_files(file_data: dict[str, str]):
|
||||
"""
|
||||
输出文件
|
||||
Args:
|
||||
file_data: 文件数据 相对路径
|
||||
"""
|
||||
|
||||
for rp, data in file_data.items():
|
||||
|
||||
if not os.path.exists(os.path.dirname(rp)):
|
||||
os.makedirs(os.path.dirname(rp))
|
||||
with open(rp, 'w', encoding='utf-8') as f:
|
||||
f.write(data)
|
||||
|
||||
|
||||
def get_file_list(module_folder: str):
|
||||
file_list = []
|
||||
for root, dirs, files in os.walk(module_folder):
|
||||
for file in files:
|
||||
if file.endswith((".py", ".pyi")):
|
||||
file_list.append(os.path.join(root, file))
|
||||
return file_list
|
||||
|
||||
|
||||
def get_module_info_normal(file_path: str, ignore_private: bool = True) -> ModuleInfo:
|
||||
"""
|
||||
获取函数和类
|
||||
Args:
|
||||
file_path: Python 文件路径
|
||||
ignore_private: 忽略私有函数和类
|
||||
Returns:
|
||||
模块信息
|
||||
"""
|
||||
|
||||
with open(file_path, 'r', encoding='utf-8') as file:
|
||||
file_content = file.read()
|
||||
tree = ast.parse(file_content)
|
||||
|
||||
dot_sep_module_path = file_path.replace(os.sep, '.').replace(".py", "").replace(".pyi", "")
|
||||
module_docstring = ast.get_docstring(tree)
|
||||
|
||||
module_info = ModuleInfo(
|
||||
module_path=dot_sep_module_path,
|
||||
functions=[],
|
||||
classes=[],
|
||||
attributes=[],
|
||||
docstring=module_docstring if module_docstring else ""
|
||||
)
|
||||
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||
# 模块函数 且不在类中 若ignore_private=True则忽略私有函数
|
||||
if not any(isinstance(parent, ast.ClassDef) for parent in ast.iter_child_nodes(node)) and (not ignore_private or not node.name.startswith('_')):
|
||||
|
||||
# 判断第一个参数是否为self或cls,后期用其他办法优化
|
||||
if node.args.args:
|
||||
first_arg = node.args.args[0]
|
||||
if first_arg.arg in ("self", "cls"):
|
||||
continue
|
||||
|
||||
function_docstring = ast.get_docstring(node)
|
||||
|
||||
func_info = FunctionInfo(
|
||||
name=node.name,
|
||||
args=[(arg.arg, ast.unparse(arg.annotation) if arg.annotation else NO_TYPE_ANY) for arg in node.args.args],
|
||||
return_type=ast.unparse(node.returns) if node.returns else "None",
|
||||
docstring=function_docstring if function_docstring else "",
|
||||
type=DefType.FUNCTION,
|
||||
is_async=isinstance(node, ast.AsyncFunctionDef)
|
||||
)
|
||||
module_info.functions.append(func_info)
|
||||
|
||||
elif isinstance(node, ast.ClassDef):
|
||||
# 模块类
|
||||
class_docstring = ast.get_docstring(node)
|
||||
|
||||
class_info = ClassInfo(
|
||||
name=node.name,
|
||||
docstring=class_docstring if class_docstring else "",
|
||||
methods=[],
|
||||
attributes=[],
|
||||
inherit=[ast.unparse(base) for base in node.bases]
|
||||
)
|
||||
|
||||
for class_node in node.body:
|
||||
# methods [instance, static, class, property],保留__init__方法
|
||||
if isinstance(class_node, ast.FunctionDef) and (not ignore_private or not class_node.name.startswith('_') or class_node.name == "__init__"):
|
||||
method_docstring = ast.get_docstring(class_node)
|
||||
def_type = DefType.METHOD
|
||||
if class_node.decorator_list:
|
||||
if any(isinstance(decorator, ast.Name) and decorator.id == "staticmethod" for decorator in class_node.decorator_list):
|
||||
def_type = DefType.STATIC_METHOD
|
||||
elif any(isinstance(decorator, ast.Name) and decorator.id == "classmethod" for decorator in class_node.decorator_list):
|
||||
def_type = DefType.CLASS_METHOD
|
||||
elif any(isinstance(decorator, ast.Name) and decorator.id == "property" for decorator in class_node.decorator_list):
|
||||
def_type = DefType.PROPERTY
|
||||
class_info.methods.append(FunctionInfo(
|
||||
name=class_node.name,
|
||||
args=[(arg.arg, ast.unparse(arg.annotation) if arg.annotation else NO_TYPE_ANY) for arg in class_node.args.args],
|
||||
return_type=ast.unparse(class_node.returns) if class_node.returns else "None",
|
||||
docstring=method_docstring if method_docstring else "",
|
||||
type=def_type,
|
||||
is_async=isinstance(class_node, ast.AsyncFunctionDef)
|
||||
))
|
||||
# attributes
|
||||
elif isinstance(class_node, ast.Assign):
|
||||
for target in class_node.targets:
|
||||
if isinstance(target, ast.Name):
|
||||
class_info.attributes.append(AttributeInfo(
|
||||
name=target.id,
|
||||
type=ast.unparse(class_node.value)
|
||||
))
|
||||
module_info.classes.append(class_info)
|
||||
|
||||
elif isinstance(node, ast.Assign):
|
||||
# 检查是否在类或函数中
|
||||
if not any(isinstance(parent, (ast.ClassDef, ast.FunctionDef)) for parent in ast.iter_child_nodes(node)):
|
||||
# 模块属性变量
|
||||
for target in node.targets:
|
||||
if isinstance(target, ast.Name) and (not ignore_private or not target.id.startswith('_')):
|
||||
attr_type = NO_TYPE_HINT
|
||||
if isinstance(node.value, ast.AnnAssign) and node.value.annotation:
|
||||
attr_type = ast.unparse(node.value.annotation)
|
||||
module_info.attributes.append(AttributeInfo(
|
||||
name=target.id,
|
||||
type=attr_type,
|
||||
value=ast.unparse(node.value) if node.value else None
|
||||
))
|
||||
|
||||
return module_info
|
||||
|
||||
|
||||
def generate_markdown(module_info: ModuleInfo, front_matter=None) -> str:
|
||||
"""
|
||||
生成模块的Markdown
|
||||
你可在此自定义生成的Markdown格式
|
||||
Args:
|
||||
module_info: 模块信息
|
||||
front_matter: 自定义选项title, index, icon, category
|
||||
Returns:
|
||||
Markdown 字符串
|
||||
"""
|
||||
|
||||
content = ""
|
||||
|
||||
front_matter = "---\n" + "\n".join([f"{k}: {v}" for k, v in front_matter.items()]) + "\n---\n\n"
|
||||
|
||||
content += front_matter
|
||||
|
||||
|
||||
|
||||
# 模块函数
|
||||
for func in module_info.functions:
|
||||
args_with_type = [f"{arg[0]}: {arg[1]}" if arg[1] else arg[0] for arg in func.args]
|
||||
content += f"### ***{'async ' if func.is_async else ''}def*** `{func.name}({', '.join(args_with_type)}) -> {func.return_type}`\n\n"
|
||||
|
||||
func.docstring = func.docstring.replace("\n", "\n\n")
|
||||
content += f"{func.docstring}\n\n"
|
||||
|
||||
# 类
|
||||
for cls in module_info.classes:
|
||||
if cls.inherit:
|
||||
inherit = f"({', '.join(cls.inherit)})" if cls.inherit else ""
|
||||
content += f"### ***class*** `{cls.name}{inherit}`\n\n"
|
||||
else:
|
||||
content += f"### ***class*** `{cls.name}`\n\n"
|
||||
|
||||
cls.docstring = cls.docstring.replace("\n", "\n\n")
|
||||
content += f"{cls.docstring}\n\n"
|
||||
for method in cls.methods:
|
||||
# 类函数
|
||||
|
||||
if method.type != DefType.METHOD:
|
||||
args_with_type = [f"{arg[0]}: {arg[1]}" if arg[1] else arg[0] for arg in method.args]
|
||||
content += f"###   ***@{method.type.value}***\n"
|
||||
else:
|
||||
# self不加类型提示
|
||||
args_with_type = [f"{arg[0]}: {arg[1]}" if arg[1] and arg[0] != "self" else arg[0] for arg in method.args]
|
||||
content += f"###   ***{'async ' if method.is_async else ''}def*** `{method.name}({', '.join(args_with_type)}) -> {method.return_type}`\n\n"
|
||||
|
||||
method.docstring = method.docstring.replace("\n", "\n\n")
|
||||
content += f" {method.docstring}\n\n"
|
||||
for attr in cls.attributes:
|
||||
content += f"###   ***attr*** `{attr.name}: {attr.type}`\n\n"
|
||||
|
||||
# 模块属性
|
||||
for attr in module_info.attributes:
|
||||
if attr.type == NO_TYPE_HINT:
|
||||
content += f"### ***var*** `{attr.name} = {attr.value}`\n\n"
|
||||
else:
|
||||
content += f"### ***var*** `{attr.name}: {attr.type} = {attr.value}`\n\n"
|
||||
|
||||
attr.docstring = attr.docstring.replace("\n", "\n\n")
|
||||
content += f"{attr.docstring}\n\n"
|
||||
|
||||
return content
|
||||
|
||||
|
||||
def generate_docs(module_folder: str, output_dir: str, with_top: bool = False, ignored_paths=None):
|
||||
"""
|
||||
生成文档
|
||||
Args:
|
||||
module_folder: 模块文件夹
|
||||
output_dir: 输出文件夹
|
||||
with_top: 是否包含顶层文件夹 False时例如docs/api/module_a, docs/api/module_b, True时例如docs/api/module/module_a.md, docs/api/module/module_b.md
|
||||
ignored_paths: 忽略的路径
|
||||
"""
|
||||
if ignored_paths is None:
|
||||
ignored_paths = []
|
||||
file_data: dict[str, str] = {} # 路径 -> 字串
|
||||
|
||||
file_list = get_file_list(module_folder)
|
||||
|
||||
# 清理输出目录
|
||||
shutil.rmtree(output_dir, ignore_errors=True)
|
||||
os.mkdir(output_dir)
|
||||
|
||||
replace_data = {
|
||||
"__init__": "README",
|
||||
".py" : ".md",
|
||||
}
|
||||
|
||||
for pyfile_path in file_list:
|
||||
if any(ignored_path.replace("\\", "/") in pyfile_path.replace("\\", "/") for ignored_path in ignored_paths):
|
||||
continue
|
||||
|
||||
no_module_name_pyfile_path = get_relative_path(module_folder, pyfile_path) # 去头路径
|
||||
|
||||
# markdown相对路径
|
||||
rel_md_path = pyfile_path if with_top else no_module_name_pyfile_path
|
||||
for rk, rv in replace_data.items():
|
||||
rel_md_path = rel_md_path.replace(rk, rv)
|
||||
|
||||
abs_md_path = os.path.join(output_dir, rel_md_path)
|
||||
|
||||
# 获取模块信息
|
||||
module_info = get_module_info_normal(pyfile_path)
|
||||
|
||||
# 生成markdown
|
||||
|
||||
if "README" in abs_md_path:
|
||||
front_matter = {
|
||||
"title" : module_info.module_path.replace(".__init__", "").replace("_", "\\n"),
|
||||
"index" : "true",
|
||||
"icon" : "laptop-code",
|
||||
"category": "API"
|
||||
}
|
||||
else:
|
||||
front_matter = {
|
||||
"title" : module_info.module_path.replace("_", "\\n"),
|
||||
"order" : "1",
|
||||
"icon" : "laptop-code",
|
||||
"category": "API"
|
||||
}
|
||||
|
||||
md_content = generate_markdown(module_info, front_matter)
|
||||
print(f"Generate {pyfile_path} -> {abs_md_path}")
|
||||
file_data[abs_md_path] = md_content
|
||||
|
||||
write_to_files(file_data)
|
||||
|
||||
|
||||
# 入口脚本
|
||||
if __name__ == '__main__':
|
||||
# 这里填入你的模块路径
|
||||
generate_docs('liteyuki', 'docs/dev/api', with_top=False, ignored_paths=["liteyuki/plugins"])
|
||||
generate_docs('liteyuki', 'docs/en/dev/api', with_top=False, ignored_paths=["liteyuki/plugins"])
|
@ -117,7 +117,7 @@ def format_display_name(display_name: str, plugin_type: PluginType) -> str:
|
||||
match plugin_type:
|
||||
case PluginType.APPLICATION:
|
||||
color = "m"
|
||||
case PluginType.IMPLEMENTATION:
|
||||
case PluginType.TEST:
|
||||
color = "g"
|
||||
case PluginType.MODULE:
|
||||
color = "e"
|
||||
|
@ -25,15 +25,15 @@ class PluginType(Enum):
|
||||
SERVICE = "service"
|
||||
"""服务端:例如AI绘画后端"""
|
||||
|
||||
IMPLEMENTATION = "implementation"
|
||||
"""实现端:例如与聊天平台的协议实现"""
|
||||
|
||||
MODULE = "module"
|
||||
"""模块:导出对象给其他插件使用"""
|
||||
|
||||
UNCLASSIFIED = "unclassified"
|
||||
"""未分类:默认值"""
|
||||
|
||||
TEST = "test"
|
||||
"""测试:测试插件"""
|
||||
|
||||
|
||||
class PluginMetadata(BaseModel):
|
||||
"""
|
||||
|
@ -1,20 +1,21 @@
|
||||
import base64
|
||||
import time
|
||||
from typing import Any, AnyStr
|
||||
from typing import AnyStr
|
||||
|
||||
import time
|
||||
from typing import AnyStr
|
||||
|
||||
import nonebot
|
||||
import pip
|
||||
from nonebot import Bot, get_driver, require
|
||||
from nonebot import get_driver, require
|
||||
from nonebot.adapters import onebot, satori
|
||||
from nonebot.adapters.onebot.v11 import Message, unescape
|
||||
from nonebot.exception import MockApiException
|
||||
from nonebot.internal.matcher import Matcher
|
||||
from nonebot.permission import SUPERUSER
|
||||
|
||||
# from src.liteyuki.core import Reloader
|
||||
from src.utils import event as event_utils, satori_utils
|
||||
from src.utils.base.config import get_config, load_from_yaml
|
||||
from src.utils.base.data_manager import StoredConfig, TempConfig, common_db
|
||||
from src.utils.base.config import get_config
|
||||
from src.utils.base.data_manager import TempConfig, common_db
|
||||
from src.utils.base.language import get_user_lang
|
||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
from src.utils.message.message import MarkdownMessage as md, broadcast_to_superusers
|
||||
@ -24,13 +25,11 @@ from ..utils.base.ly_function import get_function
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
require("nonebot_plugin_apscheduler")
|
||||
from nonebot_plugin_alconna import on_alconna, Alconna, Args, Subcommand, Arparma, MultiVar
|
||||
from nonebot_plugin_alconna import on_alconna, Alconna, Args, Arparma, MultiVar
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
|
||||
driver = get_driver()
|
||||
|
||||
markdown_image = common_db.where_one(StoredConfig(), default=StoredConfig()).config.get("markdown_image", False)
|
||||
|
||||
|
||||
@on_alconna(
|
||||
command=Alconna(
|
||||
@ -87,7 +86,7 @@ async def _(matcher: Matcher, bot: T_Bot, event: T_MessageEvent):
|
||||
"reload_bot_id" : bot.self_id,
|
||||
"reload_session_type": event_utils.get_message_type(event),
|
||||
"reload_session_id" : (event.group_id if event.message_type == "group" else event.user_id)
|
||||
if not isinstance(event, satori.event.Event) else event.chan_active.id,
|
||||
if not isinstance(event, satori.event.Event) else event.chan_active.id,
|
||||
"delta_time" : 0
|
||||
}
|
||||
)
|
||||
@ -96,90 +95,6 @@ async def _(matcher: Matcher, bot: T_Bot, event: T_MessageEvent):
|
||||
reload()
|
||||
|
||||
|
||||
@on_alconna(
|
||||
aliases={"配置"},
|
||||
command=Alconna(
|
||||
"config",
|
||||
Subcommand(
|
||||
"set",
|
||||
Args["key", str]["value", Any],
|
||||
alias=["设置"],
|
||||
|
||||
),
|
||||
Subcommand(
|
||||
"get",
|
||||
Args["key", str, None],
|
||||
alias=["查询", "获取"]
|
||||
),
|
||||
Subcommand(
|
||||
"remove",
|
||||
Args["key", str],
|
||||
alias=["删除"]
|
||||
)
|
||||
),
|
||||
permission=SUPERUSER
|
||||
).handle()
|
||||
# Satori OK
|
||||
async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot, matcher: Matcher):
|
||||
ulang = get_user_lang(str(event_utils.get_user_id(event)))
|
||||
stored_config: StoredConfig = common_db.where_one(StoredConfig(), default=StoredConfig())
|
||||
if result.subcommands.get("set"):
|
||||
key, value = result.subcommands.get("set").args.get("key"), result.subcommands.get("set").args.get("value")
|
||||
try:
|
||||
value = eval(value)
|
||||
except:
|
||||
pass
|
||||
stored_config.config[key] = value
|
||||
common_db.save(stored_config)
|
||||
await matcher.finish(f"{ulang.get('liteyuki.config_set_success', KEY=key, VAL=value)}")
|
||||
elif result.subcommands.get("get"):
|
||||
key = result.subcommands.get("get").args.get("key")
|
||||
file_config = load_from_yaml("config.yml")
|
||||
reply = f"{ulang.get('liteyuki.current_config')}"
|
||||
if key:
|
||||
reply += f"```dotenv\n{key}={file_config.get(key, stored_config.config.get(key))}\n```"
|
||||
else:
|
||||
reply = f"{ulang.get('liteyuki.current_config')}"
|
||||
reply += f"\n{ulang.get('liteyuki.static_config')}\n```dotenv"
|
||||
for k, v in file_config.items():
|
||||
reply += f"\n{k}={v}"
|
||||
reply += "\n```"
|
||||
if len(stored_config.config) > 0:
|
||||
reply += f"\n{ulang.get('liteyuki.stored_config')}\n```dotenv"
|
||||
for k, v in stored_config.config.items():
|
||||
reply += f"\n{k}={v} {type(v)}"
|
||||
reply += "\n```"
|
||||
await md.send_md(reply, bot, event=event)
|
||||
elif result.subcommands.get("remove"):
|
||||
key = result.subcommands.get("remove").args.get("key")
|
||||
if key in stored_config.config:
|
||||
stored_config.config.pop(key)
|
||||
common_db.save(stored_config)
|
||||
await matcher.finish(f"{ulang.get('liteyuki.config_remove_success', KEY=key)}")
|
||||
else:
|
||||
await matcher.finish(f"{ulang.get('liteyuki.invalid_command', TEXT=key)}")
|
||||
|
||||
|
||||
@on_alconna(
|
||||
aliases={"切换图片模式"},
|
||||
command=Alconna(
|
||||
"switch-image-mode"
|
||||
),
|
||||
permission=SUPERUSER
|
||||
).handle()
|
||||
# Satori OK
|
||||
async def _(event: T_MessageEvent, matcher: Matcher):
|
||||
global markdown_image
|
||||
# 切换图片模式,False以图片形式发送,True以markdown形式发送
|
||||
ulang = get_user_lang(str(event_utils.get_user_id(event)))
|
||||
stored_config: StoredConfig = common_db.where_one(StoredConfig(), default=StoredConfig())
|
||||
stored_config.config["markdown_image"] = not stored_config.config.get("markdown_image", False)
|
||||
markdown_image = stored_config.config["markdown_image"]
|
||||
common_db.save(stored_config)
|
||||
await matcher.finish(
|
||||
ulang.get("liteyuki.image_mode_on" if stored_config.config["markdown_image"] else "liteyuki.image_mode_off"))
|
||||
|
||||
|
||||
@on_alconna(
|
||||
command=Alconna(
|
||||
"liteyuki-docs",
|
||||
@ -285,38 +200,6 @@ async def _(result: Arparma, bot: T_Bot, event: T_MessageEvent, matcher: Matcher
|
||||
await matcher.finish(f"API: {api_name}\n\nArgs: \n{args_show}\n\nResult: {result}")
|
||||
|
||||
|
||||
# system hook
|
||||
@Bot.on_calling_api # 图片模式检测
|
||||
async def test_for_md_image(bot: T_Bot, api: str, data: dict):
|
||||
# 截获大图发送,转换为markdown发送
|
||||
if api in ["send_msg", "send_private_msg", "send_group_msg"] and markdown_image and data.get(
|
||||
"user_id") != bot.self_id:
|
||||
if api == "send_msg" and data.get("message_type") == "private" or api == "send_private_msg":
|
||||
session_type = "private"
|
||||
session_id = data.get("user_id")
|
||||
elif api == "send_msg" and data.get("message_type") == "group" or api == "send_group_msg":
|
||||
session_type = "group"
|
||||
session_id = data.get("group_id")
|
||||
else:
|
||||
return
|
||||
if len(data.get("message", [])) == 1 and data["message"][0].get("type") == "image":
|
||||
file: str = data["message"][0].data.get("file")
|
||||
# file:// http:// base64://
|
||||
if file.startswith("http"):
|
||||
result = await md.send_md(await md.image_async(file), bot, message_type=session_type,
|
||||
session_id=session_id)
|
||||
elif file.startswith("file"):
|
||||
file = file.replace("file://", "")
|
||||
result = await md.send_image(open(file, "rb").read(), bot, message_type=session_type,
|
||||
session_id=session_id)
|
||||
elif file.startswith("base64"):
|
||||
file_bytes = base64.b64decode(file.replace("base64://", ""))
|
||||
result = await md.send_image(file_bytes, bot, message_type=session_type, session_id=session_id)
|
||||
else:
|
||||
return
|
||||
raise MockApiException(result=result)
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
async def on_startup():
|
||||
temp_data = common_db.where_one(TempConfig(), default=TempConfig())
|
||||
|
24
src/liteyuki_plugins/hello_liteyuki.py
Normal file
24
src/liteyuki_plugins/hello_liteyuki.py
Normal file
@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/8/20 上午5:12
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : liteyuki_reply.py
|
||||
@Software: PyCharm
|
||||
"""
|
||||
from liteyuki.plugin import PluginMetadata, PluginType
|
||||
from liteyuki.message.on import on_message
|
||||
from liteyuki.message.event import Event
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="你好轻雪",
|
||||
type=PluginType.TEST
|
||||
)
|
||||
|
||||
|
||||
@on_message().handle
|
||||
async def _(event: Event):
|
||||
if str(event.data["raw_message"]) == "你好轻雪":
|
||||
event.reply("你好呀")
|
@ -69,6 +69,8 @@ async def get_stat_msg_image(
|
||||
condition,
|
||||
*condition_args
|
||||
)
|
||||
if not msg_rows:
|
||||
msg_rows = []
|
||||
timestamps = []
|
||||
msg_count = []
|
||||
msg_rows.sort(key=lambda x: x.time)
|
||||
|
@ -98,8 +98,10 @@ async def _(result: Arparma, event: T_MessageEvent, bot: Bot):
|
||||
bot_id = result.other_args.get("bot_id")
|
||||
user_id = result.other_args.get("user_id")
|
||||
|
||||
if group_id in ["current", "c"]:
|
||||
if group_id in ["current", "c"] and hasattr(event, "group_id"):
|
||||
group_id = str(event_utils.get_group_id(event))
|
||||
else:
|
||||
group_id = "all"
|
||||
|
||||
if group_id in ["all", "a"]:
|
||||
group_id = "all"
|
||||
|
18
src/nonebot_plugins/nonebot-plugin-toliteyuki/__init__.py
Normal file
18
src/nonebot_plugins/nonebot-plugin-toliteyuki/__init__.py
Normal file
@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/8/19 下午10:30
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : __init__.py.py
|
||||
@Software: PyCharm
|
||||
"""
|
||||
from nonebot import require
|
||||
|
||||
from liteyuki.comm.storage import shared_memory
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
|
||||
from nonebot_plugin_alconna import UniMessage, Command, on_alconna
|
||||
|
40
src/nonebot_plugins/to_liteyuki.py
Normal file
40
src/nonebot_plugins/to_liteyuki.py
Normal file
@ -0,0 +1,40 @@
|
||||
# -*- 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 nonebot import Bot, get_bot, on_message
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot.adapters.onebot.v11 import MessageEvent, Bot
|
||||
from liteyuki.comm.storage import shared_memory
|
||||
from liteyuki.message.event import Event
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪物流",
|
||||
description="把消息事件传递给轻雪框架进行处理",
|
||||
usage="用户无需使用",
|
||||
)
|
||||
|
||||
|
||||
@on_message().handle()
|
||||
async def _(bot: Bot, event: MessageEvent):
|
||||
liteyuki_event = Event(
|
||||
type=event.message_type,
|
||||
data=event.dict(),
|
||||
bot_id=bot.self_id,
|
||||
session_id=str(event.user_id if event.message_type == "private" else event.group_id),
|
||||
session_type=event.message_type,
|
||||
receive_channel="event_to_nonebot"
|
||||
)
|
||||
shared_memory.publish("event_to_liteyuki", liteyuki_event)
|
||||
|
||||
|
||||
@shared_memory.on_subscriber_receive("event_to_nonebot")
|
||||
async def _(event: Event):
|
||||
bot: Bot = get_bot(event.bot_id)
|
||||
await bot.send_msg(message_type=event.type, user_id=int(event.session_id), group_id=int(event.session_id), message=event.data["message"])
|
@ -28,7 +28,7 @@ class Database:
|
||||
os.makedirs(os.path.dirname(db_name))
|
||||
|
||||
self.db_name = db_name
|
||||
self.conn = sqlite3.connect(db_name)
|
||||
self.conn = sqlite3.connect(db_name, check_same_thread=False)
|
||||
self.cursor = self.conn.cursor()
|
||||
|
||||
self._on_save_callbacks = []
|
||||
@ -105,12 +105,12 @@ class Database:
|
||||
return [model_type(**self._load(dict(zip(fields, result)))) for result in results]
|
||||
|
||||
def save(self, *args: LiteModel):
|
||||
"""增/改操作
|
||||
self.returns_ = """增/改操作
|
||||
Args:
|
||||
*args:
|
||||
Returns:
|
||||
"""
|
||||
table_list = [item[0] for item in self.cursor.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()]
|
||||
table_list = [item[0] for item in self.cursor.execute("SELECT name FROM sqlite_master WHERE type ='table'").fetchall()]
|
||||
for model in args:
|
||||
logger.debug(f"Upserting {model}")
|
||||
if not model.TABLE_NAME:
|
||||
@ -433,4 +433,4 @@ def check_sqlite_keyword(name):
|
||||
]
|
||||
return True
|
||||
# if name.upper() in sqlite_keywords:
|
||||
# raise ValueError(f"'{name}' 是SQLite保留字,不建议使用,请更换名称")
|
||||
# raise ValueError(f"'{name}' 是SQLite保留字,不建议使用,请更换名称")
|
@ -65,7 +65,7 @@ def auto_migrate():
|
||||
user_db.auto_migrate(User())
|
||||
group_db.auto_migrate(Group())
|
||||
plugin_db.auto_migrate(InstalledPlugin(), GlobalPlugin())
|
||||
common_db.auto_migrate(GlobalPlugin(), StoredConfig(), TempConfig())
|
||||
common_db.auto_migrate(GlobalPlugin(), TempConfig())
|
||||
|
||||
|
||||
auto_migrate()
|
||||
|
@ -1,6 +1,6 @@
|
||||
from nonebot.adapters import satori
|
||||
|
||||
from src.utils.base.ly_typing import T_MessageEvent
|
||||
from nonebot.adapters import onebot
|
||||
from src.utils.base.ly_typing import T_MessageEvent, T_GroupMessageEvent
|
||||
|
||||
|
||||
def get_user_id(event: T_MessageEvent):
|
||||
@ -10,11 +10,13 @@ def get_user_id(event: T_MessageEvent):
|
||||
return event.user_id
|
||||
|
||||
|
||||
def get_group_id(event: T_MessageEvent):
|
||||
def get_group_id(event: T_GroupMessageEvent):
|
||||
if isinstance(event, satori.event.Event):
|
||||
return event.guild.id
|
||||
else:
|
||||
elif isinstance(event, onebot.v11.GroupMessageEvent):
|
||||
return event.group_id
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def get_message_type(event: T_MessageEvent) -> str:
|
||||
|
Reference in New Issue
Block a user