mirror of
https://github.com/LiteyukiStudio/LiteyukiBot.git
synced 2025-07-17 22:10:33 +00:00
Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
4bf8512a7d | |||
a3a31a2c94 | |||
9ed4c1abb1 | |||
a9c6ea0452 | |||
9e2bbe2e5c | |||
598bff8c49 | |||
eb7c8300fa |
3
.gitignore
vendored
3
.gitignore
vendored
@ -53,4 +53,5 @@ dist
|
|||||||
|
|
||||||
doc
|
doc
|
||||||
|
|
||||||
mkdoc2.py
|
mkdoc2.py
|
||||||
|
result.json
|
@ -15,6 +15,26 @@ Returns:
|
|||||||
|
|
||||||
LiteyukiBot: 当前的轻雪实例
|
LiteyukiBot: 当前的轻雪实例
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_bot() -> LiteyukiBot:
|
||||||
|
"""
|
||||||
|
获取轻雪实例
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
LiteyukiBot: 当前的轻雪实例
|
||||||
|
"""
|
||||||
|
if IS_MAIN_PROCESS:
|
||||||
|
if _BOT_INSTANCE is None:
|
||||||
|
raise RuntimeError('Liteyuki instance not initialized.')
|
||||||
|
return _BOT_INSTANCE
|
||||||
|
else:
|
||||||
|
raise RuntimeError("Can't get bot instance in sub process.")
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `get_config(key: str, default: Any) -> Any`
|
### ***def*** `get_config(key: str, default: Any) -> Any`
|
||||||
|
|
||||||
获取配置
|
获取配置
|
||||||
@ -31,6 +51,24 @@ Returns:
|
|||||||
|
|
||||||
Any: 配置值
|
Any: 配置值
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_config(key: str, default: Any=None) -> Any:
|
||||||
|
"""
|
||||||
|
获取配置
|
||||||
|
Args:
|
||||||
|
key: 配置键
|
||||||
|
default: 默认值
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Any: 配置值
|
||||||
|
"""
|
||||||
|
return get_bot().config.get(key, default)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `get_config_with_compat(key: str, compat_keys: tuple[str], default: Any) -> Any`
|
### ***def*** `get_config_with_compat(key: str, compat_keys: tuple[str], default: Any) -> Any`
|
||||||
|
|
||||||
获取配置,兼容旧版本
|
获取配置,兼容旧版本
|
||||||
@ -49,10 +87,44 @@ Returns:
|
|||||||
|
|
||||||
Any: 配置值
|
Any: 配置值
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_config_with_compat(key: str, compat_keys: tuple[str], default: Any=None) -> Any:
|
||||||
|
"""
|
||||||
|
获取配置,兼容旧版本
|
||||||
|
Args:
|
||||||
|
key: 配置键
|
||||||
|
compat_keys: 兼容键
|
||||||
|
default: 默认值
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Any: 配置值
|
||||||
|
"""
|
||||||
|
if key in get_bot().config:
|
||||||
|
return get_bot().config[key]
|
||||||
|
for compat_key in compat_keys:
|
||||||
|
if compat_key in get_bot().config:
|
||||||
|
logger.warning(f'Config key "{compat_key}" will be deprecated, use "{key}" instead.')
|
||||||
|
return get_bot().config[compat_key]
|
||||||
|
return default
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `print_logo() -> None`
|
### ***def*** `print_logo() -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def print_logo():
|
||||||
|
print('\x1b[34m' + '\n __ ______ ________ ________ __ __ __ __ __ __ ______ \n / | / |/ |/ |/ \\ / |/ | / |/ | / |/ |\n $$ | $$$$$$/ $$$$$$$$/ $$$$$$$$/ $$ \\ /$$/ $$ | $$ |$$ | /$$/ $$$$$$/ \n $$ | $$ | $$ | $$ |__ $$ \\/$$/ $$ | $$ |$$ |/$$/ $$ | \n $$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |$$ $$< $$ | \n $$ | $$ | $$ | $$$$$/ $$$$/ $$ | $$ |$$$$$ \\ $$ | \n $$ |_____ _$$ |_ $$ | $$ |_____ $$ | $$ \\__$$ |$$ |$$ \\ _$$ |_ \n $$ |/ $$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |/ $$ |\n $$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/ \n ' + '\x1b[0m')
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***class*** `LiteyukiBot`
|
### ***class*** `LiteyukiBot`
|
||||||
|
|
||||||
|
|
||||||
@ -67,22 +139,124 @@ Args:
|
|||||||
|
|
||||||
**kwargs: 配置
|
**kwargs: 配置
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
|
"""
|
||||||
|
初始化轻雪实例
|
||||||
|
Args:
|
||||||
|
*args:
|
||||||
|
**kwargs: 配置
|
||||||
|
|
||||||
|
"""
|
||||||
|
'常规操作'
|
||||||
|
print_logo()
|
||||||
|
global _BOT_INSTANCE
|
||||||
|
_BOT_INSTANCE = self
|
||||||
|
'配置'
|
||||||
|
self.config: dict[str, Any] = kwargs
|
||||||
|
'初始化'
|
||||||
|
self.init(**self.config)
|
||||||
|
logger.info('Liteyuki is initializing...')
|
||||||
|
'生命周期管理'
|
||||||
|
self.lifespan = Lifespan()
|
||||||
|
self.process_manager: ProcessManager = ProcessManager(lifespan=self.lifespan)
|
||||||
|
'事件循环'
|
||||||
|
self.loop = asyncio.new_event_loop()
|
||||||
|
asyncio.set_event_loop(self.loop)
|
||||||
|
self.stop_event = threading.Event()
|
||||||
|
self.call_restart_count = 0
|
||||||
|
'加载插件加载器'
|
||||||
|
load_plugin('liteyuki.plugins.plugin_loader')
|
||||||
|
'信号处理'
|
||||||
|
signal.signal(signal.SIGINT, self._handle_exit)
|
||||||
|
signal.signal(signal.SIGTERM, self._handle_exit)
|
||||||
|
atexit.register(self.process_manager.terminate_all)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `run(self) -> None`
|
###   ***def*** `run(self) -> None`
|
||||||
|
|
||||||
 启动逻辑
|
 启动逻辑
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def run(self):
|
||||||
|
"""
|
||||||
|
启动逻辑
|
||||||
|
"""
|
||||||
|
self.lifespan.before_start()
|
||||||
|
self.process_manager.start_all()
|
||||||
|
self.lifespan.after_start()
|
||||||
|
self.keep_alive()
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `keep_alive(self) -> None`
|
###   ***def*** `keep_alive(self) -> None`
|
||||||
|
|
||||||
 保持轻雪运行
|
 保持轻雪运行
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def keep_alive(self):
|
||||||
|
"""
|
||||||
|
保持轻雪运行
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
while not self.stop_event.is_set():
|
||||||
|
time.sleep(0.5)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.info('Liteyuki is stopping...')
|
||||||
|
self.stop()
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `restart(self, delay: int) -> None`
|
###   ***def*** `restart(self, delay: int) -> None`
|
||||||
|
|
||||||
 重启轻雪本体
|
 重启轻雪本体
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def restart(self, delay: int=0):
|
||||||
|
"""
|
||||||
|
重启轻雪本体
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self.call_restart_count < 1:
|
||||||
|
executable = sys.executable
|
||||||
|
args = sys.argv
|
||||||
|
logger.info('Restarting LiteyukiBot...')
|
||||||
|
time.sleep(delay)
|
||||||
|
if platform.system() == 'Windows':
|
||||||
|
cmd = 'start'
|
||||||
|
elif platform.system() == 'Linux':
|
||||||
|
cmd = 'nohup'
|
||||||
|
elif platform.system() == 'Darwin':
|
||||||
|
cmd = 'open'
|
||||||
|
else:
|
||||||
|
cmd = 'nohup'
|
||||||
|
self.process_manager.terminate_all()
|
||||||
|
threading.Thread(target=os.system, args=(f"{cmd} {executable} {' '.join(args)}",)).start()
|
||||||
|
sys.exit(0)
|
||||||
|
self.call_restart_count += 1
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `restart_process(self, name: Optional[str]) -> None`
|
###   ***def*** `restart_process(self, name: Optional[str]) -> None`
|
||||||
|
|
||||||
 停止轻雪
|
 停止轻雪
|
||||||
@ -93,22 +267,83 @@ Args:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def restart_process(self, name: Optional[str]=None):
|
||||||
|
"""
|
||||||
|
停止轻雪
|
||||||
|
Args:
|
||||||
|
name: 进程名称, 默认为None, 所有进程
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
self.lifespan.before_process_shutdown()
|
||||||
|
self.lifespan.before_process_shutdown()
|
||||||
|
if name is not None:
|
||||||
|
chan_active = get_channel(f'{name}-active')
|
||||||
|
chan_active.send(1)
|
||||||
|
else:
|
||||||
|
for process_name in self.process_manager.processes:
|
||||||
|
chan_active = get_channel(f'{process_name}-active')
|
||||||
|
chan_active.send(1)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `init(self) -> None`
|
###   ***def*** `init(self) -> None`
|
||||||
|
|
||||||
 初始化轻雪, 自动调用
|
 初始化轻雪, 自动调用
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def init(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
初始化轻雪, 自动调用
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.init_logger()
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `init_logger(self) -> None`
|
###   ***def*** `init_logger(self) -> None`
|
||||||
|
|
||||||
 
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def init_logger(self):
|
||||||
|
init_log(config=self.config)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `stop(self) -> None`
|
###   ***def*** `stop(self) -> None`
|
||||||
|
|
||||||
 停止轻雪
|
 停止轻雪
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def stop(self):
|
||||||
|
"""
|
||||||
|
停止轻雪
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.stop_event.set()
|
||||||
|
self.loop.stop()
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `on_before_start(self, func: LIFESPAN_FUNC) -> None`
|
###   ***def*** `on_before_start(self, func: LIFESPAN_FUNC) -> None`
|
||||||
|
|
||||||
 注册启动前的函数
|
 注册启动前的函数
|
||||||
@ -121,6 +356,23 @@ Args:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_before_start(self, func: LIFESPAN_FUNC):
|
||||||
|
"""
|
||||||
|
注册启动前的函数
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.lifespan.on_before_start(func)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `on_after_start(self, func: LIFESPAN_FUNC) -> None`
|
###   ***def*** `on_after_start(self, func: LIFESPAN_FUNC) -> None`
|
||||||
|
|
||||||
 注册启动后的函数
|
 注册启动后的函数
|
||||||
@ -133,6 +385,23 @@ Args:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_after_start(self, func: LIFESPAN_FUNC):
|
||||||
|
"""
|
||||||
|
注册启动后的函数
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.lifespan.on_after_start(func)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `on_after_shutdown(self, func: LIFESPAN_FUNC) -> None`
|
###   ***def*** `on_after_shutdown(self, func: LIFESPAN_FUNC) -> None`
|
||||||
|
|
||||||
 注册停止后的函数:未实现
|
 注册停止后的函数:未实现
|
||||||
@ -145,6 +414,23 @@ Args:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_after_shutdown(self, func: LIFESPAN_FUNC):
|
||||||
|
"""
|
||||||
|
注册停止后的函数:未实现
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.lifespan.on_after_shutdown(func)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `on_before_process_shutdown(self, func: LIFESPAN_FUNC) -> None`
|
###   ***def*** `on_before_process_shutdown(self, func: LIFESPAN_FUNC) -> None`
|
||||||
|
|
||||||
 注册进程停止前的函数,为子进程停止时调用
|
 注册进程停止前的函数,为子进程停止时调用
|
||||||
@ -157,6 +443,23 @@ Args:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_before_process_shutdown(self, func: LIFESPAN_FUNC):
|
||||||
|
"""
|
||||||
|
注册进程停止前的函数,为子进程停止时调用
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.lifespan.on_before_process_shutdown(func)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `on_before_process_restart(self, func: LIFESPAN_FUNC) -> None`
|
###   ***def*** `on_before_process_restart(self, func: LIFESPAN_FUNC) -> None`
|
||||||
|
|
||||||
 注册进程重启前的函数,为子进程重启时调用
|
 注册进程重启前的函数,为子进程重启时调用
|
||||||
@ -169,6 +472,23 @@ Args:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_before_process_restart(self, func: LIFESPAN_FUNC):
|
||||||
|
"""
|
||||||
|
注册进程重启前的函数,为子进程重启时调用
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.lifespan.on_before_process_restart(func)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `on_after_restart(self, func: LIFESPAN_FUNC) -> None`
|
###   ***def*** `on_after_restart(self, func: LIFESPAN_FUNC) -> None`
|
||||||
|
|
||||||
 注册重启后的函数:未实现
|
 注册重启后的函数:未实现
|
||||||
@ -181,6 +501,23 @@ Args:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_after_restart(self, func: LIFESPAN_FUNC):
|
||||||
|
"""
|
||||||
|
注册重启后的函数:未实现
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.lifespan.on_after_restart(func)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `on_after_nonebot_init(self, func: LIFESPAN_FUNC) -> None`
|
###   ***def*** `on_after_nonebot_init(self, func: LIFESPAN_FUNC) -> None`
|
||||||
|
|
||||||
 注册nonebot初始化后的函数
|
 注册nonebot初始化后的函数
|
||||||
@ -193,6 +530,23 @@ Args:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_after_nonebot_init(self, func: LIFESPAN_FUNC):
|
||||||
|
"""
|
||||||
|
注册nonebot初始化后的函数
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.lifespan.on_after_nonebot_init(func)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***var*** `executable = sys.executable`
|
### ***var*** `executable = sys.executable`
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,6 +15,33 @@ Args:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
@staticmethod
|
||||||
|
def run_funcs(funcs: list[LIFESPAN_FUNC | PROCESS_LIFESPAN_FUNC], *args, **kwargs) -> None:
|
||||||
|
"""
|
||||||
|
运行函数
|
||||||
|
Args:
|
||||||
|
funcs:
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
except RuntimeError:
|
||||||
|
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))
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***class*** `Lifespan`
|
### ***class*** `Lifespan`
|
||||||
|
|
||||||
|
|
||||||
@ -23,6 +50,25 @@ Returns:
|
|||||||
|
|
||||||
 轻雪生命周期管理,启动、停止、重启
|
 轻雪生命周期管理,启动、停止、重启
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def __init__(self) -> None:
|
||||||
|
"""
|
||||||
|
轻雪生命周期管理,启动、停止、重启
|
||||||
|
"""
|
||||||
|
self.life_flag: int = 0
|
||||||
|
self._before_start_funcs: list[LIFESPAN_FUNC] = []
|
||||||
|
self._after_start_funcs: list[LIFESPAN_FUNC] = []
|
||||||
|
self._before_process_shutdown_funcs: list[LIFESPAN_FUNC] = []
|
||||||
|
self._after_shutdown_funcs: list[LIFESPAN_FUNC] = []
|
||||||
|
self._before_process_restart_funcs: list[LIFESPAN_FUNC] = []
|
||||||
|
self._after_restart_funcs: list[LIFESPAN_FUNC] = []
|
||||||
|
self._after_nonebot_init_funcs: list[LIFESPAN_FUNC] = []
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***@staticmethod***
|
###   ***@staticmethod***
|
||||||
###   ***def*** `run_funcs(funcs: list[LIFESPAN_FUNC | PROCESS_LIFESPAN_FUNC]) -> None`
|
###   ***def*** `run_funcs(funcs: list[LIFESPAN_FUNC | PROCESS_LIFESPAN_FUNC]) -> None`
|
||||||
|
|
||||||
@ -34,6 +80,33 @@ Args:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
@staticmethod
|
||||||
|
def run_funcs(funcs: list[LIFESPAN_FUNC | PROCESS_LIFESPAN_FUNC], *args, **kwargs) -> None:
|
||||||
|
"""
|
||||||
|
运行函数
|
||||||
|
Args:
|
||||||
|
funcs:
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
except RuntimeError:
|
||||||
|
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))
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `on_before_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
###   ***def*** `on_before_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
||||||
|
|
||||||
 注册启动时的函数
|
 注册启动时的函数
|
||||||
@ -46,6 +119,23 @@ Returns:
|
|||||||
|
|
||||||
LIFESPAN_FUNC:
|
LIFESPAN_FUNC:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_before_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||||
|
"""
|
||||||
|
注册启动时的函数
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
Returns:
|
||||||
|
LIFESPAN_FUNC:
|
||||||
|
"""
|
||||||
|
self._before_start_funcs.append(func)
|
||||||
|
return func
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `on_after_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
###   ***def*** `on_after_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
||||||
|
|
||||||
 注册启动时的函数
|
 注册启动时的函数
|
||||||
@ -58,6 +148,23 @@ Returns:
|
|||||||
|
|
||||||
LIFESPAN_FUNC:
|
LIFESPAN_FUNC:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_after_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||||
|
"""
|
||||||
|
注册启动时的函数
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
Returns:
|
||||||
|
LIFESPAN_FUNC:
|
||||||
|
"""
|
||||||
|
self._after_start_funcs.append(func)
|
||||||
|
return func
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `on_before_process_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
###   ***def*** `on_before_process_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
||||||
|
|
||||||
 注册停止前的函数
|
 注册停止前的函数
|
||||||
@ -70,6 +177,23 @@ Returns:
|
|||||||
|
|
||||||
LIFESPAN_FUNC:
|
LIFESPAN_FUNC:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_before_process_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||||
|
"""
|
||||||
|
注册停止前的函数
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
Returns:
|
||||||
|
LIFESPAN_FUNC:
|
||||||
|
"""
|
||||||
|
self._before_process_shutdown_funcs.append(func)
|
||||||
|
return func
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `on_after_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
###   ***def*** `on_after_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
||||||
|
|
||||||
 注册停止后的函数
|
 注册停止后的函数
|
||||||
@ -84,6 +208,25 @@ Returns:
|
|||||||
|
|
||||||
LIFESPAN_FUNC:
|
LIFESPAN_FUNC:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_after_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||||
|
"""
|
||||||
|
注册停止后的函数
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
LIFESPAN_FUNC:
|
||||||
|
|
||||||
|
"""
|
||||||
|
self._after_shutdown_funcs.append(func)
|
||||||
|
return func
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `on_before_process_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
###   ***def*** `on_before_process_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
||||||
|
|
||||||
 注册重启时的函数
|
 注册重启时的函数
|
||||||
@ -96,6 +239,23 @@ Returns:
|
|||||||
|
|
||||||
LIFESPAN_FUNC:
|
LIFESPAN_FUNC:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_before_process_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||||
|
"""
|
||||||
|
注册重启时的函数
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
Returns:
|
||||||
|
LIFESPAN_FUNC:
|
||||||
|
"""
|
||||||
|
self._before_process_restart_funcs.append(func)
|
||||||
|
return func
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `on_after_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
###   ***def*** `on_after_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
||||||
|
|
||||||
 注册重启后的函数
|
 注册重启后的函数
|
||||||
@ -108,6 +268,23 @@ Returns:
|
|||||||
|
|
||||||
LIFESPAN_FUNC:
|
LIFESPAN_FUNC:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_after_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||||
|
"""
|
||||||
|
注册重启后的函数
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
Returns:
|
||||||
|
LIFESPAN_FUNC:
|
||||||
|
"""
|
||||||
|
self._after_restart_funcs.append(func)
|
||||||
|
return func
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `on_after_nonebot_init(self, func: Any) -> None`
|
###   ***def*** `on_after_nonebot_init(self, func: Any) -> None`
|
||||||
|
|
||||||
 注册 NoneBot 初始化后的函数
|
 注册 NoneBot 初始化后的函数
|
||||||
@ -120,42 +297,145 @@ Args:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_after_nonebot_init(self, func):
|
||||||
|
"""
|
||||||
|
注册 NoneBot 初始化后的函数
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
self._after_nonebot_init_funcs.append(func)
|
||||||
|
return func
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `before_start(self) -> None`
|
###   ***def*** `before_start(self) -> None`
|
||||||
|
|
||||||
 启动前
|
 启动前
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def before_start(self) -> None:
|
||||||
|
"""
|
||||||
|
启动前
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
logger.debug('Running before_start functions')
|
||||||
|
self.run_funcs(self._before_start_funcs)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `after_start(self) -> None`
|
###   ***def*** `after_start(self) -> None`
|
||||||
|
|
||||||
 启动后
|
 启动后
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def after_start(self) -> None:
|
||||||
|
"""
|
||||||
|
启动后
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
logger.debug('Running after_start functions')
|
||||||
|
self.run_funcs(self._after_start_funcs)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `before_process_shutdown(self) -> None`
|
###   ***def*** `before_process_shutdown(self) -> None`
|
||||||
|
|
||||||
 停止前
|
 停止前
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def before_process_shutdown(self) -> None:
|
||||||
|
"""
|
||||||
|
停止前
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
logger.debug('Running before_shutdown functions')
|
||||||
|
self.run_funcs(self._before_process_shutdown_funcs)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `after_shutdown(self) -> None`
|
###   ***def*** `after_shutdown(self) -> None`
|
||||||
|
|
||||||
 停止后
|
 停止后
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def after_shutdown(self) -> None:
|
||||||
|
"""
|
||||||
|
停止后
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
logger.debug('Running after_shutdown functions')
|
||||||
|
self.run_funcs(self._after_shutdown_funcs)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `before_process_restart(self) -> None`
|
###   ***def*** `before_process_restart(self) -> None`
|
||||||
|
|
||||||
 重启前
|
 重启前
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def before_process_restart(self) -> None:
|
||||||
|
"""
|
||||||
|
重启前
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
logger.debug('Running before_restart functions')
|
||||||
|
self.run_funcs(self._before_process_restart_funcs)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `after_restart(self) -> None`
|
###   ***def*** `after_restart(self) -> None`
|
||||||
|
|
||||||
 重启后
|
 重启后
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def after_restart(self) -> None:
|
||||||
|
"""
|
||||||
|
重启后
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
logger.debug('Running after_restart functions')
|
||||||
|
self.run_funcs(self._after_restart_funcs)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***var*** `tasks = []`
|
### ***var*** `tasks = []`
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: liteyuki.comm.channel_
|
title: liteyuki.comm.channel
|
||||||
order: 1
|
order: 1
|
||||||
icon: laptop-code
|
icon: laptop-code
|
||||||
category: API
|
category: API
|
||||||
@ -15,6 +15,26 @@ Args:
|
|||||||
|
|
||||||
channel: 通道实例
|
channel: 通道实例
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def set_channel(name: str, channel: Channel):
|
||||||
|
"""
|
||||||
|
设置通道实例
|
||||||
|
Args:
|
||||||
|
name: 通道名称
|
||||||
|
channel: 通道实例
|
||||||
|
"""
|
||||||
|
if not isinstance(channel, Channel):
|
||||||
|
raise TypeError(f'channel_ must be an instance of Channel, {type(channel)} found')
|
||||||
|
if IS_MAIN_PROCESS:
|
||||||
|
_channel[name] = channel
|
||||||
|
else:
|
||||||
|
channel_deliver_passive_channel.send(('set_channel', {'name': name, 'channel_': channel}))
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `set_channels(channels: dict[str, Channel]) -> None`
|
### ***def*** `set_channels(channels: dict[str, Channel]) -> None`
|
||||||
|
|
||||||
设置通道实例
|
设置通道实例
|
||||||
@ -23,6 +43,21 @@ Args:
|
|||||||
|
|
||||||
channels: 通道名称
|
channels: 通道名称
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def set_channels(channels: dict[str, Channel]):
|
||||||
|
"""
|
||||||
|
设置通道实例
|
||||||
|
Args:
|
||||||
|
channels: 通道名称
|
||||||
|
"""
|
||||||
|
for name, channel in channels.items():
|
||||||
|
set_channel(name, channel)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `get_channel(name: str) -> Channel`
|
### ***def*** `get_channel(name: str) -> Channel`
|
||||||
|
|
||||||
获取通道实例
|
获取通道实例
|
||||||
@ -33,39 +68,156 @@ Args:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_channel(name: str) -> Channel:
|
||||||
|
"""
|
||||||
|
获取通道实例
|
||||||
|
Args:
|
||||||
|
name: 通道名称
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
if IS_MAIN_PROCESS:
|
||||||
|
return _channel[name]
|
||||||
|
else:
|
||||||
|
recv_chan = Channel[Channel[Any]]('recv_chan')
|
||||||
|
channel_deliver_passive_channel.send(('get_channel', {'name': name, 'recv_chan': recv_chan}))
|
||||||
|
return recv_chan.receive()
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `get_channels() -> dict[str, Channel]`
|
### ***def*** `get_channels() -> dict[str, Channel]`
|
||||||
|
|
||||||
获取通道实例
|
获取通道实例
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_channels() -> dict[str, Channel]:
|
||||||
|
"""
|
||||||
|
获取通道实例
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
if IS_MAIN_PROCESS:
|
||||||
|
return _channel
|
||||||
|
else:
|
||||||
|
recv_chan = Channel[dict[str, Channel[Any]]]('recv_chan')
|
||||||
|
channel_deliver_passive_channel.send(('get_channels', {'recv_chan': recv_chan}))
|
||||||
|
return recv_chan.receive()
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `on_set_channel(data: tuple[str, dict[str, Any]]) -> None`
|
### ***def*** `on_set_channel(data: tuple[str, dict[str, Any]]) -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
@channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == 'set_channel')
|
||||||
|
def on_set_channel(data: tuple[str, dict[str, Any]]):
|
||||||
|
name, channel = (data[1]['name'], data[1]['channel_'])
|
||||||
|
set_channel(name, channel)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `on_get_channel(data: tuple[str, dict[str, Any]]) -> None`
|
### ***def*** `on_get_channel(data: tuple[str, dict[str, Any]]) -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
@channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == 'get_channel')
|
||||||
|
def on_get_channel(data: tuple[str, dict[str, Any]]):
|
||||||
|
name, recv_chan = (data[1]['name'], data[1]['recv_chan'])
|
||||||
|
recv_chan.send(get_channel(name))
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `on_get_channels(data: tuple[str, dict[str, Any]]) -> None`
|
### ***def*** `on_get_channels(data: tuple[str, dict[str, Any]]) -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
@channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == 'get_channels')
|
||||||
|
def on_get_channels(data: tuple[str, dict[str, Any]]):
|
||||||
|
recv_chan = data[1]['recv_chan']
|
||||||
|
recv_chan.send(get_channels())
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `decorator(func: Callable[[T], Any]) -> Callable[[T], Any]`
|
### ***def*** `decorator(func: Callable[[T], Any]) -> Callable[[T], Any]`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def decorator(func: Callable[[T], Any]) -> Callable[[T], Any]:
|
||||||
|
global _func_id
|
||||||
|
|
||||||
|
async def wrapper(data: T) -> Any:
|
||||||
|
if filter_func is not None:
|
||||||
|
if is_coroutine_callable(filter_func):
|
||||||
|
if not await filter_func(data):
|
||||||
|
return
|
||||||
|
elif not filter_func(data):
|
||||||
|
return
|
||||||
|
if is_coroutine_callable(func):
|
||||||
|
return await func(data)
|
||||||
|
else:
|
||||||
|
return func(data)
|
||||||
|
_callback_funcs[_func_id] = wrapper
|
||||||
|
if IS_MAIN_PROCESS:
|
||||||
|
self._on_main_receive_funcs.append(_func_id)
|
||||||
|
else:
|
||||||
|
self._on_sub_receive_funcs.append(_func_id)
|
||||||
|
_func_id += 1
|
||||||
|
return func
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***async def*** `wrapper(data: T) -> Any`
|
### ***async def*** `wrapper(data: T) -> Any`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def wrapper(data: T) -> Any:
|
||||||
|
if filter_func is not None:
|
||||||
|
if is_coroutine_callable(filter_func):
|
||||||
|
if not await filter_func(data):
|
||||||
|
return
|
||||||
|
elif not filter_func(data):
|
||||||
|
return
|
||||||
|
if is_coroutine_callable(func):
|
||||||
|
return await func(data)
|
||||||
|
else:
|
||||||
|
return func(data)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***class*** `Channel(Generic[T])`
|
### ***class*** `Channel(Generic[T])`
|
||||||
|
|
||||||
通道类,可以在进程间和进程内通信,双向但同时只能有一个发送者和一个接收者
|
通道类,可以在进程间和进程内通信,双向但同时只能有一个发送者和一个接收者
|
||||||
|
|
||||||
有两种接收工作方式,但是只能选择一种,主动接收和被动接收,主动接收使用 `receive` 方法,被动接收使用 `on_receive` 装饰器
|
有两种接收工作方式,但是只能选择一种,主动接收和被动接收,主动接收使用 `receive` 方法,被动接收使用 `on_receive` 装饰器
|
||||||
|
|
||||||
###   ***def*** `__init__(self, _id: str, type_check: bool) -> None`
|
###   ***def*** `__init__(self, _id: str, type_check: Optional[bool]) -> None`
|
||||||
|
|
||||||
 初始化通道
|
 初始化通道
|
||||||
|
|
||||||
@ -73,6 +225,35 @@ Args:
|
|||||||
|
|
||||||
_id: 通道ID
|
_id: 通道ID
|
||||||
|
|
||||||
|
type_check: 是否开启类型检查, 若为空,则传入泛型默认开启,否则默认关闭
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
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
|
||||||
|
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.is_sub_receive_loop_running = False
|
||||||
|
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
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `send(self, data: T) -> None`
|
###   ***def*** `send(self, data: T) -> None`
|
||||||
|
|
||||||
 发送数据
|
 发送数据
|
||||||
@ -81,16 +262,67 @@ Args:
|
|||||||
|
|
||||||
data: 数据
|
data: 数据
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def send(self, data: T):
|
||||||
|
"""
|
||||||
|
发送数据
|
||||||
|
Args:
|
||||||
|
data: 数据
|
||||||
|
"""
|
||||||
|
if self.type_check:
|
||||||
|
_type = self._get_generic_type()
|
||||||
|
if _type is not None and (not self._validate_structure(data, _type)):
|
||||||
|
raise TypeError(f'Data must be an instance of {_type}, {type(data)} found')
|
||||||
|
if self._closed:
|
||||||
|
raise RuntimeError('Cannot send to a closed channel_')
|
||||||
|
self.conn_send.send(data)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `receive(self) -> T`
|
###   ***def*** `receive(self) -> T`
|
||||||
|
|
||||||
 接收数据
|
 接收数据
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def receive(self) -> T:
|
||||||
|
"""
|
||||||
|
接收数据
|
||||||
|
Args:
|
||||||
|
"""
|
||||||
|
if self._closed:
|
||||||
|
raise RuntimeError('Cannot receive from a closed channel_')
|
||||||
|
while True:
|
||||||
|
data = self.conn_recv.recv()
|
||||||
|
return data
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `close(self) -> None`
|
###   ***def*** `close(self) -> None`
|
||||||
|
|
||||||
 关闭通道
|
 关闭通道
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def close(self):
|
||||||
|
"""
|
||||||
|
关闭通道
|
||||||
|
"""
|
||||||
|
self._closed = True
|
||||||
|
self.conn_send.close()
|
||||||
|
self.conn_recv.close()
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `on_receive(self, filter_func: Optional[FILTER_FUNC]) -> Callable[[Callable[[T], Any]], Callable[[T], Any]]`
|
###   ***def*** `on_receive(self, filter_func: Optional[FILTER_FUNC]) -> Callable[[Callable[[T], Any]], Callable[[T], Any]]`
|
||||||
|
|
||||||
 接收数据并执行函数
|
 接收数据并执行函数
|
||||||
@ -103,6 +335,48 @@ Returns:
|
|||||||
|
|
||||||
装饰器,装饰一个函数在接收到数据后执行
|
装饰器,装饰一个函数在接收到数据后执行
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_receive(self, filter_func: Optional[FILTER_FUNC]=None) -> Callable[[Callable[[T], Any]], Callable[[T], Any]]:
|
||||||
|
"""
|
||||||
|
接收数据并执行函数
|
||||||
|
Args:
|
||||||
|
filter_func: 过滤函数,为None则不过滤
|
||||||
|
Returns:
|
||||||
|
装饰器,装饰一个函数在接收到数据后执行
|
||||||
|
"""
|
||||||
|
if not self.is_sub_receive_loop_running and (not IS_MAIN_PROCESS):
|
||||||
|
threading.Thread(target=self._start_sub_receive_loop, daemon=True).start()
|
||||||
|
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]:
|
||||||
|
global _func_id
|
||||||
|
|
||||||
|
async def wrapper(data: T) -> Any:
|
||||||
|
if filter_func is not None:
|
||||||
|
if is_coroutine_callable(filter_func):
|
||||||
|
if not await filter_func(data):
|
||||||
|
return
|
||||||
|
elif not filter_func(data):
|
||||||
|
return
|
||||||
|
if is_coroutine_callable(func):
|
||||||
|
return await func(data)
|
||||||
|
else:
|
||||||
|
return func(data)
|
||||||
|
_callback_funcs[_func_id] = wrapper
|
||||||
|
if IS_MAIN_PROCESS:
|
||||||
|
self._on_main_receive_funcs.append(_func_id)
|
||||||
|
else:
|
||||||
|
self._on_sub_receive_funcs.append(_func_id)
|
||||||
|
_func_id += 1
|
||||||
|
return func
|
||||||
|
return decorator
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***var*** `T = TypeVar('T')`
|
### ***var*** `T = TypeVar('T')`
|
||||||
|
|
||||||
|
|
||||||
@ -127,6 +401,10 @@ Returns:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ***var*** `type_check = self._get_generic_type() is not None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### ***var*** `data = self.conn_recv.recv()`
|
### ***var*** `data = self.conn_recv.recv()`
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,3 +13,13 @@ category: API
|
|||||||
|
|
||||||
 
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def __init__(self, name: str, data: dict[str, Any]):
|
||||||
|
self.name = name
|
||||||
|
self.data = data
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
@ -5,22 +5,200 @@ icon: laptop-code
|
|||||||
category: API
|
category: API
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### ***def*** `run_subscriber_receive_funcs(channel_: str, data: Any) -> None`
|
||||||
|
|
||||||
|
运行订阅者接收函数
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
channel_: 频道
|
||||||
|
|
||||||
|
data: 数据
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
@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_]])
|
||||||
|
elif 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_]])
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `on_get(data: tuple[str, dict[str, Any]]) -> None`
|
### ***def*** `on_get(data: tuple[str, dict[str, Any]]) -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
@shared_memory.passive_chan.on_receive(lambda d: d[0] == 'get')
|
||||||
|
def on_get(data: tuple[str, dict[str, Any]]):
|
||||||
|
key = data[1]['key']
|
||||||
|
default = data[1]['default']
|
||||||
|
recv_chan = data[1]['recv_chan']
|
||||||
|
recv_chan.send(shared_memory.get(key, default))
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `on_set(data: tuple[str, dict[str, Any]]) -> None`
|
### ***def*** `on_set(data: tuple[str, dict[str, Any]]) -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
@shared_memory.passive_chan.on_receive(lambda d: d[0] == 'set')
|
||||||
|
def on_set(data: tuple[str, dict[str, Any]]):
|
||||||
|
key = data[1]['key']
|
||||||
|
value = data[1]['value']
|
||||||
|
shared_memory.set(key, value)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `on_delete(data: tuple[str, dict[str, Any]]) -> None`
|
### ***def*** `on_delete(data: tuple[str, dict[str, Any]]) -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
@shared_memory.passive_chan.on_receive(lambda d: d[0] == 'delete')
|
||||||
|
def on_delete(data: tuple[str, dict[str, Any]]):
|
||||||
|
key = data[1]['key']
|
||||||
|
shared_memory.delete(key)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `on_get_all(data: tuple[str, dict[str, Any]]) -> None`
|
### ***def*** `on_get_all(data: tuple[str, dict[str, Any]]) -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
@shared_memory.passive_chan.on_receive(lambda d: d[0] == 'get_all')
|
||||||
|
def on_get_all(data: tuple[str, dict[str, Any]]):
|
||||||
|
recv_chan = data[1]['recv_chan']
|
||||||
|
recv_chan.send(shared_memory.get_all())
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `on_publish(data: tuple[str, Any]) -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
@channel.publish_channel.on_receive()
|
||||||
|
def on_publish(data: tuple[str, Any]):
|
||||||
|
channel_, data = data
|
||||||
|
shared_memory.run_subscriber_receive_funcs(channel_, data)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `decorator(func: ON_RECEIVE_FUNC) -> ON_RECEIVE_FUNC`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
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
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***async def*** `wrapper(data: Any) -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def wrapper(data: Any):
|
||||||
|
if is_coroutine_callable(func):
|
||||||
|
await func(data)
|
||||||
|
else:
|
||||||
|
func(data)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***class*** `Subscriber`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###   ***def*** `__init__(self) -> None`
|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def __init__(self):
|
||||||
|
self._subscribers = {}
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `receive(self) -> Any`
|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def receive(self) -> Any:
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `unsubscribe(self) -> None`
|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def unsubscribe(self) -> None:
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***class*** `KeyValueStore`
|
### ***class*** `KeyValueStore`
|
||||||
|
|
||||||
|
|
||||||
@ -29,6 +207,20 @@ category: API
|
|||||||
|
|
||||||
 
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
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
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `set(self, key: str, value: Any) -> None`
|
###   ***def*** `set(self, key: str, value: Any) -> None`
|
||||||
|
|
||||||
 设置键值对
|
 设置键值对
|
||||||
@ -39,6 +231,27 @@ Args:
|
|||||||
|
|
||||||
value: 值
|
value: 值
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def set(self, key: str, value: Any) -> None:
|
||||||
|
"""
|
||||||
|
设置键值对
|
||||||
|
Args:
|
||||||
|
key: 键
|
||||||
|
value: 值
|
||||||
|
|
||||||
|
"""
|
||||||
|
if IS_MAIN_PROCESS:
|
||||||
|
lock = _get_lock(key)
|
||||||
|
with lock:
|
||||||
|
self._store[key] = value
|
||||||
|
else:
|
||||||
|
self.passive_chan.send(('set', {'key': key, 'value': value}))
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `get(self, key: str, default: Optional[Any]) -> Optional[Any]`
|
###   ***def*** `get(self, key: str, default: Optional[Any]) -> Optional[Any]`
|
||||||
|
|
||||||
 获取键值对
|
 获取键值对
|
||||||
@ -55,6 +268,31 @@ Returns:
|
|||||||
|
|
||||||
Any: 值
|
Any: 值
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get(self, key: str, default: Optional[Any]=None) -> Optional[Any]:
|
||||||
|
"""
|
||||||
|
获取键值对
|
||||||
|
Args:
|
||||||
|
key: 键
|
||||||
|
default: 默认值
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Any: 值
|
||||||
|
"""
|
||||||
|
if IS_MAIN_PROCESS:
|
||||||
|
lock = _get_lock(key)
|
||||||
|
with lock:
|
||||||
|
return self._store.get(key, default)
|
||||||
|
else:
|
||||||
|
recv_chan = Channel[Optional[Any]]('recv_chan')
|
||||||
|
self.passive_chan.send(('get', {'key': key, 'default': default, 'recv_chan': recv_chan}))
|
||||||
|
return recv_chan.receive()
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `delete(self, key: str, ignore_key_error: bool) -> None`
|
###   ***def*** `delete(self, key: str, ignore_key_error: bool) -> None`
|
||||||
|
|
||||||
 删除键值对
|
 删除键值对
|
||||||
@ -69,6 +307,34 @@ Args:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def delete(self, key: str, ignore_key_error: bool=True) -> None:
|
||||||
|
"""
|
||||||
|
删除键值对
|
||||||
|
Args:
|
||||||
|
key: 键
|
||||||
|
ignore_key_error: 是否忽略键不存在的错误
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
if IS_MAIN_PROCESS:
|
||||||
|
lock = _get_lock(key)
|
||||||
|
with lock:
|
||||||
|
if key in self._store:
|
||||||
|
try:
|
||||||
|
del self._store[key]
|
||||||
|
del _locks[key]
|
||||||
|
except KeyError as e:
|
||||||
|
if not ignore_key_error:
|
||||||
|
raise e
|
||||||
|
else:
|
||||||
|
self.passive_chan.send(('delete', {'key': key}))
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `get_all(self) -> dict[str, Any]`
|
###   ***def*** `get_all(self) -> dict[str, Any]`
|
||||||
|
|
||||||
 获取所有键值对
|
 获取所有键值对
|
||||||
@ -77,6 +343,141 @@ Returns:
|
|||||||
|
|
||||||
dict[str, Any]: 键值对
|
dict[str, Any]: 键值对
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_all(self) -> dict[str, Any]:
|
||||||
|
"""
|
||||||
|
获取所有键值对
|
||||||
|
Returns:
|
||||||
|
dict[str, Any]: 键值对
|
||||||
|
"""
|
||||||
|
if IS_MAIN_PROCESS:
|
||||||
|
return self._store
|
||||||
|
else:
|
||||||
|
recv_chan = Channel[dict[str, Any]]('recv_chan')
|
||||||
|
self.passive_chan.send(('get_all', {'recv_chan': recv_chan}))
|
||||||
|
return recv_chan.receive()
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `publish(self, channel_: str, data: Any) -> None`
|
||||||
|
|
||||||
|
 发布消息
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
channel_: 频道
|
||||||
|
|
||||||
|
data: 数据
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def publish(self, channel_: str, data: Any) -> None:
|
||||||
|
"""
|
||||||
|
发布消息
|
||||||
|
Args:
|
||||||
|
channel_: 频道
|
||||||
|
data: 数据
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
self.active_chan.send(('publish', {'channel': channel_, 'data': data}))
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `on_subscriber_receive(self, channel_: str) -> Callable[[ON_RECEIVE_FUNC], ON_RECEIVE_FUNC]`
|
||||||
|
|
||||||
|
 订阅者接收消息时的回调
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
channel_: 频道
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
装饰器
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
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
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***@staticmethod***
|
||||||
|
###   ***def*** `run_subscriber_receive_funcs(channel_: str, data: Any) -> None`
|
||||||
|
|
||||||
|
 运行订阅者接收函数
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
channel_: 频道
|
||||||
|
|
||||||
|
data: 数据
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
@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_]])
|
||||||
|
elif 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_]])
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***class*** `GlobalKeyValueStore`
|
### ***class*** `GlobalKeyValueStore`
|
||||||
|
|
||||||
|
|
||||||
@ -86,6 +487,20 @@ Returns:
|
|||||||
|
|
||||||
 
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
@classmethod
|
||||||
|
def get_instance(cls):
|
||||||
|
if cls._instance is None:
|
||||||
|
with cls._lock:
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = KeyValueStore()
|
||||||
|
return cls._instance
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***attr*** `_instance: None`
|
###   ***attr*** `_instance: None`
|
||||||
|
|
||||||
###   ***attr*** `_lock: threading.Lock()`
|
###   ***attr*** `_lock: threading.Lock()`
|
||||||
@ -138,3 +553,11 @@ Returns:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ***var*** `data = self.active_chan.receive()`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ***var*** `data = self.publish_channel.receive()`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,24 +23,117 @@ Returns:
|
|||||||
|
|
||||||
扁平化后的配置文件,但也包含原有的键值对
|
扁平化后的配置文件,但也包含原有的键值对
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def flat_config(config: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
"""
|
||||||
|
扁平化配置文件
|
||||||
|
|
||||||
|
{a:{b:{c:1}}} -> {"a.b.c": 1}
|
||||||
|
Args:
|
||||||
|
config: 配置项目
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
扁平化后的配置文件,但也包含原有的键值对
|
||||||
|
"""
|
||||||
|
new_config = copy.deepcopy(config)
|
||||||
|
for key, value in config.items():
|
||||||
|
if isinstance(value, dict):
|
||||||
|
for k, v in flat_config(value).items():
|
||||||
|
new_config[f'{key}.{k}'] = v
|
||||||
|
return new_config
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `load_from_yaml(file: str) -> dict[str, Any]`
|
### ***def*** `load_from_yaml(file: str) -> dict[str, Any]`
|
||||||
|
|
||||||
Load config from yaml file
|
Load config from yaml file
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def load_from_yaml(file: str) -> dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Load config from yaml file
|
||||||
|
|
||||||
|
"""
|
||||||
|
logger.debug(f'Loading YAML config from {file}')
|
||||||
|
config = yaml.safe_load(open(file, 'r', encoding='utf-8'))
|
||||||
|
return flat_config(config if config is not None else {})
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `load_from_json(file: str) -> dict[str, Any]`
|
### ***def*** `load_from_json(file: str) -> dict[str, Any]`
|
||||||
|
|
||||||
Load config from json file
|
Load config from json file
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def load_from_json(file: str) -> dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Load config from json file
|
||||||
|
"""
|
||||||
|
logger.debug(f'Loading JSON config from {file}')
|
||||||
|
config = json.load(open(file, 'r', encoding='utf-8'))
|
||||||
|
return flat_config(config if config is not None else {})
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `load_from_toml(file: str) -> dict[str, Any]`
|
### ***def*** `load_from_toml(file: str) -> dict[str, Any]`
|
||||||
|
|
||||||
Load config from toml file
|
Load config from toml file
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def load_from_toml(file: str) -> dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Load config from toml file
|
||||||
|
"""
|
||||||
|
logger.debug(f'Loading TOML config from {file}')
|
||||||
|
config = toml.load(open(file, 'r', encoding='utf-8'))
|
||||||
|
return flat_config(config if config is not None else {})
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `load_from_files() -> dict[str, Any]`
|
### ***def*** `load_from_files() -> dict[str, Any]`
|
||||||
|
|
||||||
从指定文件加载配置项,会自动识别文件格式
|
从指定文件加载配置项,会自动识别文件格式
|
||||||
|
|
||||||
默认执行扁平化选项
|
默认执行扁平化选项
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def load_from_files(*files: str, no_warning: bool=False) -> dict[str, Any]:
|
||||||
|
"""
|
||||||
|
从指定文件加载配置项,会自动识别文件格式
|
||||||
|
默认执行扁平化选项
|
||||||
|
"""
|
||||||
|
config = {}
|
||||||
|
for file in files:
|
||||||
|
if os.path.exists(file):
|
||||||
|
if file.endswith(('.yaml', 'yml')):
|
||||||
|
config.update(load_from_yaml(file))
|
||||||
|
elif file.endswith('.json'):
|
||||||
|
config.update(load_from_json(file))
|
||||||
|
elif file.endswith('.toml'):
|
||||||
|
config.update(load_from_toml(file))
|
||||||
|
elif not no_warning:
|
||||||
|
logger.warning(f'Unsupported config file format: {file}')
|
||||||
|
elif not no_warning:
|
||||||
|
logger.warning(f'Config file not found: {file}')
|
||||||
|
return config
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `load_configs_from_dirs() -> dict[str, Any]`
|
### ***def*** `load_configs_from_dirs() -> dict[str, Any]`
|
||||||
|
|
||||||
从目录下加载配置文件,不递归
|
从目录下加载配置文件,不递归
|
||||||
@ -49,6 +142,29 @@ Load config from toml file
|
|||||||
|
|
||||||
默认执行扁平化选项
|
默认执行扁平化选项
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def load_configs_from_dirs(*directories: str, no_waring: bool=False) -> dict[str, Any]:
|
||||||
|
"""
|
||||||
|
从目录下加载配置文件,不递归
|
||||||
|
按照读取文件的优先级反向覆盖
|
||||||
|
默认执行扁平化选项
|
||||||
|
"""
|
||||||
|
config = {}
|
||||||
|
for directory in directories:
|
||||||
|
if not os.path.exists(directory):
|
||||||
|
if not no_waring:
|
||||||
|
logger.warning(f'Directory not found: {directory}')
|
||||||
|
continue
|
||||||
|
for file in os.listdir(directory):
|
||||||
|
if file.endswith(_SUPPORTED_CONFIG_FORMATS):
|
||||||
|
config.update(load_from_files(os.path.join(directory, file), no_warning=no_waring))
|
||||||
|
return config
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `load_config_in_default(no_waring: bool) -> dict[str, Any]`
|
### ***def*** `load_config_in_default(no_waring: bool) -> dict[str, Any]`
|
||||||
|
|
||||||
从一个标准的轻雪项目加载配置文件
|
从一个标准的轻雪项目加载配置文件
|
||||||
@ -57,6 +173,22 @@ Load config from toml file
|
|||||||
|
|
||||||
项目目录下的配置文件优先
|
项目目录下的配置文件优先
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def load_config_in_default(no_waring: bool=False) -> dict[str, Any]:
|
||||||
|
"""
|
||||||
|
从一个标准的轻雪项目加载配置文件
|
||||||
|
项目目录下的config.*和config目录下的所有配置文件
|
||||||
|
项目目录下的配置文件优先
|
||||||
|
"""
|
||||||
|
config = load_configs_from_dirs('config', no_waring=no_waring)
|
||||||
|
config.update(load_from_files('config.yaml', 'config.toml', 'config.json', 'config.yml', no_warning=no_waring))
|
||||||
|
return config
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***class*** `SatoriNodeConfig(BaseModel)`
|
### ***class*** `SatoriNodeConfig(BaseModel)`
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,10 +9,23 @@ category: API
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
###   ***def*** `__init__(self, active: Channel[Any], passive: Channel[Any], channel_deliver_active: Channel[Channel[Any]], channel_deliver_passive: Channel[tuple[str, dict]]) -> None`
|
###   ***def*** `__init__(self, active: Channel[Any], passive: Channel[Any], channel_deliver_active: Channel[Channel[Any]], channel_deliver_passive: Channel[tuple[str, dict]], publish: Channel[tuple[str, Any]]) -> None`
|
||||||
|
|
||||||
 
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def __init__(self, active: Channel[Any], passive: Channel[Any], channel_deliver_active: Channel[Channel[Any]], 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
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***class*** `ProcessManager`
|
### ***class*** `ProcessManager`
|
||||||
|
|
||||||
进程管理器
|
进程管理器
|
||||||
@ -21,6 +34,17 @@ category: API
|
|||||||
|
|
||||||
 
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def __init__(self, lifespan: 'Lifespan'):
|
||||||
|
self.lifespan = lifespan
|
||||||
|
self.targets: dict[str, tuple[Callable, tuple, dict]] = {}
|
||||||
|
self.processes: dict[str, Process] = {}
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `start(self, name: str) -> None`
|
###   ***def*** `start(self, name: str) -> None`
|
||||||
|
|
||||||
 开启后自动监控进程,并添加到进程字典中
|
 开启后自动监控进程,并添加到进程字典中
|
||||||
@ -31,10 +55,63 @@ Args:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def start(self, name: str):
|
||||||
|
"""
|
||||||
|
开启后自动监控进程,并添加到进程字典中
|
||||||
|
Args:
|
||||||
|
name:
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
if name not in self.targets:
|
||||||
|
raise KeyError(f'Process {name} not found.')
|
||||||
|
chan_active = get_channel(f'{name}-active')
|
||||||
|
|
||||||
|
def _start_process():
|
||||||
|
process = Process(target=self.targets[name][0], args=self.targets[name][1], kwargs=self.targets[name][2], daemon=True)
|
||||||
|
self.processes[name] = process
|
||||||
|
process.start()
|
||||||
|
_start_process()
|
||||||
|
while True:
|
||||||
|
data = chan_active.receive()
|
||||||
|
if data == 0:
|
||||||
|
logger.info(f'Stopping process {name}')
|
||||||
|
self.lifespan.before_process_shutdown()
|
||||||
|
self.terminate(name)
|
||||||
|
break
|
||||||
|
elif data == 1:
|
||||||
|
logger.info(f'Restarting process {name}')
|
||||||
|
self.lifespan.before_process_shutdown()
|
||||||
|
self.lifespan.before_process_restart()
|
||||||
|
self.terminate(name)
|
||||||
|
_start_process()
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
logger.warning('Unknown data received, ignored.')
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `start_all(self) -> None`
|
###   ***def*** `start_all(self) -> None`
|
||||||
|
|
||||||
 启动所有进程
|
 启动所有进程
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def start_all(self):
|
||||||
|
"""
|
||||||
|
启动所有进程
|
||||||
|
"""
|
||||||
|
for name in self.targets:
|
||||||
|
threading.Thread(target=self.start, args=(name,), daemon=True).start()
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `add_target(self, name: str, target: TARGET_FUNC, args: tuple, kwargs: Any) -> None`
|
###   ***def*** `add_target(self, name: str, target: TARGET_FUNC, args: tuple, kwargs: Any) -> None`
|
||||||
|
|
||||||
 添加进程
|
 添加进程
|
||||||
@ -49,10 +126,43 @@ Args:
|
|||||||
|
|
||||||
kwargs: 进程函数关键字参数,通常会默认传入chan_active和chan_passive
|
kwargs: 进程函数关键字参数,通常会默认传入chan_active和chan_passive
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def add_target(self, name: str, target: TARGET_FUNC, args: tuple=(), kwargs=None):
|
||||||
|
"""
|
||||||
|
添加进程
|
||||||
|
Args:
|
||||||
|
name: 进程名,用于获取和唯一标识
|
||||||
|
target: 进程函数
|
||||||
|
args: 进程函数参数
|
||||||
|
kwargs: 进程函数关键字参数,通常会默认传入chan_active和chan_passive
|
||||||
|
"""
|
||||||
|
if kwargs is None:
|
||||||
|
kwargs = {}
|
||||||
|
chan_active: Channel = Channel(_id=f'{name}-active')
|
||||||
|
chan_passive: Channel = Channel(_id=f'{name}-passive')
|
||||||
|
channel_deliver = ChannelDeliver(active=chan_active, passive=chan_passive, channel_deliver_active=channel_deliver_active_channel, channel_deliver_passive=channel_deliver_passive_channel, publish=publish_channel)
|
||||||
|
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})
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `join_all(self) -> None`
|
###   ***def*** `join_all(self) -> None`
|
||||||
|
|
||||||
 
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def join_all(self):
|
||||||
|
for name, process in self.targets:
|
||||||
|
process.join()
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `terminate(self, name: str) -> None`
|
###   ***def*** `terminate(self, name: str) -> None`
|
||||||
|
|
||||||
 终止进程并从进程字典中删除
|
 终止进程并从进程字典中删除
|
||||||
@ -65,10 +175,45 @@ Args:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def terminate(self, name: str):
|
||||||
|
"""
|
||||||
|
终止进程并从进程字典中删除
|
||||||
|
Args:
|
||||||
|
name:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
if name not in self.processes:
|
||||||
|
logger.warning(f'Process {name} not found.')
|
||||||
|
return
|
||||||
|
process = self.processes[name]
|
||||||
|
process.terminate()
|
||||||
|
process.join(TIMEOUT)
|
||||||
|
if process.is_alive():
|
||||||
|
process.kill()
|
||||||
|
logger.success(f'Process {name} terminated.')
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `terminate_all(self) -> None`
|
###   ***def*** `terminate_all(self) -> None`
|
||||||
|
|
||||||
 
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def terminate_all(self):
|
||||||
|
for name in self.targets:
|
||||||
|
self.terminate(name)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `is_process_alive(self, name: str) -> bool`
|
###   ***def*** `is_process_alive(self, name: str) -> bool`
|
||||||
|
|
||||||
 检查进程是否存活
|
 检查进程是否存活
|
||||||
@ -81,6 +226,25 @@ Args:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def is_process_alive(self, name: str) -> bool:
|
||||||
|
"""
|
||||||
|
检查进程是否存活
|
||||||
|
Args:
|
||||||
|
name:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
if name not in self.targets:
|
||||||
|
logger.warning(f'Process {name} not found.')
|
||||||
|
return self.processes[name].is_alive()
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***var*** `TIMEOUT = 10`
|
### ***var*** `TIMEOUT = 10`
|
||||||
|
|
||||||
|
|
||||||
@ -89,7 +253,7 @@ Returns:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
### ***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*** `channel_deliver = ChannelDeliver(active=chan_active, passive=chan_passive, channel_deliver_active=channel_deliver_active_channel, channel_deliver_passive=channel_deliver_passive_channel, publish=publish_channel)`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,6 +9,29 @@ category: API
|
|||||||
|
|
||||||
防抖函数
|
防抖函数
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def debounce(wait):
|
||||||
|
"""
|
||||||
|
防抖函数
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(func):
|
||||||
|
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
nonlocal last_call_time
|
||||||
|
current_time = time.time()
|
||||||
|
if current_time - last_call_time > wait:
|
||||||
|
last_call_time = current_time
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
last_call_time = None
|
||||||
|
return wrapper
|
||||||
|
return decorator
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `on_file_system_event(directories: tuple[str], recursive: bool, event_filter: FILTER_FUNC) -> Callable[[CALLBACK_FUNC], CALLBACK_FUNC]`
|
### ***def*** `on_file_system_event(directories: tuple[str], recursive: bool, event_filter: FILTER_FUNC) -> Callable[[CALLBACK_FUNC], CALLBACK_FUNC]`
|
||||||
|
|
||||||
注册文件系统变化监听器
|
注册文件系统变化监听器
|
||||||
@ -25,22 +48,111 @@ Returns:
|
|||||||
|
|
||||||
装饰器,装饰一个函数在接收到数据后执行
|
装饰器,装饰一个函数在接收到数据后执行
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_file_system_event(directories: tuple[str], recursive: bool=True, event_filter: FILTER_FUNC=None) -> Callable[[CALLBACK_FUNC], CALLBACK_FUNC]:
|
||||||
|
"""
|
||||||
|
注册文件系统变化监听器
|
||||||
|
Args:
|
||||||
|
directories: 监听目录们
|
||||||
|
recursive: 是否递归监听子目录
|
||||||
|
event_filter: 事件过滤器, 返回True则执行回调函数
|
||||||
|
Returns:
|
||||||
|
装饰器,装饰一个函数在接收到数据后执行
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(func: CALLBACK_FUNC) -> CALLBACK_FUNC:
|
||||||
|
|
||||||
|
def wrapper(event: FileSystemEvent):
|
||||||
|
if event_filter is not None and (not event_filter(event)):
|
||||||
|
return
|
||||||
|
func(event)
|
||||||
|
code_modified_handler = CodeModifiedHandler()
|
||||||
|
code_modified_handler.on_modified = wrapper
|
||||||
|
for directory in directories:
|
||||||
|
observer.schedule(code_modified_handler, directory, recursive=recursive)
|
||||||
|
return func
|
||||||
|
return decorator
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `decorator(func: Any) -> None`
|
### ***def*** `decorator(func: Any) -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def decorator(func):
|
||||||
|
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
nonlocal last_call_time
|
||||||
|
current_time = time.time()
|
||||||
|
if current_time - last_call_time > wait:
|
||||||
|
last_call_time = current_time
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
last_call_time = None
|
||||||
|
return wrapper
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `decorator(func: CALLBACK_FUNC) -> CALLBACK_FUNC`
|
### ***def*** `decorator(func: CALLBACK_FUNC) -> CALLBACK_FUNC`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def decorator(func: CALLBACK_FUNC) -> CALLBACK_FUNC:
|
||||||
|
|
||||||
|
def wrapper(event: FileSystemEvent):
|
||||||
|
if event_filter is not None and (not event_filter(event)):
|
||||||
|
return
|
||||||
|
func(event)
|
||||||
|
code_modified_handler = CodeModifiedHandler()
|
||||||
|
code_modified_handler.on_modified = wrapper
|
||||||
|
for directory in directories:
|
||||||
|
observer.schedule(code_modified_handler, directory, recursive=recursive)
|
||||||
|
return func
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `wrapper() -> None`
|
### ***def*** `wrapper() -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
nonlocal last_call_time
|
||||||
|
current_time = time.time()
|
||||||
|
if current_time - last_call_time > wait:
|
||||||
|
last_call_time = current_time
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `wrapper(event: FileSystemEvent) -> None`
|
### ***def*** `wrapper(event: FileSystemEvent) -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def wrapper(event: FileSystemEvent):
|
||||||
|
if event_filter is not None and (not event_filter(event)):
|
||||||
|
return
|
||||||
|
func(event)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***class*** `CodeModifiedHandler(FileSystemEventHandler)`
|
### ***class*** `CodeModifiedHandler(FileSystemEventHandler)`
|
||||||
|
|
||||||
Handler for code file changes
|
Handler for code file changes
|
||||||
@ -49,22 +161,68 @@ Handler for code file changes
|
|||||||
|
|
||||||
 
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
@debounce(1)
|
||||||
|
def on_modified(self, event):
|
||||||
|
raise NotImplementedError('on_modified must be implemented')
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `on_created(self, event: Any) -> None`
|
###   ***def*** `on_created(self, event: Any) -> None`
|
||||||
|
|
||||||
 
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_created(self, event):
|
||||||
|
self.on_modified(event)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `on_deleted(self, event: Any) -> None`
|
###   ***def*** `on_deleted(self, event: Any) -> None`
|
||||||
|
|
||||||
 
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_deleted(self, event):
|
||||||
|
self.on_modified(event)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `on_moved(self, event: Any) -> None`
|
###   ***def*** `on_moved(self, event: Any) -> None`
|
||||||
|
|
||||||
 
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_moved(self, event):
|
||||||
|
self.on_modified(event)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
###   ***def*** `on_any_event(self, event: Any) -> None`
|
###   ***def*** `on_any_event(self, event: Any) -> None`
|
||||||
|
|
||||||
 
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_any_event(self, event):
|
||||||
|
self.on_modified(event)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***var*** `liteyuki_bot = get_bot()`
|
### ***var*** `liteyuki_bot = get_bot()`
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,6 +13,25 @@ Args:
|
|||||||
|
|
||||||
module_path: 插件路径,参考`liteyuki.load_plugin`的函数签名
|
module_path: 插件路径,参考`liteyuki.load_plugin`的函数签名
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def run_plugins(*module_path: str | Path):
|
||||||
|
"""
|
||||||
|
运行插件,无需手动初始化bot
|
||||||
|
Args:
|
||||||
|
module_path: 插件路径,参考`liteyuki.load_plugin`的函数签名
|
||||||
|
"""
|
||||||
|
cfg = load_config_in_default()
|
||||||
|
plugins = cfg.get('liteyuki.plugins', [])
|
||||||
|
plugins.extend(module_path)
|
||||||
|
cfg['liteyuki.plugins'] = plugins
|
||||||
|
bot = LiteyukiBot(**cfg)
|
||||||
|
bot.run()
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***var*** `cfg = load_config_in_default()`
|
### ***var*** `cfg = load_config_in_default()`
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,12 +9,49 @@ category: API
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_format(level: str) -> str:
|
||||||
|
if level == 'DEBUG':
|
||||||
|
return debug_format
|
||||||
|
else:
|
||||||
|
return default_format
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `init_log(config: dict) -> None`
|
### ***def*** `init_log(config: dict) -> None`
|
||||||
|
|
||||||
在语言加载完成后执行
|
在语言加载完成后执行
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def init_log(config: dict):
|
||||||
|
"""
|
||||||
|
在语言加载完成后执行
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
logger.remove()
|
||||||
|
logger.add(sys.stdout, level=0, diagnose=False, format=get_format(config.get('log_level', 'INFO')))
|
||||||
|
show_icon = config.get('log_icon', True)
|
||||||
|
logger.level('DEBUG', color='<blue>', icon=f"{('🐛' if show_icon else '')}DEBUG")
|
||||||
|
logger.level('INFO', color='<normal>', icon=f"{('ℹ️' if show_icon else '')}INFO")
|
||||||
|
logger.level('SUCCESS', color='<green>', icon=f"{('✅' if show_icon else '')}SUCCESS")
|
||||||
|
logger.level('WARNING', color='<yellow>', icon=f"{('⚠️' if show_icon else '')}WARNING")
|
||||||
|
logger.level('ERROR', color='<red>', icon=f"{('⭕' if show_icon else '')}ERROR")
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***var*** `logger = loguru.logger`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### ***var*** `show_icon = config.get('log_icon', True)`
|
### ***var*** `show_icon = config.get('log_icon', True)`
|
||||||
|
|
||||||
|
|
||||||
|
7
docs/dev/api/message/README.md
Normal file
7
docs/dev/api/message/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: liteyuki.message
|
||||||
|
index: true
|
||||||
|
icon: laptop-code
|
||||||
|
category: API
|
||||||
|
---
|
||||||
|
|
106
docs/dev/api/message/event.md
Normal file
106
docs/dev/api/message/event.md
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
---
|
||||||
|
title: liteyuki.message.event
|
||||||
|
order: 1
|
||||||
|
icon: laptop-code
|
||||||
|
category: API
|
||||||
|
---
|
||||||
|
|
||||||
|
### ***class*** `MessageEvent`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###   ***def*** `__init__(self, bot_id: str, message: list[dict[str, Any]] | str, message_type: str, raw_message: str, session_id: str, session_type: str, receive_channel: str, data: Optional[dict[str, Any]]) -> None`
|
||||||
|
|
||||||
|
 轻雪抽象消息事件
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bot_id: 机器人ID
|
||||||
|
|
||||||
|
message: 消息,消息段数组[{type: str, data: dict[str, Any]}]
|
||||||
|
|
||||||
|
raw_message: 原始消息(通常为纯文本的格式)
|
||||||
|
|
||||||
|
message_type: 消息类型(private, group, other)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
session_id: 会话ID(私聊通常为用户ID,群聊通常为群ID)
|
||||||
|
|
||||||
|
session_type: 会话类型(private, group)
|
||||||
|
|
||||||
|
receive_channel: 接收频道(用于回复消息)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
data: 附加数据
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def __init__(self, bot_id: str, message: list[dict[str, Any]] | str, message_type: str, raw_message: str, session_id: str, session_type: str, receive_channel: str, data: Optional[dict[str, Any]]=None):
|
||||||
|
"""
|
||||||
|
轻雪抽象消息事件
|
||||||
|
Args:
|
||||||
|
|
||||||
|
bot_id: 机器人ID
|
||||||
|
message: 消息,消息段数组[{type: str, data: dict[str, Any]}]
|
||||||
|
raw_message: 原始消息(通常为纯文本的格式)
|
||||||
|
message_type: 消息类型(private, group, other)
|
||||||
|
|
||||||
|
session_id: 会话ID(私聊通常为用户ID,群聊通常为群ID)
|
||||||
|
session_type: 会话类型(private, group)
|
||||||
|
receive_channel: 接收频道(用于回复消息)
|
||||||
|
|
||||||
|
data: 附加数据
|
||||||
|
"""
|
||||||
|
if data is None:
|
||||||
|
data = {}
|
||||||
|
self.message_type = message_type
|
||||||
|
self.data = data
|
||||||
|
self.bot_id = bot_id
|
||||||
|
self.message = message
|
||||||
|
self.raw_message = raw_message
|
||||||
|
self.session_id = session_id
|
||||||
|
self.session_type = session_type
|
||||||
|
self.receive_channel = receive_channel
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `reply(self, message: str | dict[str, Any]) -> None`
|
||||||
|
|
||||||
|
 回复消息
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
message:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def reply(self, message: str | dict[str, Any]):
|
||||||
|
"""
|
||||||
|
回复消息
|
||||||
|
Args:
|
||||||
|
message:
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
reply_event = MessageEvent(message_type=self.session_type, message=message, raw_message='', data={'message': message}, bot_id=self.bot_id, session_id=self.session_id, session_type=self.session_type, receive_channel='_')
|
||||||
|
shared_memory.publish(self.receive_channel, reply_event)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***var*** `reply_event = MessageEvent(message_type=self.session_type, message=message, raw_message='', data={'message': message}, bot_id=self.bot_id, session_id=self.session_id, session_type=self.session_type, receive_channel='_')`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ***var*** `data = {}`
|
||||||
|
|
||||||
|
|
||||||
|
|
71
docs/dev/api/message/matcher.md
Normal file
71
docs/dev/api/message/matcher.md
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
---
|
||||||
|
title: liteyuki.message.matcher
|
||||||
|
order: 1
|
||||||
|
icon: laptop-code
|
||||||
|
category: API
|
||||||
|
---
|
||||||
|
|
||||||
|
### ***class*** `Matcher`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###   ***def*** `__init__(self, rule: Rule, priority: int, block: bool) -> None`
|
||||||
|
|
||||||
|
 匹配器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
rule: 规则
|
||||||
|
|
||||||
|
priority: 优先级 >= 0
|
||||||
|
|
||||||
|
block: 是否阻断后续优先级更低的匹配器
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
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] = []
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `handle(self, handler: EventHandler) -> EventHandler`
|
||||||
|
|
||||||
|
 添加处理函数,装饰器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
handler:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
EventHandler
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def handle(self, handler: EventHandler) -> EventHandler:
|
||||||
|
"""
|
||||||
|
添加处理函数,装饰器
|
||||||
|
Args:
|
||||||
|
handler:
|
||||||
|
Returns:
|
||||||
|
EventHandler
|
||||||
|
"""
|
||||||
|
self.handlers.append(handler)
|
||||||
|
return handler
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
39
docs/dev/api/message/on.md
Normal file
39
docs/dev/api/message/on.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
title: liteyuki.message.on
|
||||||
|
order: 1
|
||||||
|
icon: laptop-code
|
||||||
|
category: API
|
||||||
|
---
|
||||||
|
|
||||||
|
### ***def*** `on_message(rule: Rule, priority: int, block: bool) -> Matcher`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
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
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***var*** `current_priority = -1`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ***var*** `matcher = Matcher(rule, priority, block)`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ***var*** `current_priority = matcher.priority`
|
||||||
|
|
||||||
|
|
||||||
|
|
24
docs/dev/api/message/rule.md
Normal file
24
docs/dev/api/message/rule.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
title: liteyuki.message.rule
|
||||||
|
order: 1
|
||||||
|
icon: laptop-code
|
||||||
|
category: API
|
||||||
|
---
|
||||||
|
|
||||||
|
### ***class*** `Rule`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###   ***def*** `__init__(self, handler: Optional[RuleHandler]) -> None`
|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def __init__(self, handler: Optional[RuleHandler]=None):
|
||||||
|
self.handler = handler
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
7
docs/dev/api/message/session.md
Normal file
7
docs/dev/api/message/session.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: liteyuki.message.session
|
||||||
|
order: 1
|
||||||
|
icon: laptop-code
|
||||||
|
category: API
|
||||||
|
---
|
||||||
|
|
@ -15,6 +15,21 @@ Args:
|
|||||||
|
|
||||||
target_path: 目标路径
|
target_path: 目标路径
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_relative_path(base_path: str, target_path: str) -> str:
|
||||||
|
"""
|
||||||
|
获取相对路径
|
||||||
|
Args:
|
||||||
|
base_path: 基础路径
|
||||||
|
target_path: 目标路径
|
||||||
|
"""
|
||||||
|
return os.path.relpath(target_path, base_path)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `write_to_files(file_data: dict[str, str]) -> None`
|
### ***def*** `write_to_files(file_data: dict[str, str]) -> None`
|
||||||
|
|
||||||
输出文件
|
输出文件
|
||||||
@ -23,10 +38,42 @@ Args:
|
|||||||
|
|
||||||
file_data: 文件数据 相对路径
|
file_data: 文件数据 相对路径
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
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)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `get_file_list(module_folder: str) -> None`
|
### ***def*** `get_file_list(module_folder: str) -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
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
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `get_module_info_normal(file_path: str, ignore_private: bool) -> ModuleInfo`
|
### ***def*** `get_module_info_normal(file_path: str, ignore_private: bool) -> ModuleInfo`
|
||||||
|
|
||||||
获取函数和类
|
获取函数和类
|
||||||
@ -41,6 +88,67 @@ Returns:
|
|||||||
|
|
||||||
模块信息
|
模块信息
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
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)):
|
||||||
|
if not any((isinstance(parent, ast.ClassDef) for parent in ast.iter_child_nodes(node))) and (not ignore_private or not node.name.startswith('_')):
|
||||||
|
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), source_code=ast.unparse(node))
|
||||||
|
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:
|
||||||
|
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), source_code=ast.unparse(class_node)))
|
||||||
|
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
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `generate_markdown(module_info: ModuleInfo, front_matter: Any) -> str`
|
### ***def*** `generate_markdown(module_info: ModuleInfo, front_matter: Any) -> str`
|
||||||
|
|
||||||
生成模块的Markdown
|
生成模块的Markdown
|
||||||
@ -57,6 +165,60 @@ Returns:
|
|||||||
|
|
||||||
Markdown 字符串
|
Markdown 字符串
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
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'
|
||||||
|
content += f'<details>\n<summary>源代码</summary>\n\n```python\n{func.source_code}\n```\n</details>\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:
|
||||||
|
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'
|
||||||
|
content += f'<details>\n<summary>源代码</summary>\n\n```python\n{method.source_code}\n```\n</details>\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
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `generate_docs(module_folder: str, output_dir: str, with_top: bool, ignored_paths: Any) -> None`
|
### ***def*** `generate_docs(module_folder: str, output_dir: str, with_top: bool, ignored_paths: Any) -> None`
|
||||||
|
|
||||||
生成文档
|
生成文档
|
||||||
@ -71,6 +233,46 @@ Args:
|
|||||||
|
|
||||||
ignored_paths: 忽略的路径
|
ignored_paths: 忽略的路径
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***class*** `DefType(Enum)`
|
### ***class*** `DefType(Enum)`
|
||||||
|
|
||||||
|
|
||||||
@ -217,7 +419,7 @@ Args:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
### ***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*** `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), source_code=ast.unparse(node))`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,3 +13,17 @@ Returns:
|
|||||||
|
|
||||||
dict[str, Plugin]: 插件字典
|
dict[str, Plugin]: 插件字典
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_loaded_plugins() -> dict[str, Plugin]:
|
||||||
|
"""
|
||||||
|
获取已加载的插件
|
||||||
|
Returns:
|
||||||
|
dict[str, Plugin]: 插件字典
|
||||||
|
"""
|
||||||
|
return _plugins
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
@ -17,6 +17,34 @@ category: API
|
|||||||
|
|
||||||
或插件路径 `pathlib.Path(path/to/your/plugin)`
|
或插件路径 `pathlib.Path(path/to/your/plugin)`
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def load_plugin(module_path: str | Path) -> Optional[Plugin]:
|
||||||
|
"""加载单个插件,可以是本地插件或是通过 `pip` 安装的插件。
|
||||||
|
|
||||||
|
参数:
|
||||||
|
module_path: 插件名称 `path.to.your.plugin`
|
||||||
|
或插件路径 `pathlib.Path(path/to/your/plugin)`
|
||||||
|
"""
|
||||||
|
module_path = path_to_module_name(Path(module_path)) if isinstance(module_path, Path) else module_path
|
||||||
|
try:
|
||||||
|
module = import_module(module_path)
|
||||||
|
_plugins[module.__name__] = Plugin(name=module.__name__, module=module, module_name=module_path, metadata=module.__dict__.get('__plugin_metadata__', None))
|
||||||
|
display_name = module.__name__.split('.')[-1]
|
||||||
|
if module.__dict__.get('__plugin_meta__'):
|
||||||
|
metadata: 'PluginMetadata' = module.__dict__['__plugin_meta__']
|
||||||
|
display_name = format_display_name(f"{metadata.name}({module.__name__.split('.')[-1]})", metadata.type)
|
||||||
|
logger.opt(colors=True).success(f'Succeeded to load liteyuki plugin "{display_name}"')
|
||||||
|
return _plugins[module.__name__]
|
||||||
|
except Exception as e:
|
||||||
|
logger.opt(colors=True).success(f'Failed to load liteyuki plugin "<r>{module_path}</r>"')
|
||||||
|
traceback.print_exc()
|
||||||
|
return None
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `load_plugins() -> set[Plugin]`
|
### ***def*** `load_plugins() -> set[Plugin]`
|
||||||
|
|
||||||
导入文件夹下多个插件
|
导入文件夹下多个插件
|
||||||
@ -29,6 +57,46 @@ category: API
|
|||||||
|
|
||||||
ignore_warning: 是否忽略警告,通常是目录不存在或目录为空
|
ignore_warning: 是否忽略警告,通常是目录不存在或目录为空
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def load_plugins(*plugin_dir: str, ignore_warning: bool=True) -> set[Plugin]:
|
||||||
|
"""导入文件夹下多个插件
|
||||||
|
|
||||||
|
参数:
|
||||||
|
plugin_dir: 文件夹路径
|
||||||
|
ignore_warning: 是否忽略警告,通常是目录不存在或目录为空
|
||||||
|
"""
|
||||||
|
plugins = set()
|
||||||
|
for dir_path in plugin_dir:
|
||||||
|
if not os.path.exists(dir_path):
|
||||||
|
if not ignore_warning:
|
||||||
|
logger.warning(f"Plugins dir '{dir_path}' does not exist.")
|
||||||
|
continue
|
||||||
|
if not os.listdir(dir_path):
|
||||||
|
if not ignore_warning:
|
||||||
|
logger.warning(f"Plugins dir '{dir_path}' is empty.")
|
||||||
|
continue
|
||||||
|
if not os.path.isdir(dir_path):
|
||||||
|
if not ignore_warning:
|
||||||
|
logger.warning(f"Plugins dir '{dir_path}' is not a directory.")
|
||||||
|
continue
|
||||||
|
for f in os.listdir(dir_path):
|
||||||
|
path = Path(os.path.join(dir_path, f))
|
||||||
|
module_name = None
|
||||||
|
if os.path.isfile(path) and f.endswith('.py') and (f != '__init__.py'):
|
||||||
|
module_name = f'{path_to_module_name(Path(dir_path))}.{f[:-3]}'
|
||||||
|
elif os.path.isdir(path) and os.path.exists(os.path.join(path, '__init__.py')):
|
||||||
|
module_name = path_to_module_name(path)
|
||||||
|
if module_name:
|
||||||
|
load_plugin(module_name)
|
||||||
|
if _plugins.get(module_name):
|
||||||
|
plugins.add(_plugins[module_name])
|
||||||
|
return plugins
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `format_display_name(display_name: str, plugin_type: PluginType) -> str`
|
### ***def*** `format_display_name(display_name: str, plugin_type: PluginType) -> str`
|
||||||
|
|
||||||
设置插件名称颜色,根据不同类型插件设置颜色
|
设置插件名称颜色,根据不同类型插件设置颜色
|
||||||
@ -45,6 +113,34 @@ Returns:
|
|||||||
|
|
||||||
str: 设置后的插件名称 <y>name</y>
|
str: 设置后的插件名称 <y>name</y>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def format_display_name(display_name: str, plugin_type: PluginType) -> str:
|
||||||
|
"""
|
||||||
|
设置插件名称颜色,根据不同类型插件设置颜色
|
||||||
|
Args:
|
||||||
|
display_name: 插件名称
|
||||||
|
plugin_type: 插件类型
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 设置后的插件名称 <y>name</y>
|
||||||
|
"""
|
||||||
|
color = 'y'
|
||||||
|
match plugin_type:
|
||||||
|
case PluginType.APPLICATION:
|
||||||
|
color = 'm'
|
||||||
|
case PluginType.TEST:
|
||||||
|
color = 'g'
|
||||||
|
case PluginType.MODULE:
|
||||||
|
color = 'e'
|
||||||
|
case PluginType.SERVICE:
|
||||||
|
color = 'c'
|
||||||
|
return f'<{color}>{display_name} [{plugin_type.name}]</{color}>'
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***var*** `module_path = path_to_module_name(Path(module_path)) if isinstance(module_path, Path) else module_path`
|
### ***var*** `module_path = path_to_module_name(Path(module_path)) if isinstance(module_path, Path) else module_path`
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,12 +13,12 @@ category: API
|
|||||||
|
|
||||||
###   ***attr*** `SERVICE: 'service'`
|
###   ***attr*** `SERVICE: 'service'`
|
||||||
|
|
||||||
###   ***attr*** `IMPLEMENTATION: 'implementation'`
|
|
||||||
|
|
||||||
###   ***attr*** `MODULE: 'module'`
|
###   ***attr*** `MODULE: 'module'`
|
||||||
|
|
||||||
###   ***attr*** `UNCLASSIFIED: 'unclassified'`
|
###   ***attr*** `UNCLASSIFIED: 'unclassified'`
|
||||||
|
|
||||||
|
###   ***attr*** `TEST: 'test'`
|
||||||
|
|
||||||
### ***class*** `PluginMetadata(BaseModel)`
|
### ***class*** `PluginMetadata(BaseModel)`
|
||||||
|
|
||||||
轻雪插件元数据,由插件编写者提供,name为必填项
|
轻雪插件元数据,由插件编写者提供,name为必填项
|
||||||
@ -71,10 +71,6 @@ extra: dict[str, Any]
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
### ***var*** `IMPLEMENTATION = 'implementation'`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### ***var*** `MODULE = 'module'`
|
### ***var*** `MODULE = 'module'`
|
||||||
|
|
||||||
|
|
||||||
@ -83,6 +79,10 @@ extra: dict[str, Any]
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ***var*** `TEST = 'test'`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### ***var*** `model_config = {'arbitrary_types_allowed': True}`
|
### ***var*** `model_config = {'arbitrary_types_allowed': True}`
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,6 +17,27 @@ Returns:
|
|||||||
|
|
||||||
bool: 是否为协程可调用对象
|
bool: 是否为协程可调用对象
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def is_coroutine_callable(call: Callable[..., Any]) -> bool:
|
||||||
|
"""
|
||||||
|
判断是否为协程可调用对象
|
||||||
|
Args:
|
||||||
|
call: 可调用对象
|
||||||
|
Returns:
|
||||||
|
bool: 是否为协程可调用对象
|
||||||
|
"""
|
||||||
|
if inspect.isroutine(call):
|
||||||
|
return inspect.iscoroutinefunction(call)
|
||||||
|
if inspect.isclass(call):
|
||||||
|
return False
|
||||||
|
func_ = getattr(call, '__call__', None)
|
||||||
|
return inspect.iscoroutinefunction(func_)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `run_coroutine() -> None`
|
### ***def*** `run_coroutine() -> None`
|
||||||
|
|
||||||
运行协程
|
运行协程
|
||||||
@ -29,6 +50,37 @@ Args:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def run_coroutine(*coro: Coroutine):
|
||||||
|
"""
|
||||||
|
运行协程
|
||||||
|
Args:
|
||||||
|
coro:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
if loop.is_running():
|
||||||
|
for c in coro:
|
||||||
|
asyncio.ensure_future(c)
|
||||||
|
else:
|
||||||
|
for c in coro:
|
||||||
|
loop.run_until_complete(c)
|
||||||
|
except RuntimeError:
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
loop.run_until_complete(asyncio.gather(*coro))
|
||||||
|
loop.close()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f'Exception occurred: {e}')
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `path_to_module_name(path: Path) -> str`
|
### ***def*** `path_to_module_name(path: Path) -> str`
|
||||||
|
|
||||||
转换路径为模块名
|
转换路径为模块名
|
||||||
@ -41,6 +93,26 @@ Returns:
|
|||||||
|
|
||||||
str: 模块名
|
str: 模块名
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def path_to_module_name(path: Path) -> str:
|
||||||
|
"""
|
||||||
|
转换路径为模块名
|
||||||
|
Args:
|
||||||
|
path: 路径a/b/c/d -> a.b.c.d
|
||||||
|
Returns:
|
||||||
|
str: 模块名
|
||||||
|
"""
|
||||||
|
rel_path = path.resolve().relative_to(Path.cwd().resolve())
|
||||||
|
if rel_path.stem == '__init__':
|
||||||
|
return '.'.join(rel_path.parts[:-1])
|
||||||
|
else:
|
||||||
|
return '.'.join(rel_path.parts[:-1] + (rel_path.stem,))
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***def*** `async_wrapper(func: Callable[..., Any]) -> Callable[..., Coroutine]`
|
### ***def*** `async_wrapper(func: Callable[..., Any]) -> Callable[..., Coroutine]`
|
||||||
|
|
||||||
异步包装器
|
异步包装器
|
||||||
@ -53,10 +125,39 @@ Returns:
|
|||||||
|
|
||||||
Coroutine: Asynchronous Callable
|
Coroutine: Asynchronous Callable
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def async_wrapper(func: Callable[..., Any]) -> Callable[..., Coroutine]:
|
||||||
|
"""
|
||||||
|
异步包装器
|
||||||
|
Args:
|
||||||
|
func: Sync Callable
|
||||||
|
Returns:
|
||||||
|
Coroutine: Asynchronous Callable
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def wrapper(*args, **kwargs):
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
wrapper.__signature__ = inspect.signature(func)
|
||||||
|
return wrapper
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***async def*** `wrapper() -> None`
|
### ***async def*** `wrapper() -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def wrapper(*args, **kwargs):
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### ***var*** `IS_MAIN_PROCESS = multiprocessing.current_process().name == 'MainProcess'`
|
### ***var*** `IS_MAIN_PROCESS = multiprocessing.current_process().name == 'MainProcess'`
|
||||||
|
|
||||||
|
|
||||||
|
7
docs/en/dev/api/README.md
Normal file
7
docs/en/dev/api/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: liteyuki
|
||||||
|
index: true
|
||||||
|
icon: laptop-code
|
||||||
|
category: API
|
||||||
|
---
|
||||||
|
|
581
docs/en/dev/api/bot/README.md
Normal file
581
docs/en/dev/api/bot/README.md
Normal file
@ -0,0 +1,581 @@
|
|||||||
|
---
|
||||||
|
title: liteyuki.bot
|
||||||
|
index: true
|
||||||
|
icon: laptop-code
|
||||||
|
category: API
|
||||||
|
---
|
||||||
|
|
||||||
|
### ***def*** `get_bot() -> LiteyukiBot`
|
||||||
|
|
||||||
|
获取轻雪实例
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
LiteyukiBot: 当前的轻雪实例
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_bot() -> LiteyukiBot:
|
||||||
|
"""
|
||||||
|
获取轻雪实例
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
LiteyukiBot: 当前的轻雪实例
|
||||||
|
"""
|
||||||
|
if IS_MAIN_PROCESS:
|
||||||
|
if _BOT_INSTANCE is None:
|
||||||
|
raise RuntimeError('Liteyuki instance not initialized.')
|
||||||
|
return _BOT_INSTANCE
|
||||||
|
else:
|
||||||
|
raise RuntimeError("Can't get bot instance in sub process.")
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `get_config(key: str, default: Any) -> Any`
|
||||||
|
|
||||||
|
获取配置
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
key: 配置键
|
||||||
|
|
||||||
|
default: 默认值
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
Any: 配置值
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_config(key: str, default: Any=None) -> Any:
|
||||||
|
"""
|
||||||
|
获取配置
|
||||||
|
Args:
|
||||||
|
key: 配置键
|
||||||
|
default: 默认值
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Any: 配置值
|
||||||
|
"""
|
||||||
|
return get_bot().config.get(key, default)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `get_config_with_compat(key: str, compat_keys: tuple[str], default: Any) -> Any`
|
||||||
|
|
||||||
|
获取配置,兼容旧版本
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
key: 配置键
|
||||||
|
|
||||||
|
compat_keys: 兼容键
|
||||||
|
|
||||||
|
default: 默认值
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
Any: 配置值
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_config_with_compat(key: str, compat_keys: tuple[str], default: Any=None) -> Any:
|
||||||
|
"""
|
||||||
|
获取配置,兼容旧版本
|
||||||
|
Args:
|
||||||
|
key: 配置键
|
||||||
|
compat_keys: 兼容键
|
||||||
|
default: 默认值
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Any: 配置值
|
||||||
|
"""
|
||||||
|
if key in get_bot().config:
|
||||||
|
return get_bot().config[key]
|
||||||
|
for compat_key in compat_keys:
|
||||||
|
if compat_key in get_bot().config:
|
||||||
|
logger.warning(f'Config key "{compat_key}" will be deprecated, use "{key}" instead.')
|
||||||
|
return get_bot().config[compat_key]
|
||||||
|
return default
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `print_logo() -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def print_logo():
|
||||||
|
print('\x1b[34m' + '\n __ ______ ________ ________ __ __ __ __ __ __ ______ \n / | / |/ |/ |/ \\ / |/ | / |/ | / |/ |\n $$ | $$$$$$/ $$$$$$$$/ $$$$$$$$/ $$ \\ /$$/ $$ | $$ |$$ | /$$/ $$$$$$/ \n $$ | $$ | $$ | $$ |__ $$ \\/$$/ $$ | $$ |$$ |/$$/ $$ | \n $$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |$$ $$< $$ | \n $$ | $$ | $$ | $$$$$/ $$$$/ $$ | $$ |$$$$$ \\ $$ | \n $$ |_____ _$$ |_ $$ | $$ |_____ $$ | $$ \\__$$ |$$ |$$ \\ _$$ |_ \n $$ |/ $$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |/ $$ |\n $$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/ \n ' + '\x1b[0m')
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***class*** `LiteyukiBot`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###   ***def*** `__init__(self) -> None`
|
||||||
|
|
||||||
|
 初始化轻雪实例
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
*args:
|
||||||
|
|
||||||
|
**kwargs: 配置
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
|
"""
|
||||||
|
初始化轻雪实例
|
||||||
|
Args:
|
||||||
|
*args:
|
||||||
|
**kwargs: 配置
|
||||||
|
|
||||||
|
"""
|
||||||
|
'常规操作'
|
||||||
|
print_logo()
|
||||||
|
global _BOT_INSTANCE
|
||||||
|
_BOT_INSTANCE = self
|
||||||
|
'配置'
|
||||||
|
self.config: dict[str, Any] = kwargs
|
||||||
|
'初始化'
|
||||||
|
self.init(**self.config)
|
||||||
|
logger.info('Liteyuki is initializing...')
|
||||||
|
'生命周期管理'
|
||||||
|
self.lifespan = Lifespan()
|
||||||
|
self.process_manager: ProcessManager = ProcessManager(lifespan=self.lifespan)
|
||||||
|
'事件循环'
|
||||||
|
self.loop = asyncio.new_event_loop()
|
||||||
|
asyncio.set_event_loop(self.loop)
|
||||||
|
self.stop_event = threading.Event()
|
||||||
|
self.call_restart_count = 0
|
||||||
|
'加载插件加载器'
|
||||||
|
load_plugin('liteyuki.plugins.plugin_loader')
|
||||||
|
'信号处理'
|
||||||
|
signal.signal(signal.SIGINT, self._handle_exit)
|
||||||
|
signal.signal(signal.SIGTERM, self._handle_exit)
|
||||||
|
atexit.register(self.process_manager.terminate_all)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `run(self) -> None`
|
||||||
|
|
||||||
|
 启动逻辑
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def run(self):
|
||||||
|
"""
|
||||||
|
启动逻辑
|
||||||
|
"""
|
||||||
|
self.lifespan.before_start()
|
||||||
|
self.process_manager.start_all()
|
||||||
|
self.lifespan.after_start()
|
||||||
|
self.keep_alive()
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `keep_alive(self) -> None`
|
||||||
|
|
||||||
|
 保持轻雪运行
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def keep_alive(self):
|
||||||
|
"""
|
||||||
|
保持轻雪运行
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
while not self.stop_event.is_set():
|
||||||
|
time.sleep(0.5)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.info('Liteyuki is stopping...')
|
||||||
|
self.stop()
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `restart(self, delay: int) -> None`
|
||||||
|
|
||||||
|
 重启轻雪本体
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def restart(self, delay: int=0):
|
||||||
|
"""
|
||||||
|
重启轻雪本体
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self.call_restart_count < 1:
|
||||||
|
executable = sys.executable
|
||||||
|
args = sys.argv
|
||||||
|
logger.info('Restarting LiteyukiBot...')
|
||||||
|
time.sleep(delay)
|
||||||
|
if platform.system() == 'Windows':
|
||||||
|
cmd = 'start'
|
||||||
|
elif platform.system() == 'Linux':
|
||||||
|
cmd = 'nohup'
|
||||||
|
elif platform.system() == 'Darwin':
|
||||||
|
cmd = 'open'
|
||||||
|
else:
|
||||||
|
cmd = 'nohup'
|
||||||
|
self.process_manager.terminate_all()
|
||||||
|
threading.Thread(target=os.system, args=(f"{cmd} {executable} {' '.join(args)}",)).start()
|
||||||
|
sys.exit(0)
|
||||||
|
self.call_restart_count += 1
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `restart_process(self, name: Optional[str]) -> None`
|
||||||
|
|
||||||
|
 停止轻雪
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
name: 进程名称, 默认为None, 所有进程
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def restart_process(self, name: Optional[str]=None):
|
||||||
|
"""
|
||||||
|
停止轻雪
|
||||||
|
Args:
|
||||||
|
name: 进程名称, 默认为None, 所有进程
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
self.lifespan.before_process_shutdown()
|
||||||
|
self.lifespan.before_process_shutdown()
|
||||||
|
if name is not None:
|
||||||
|
chan_active = get_channel(f'{name}-active')
|
||||||
|
chan_active.send(1)
|
||||||
|
else:
|
||||||
|
for process_name in self.process_manager.processes:
|
||||||
|
chan_active = get_channel(f'{process_name}-active')
|
||||||
|
chan_active.send(1)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `init(self) -> None`
|
||||||
|
|
||||||
|
 初始化轻雪, 自动调用
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def init(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
初始化轻雪, 自动调用
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.init_logger()
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `init_logger(self) -> None`
|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def init_logger(self):
|
||||||
|
init_log(config=self.config)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `stop(self) -> None`
|
||||||
|
|
||||||
|
 停止轻雪
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def stop(self):
|
||||||
|
"""
|
||||||
|
停止轻雪
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.stop_event.set()
|
||||||
|
self.loop.stop()
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `on_before_start(self, func: LIFESPAN_FUNC) -> None`
|
||||||
|
|
||||||
|
 注册启动前的函数
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
func:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_before_start(self, func: LIFESPAN_FUNC):
|
||||||
|
"""
|
||||||
|
注册启动前的函数
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.lifespan.on_before_start(func)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `on_after_start(self, func: LIFESPAN_FUNC) -> None`
|
||||||
|
|
||||||
|
 注册启动后的函数
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
func:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_after_start(self, func: LIFESPAN_FUNC):
|
||||||
|
"""
|
||||||
|
注册启动后的函数
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.lifespan.on_after_start(func)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `on_after_shutdown(self, func: LIFESPAN_FUNC) -> None`
|
||||||
|
|
||||||
|
 注册停止后的函数:未实现
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
func:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_after_shutdown(self, func: LIFESPAN_FUNC):
|
||||||
|
"""
|
||||||
|
注册停止后的函数:未实现
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.lifespan.on_after_shutdown(func)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `on_before_process_shutdown(self, func: LIFESPAN_FUNC) -> None`
|
||||||
|
|
||||||
|
 注册进程停止前的函数,为子进程停止时调用
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
func:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_before_process_shutdown(self, func: LIFESPAN_FUNC):
|
||||||
|
"""
|
||||||
|
注册进程停止前的函数,为子进程停止时调用
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.lifespan.on_before_process_shutdown(func)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `on_before_process_restart(self, func: LIFESPAN_FUNC) -> None`
|
||||||
|
|
||||||
|
 注册进程重启前的函数,为子进程重启时调用
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
func:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_before_process_restart(self, func: LIFESPAN_FUNC):
|
||||||
|
"""
|
||||||
|
注册进程重启前的函数,为子进程重启时调用
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.lifespan.on_before_process_restart(func)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `on_after_restart(self, func: LIFESPAN_FUNC) -> None`
|
||||||
|
|
||||||
|
 注册重启后的函数:未实现
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
func:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_after_restart(self, func: LIFESPAN_FUNC):
|
||||||
|
"""
|
||||||
|
注册重启后的函数:未实现
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.lifespan.on_after_restart(func)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `on_after_nonebot_init(self, func: LIFESPAN_FUNC) -> None`
|
||||||
|
|
||||||
|
 注册nonebot初始化后的函数
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
func:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_after_nonebot_init(self, func: LIFESPAN_FUNC):
|
||||||
|
"""
|
||||||
|
注册nonebot初始化后的函数
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.lifespan.on_after_nonebot_init(func)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***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'`
|
||||||
|
|
||||||
|
|
||||||
|
|
450
docs/en/dev/api/bot/lifespan.md
Normal file
450
docs/en/dev/api/bot/lifespan.md
Normal file
@ -0,0 +1,450 @@
|
|||||||
|
---
|
||||||
|
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:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
@staticmethod
|
||||||
|
def run_funcs(funcs: list[LIFESPAN_FUNC | PROCESS_LIFESPAN_FUNC], *args, **kwargs) -> None:
|
||||||
|
"""
|
||||||
|
运行函数
|
||||||
|
Args:
|
||||||
|
funcs:
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
except RuntimeError:
|
||||||
|
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))
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***class*** `Lifespan`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###   ***def*** `__init__(self) -> None`
|
||||||
|
|
||||||
|
 轻雪生命周期管理,启动、停止、重启
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def __init__(self) -> None:
|
||||||
|
"""
|
||||||
|
轻雪生命周期管理,启动、停止、重启
|
||||||
|
"""
|
||||||
|
self.life_flag: int = 0
|
||||||
|
self._before_start_funcs: list[LIFESPAN_FUNC] = []
|
||||||
|
self._after_start_funcs: list[LIFESPAN_FUNC] = []
|
||||||
|
self._before_process_shutdown_funcs: list[LIFESPAN_FUNC] = []
|
||||||
|
self._after_shutdown_funcs: list[LIFESPAN_FUNC] = []
|
||||||
|
self._before_process_restart_funcs: list[LIFESPAN_FUNC] = []
|
||||||
|
self._after_restart_funcs: list[LIFESPAN_FUNC] = []
|
||||||
|
self._after_nonebot_init_funcs: list[LIFESPAN_FUNC] = []
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***@staticmethod***
|
||||||
|
###   ***def*** `run_funcs(funcs: list[LIFESPAN_FUNC | PROCESS_LIFESPAN_FUNC]) -> None`
|
||||||
|
|
||||||
|
 运行函数
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
funcs:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
@staticmethod
|
||||||
|
def run_funcs(funcs: list[LIFESPAN_FUNC | PROCESS_LIFESPAN_FUNC], *args, **kwargs) -> None:
|
||||||
|
"""
|
||||||
|
运行函数
|
||||||
|
Args:
|
||||||
|
funcs:
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
except RuntimeError:
|
||||||
|
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))
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `on_before_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
||||||
|
|
||||||
|
 注册启动时的函数
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
func:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
LIFESPAN_FUNC:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_before_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||||
|
"""
|
||||||
|
注册启动时的函数
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
Returns:
|
||||||
|
LIFESPAN_FUNC:
|
||||||
|
"""
|
||||||
|
self._before_start_funcs.append(func)
|
||||||
|
return func
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `on_after_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
||||||
|
|
||||||
|
 注册启动时的函数
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
func:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
LIFESPAN_FUNC:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_after_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||||
|
"""
|
||||||
|
注册启动时的函数
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
Returns:
|
||||||
|
LIFESPAN_FUNC:
|
||||||
|
"""
|
||||||
|
self._after_start_funcs.append(func)
|
||||||
|
return func
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `on_before_process_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
||||||
|
|
||||||
|
 注册停止前的函数
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
func:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
LIFESPAN_FUNC:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_before_process_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||||
|
"""
|
||||||
|
注册停止前的函数
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
Returns:
|
||||||
|
LIFESPAN_FUNC:
|
||||||
|
"""
|
||||||
|
self._before_process_shutdown_funcs.append(func)
|
||||||
|
return func
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `on_after_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
||||||
|
|
||||||
|
 注册停止后的函数
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
func:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
LIFESPAN_FUNC:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_after_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||||
|
"""
|
||||||
|
注册停止后的函数
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
LIFESPAN_FUNC:
|
||||||
|
|
||||||
|
"""
|
||||||
|
self._after_shutdown_funcs.append(func)
|
||||||
|
return func
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `on_before_process_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
||||||
|
|
||||||
|
 注册重启时的函数
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
func:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
LIFESPAN_FUNC:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_before_process_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||||
|
"""
|
||||||
|
注册重启时的函数
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
Returns:
|
||||||
|
LIFESPAN_FUNC:
|
||||||
|
"""
|
||||||
|
self._before_process_restart_funcs.append(func)
|
||||||
|
return func
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `on_after_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
||||||
|
|
||||||
|
 注册重启后的函数
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
func:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
LIFESPAN_FUNC:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_after_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||||
|
"""
|
||||||
|
注册重启后的函数
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
Returns:
|
||||||
|
LIFESPAN_FUNC:
|
||||||
|
"""
|
||||||
|
self._after_restart_funcs.append(func)
|
||||||
|
return func
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `on_after_nonebot_init(self, func: Any) -> None`
|
||||||
|
|
||||||
|
 注册 NoneBot 初始化后的函数
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
func:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_after_nonebot_init(self, func):
|
||||||
|
"""
|
||||||
|
注册 NoneBot 初始化后的函数
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
self._after_nonebot_init_funcs.append(func)
|
||||||
|
return func
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `before_start(self) -> None`
|
||||||
|
|
||||||
|
 启动前
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def before_start(self) -> None:
|
||||||
|
"""
|
||||||
|
启动前
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
logger.debug('Running before_start functions')
|
||||||
|
self.run_funcs(self._before_start_funcs)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `after_start(self) -> None`
|
||||||
|
|
||||||
|
 启动后
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def after_start(self) -> None:
|
||||||
|
"""
|
||||||
|
启动后
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
logger.debug('Running after_start functions')
|
||||||
|
self.run_funcs(self._after_start_funcs)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `before_process_shutdown(self) -> None`
|
||||||
|
|
||||||
|
 停止前
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def before_process_shutdown(self) -> None:
|
||||||
|
"""
|
||||||
|
停止前
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
logger.debug('Running before_shutdown functions')
|
||||||
|
self.run_funcs(self._before_process_shutdown_funcs)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `after_shutdown(self) -> None`
|
||||||
|
|
||||||
|
 停止后
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def after_shutdown(self) -> None:
|
||||||
|
"""
|
||||||
|
停止后
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
logger.debug('Running after_shutdown functions')
|
||||||
|
self.run_funcs(self._after_shutdown_funcs)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `before_process_restart(self) -> None`
|
||||||
|
|
||||||
|
 重启前
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def before_process_restart(self) -> None:
|
||||||
|
"""
|
||||||
|
重启前
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
logger.debug('Running before_restart functions')
|
||||||
|
self.run_funcs(self._before_process_restart_funcs)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `after_restart(self) -> None`
|
||||||
|
|
||||||
|
 重启后
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def after_restart(self) -> None:
|
||||||
|
"""
|
||||||
|
重启后
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
logger.debug('Running after_restart functions')
|
||||||
|
self.run_funcs(self._after_restart_funcs)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***var*** `tasks = []`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ***var*** `loop = asyncio.get_event_loop()`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ***var*** `loop = asyncio.new_event_loop()`
|
||||||
|
|
||||||
|
|
||||||
|
|
7
docs/en/dev/api/comm/README.md
Normal file
7
docs/en/dev/api/comm/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: liteyuki.comm
|
||||||
|
index: true
|
||||||
|
icon: laptop-code
|
||||||
|
category: API
|
||||||
|
---
|
||||||
|
|
427
docs/en/dev/api/comm/channel.md
Normal file
427
docs/en/dev/api/comm/channel.md
Normal file
@ -0,0 +1,427 @@
|
|||||||
|
---
|
||||||
|
title: liteyuki.comm.channel
|
||||||
|
order: 1
|
||||||
|
icon: laptop-code
|
||||||
|
category: API
|
||||||
|
---
|
||||||
|
|
||||||
|
### ***def*** `set_channel(name: str, channel: Channel) -> None`
|
||||||
|
|
||||||
|
设置通道实例
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
name: 通道名称
|
||||||
|
|
||||||
|
channel: 通道实例
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def set_channel(name: str, channel: Channel):
|
||||||
|
"""
|
||||||
|
设置通道实例
|
||||||
|
Args:
|
||||||
|
name: 通道名称
|
||||||
|
channel: 通道实例
|
||||||
|
"""
|
||||||
|
if not isinstance(channel, Channel):
|
||||||
|
raise TypeError(f'channel_ must be an instance of Channel, {type(channel)} found')
|
||||||
|
if IS_MAIN_PROCESS:
|
||||||
|
_channel[name] = channel
|
||||||
|
else:
|
||||||
|
channel_deliver_passive_channel.send(('set_channel', {'name': name, 'channel_': channel}))
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `set_channels(channels: dict[str, Channel]) -> None`
|
||||||
|
|
||||||
|
设置通道实例
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
channels: 通道名称
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def set_channels(channels: dict[str, Channel]):
|
||||||
|
"""
|
||||||
|
设置通道实例
|
||||||
|
Args:
|
||||||
|
channels: 通道名称
|
||||||
|
"""
|
||||||
|
for name, channel in channels.items():
|
||||||
|
set_channel(name, channel)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `get_channel(name: str) -> Channel`
|
||||||
|
|
||||||
|
获取通道实例
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
name: 通道名称
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_channel(name: str) -> Channel:
|
||||||
|
"""
|
||||||
|
获取通道实例
|
||||||
|
Args:
|
||||||
|
name: 通道名称
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
if IS_MAIN_PROCESS:
|
||||||
|
return _channel[name]
|
||||||
|
else:
|
||||||
|
recv_chan = Channel[Channel[Any]]('recv_chan')
|
||||||
|
channel_deliver_passive_channel.send(('get_channel', {'name': name, 'recv_chan': recv_chan}))
|
||||||
|
return recv_chan.receive()
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `get_channels() -> dict[str, Channel]`
|
||||||
|
|
||||||
|
获取通道实例
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_channels() -> dict[str, Channel]:
|
||||||
|
"""
|
||||||
|
获取通道实例
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
if IS_MAIN_PROCESS:
|
||||||
|
return _channel
|
||||||
|
else:
|
||||||
|
recv_chan = Channel[dict[str, Channel[Any]]]('recv_chan')
|
||||||
|
channel_deliver_passive_channel.send(('get_channels', {'recv_chan': recv_chan}))
|
||||||
|
return recv_chan.receive()
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `on_set_channel(data: tuple[str, dict[str, Any]]) -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
@channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == 'set_channel')
|
||||||
|
def on_set_channel(data: tuple[str, dict[str, Any]]):
|
||||||
|
name, channel = (data[1]['name'], data[1]['channel_'])
|
||||||
|
set_channel(name, channel)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `on_get_channel(data: tuple[str, dict[str, Any]]) -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
@channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == 'get_channel')
|
||||||
|
def on_get_channel(data: tuple[str, dict[str, Any]]):
|
||||||
|
name, recv_chan = (data[1]['name'], data[1]['recv_chan'])
|
||||||
|
recv_chan.send(get_channel(name))
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `on_get_channels(data: tuple[str, dict[str, Any]]) -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
@channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == 'get_channels')
|
||||||
|
def on_get_channels(data: tuple[str, dict[str, Any]]):
|
||||||
|
recv_chan = data[1]['recv_chan']
|
||||||
|
recv_chan.send(get_channels())
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `decorator(func: Callable[[T], Any]) -> Callable[[T], Any]`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def decorator(func: Callable[[T], Any]) -> Callable[[T], Any]:
|
||||||
|
global _func_id
|
||||||
|
|
||||||
|
async def wrapper(data: T) -> Any:
|
||||||
|
if filter_func is not None:
|
||||||
|
if is_coroutine_callable(filter_func):
|
||||||
|
if not await filter_func(data):
|
||||||
|
return
|
||||||
|
elif not filter_func(data):
|
||||||
|
return
|
||||||
|
if is_coroutine_callable(func):
|
||||||
|
return await func(data)
|
||||||
|
else:
|
||||||
|
return func(data)
|
||||||
|
_callback_funcs[_func_id] = wrapper
|
||||||
|
if IS_MAIN_PROCESS:
|
||||||
|
self._on_main_receive_funcs.append(_func_id)
|
||||||
|
else:
|
||||||
|
self._on_sub_receive_funcs.append(_func_id)
|
||||||
|
_func_id += 1
|
||||||
|
return func
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***async def*** `wrapper(data: T) -> Any`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def wrapper(data: T) -> Any:
|
||||||
|
if filter_func is not None:
|
||||||
|
if is_coroutine_callable(filter_func):
|
||||||
|
if not await filter_func(data):
|
||||||
|
return
|
||||||
|
elif not filter_func(data):
|
||||||
|
return
|
||||||
|
if is_coroutine_callable(func):
|
||||||
|
return await func(data)
|
||||||
|
else:
|
||||||
|
return func(data)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***class*** `Channel(Generic[T])`
|
||||||
|
|
||||||
|
通道类,可以在进程间和进程内通信,双向但同时只能有一个发送者和一个接收者
|
||||||
|
|
||||||
|
有两种接收工作方式,但是只能选择一种,主动接收和被动接收,主动接收使用 `receive` 方法,被动接收使用 `on_receive` 装饰器
|
||||||
|
|
||||||
|
###   ***def*** `__init__(self, _id: str, type_check: Optional[bool]) -> None`
|
||||||
|
|
||||||
|
 初始化通道
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
_id: 通道ID
|
||||||
|
|
||||||
|
type_check: 是否开启类型检查, 若为空,则传入泛型默认开启,否则默认关闭
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
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
|
||||||
|
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.is_sub_receive_loop_running = False
|
||||||
|
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
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `send(self, data: T) -> None`
|
||||||
|
|
||||||
|
 发送数据
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
data: 数据
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def send(self, data: T):
|
||||||
|
"""
|
||||||
|
发送数据
|
||||||
|
Args:
|
||||||
|
data: 数据
|
||||||
|
"""
|
||||||
|
if self.type_check:
|
||||||
|
_type = self._get_generic_type()
|
||||||
|
if _type is not None and (not self._validate_structure(data, _type)):
|
||||||
|
raise TypeError(f'Data must be an instance of {_type}, {type(data)} found')
|
||||||
|
if self._closed:
|
||||||
|
raise RuntimeError('Cannot send to a closed channel_')
|
||||||
|
self.conn_send.send(data)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `receive(self) -> T`
|
||||||
|
|
||||||
|
 接收数据
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def receive(self) -> T:
|
||||||
|
"""
|
||||||
|
接收数据
|
||||||
|
Args:
|
||||||
|
"""
|
||||||
|
if self._closed:
|
||||||
|
raise RuntimeError('Cannot receive from a closed channel_')
|
||||||
|
while True:
|
||||||
|
data = self.conn_recv.recv()
|
||||||
|
return data
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `close(self) -> None`
|
||||||
|
|
||||||
|
 关闭通道
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def close(self):
|
||||||
|
"""
|
||||||
|
关闭通道
|
||||||
|
"""
|
||||||
|
self._closed = True
|
||||||
|
self.conn_send.close()
|
||||||
|
self.conn_recv.close()
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `on_receive(self, filter_func: Optional[FILTER_FUNC]) -> Callable[[Callable[[T], Any]], Callable[[T], Any]]`
|
||||||
|
|
||||||
|
 接收数据并执行函数
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
filter_func: 过滤函数,为None则不过滤
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
装饰器,装饰一个函数在接收到数据后执行
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_receive(self, filter_func: Optional[FILTER_FUNC]=None) -> Callable[[Callable[[T], Any]], Callable[[T], Any]]:
|
||||||
|
"""
|
||||||
|
接收数据并执行函数
|
||||||
|
Args:
|
||||||
|
filter_func: 过滤函数,为None则不过滤
|
||||||
|
Returns:
|
||||||
|
装饰器,装饰一个函数在接收到数据后执行
|
||||||
|
"""
|
||||||
|
if not self.is_sub_receive_loop_running and (not IS_MAIN_PROCESS):
|
||||||
|
threading.Thread(target=self._start_sub_receive_loop, daemon=True).start()
|
||||||
|
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]:
|
||||||
|
global _func_id
|
||||||
|
|
||||||
|
async def wrapper(data: T) -> Any:
|
||||||
|
if filter_func is not None:
|
||||||
|
if is_coroutine_callable(filter_func):
|
||||||
|
if not await filter_func(data):
|
||||||
|
return
|
||||||
|
elif not filter_func(data):
|
||||||
|
return
|
||||||
|
if is_coroutine_callable(func):
|
||||||
|
return await func(data)
|
||||||
|
else:
|
||||||
|
return func(data)
|
||||||
|
_callback_funcs[_func_id] = wrapper
|
||||||
|
if IS_MAIN_PROCESS:
|
||||||
|
self._on_main_receive_funcs.append(_func_id)
|
||||||
|
else:
|
||||||
|
self._on_sub_receive_funcs.append(_func_id)
|
||||||
|
_func_id += 1
|
||||||
|
return func
|
||||||
|
return decorator
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***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*** `type_check = self._get_generic_type() is not None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ***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()`
|
||||||
|
|
||||||
|
|
||||||
|
|
25
docs/en/dev/api/comm/event.md
Normal file
25
docs/en/dev/api/comm/event.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
title: liteyuki.comm.event
|
||||||
|
order: 1
|
||||||
|
icon: laptop-code
|
||||||
|
category: API
|
||||||
|
---
|
||||||
|
|
||||||
|
### ***class*** `Event`
|
||||||
|
|
||||||
|
事件类
|
||||||
|
|
||||||
|
###   ***def*** `__init__(self, name: str, data: dict[str, Any]) -> None`
|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def __init__(self, name: str, data: dict[str, Any]):
|
||||||
|
self.name = name
|
||||||
|
self.data = data
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
563
docs/en/dev/api/comm/storage.md
Normal file
563
docs/en/dev/api/comm/storage.md
Normal file
@ -0,0 +1,563 @@
|
|||||||
|
---
|
||||||
|
title: liteyuki.comm.storage
|
||||||
|
order: 1
|
||||||
|
icon: laptop-code
|
||||||
|
category: API
|
||||||
|
---
|
||||||
|
|
||||||
|
### ***def*** `run_subscriber_receive_funcs(channel_: str, data: Any) -> None`
|
||||||
|
|
||||||
|
运行订阅者接收函数
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
channel_: 频道
|
||||||
|
|
||||||
|
data: 数据
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
@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_]])
|
||||||
|
elif 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_]])
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `on_get(data: tuple[str, dict[str, Any]]) -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
@shared_memory.passive_chan.on_receive(lambda d: d[0] == 'get')
|
||||||
|
def on_get(data: tuple[str, dict[str, Any]]):
|
||||||
|
key = data[1]['key']
|
||||||
|
default = data[1]['default']
|
||||||
|
recv_chan = data[1]['recv_chan']
|
||||||
|
recv_chan.send(shared_memory.get(key, default))
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `on_set(data: tuple[str, dict[str, Any]]) -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
@shared_memory.passive_chan.on_receive(lambda d: d[0] == 'set')
|
||||||
|
def on_set(data: tuple[str, dict[str, Any]]):
|
||||||
|
key = data[1]['key']
|
||||||
|
value = data[1]['value']
|
||||||
|
shared_memory.set(key, value)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `on_delete(data: tuple[str, dict[str, Any]]) -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
@shared_memory.passive_chan.on_receive(lambda d: d[0] == 'delete')
|
||||||
|
def on_delete(data: tuple[str, dict[str, Any]]):
|
||||||
|
key = data[1]['key']
|
||||||
|
shared_memory.delete(key)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `on_get_all(data: tuple[str, dict[str, Any]]) -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
@shared_memory.passive_chan.on_receive(lambda d: d[0] == 'get_all')
|
||||||
|
def on_get_all(data: tuple[str, dict[str, Any]]):
|
||||||
|
recv_chan = data[1]['recv_chan']
|
||||||
|
recv_chan.send(shared_memory.get_all())
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `on_publish(data: tuple[str, Any]) -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
@channel.publish_channel.on_receive()
|
||||||
|
def on_publish(data: tuple[str, Any]):
|
||||||
|
channel_, data = data
|
||||||
|
shared_memory.run_subscriber_receive_funcs(channel_, data)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `decorator(func: ON_RECEIVE_FUNC) -> ON_RECEIVE_FUNC`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
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
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***async def*** `wrapper(data: Any) -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def wrapper(data: Any):
|
||||||
|
if is_coroutine_callable(func):
|
||||||
|
await func(data)
|
||||||
|
else:
|
||||||
|
func(data)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***class*** `Subscriber`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###   ***def*** `__init__(self) -> None`
|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def __init__(self):
|
||||||
|
self._subscribers = {}
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `receive(self) -> Any`
|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def receive(self) -> Any:
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `unsubscribe(self) -> None`
|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def unsubscribe(self) -> None:
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***class*** `KeyValueStore`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###   ***def*** `__init__(self) -> None`
|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
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
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `set(self, key: str, value: Any) -> None`
|
||||||
|
|
||||||
|
 设置键值对
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
key: 键
|
||||||
|
|
||||||
|
value: 值
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def set(self, key: str, value: Any) -> None:
|
||||||
|
"""
|
||||||
|
设置键值对
|
||||||
|
Args:
|
||||||
|
key: 键
|
||||||
|
value: 值
|
||||||
|
|
||||||
|
"""
|
||||||
|
if IS_MAIN_PROCESS:
|
||||||
|
lock = _get_lock(key)
|
||||||
|
with lock:
|
||||||
|
self._store[key] = value
|
||||||
|
else:
|
||||||
|
self.passive_chan.send(('set', {'key': key, 'value': value}))
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `get(self, key: str, default: Optional[Any]) -> Optional[Any]`
|
||||||
|
|
||||||
|
 获取键值对
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
key: 键
|
||||||
|
|
||||||
|
default: 默认值
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
Any: 值
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get(self, key: str, default: Optional[Any]=None) -> Optional[Any]:
|
||||||
|
"""
|
||||||
|
获取键值对
|
||||||
|
Args:
|
||||||
|
key: 键
|
||||||
|
default: 默认值
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Any: 值
|
||||||
|
"""
|
||||||
|
if IS_MAIN_PROCESS:
|
||||||
|
lock = _get_lock(key)
|
||||||
|
with lock:
|
||||||
|
return self._store.get(key, default)
|
||||||
|
else:
|
||||||
|
recv_chan = Channel[Optional[Any]]('recv_chan')
|
||||||
|
self.passive_chan.send(('get', {'key': key, 'default': default, 'recv_chan': recv_chan}))
|
||||||
|
return recv_chan.receive()
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `delete(self, key: str, ignore_key_error: bool) -> None`
|
||||||
|
|
||||||
|
 删除键值对
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
key: 键
|
||||||
|
|
||||||
|
ignore_key_error: 是否忽略键不存在的错误
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def delete(self, key: str, ignore_key_error: bool=True) -> None:
|
||||||
|
"""
|
||||||
|
删除键值对
|
||||||
|
Args:
|
||||||
|
key: 键
|
||||||
|
ignore_key_error: 是否忽略键不存在的错误
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
if IS_MAIN_PROCESS:
|
||||||
|
lock = _get_lock(key)
|
||||||
|
with lock:
|
||||||
|
if key in self._store:
|
||||||
|
try:
|
||||||
|
del self._store[key]
|
||||||
|
del _locks[key]
|
||||||
|
except KeyError as e:
|
||||||
|
if not ignore_key_error:
|
||||||
|
raise e
|
||||||
|
else:
|
||||||
|
self.passive_chan.send(('delete', {'key': key}))
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `get_all(self) -> dict[str, Any]`
|
||||||
|
|
||||||
|
 获取所有键值对
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
dict[str, Any]: 键值对
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_all(self) -> dict[str, Any]:
|
||||||
|
"""
|
||||||
|
获取所有键值对
|
||||||
|
Returns:
|
||||||
|
dict[str, Any]: 键值对
|
||||||
|
"""
|
||||||
|
if IS_MAIN_PROCESS:
|
||||||
|
return self._store
|
||||||
|
else:
|
||||||
|
recv_chan = Channel[dict[str, Any]]('recv_chan')
|
||||||
|
self.passive_chan.send(('get_all', {'recv_chan': recv_chan}))
|
||||||
|
return recv_chan.receive()
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `publish(self, channel_: str, data: Any) -> None`
|
||||||
|
|
||||||
|
 发布消息
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
channel_: 频道
|
||||||
|
|
||||||
|
data: 数据
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def publish(self, channel_: str, data: Any) -> None:
|
||||||
|
"""
|
||||||
|
发布消息
|
||||||
|
Args:
|
||||||
|
channel_: 频道
|
||||||
|
data: 数据
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
self.active_chan.send(('publish', {'channel': channel_, 'data': data}))
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `on_subscriber_receive(self, channel_: str) -> Callable[[ON_RECEIVE_FUNC], ON_RECEIVE_FUNC]`
|
||||||
|
|
||||||
|
 订阅者接收消息时的回调
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
channel_: 频道
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
装饰器
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
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
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***@staticmethod***
|
||||||
|
###   ***def*** `run_subscriber_receive_funcs(channel_: str, data: Any) -> None`
|
||||||
|
|
||||||
|
 运行订阅者接收函数
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
channel_: 频道
|
||||||
|
|
||||||
|
data: 数据
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
@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_]])
|
||||||
|
elif 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_]])
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***class*** `GlobalKeyValueStore`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###   ***@classmethod***
|
||||||
|
###   ***def*** `get_instance(cls: Any) -> None`
|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
@classmethod
|
||||||
|
def get_instance(cls):
|
||||||
|
if cls._instance is None:
|
||||||
|
with cls._lock:
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = KeyValueStore()
|
||||||
|
return cls._instance
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***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')`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ***var*** `data = self.active_chan.receive()`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ***var*** `data = self.publish_channel.receive()`
|
||||||
|
|
||||||
|
|
||||||
|
|
231
docs/en/dev/api/config.md
Normal file
231
docs/en/dev/api/config.md
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
---
|
||||||
|
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:
|
||||||
|
|
||||||
|
扁平化后的配置文件,但也包含原有的键值对
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def flat_config(config: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
"""
|
||||||
|
扁平化配置文件
|
||||||
|
|
||||||
|
{a:{b:{c:1}}} -> {"a.b.c": 1}
|
||||||
|
Args:
|
||||||
|
config: 配置项目
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
扁平化后的配置文件,但也包含原有的键值对
|
||||||
|
"""
|
||||||
|
new_config = copy.deepcopy(config)
|
||||||
|
for key, value in config.items():
|
||||||
|
if isinstance(value, dict):
|
||||||
|
for k, v in flat_config(value).items():
|
||||||
|
new_config[f'{key}.{k}'] = v
|
||||||
|
return new_config
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `load_from_yaml(file: str) -> dict[str, Any]`
|
||||||
|
|
||||||
|
Load config from yaml file
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def load_from_yaml(file: str) -> dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Load config from yaml file
|
||||||
|
|
||||||
|
"""
|
||||||
|
logger.debug(f'Loading YAML config from {file}')
|
||||||
|
config = yaml.safe_load(open(file, 'r', encoding='utf-8'))
|
||||||
|
return flat_config(config if config is not None else {})
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `load_from_json(file: str) -> dict[str, Any]`
|
||||||
|
|
||||||
|
Load config from json file
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def load_from_json(file: str) -> dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Load config from json file
|
||||||
|
"""
|
||||||
|
logger.debug(f'Loading JSON config from {file}')
|
||||||
|
config = json.load(open(file, 'r', encoding='utf-8'))
|
||||||
|
return flat_config(config if config is not None else {})
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `load_from_toml(file: str) -> dict[str, Any]`
|
||||||
|
|
||||||
|
Load config from toml file
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def load_from_toml(file: str) -> dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Load config from toml file
|
||||||
|
"""
|
||||||
|
logger.debug(f'Loading TOML config from {file}')
|
||||||
|
config = toml.load(open(file, 'r', encoding='utf-8'))
|
||||||
|
return flat_config(config if config is not None else {})
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `load_from_files() -> dict[str, Any]`
|
||||||
|
|
||||||
|
从指定文件加载配置项,会自动识别文件格式
|
||||||
|
|
||||||
|
默认执行扁平化选项
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def load_from_files(*files: str, no_warning: bool=False) -> dict[str, Any]:
|
||||||
|
"""
|
||||||
|
从指定文件加载配置项,会自动识别文件格式
|
||||||
|
默认执行扁平化选项
|
||||||
|
"""
|
||||||
|
config = {}
|
||||||
|
for file in files:
|
||||||
|
if os.path.exists(file):
|
||||||
|
if file.endswith(('.yaml', 'yml')):
|
||||||
|
config.update(load_from_yaml(file))
|
||||||
|
elif file.endswith('.json'):
|
||||||
|
config.update(load_from_json(file))
|
||||||
|
elif file.endswith('.toml'):
|
||||||
|
config.update(load_from_toml(file))
|
||||||
|
elif not no_warning:
|
||||||
|
logger.warning(f'Unsupported config file format: {file}')
|
||||||
|
elif not no_warning:
|
||||||
|
logger.warning(f'Config file not found: {file}')
|
||||||
|
return config
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `load_configs_from_dirs() -> dict[str, Any]`
|
||||||
|
|
||||||
|
从目录下加载配置文件,不递归
|
||||||
|
|
||||||
|
按照读取文件的优先级反向覆盖
|
||||||
|
|
||||||
|
默认执行扁平化选项
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def load_configs_from_dirs(*directories: str, no_waring: bool=False) -> dict[str, Any]:
|
||||||
|
"""
|
||||||
|
从目录下加载配置文件,不递归
|
||||||
|
按照读取文件的优先级反向覆盖
|
||||||
|
默认执行扁平化选项
|
||||||
|
"""
|
||||||
|
config = {}
|
||||||
|
for directory in directories:
|
||||||
|
if not os.path.exists(directory):
|
||||||
|
if not no_waring:
|
||||||
|
logger.warning(f'Directory not found: {directory}')
|
||||||
|
continue
|
||||||
|
for file in os.listdir(directory):
|
||||||
|
if file.endswith(_SUPPORTED_CONFIG_FORMATS):
|
||||||
|
config.update(load_from_files(os.path.join(directory, file), no_warning=no_waring))
|
||||||
|
return config
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `load_config_in_default(no_waring: bool) -> dict[str, Any]`
|
||||||
|
|
||||||
|
从一个标准的轻雪项目加载配置文件
|
||||||
|
|
||||||
|
项目目录下的config.*和config目录下的所有配置文件
|
||||||
|
|
||||||
|
项目目录下的配置文件优先
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def load_config_in_default(no_waring: bool=False) -> dict[str, Any]:
|
||||||
|
"""
|
||||||
|
从一个标准的轻雪项目加载配置文件
|
||||||
|
项目目录下的config.*和config目录下的所有配置文件
|
||||||
|
项目目录下的配置文件优先
|
||||||
|
"""
|
||||||
|
config = load_configs_from_dirs('config', no_waring=no_waring)
|
||||||
|
config.update(load_from_files('config.yaml', 'config.toml', 'config.json', 'config.yml', no_warning=no_waring))
|
||||||
|
return config
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***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/en/dev/api/core/README.md
Normal file
7
docs/en/dev/api/core/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: liteyuki.core
|
||||||
|
index: true
|
||||||
|
icon: laptop-code
|
||||||
|
category: API
|
||||||
|
---
|
||||||
|
|
275
docs/en/dev/api/core/manager.md
Normal file
275
docs/en/dev/api/core/manager.md
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
---
|
||||||
|
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]], publish: Channel[tuple[str, Any]]) -> None`
|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def __init__(self, active: Channel[Any], passive: Channel[Any], channel_deliver_active: Channel[Channel[Any]], 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
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***class*** `ProcessManager`
|
||||||
|
|
||||||
|
进程管理器
|
||||||
|
|
||||||
|
###   ***def*** `__init__(self, lifespan: 'Lifespan') -> None`
|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def __init__(self, lifespan: 'Lifespan'):
|
||||||
|
self.lifespan = lifespan
|
||||||
|
self.targets: dict[str, tuple[Callable, tuple, dict]] = {}
|
||||||
|
self.processes: dict[str, Process] = {}
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `start(self, name: str) -> None`
|
||||||
|
|
||||||
|
 开启后自动监控进程,并添加到进程字典中
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
name:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def start(self, name: str):
|
||||||
|
"""
|
||||||
|
开启后自动监控进程,并添加到进程字典中
|
||||||
|
Args:
|
||||||
|
name:
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
if name not in self.targets:
|
||||||
|
raise KeyError(f'Process {name} not found.')
|
||||||
|
chan_active = get_channel(f'{name}-active')
|
||||||
|
|
||||||
|
def _start_process():
|
||||||
|
process = Process(target=self.targets[name][0], args=self.targets[name][1], kwargs=self.targets[name][2], daemon=True)
|
||||||
|
self.processes[name] = process
|
||||||
|
process.start()
|
||||||
|
_start_process()
|
||||||
|
while True:
|
||||||
|
data = chan_active.receive()
|
||||||
|
if data == 0:
|
||||||
|
logger.info(f'Stopping process {name}')
|
||||||
|
self.lifespan.before_process_shutdown()
|
||||||
|
self.terminate(name)
|
||||||
|
break
|
||||||
|
elif data == 1:
|
||||||
|
logger.info(f'Restarting process {name}')
|
||||||
|
self.lifespan.before_process_shutdown()
|
||||||
|
self.lifespan.before_process_restart()
|
||||||
|
self.terminate(name)
|
||||||
|
_start_process()
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
logger.warning('Unknown data received, ignored.')
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `start_all(self) -> None`
|
||||||
|
|
||||||
|
 启动所有进程
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def start_all(self):
|
||||||
|
"""
|
||||||
|
启动所有进程
|
||||||
|
"""
|
||||||
|
for name in self.targets:
|
||||||
|
threading.Thread(target=self.start, args=(name,), daemon=True).start()
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `add_target(self, name: str, target: TARGET_FUNC, args: tuple, kwargs: Any) -> None`
|
||||||
|
|
||||||
|
 添加进程
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
name: 进程名,用于获取和唯一标识
|
||||||
|
|
||||||
|
target: 进程函数
|
||||||
|
|
||||||
|
args: 进程函数参数
|
||||||
|
|
||||||
|
kwargs: 进程函数关键字参数,通常会默认传入chan_active和chan_passive
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def add_target(self, name: str, target: TARGET_FUNC, args: tuple=(), kwargs=None):
|
||||||
|
"""
|
||||||
|
添加进程
|
||||||
|
Args:
|
||||||
|
name: 进程名,用于获取和唯一标识
|
||||||
|
target: 进程函数
|
||||||
|
args: 进程函数参数
|
||||||
|
kwargs: 进程函数关键字参数,通常会默认传入chan_active和chan_passive
|
||||||
|
"""
|
||||||
|
if kwargs is None:
|
||||||
|
kwargs = {}
|
||||||
|
chan_active: Channel = Channel(_id=f'{name}-active')
|
||||||
|
chan_passive: Channel = Channel(_id=f'{name}-passive')
|
||||||
|
channel_deliver = ChannelDeliver(active=chan_active, passive=chan_passive, channel_deliver_active=channel_deliver_active_channel, channel_deliver_passive=channel_deliver_passive_channel, publish=publish_channel)
|
||||||
|
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})
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `join_all(self) -> None`
|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def join_all(self):
|
||||||
|
for name, process in self.targets:
|
||||||
|
process.join()
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `terminate(self, name: str) -> None`
|
||||||
|
|
||||||
|
 终止进程并从进程字典中删除
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
name:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def terminate(self, name: str):
|
||||||
|
"""
|
||||||
|
终止进程并从进程字典中删除
|
||||||
|
Args:
|
||||||
|
name:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
if name not in self.processes:
|
||||||
|
logger.warning(f'Process {name} not found.')
|
||||||
|
return
|
||||||
|
process = self.processes[name]
|
||||||
|
process.terminate()
|
||||||
|
process.join(TIMEOUT)
|
||||||
|
if process.is_alive():
|
||||||
|
process.kill()
|
||||||
|
logger.success(f'Process {name} terminated.')
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `terminate_all(self) -> None`
|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def terminate_all(self):
|
||||||
|
for name in self.targets:
|
||||||
|
self.terminate(name)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `is_process_alive(self, name: str) -> bool`
|
||||||
|
|
||||||
|
 检查进程是否存活
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
name:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def is_process_alive(self, name: str) -> bool:
|
||||||
|
"""
|
||||||
|
检查进程是否存活
|
||||||
|
Args:
|
||||||
|
name:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
if name not in self.targets:
|
||||||
|
logger.warning(f'Process {name} not found.')
|
||||||
|
return self.processes[name].is_alive()
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***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, publish=publish_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/en/dev/api/dev/README.md
Normal file
7
docs/en/dev/api/dev/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: liteyuki.dev
|
||||||
|
index: true
|
||||||
|
icon: laptop-code
|
||||||
|
category: API
|
||||||
|
---
|
||||||
|
|
249
docs/en/dev/api/dev/observer.md
Normal file
249
docs/en/dev/api/dev/observer.md
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
---
|
||||||
|
title: liteyuki.dev.observer
|
||||||
|
order: 1
|
||||||
|
icon: laptop-code
|
||||||
|
category: API
|
||||||
|
---
|
||||||
|
|
||||||
|
### ***def*** `debounce(wait: Any) -> None`
|
||||||
|
|
||||||
|
防抖函数
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def debounce(wait):
|
||||||
|
"""
|
||||||
|
防抖函数
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(func):
|
||||||
|
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
nonlocal last_call_time
|
||||||
|
current_time = time.time()
|
||||||
|
if current_time - last_call_time > wait:
|
||||||
|
last_call_time = current_time
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
last_call_time = None
|
||||||
|
return wrapper
|
||||||
|
return decorator
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***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:
|
||||||
|
|
||||||
|
装饰器,装饰一个函数在接收到数据后执行
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_file_system_event(directories: tuple[str], recursive: bool=True, event_filter: FILTER_FUNC=None) -> Callable[[CALLBACK_FUNC], CALLBACK_FUNC]:
|
||||||
|
"""
|
||||||
|
注册文件系统变化监听器
|
||||||
|
Args:
|
||||||
|
directories: 监听目录们
|
||||||
|
recursive: 是否递归监听子目录
|
||||||
|
event_filter: 事件过滤器, 返回True则执行回调函数
|
||||||
|
Returns:
|
||||||
|
装饰器,装饰一个函数在接收到数据后执行
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(func: CALLBACK_FUNC) -> CALLBACK_FUNC:
|
||||||
|
|
||||||
|
def wrapper(event: FileSystemEvent):
|
||||||
|
if event_filter is not None and (not event_filter(event)):
|
||||||
|
return
|
||||||
|
func(event)
|
||||||
|
code_modified_handler = CodeModifiedHandler()
|
||||||
|
code_modified_handler.on_modified = wrapper
|
||||||
|
for directory in directories:
|
||||||
|
observer.schedule(code_modified_handler, directory, recursive=recursive)
|
||||||
|
return func
|
||||||
|
return decorator
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `decorator(func: Any) -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def decorator(func):
|
||||||
|
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
nonlocal last_call_time
|
||||||
|
current_time = time.time()
|
||||||
|
if current_time - last_call_time > wait:
|
||||||
|
last_call_time = current_time
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
last_call_time = None
|
||||||
|
return wrapper
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `decorator(func: CALLBACK_FUNC) -> CALLBACK_FUNC`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def decorator(func: CALLBACK_FUNC) -> CALLBACK_FUNC:
|
||||||
|
|
||||||
|
def wrapper(event: FileSystemEvent):
|
||||||
|
if event_filter is not None and (not event_filter(event)):
|
||||||
|
return
|
||||||
|
func(event)
|
||||||
|
code_modified_handler = CodeModifiedHandler()
|
||||||
|
code_modified_handler.on_modified = wrapper
|
||||||
|
for directory in directories:
|
||||||
|
observer.schedule(code_modified_handler, directory, recursive=recursive)
|
||||||
|
return func
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `wrapper() -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
nonlocal last_call_time
|
||||||
|
current_time = time.time()
|
||||||
|
if current_time - last_call_time > wait:
|
||||||
|
last_call_time = current_time
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `wrapper(event: FileSystemEvent) -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def wrapper(event: FileSystemEvent):
|
||||||
|
if event_filter is not None and (not event_filter(event)):
|
||||||
|
return
|
||||||
|
func(event)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***class*** `CodeModifiedHandler(FileSystemEventHandler)`
|
||||||
|
|
||||||
|
Handler for code file changes
|
||||||
|
|
||||||
|
###   ***def*** `on_modified(self, event: Any) -> None`
|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
@debounce(1)
|
||||||
|
def on_modified(self, event):
|
||||||
|
raise NotImplementedError('on_modified must be implemented')
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `on_created(self, event: Any) -> None`
|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_created(self, event):
|
||||||
|
self.on_modified(event)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `on_deleted(self, event: Any) -> None`
|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_deleted(self, event):
|
||||||
|
self.on_modified(event)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `on_moved(self, event: Any) -> None`
|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_moved(self, event):
|
||||||
|
self.on_modified(event)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `on_any_event(self, event: Any) -> None`
|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_any_event(self, event):
|
||||||
|
self.on_modified(event)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***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`
|
||||||
|
|
||||||
|
|
||||||
|
|
46
docs/en/dev/api/dev/plugin.md
Normal file
46
docs/en/dev/api/dev/plugin.md
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
title: liteyuki.dev.plugin
|
||||||
|
order: 1
|
||||||
|
icon: laptop-code
|
||||||
|
category: API
|
||||||
|
---
|
||||||
|
|
||||||
|
### ***def*** `run_plugins() -> None`
|
||||||
|
|
||||||
|
运行插件,无需手动初始化bot
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
module_path: 插件路径,参考`liteyuki.load_plugin`的函数签名
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def run_plugins(*module_path: str | Path):
|
||||||
|
"""
|
||||||
|
运行插件,无需手动初始化bot
|
||||||
|
Args:
|
||||||
|
module_path: 插件路径,参考`liteyuki.load_plugin`的函数签名
|
||||||
|
"""
|
||||||
|
cfg = load_config_in_default()
|
||||||
|
plugins = cfg.get('liteyuki.plugins', [])
|
||||||
|
plugins.extend(module_path)
|
||||||
|
cfg['liteyuki.plugins'] = plugins
|
||||||
|
bot = LiteyukiBot(**cfg)
|
||||||
|
bot.run()
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***var*** `cfg = load_config_in_default()`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ***var*** `plugins = cfg.get('liteyuki.plugins', [])`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ***var*** `bot = LiteyukiBot(**cfg)`
|
||||||
|
|
||||||
|
|
||||||
|
|
11
docs/en/dev/api/exception.md
Normal file
11
docs/en/dev/api/exception.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
title: liteyuki.exception
|
||||||
|
order: 1
|
||||||
|
icon: laptop-code
|
||||||
|
category: API
|
||||||
|
---
|
||||||
|
|
||||||
|
### ***class*** `LiteyukiException(BaseException)`
|
||||||
|
|
||||||
|
Liteyuki的异常基类。
|
||||||
|
|
58
docs/en/dev/api/log.md
Normal file
58
docs/en/dev/api/log.md
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
---
|
||||||
|
title: liteyuki.log
|
||||||
|
order: 1
|
||||||
|
icon: laptop-code
|
||||||
|
category: API
|
||||||
|
---
|
||||||
|
|
||||||
|
### ***def*** `get_format(level: str) -> str`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_format(level: str) -> str:
|
||||||
|
if level == 'DEBUG':
|
||||||
|
return debug_format
|
||||||
|
else:
|
||||||
|
return default_format
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `init_log(config: dict) -> None`
|
||||||
|
|
||||||
|
在语言加载完成后执行
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def init_log(config: dict):
|
||||||
|
"""
|
||||||
|
在语言加载完成后执行
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
logger.remove()
|
||||||
|
logger.add(sys.stdout, level=0, diagnose=False, format=get_format(config.get('log_level', 'INFO')))
|
||||||
|
show_icon = config.get('log_icon', True)
|
||||||
|
logger.level('DEBUG', color='<blue>', icon=f"{('🐛' if show_icon else '')}DEBUG")
|
||||||
|
logger.level('INFO', color='<normal>', icon=f"{('ℹ️' if show_icon else '')}INFO")
|
||||||
|
logger.level('SUCCESS', color='<green>', icon=f"{('✅' if show_icon else '')}SUCCESS")
|
||||||
|
logger.level('WARNING', color='<yellow>', icon=f"{('⚠️' if show_icon else '')}WARNING")
|
||||||
|
logger.level('ERROR', color='<red>', icon=f"{('⭕' if show_icon else '')}ERROR")
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***var*** `logger = loguru.logger`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ***var*** `show_icon = config.get('log_icon', True)`
|
||||||
|
|
||||||
|
|
||||||
|
|
7
docs/en/dev/api/message/README.md
Normal file
7
docs/en/dev/api/message/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: liteyuki.message
|
||||||
|
index: true
|
||||||
|
icon: laptop-code
|
||||||
|
category: API
|
||||||
|
---
|
||||||
|
|
106
docs/en/dev/api/message/event.md
Normal file
106
docs/en/dev/api/message/event.md
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
---
|
||||||
|
title: liteyuki.message.event
|
||||||
|
order: 1
|
||||||
|
icon: laptop-code
|
||||||
|
category: API
|
||||||
|
---
|
||||||
|
|
||||||
|
### ***class*** `MessageEvent`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###   ***def*** `__init__(self, bot_id: str, message: list[dict[str, Any]] | str, message_type: str, raw_message: str, session_id: str, session_type: str, receive_channel: str, data: Optional[dict[str, Any]]) -> None`
|
||||||
|
|
||||||
|
 轻雪抽象消息事件
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bot_id: 机器人ID
|
||||||
|
|
||||||
|
message: 消息,消息段数组[{type: str, data: dict[str, Any]}]
|
||||||
|
|
||||||
|
raw_message: 原始消息(通常为纯文本的格式)
|
||||||
|
|
||||||
|
message_type: 消息类型(private, group, other)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
session_id: 会话ID(私聊通常为用户ID,群聊通常为群ID)
|
||||||
|
|
||||||
|
session_type: 会话类型(private, group)
|
||||||
|
|
||||||
|
receive_channel: 接收频道(用于回复消息)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
data: 附加数据
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def __init__(self, bot_id: str, message: list[dict[str, Any]] | str, message_type: str, raw_message: str, session_id: str, session_type: str, receive_channel: str, data: Optional[dict[str, Any]]=None):
|
||||||
|
"""
|
||||||
|
轻雪抽象消息事件
|
||||||
|
Args:
|
||||||
|
|
||||||
|
bot_id: 机器人ID
|
||||||
|
message: 消息,消息段数组[{type: str, data: dict[str, Any]}]
|
||||||
|
raw_message: 原始消息(通常为纯文本的格式)
|
||||||
|
message_type: 消息类型(private, group, other)
|
||||||
|
|
||||||
|
session_id: 会话ID(私聊通常为用户ID,群聊通常为群ID)
|
||||||
|
session_type: 会话类型(private, group)
|
||||||
|
receive_channel: 接收频道(用于回复消息)
|
||||||
|
|
||||||
|
data: 附加数据
|
||||||
|
"""
|
||||||
|
if data is None:
|
||||||
|
data = {}
|
||||||
|
self.message_type = message_type
|
||||||
|
self.data = data
|
||||||
|
self.bot_id = bot_id
|
||||||
|
self.message = message
|
||||||
|
self.raw_message = raw_message
|
||||||
|
self.session_id = session_id
|
||||||
|
self.session_type = session_type
|
||||||
|
self.receive_channel = receive_channel
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `reply(self, message: str | dict[str, Any]) -> None`
|
||||||
|
|
||||||
|
 回复消息
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
message:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def reply(self, message: str | dict[str, Any]):
|
||||||
|
"""
|
||||||
|
回复消息
|
||||||
|
Args:
|
||||||
|
message:
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
reply_event = MessageEvent(message_type=self.session_type, message=message, raw_message='', data={'message': message}, bot_id=self.bot_id, session_id=self.session_id, session_type=self.session_type, receive_channel='_')
|
||||||
|
shared_memory.publish(self.receive_channel, reply_event)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***var*** `reply_event = MessageEvent(message_type=self.session_type, message=message, raw_message='', data={'message': message}, bot_id=self.bot_id, session_id=self.session_id, session_type=self.session_type, receive_channel='_')`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ***var*** `data = {}`
|
||||||
|
|
||||||
|
|
||||||
|
|
71
docs/en/dev/api/message/matcher.md
Normal file
71
docs/en/dev/api/message/matcher.md
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
---
|
||||||
|
title: liteyuki.message.matcher
|
||||||
|
order: 1
|
||||||
|
icon: laptop-code
|
||||||
|
category: API
|
||||||
|
---
|
||||||
|
|
||||||
|
### ***class*** `Matcher`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###   ***def*** `__init__(self, rule: Rule, priority: int, block: bool) -> None`
|
||||||
|
|
||||||
|
 匹配器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
rule: 规则
|
||||||
|
|
||||||
|
priority: 优先级 >= 0
|
||||||
|
|
||||||
|
block: 是否阻断后续优先级更低的匹配器
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
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] = []
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
###   ***def*** `handle(self, handler: EventHandler) -> EventHandler`
|
||||||
|
|
||||||
|
 添加处理函数,装饰器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
handler:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
EventHandler
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def handle(self, handler: EventHandler) -> EventHandler:
|
||||||
|
"""
|
||||||
|
添加处理函数,装饰器
|
||||||
|
Args:
|
||||||
|
handler:
|
||||||
|
Returns:
|
||||||
|
EventHandler
|
||||||
|
"""
|
||||||
|
self.handlers.append(handler)
|
||||||
|
return handler
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
39
docs/en/dev/api/message/on.md
Normal file
39
docs/en/dev/api/message/on.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
title: liteyuki.message.on
|
||||||
|
order: 1
|
||||||
|
icon: laptop-code
|
||||||
|
category: API
|
||||||
|
---
|
||||||
|
|
||||||
|
### ***def*** `on_message(rule: Rule, priority: int, block: bool) -> Matcher`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
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
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***var*** `current_priority = -1`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ***var*** `matcher = Matcher(rule, priority, block)`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ***var*** `current_priority = matcher.priority`
|
||||||
|
|
||||||
|
|
||||||
|
|
24
docs/en/dev/api/message/rule.md
Normal file
24
docs/en/dev/api/message/rule.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
title: liteyuki.message.rule
|
||||||
|
order: 1
|
||||||
|
icon: laptop-code
|
||||||
|
category: API
|
||||||
|
---
|
||||||
|
|
||||||
|
### ***class*** `Rule`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###   ***def*** `__init__(self, handler: Optional[RuleHandler]) -> None`
|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def __init__(self, handler: Optional[RuleHandler]=None):
|
||||||
|
self.handler = handler
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
7
docs/en/dev/api/message/session.md
Normal file
7
docs/en/dev/api/message/session.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: liteyuki.message.session
|
||||||
|
order: 1
|
||||||
|
icon: laptop-code
|
||||||
|
category: API
|
||||||
|
---
|
||||||
|
|
473
docs/en/dev/api/mkdoc.md
Normal file
473
docs/en/dev/api/mkdoc.md
Normal file
@ -0,0 +1,473 @@
|
|||||||
|
---
|
||||||
|
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: 目标路径
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_relative_path(base_path: str, target_path: str) -> str:
|
||||||
|
"""
|
||||||
|
获取相对路径
|
||||||
|
Args:
|
||||||
|
base_path: 基础路径
|
||||||
|
target_path: 目标路径
|
||||||
|
"""
|
||||||
|
return os.path.relpath(target_path, base_path)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `write_to_files(file_data: dict[str, str]) -> None`
|
||||||
|
|
||||||
|
输出文件
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
file_data: 文件数据 相对路径
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
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)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `get_file_list(module_folder: str) -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
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
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `get_module_info_normal(file_path: str, ignore_private: bool) -> ModuleInfo`
|
||||||
|
|
||||||
|
获取函数和类
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
file_path: Python 文件路径
|
||||||
|
|
||||||
|
ignore_private: 忽略私有函数和类
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
模块信息
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
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)):
|
||||||
|
if not any((isinstance(parent, ast.ClassDef) for parent in ast.iter_child_nodes(node))) and (not ignore_private or not node.name.startswith('_')):
|
||||||
|
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), source_code=ast.unparse(node))
|
||||||
|
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:
|
||||||
|
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), source_code=ast.unparse(class_node)))
|
||||||
|
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
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `generate_markdown(module_info: ModuleInfo, front_matter: Any) -> str`
|
||||||
|
|
||||||
|
生成模块的Markdown
|
||||||
|
|
||||||
|
你可在此自定义生成的Markdown格式
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
module_info: 模块信息
|
||||||
|
|
||||||
|
front_matter: 自定义选项title, index, icon, category
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
Markdown 字符串
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
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'
|
||||||
|
content += f'<details>\n<summary>源代码</summary>\n\n```python\n{func.source_code}\n```\n</details>\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:
|
||||||
|
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'
|
||||||
|
content += f'<details>\n<summary>源代码</summary>\n\n```python\n{method.source_code}\n```\n</details>\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
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***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: 忽略的路径
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***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), source_code=ast.unparse(node))`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ***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`
|
||||||
|
|
||||||
|
|
||||||
|
|
29
docs/en/dev/api/plugin/README.md
Normal file
29
docs/en/dev/api/plugin/README.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
title: liteyuki.plugin
|
||||||
|
index: true
|
||||||
|
icon: laptop-code
|
||||||
|
category: API
|
||||||
|
---
|
||||||
|
|
||||||
|
### ***def*** `get_loaded_plugins() -> dict[str, Plugin]`
|
||||||
|
|
||||||
|
获取已加载的插件
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
dict[str, Plugin]: 插件字典
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_loaded_plugins() -> dict[str, Plugin]:
|
||||||
|
"""
|
||||||
|
获取已加载的插件
|
||||||
|
Returns:
|
||||||
|
dict[str, Plugin]: 插件字典
|
||||||
|
"""
|
||||||
|
return _plugins
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
199
docs/en/dev/api/plugin/load.md
Normal file
199
docs/en/dev/api/plugin/load.md
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
---
|
||||||
|
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)`
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def load_plugin(module_path: str | Path) -> Optional[Plugin]:
|
||||||
|
"""加载单个插件,可以是本地插件或是通过 `pip` 安装的插件。
|
||||||
|
|
||||||
|
参数:
|
||||||
|
module_path: 插件名称 `path.to.your.plugin`
|
||||||
|
或插件路径 `pathlib.Path(path/to/your/plugin)`
|
||||||
|
"""
|
||||||
|
module_path = path_to_module_name(Path(module_path)) if isinstance(module_path, Path) else module_path
|
||||||
|
try:
|
||||||
|
module = import_module(module_path)
|
||||||
|
_plugins[module.__name__] = Plugin(name=module.__name__, module=module, module_name=module_path, metadata=module.__dict__.get('__plugin_metadata__', None))
|
||||||
|
display_name = module.__name__.split('.')[-1]
|
||||||
|
if module.__dict__.get('__plugin_meta__'):
|
||||||
|
metadata: 'PluginMetadata' = module.__dict__['__plugin_meta__']
|
||||||
|
display_name = format_display_name(f"{metadata.name}({module.__name__.split('.')[-1]})", metadata.type)
|
||||||
|
logger.opt(colors=True).success(f'Succeeded to load liteyuki plugin "{display_name}"')
|
||||||
|
return _plugins[module.__name__]
|
||||||
|
except Exception as e:
|
||||||
|
logger.opt(colors=True).success(f'Failed to load liteyuki plugin "<r>{module_path}</r>"')
|
||||||
|
traceback.print_exc()
|
||||||
|
return None
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `load_plugins() -> set[Plugin]`
|
||||||
|
|
||||||
|
导入文件夹下多个插件
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
参数:
|
||||||
|
|
||||||
|
plugin_dir: 文件夹路径
|
||||||
|
|
||||||
|
ignore_warning: 是否忽略警告,通常是目录不存在或目录为空
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def load_plugins(*plugin_dir: str, ignore_warning: bool=True) -> set[Plugin]:
|
||||||
|
"""导入文件夹下多个插件
|
||||||
|
|
||||||
|
参数:
|
||||||
|
plugin_dir: 文件夹路径
|
||||||
|
ignore_warning: 是否忽略警告,通常是目录不存在或目录为空
|
||||||
|
"""
|
||||||
|
plugins = set()
|
||||||
|
for dir_path in plugin_dir:
|
||||||
|
if not os.path.exists(dir_path):
|
||||||
|
if not ignore_warning:
|
||||||
|
logger.warning(f"Plugins dir '{dir_path}' does not exist.")
|
||||||
|
continue
|
||||||
|
if not os.listdir(dir_path):
|
||||||
|
if not ignore_warning:
|
||||||
|
logger.warning(f"Plugins dir '{dir_path}' is empty.")
|
||||||
|
continue
|
||||||
|
if not os.path.isdir(dir_path):
|
||||||
|
if not ignore_warning:
|
||||||
|
logger.warning(f"Plugins dir '{dir_path}' is not a directory.")
|
||||||
|
continue
|
||||||
|
for f in os.listdir(dir_path):
|
||||||
|
path = Path(os.path.join(dir_path, f))
|
||||||
|
module_name = None
|
||||||
|
if os.path.isfile(path) and f.endswith('.py') and (f != '__init__.py'):
|
||||||
|
module_name = f'{path_to_module_name(Path(dir_path))}.{f[:-3]}'
|
||||||
|
elif os.path.isdir(path) and os.path.exists(os.path.join(path, '__init__.py')):
|
||||||
|
module_name = path_to_module_name(path)
|
||||||
|
if module_name:
|
||||||
|
load_plugin(module_name)
|
||||||
|
if _plugins.get(module_name):
|
||||||
|
plugins.add(_plugins[module_name])
|
||||||
|
return plugins
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `format_display_name(display_name: str, plugin_type: PluginType) -> str`
|
||||||
|
|
||||||
|
设置插件名称颜色,根据不同类型插件设置颜色
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
display_name: 插件名称
|
||||||
|
|
||||||
|
plugin_type: 插件类型
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
str: 设置后的插件名称 <y>name</y>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def format_display_name(display_name: str, plugin_type: PluginType) -> str:
|
||||||
|
"""
|
||||||
|
设置插件名称颜色,根据不同类型插件设置颜色
|
||||||
|
Args:
|
||||||
|
display_name: 插件名称
|
||||||
|
plugin_type: 插件类型
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 设置后的插件名称 <y>name</y>
|
||||||
|
"""
|
||||||
|
color = 'y'
|
||||||
|
match plugin_type:
|
||||||
|
case PluginType.APPLICATION:
|
||||||
|
color = 'm'
|
||||||
|
case PluginType.TEST:
|
||||||
|
color = 'g'
|
||||||
|
case PluginType.MODULE:
|
||||||
|
color = 'e'
|
||||||
|
case PluginType.SERVICE:
|
||||||
|
color = 'c'
|
||||||
|
return f'<{color}>{display_name} [{plugin_type.name}]</{color}>'
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***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/en/dev/api/plugin/manager.md
Normal file
7
docs/en/dev/api/plugin/manager.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: liteyuki.plugin.manager
|
||||||
|
order: 1
|
||||||
|
icon: laptop-code
|
||||||
|
category: API
|
||||||
|
---
|
||||||
|
|
89
docs/en/dev/api/plugin/model.md
Normal file
89
docs/en/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*** `MODULE: 'module'`
|
||||||
|
|
||||||
|
###   ***attr*** `UNCLASSIFIED: 'unclassified'`
|
||||||
|
|
||||||
|
###   ***attr*** `TEST: 'test'`
|
||||||
|
|
||||||
|
### ***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*** `MODULE = 'module'`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ***var*** `UNCLASSIFIED = 'unclassified'`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ***var*** `TEST = 'test'`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ***var*** `model_config = {'arbitrary_types_allowed': True}`
|
||||||
|
|
||||||
|
|
||||||
|
|
180
docs/en/dev/api/utils.md
Normal file
180
docs/en/dev/api/utils.md
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
---
|
||||||
|
title: liteyuki.utils
|
||||||
|
order: 1
|
||||||
|
icon: laptop-code
|
||||||
|
category: API
|
||||||
|
---
|
||||||
|
|
||||||
|
### ***def*** `is_coroutine_callable(call: Callable[..., Any]) -> bool`
|
||||||
|
|
||||||
|
判断是否为协程可调用对象
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
call: 可调用对象
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
bool: 是否为协程可调用对象
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def is_coroutine_callable(call: Callable[..., Any]) -> bool:
|
||||||
|
"""
|
||||||
|
判断是否为协程可调用对象
|
||||||
|
Args:
|
||||||
|
call: 可调用对象
|
||||||
|
Returns:
|
||||||
|
bool: 是否为协程可调用对象
|
||||||
|
"""
|
||||||
|
if inspect.isroutine(call):
|
||||||
|
return inspect.iscoroutinefunction(call)
|
||||||
|
if inspect.isclass(call):
|
||||||
|
return False
|
||||||
|
func_ = getattr(call, '__call__', None)
|
||||||
|
return inspect.iscoroutinefunction(func_)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `run_coroutine() -> None`
|
||||||
|
|
||||||
|
运行协程
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
coro:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def run_coroutine(*coro: Coroutine):
|
||||||
|
"""
|
||||||
|
运行协程
|
||||||
|
Args:
|
||||||
|
coro:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
if loop.is_running():
|
||||||
|
for c in coro:
|
||||||
|
asyncio.ensure_future(c)
|
||||||
|
else:
|
||||||
|
for c in coro:
|
||||||
|
loop.run_until_complete(c)
|
||||||
|
except RuntimeError:
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
loop.run_until_complete(asyncio.gather(*coro))
|
||||||
|
loop.close()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f'Exception occurred: {e}')
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `path_to_module_name(path: Path) -> str`
|
||||||
|
|
||||||
|
转换路径为模块名
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
path: 路径a/b/c/d -> a.b.c.d
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
str: 模块名
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def path_to_module_name(path: Path) -> str:
|
||||||
|
"""
|
||||||
|
转换路径为模块名
|
||||||
|
Args:
|
||||||
|
path: 路径a/b/c/d -> a.b.c.d
|
||||||
|
Returns:
|
||||||
|
str: 模块名
|
||||||
|
"""
|
||||||
|
rel_path = path.resolve().relative_to(Path.cwd().resolve())
|
||||||
|
if rel_path.stem == '__init__':
|
||||||
|
return '.'.join(rel_path.parts[:-1])
|
||||||
|
else:
|
||||||
|
return '.'.join(rel_path.parts[:-1] + (rel_path.stem,))
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***def*** `async_wrapper(func: Callable[..., Any]) -> Callable[..., Coroutine]`
|
||||||
|
|
||||||
|
异步包装器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
func: Sync Callable
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
Coroutine: Asynchronous Callable
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
def async_wrapper(func: Callable[..., Any]) -> Callable[..., Coroutine]:
|
||||||
|
"""
|
||||||
|
异步包装器
|
||||||
|
Args:
|
||||||
|
func: Sync Callable
|
||||||
|
Returns:
|
||||||
|
Coroutine: Asynchronous Callable
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def wrapper(*args, **kwargs):
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
wrapper.__signature__ = inspect.signature(func)
|
||||||
|
return wrapper
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***async def*** `wrapper() -> None`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>源代码</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def wrapper(*args, **kwargs):
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### ***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()`
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -33,7 +33,9 @@ __all__ = [
|
|||||||
"logger",
|
"logger",
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = "6.3.8" # 测试版本号
|
__version__ = "6.3.9" # 测试版本号
|
||||||
|
# 6.3.9
|
||||||
|
# 更改了on语法
|
||||||
|
|
||||||
# 6.3.8
|
# 6.3.8
|
||||||
# 1. 初步添加对聊天的支持
|
# 1. 初步添加对聊天的支持
|
||||||
|
@ -38,7 +38,7 @@ class Channel(Generic[T]):
|
|||||||
有两种接收工作方式,但是只能选择一种,主动接收和被动接收,主动接收使用 `receive` 方法,被动接收使用 `on_receive` 装饰器
|
有两种接收工作方式,但是只能选择一种,主动接收和被动接收,主动接收使用 `receive` 方法,被动接收使用 `on_receive` 装饰器
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, _id: str, type_check: Optional[bool] = None):
|
def __init__(self, _id: str = "", type_check: Optional[bool] = None):
|
||||||
"""
|
"""
|
||||||
初始化通道
|
初始化通道
|
||||||
Args:
|
Args:
|
||||||
@ -217,16 +217,6 @@ class Channel(Generic[T]):
|
|||||||
data = self.conn_recv.recv()
|
data = self.conn_recv.recv()
|
||||||
self._run_on_sub_receive_funcs(data)
|
self._run_on_sub_receive_funcs(data)
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __next__(self) -> Any:
|
|
||||||
return self.receive()
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
self.close()
|
|
||||||
logger.debug(f"Channel {self.name} deleted.")
|
|
||||||
|
|
||||||
|
|
||||||
"""子进程可用的主动和被动通道"""
|
"""子进程可用的主动和被动通道"""
|
||||||
active_channel: Optional["Channel"] = None
|
active_channel: Optional["Channel"] = None
|
||||||
|
@ -8,7 +8,7 @@ from typing import Any, Coroutine, Optional, TypeAlias, Callable
|
|||||||
|
|
||||||
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 Channel, ON_RECEIVE_FUNC, ASYNC_ON_RECEIVE_FUNC
|
||||||
from liteyuki.utils import IS_MAIN_PROCESS, is_coroutine_callable, run_coroutine
|
from liteyuki.utils import IS_MAIN_PROCESS, is_coroutine_callable, run_coroutine, run_coroutine_in_thread
|
||||||
|
|
||||||
if IS_MAIN_PROCESS:
|
if IS_MAIN_PROCESS:
|
||||||
_locks = {}
|
_locks = {}
|
||||||
@ -220,10 +220,10 @@ class KeyValueStore:
|
|||||||
"""
|
"""
|
||||||
if IS_MAIN_PROCESS:
|
if IS_MAIN_PROCESS:
|
||||||
if channel_ in _on_main_subscriber_receive_funcs and _on_main_subscriber_receive_funcs[channel_]:
|
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_]])
|
run_coroutine_in_thread(*[func(data) for func in _on_main_subscriber_receive_funcs[channel_]])
|
||||||
else:
|
else:
|
||||||
if channel_ in _on_sub_subscriber_receive_funcs and _on_sub_subscriber_receive_funcs[channel_]:
|
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_]])
|
run_coroutine_in_thread(*[func(data) for func in _on_sub_subscriber_receive_funcs[channel_]])
|
||||||
|
|
||||||
def _start_receive_loop(self):
|
def _start_receive_loop(self):
|
||||||
"""
|
"""
|
||||||
|
@ -8,32 +8,57 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|||||||
@File : event.py
|
@File : event.py
|
||||||
@Software: PyCharm
|
@Software: PyCharm
|
||||||
"""
|
"""
|
||||||
from typing import Any
|
from typing import Any, Optional
|
||||||
|
|
||||||
from liteyuki.comm.storage import shared_memory
|
from liteyuki.comm.storage import shared_memory
|
||||||
|
|
||||||
|
|
||||||
class Event:
|
class MessageEvent:
|
||||||
def __init__(self, type: str, data: dict[str, Any], bot_id: str, session_id: str, session_type: str, receive_channel: str = "event_to_nonebot"):
|
def __init__(
|
||||||
|
self,
|
||||||
|
|
||||||
|
bot_id: str,
|
||||||
|
message: list[dict[str, Any]] | str,
|
||||||
|
message_type: str,
|
||||||
|
raw_message: str,
|
||||||
|
session_id: str,
|
||||||
|
session_type: str,
|
||||||
|
receive_channel: str,
|
||||||
|
data: Optional[dict[str, Any]] = None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
事件
|
轻雪抽象消息事件
|
||||||
Args:
|
Args:
|
||||||
type: 类型
|
|
||||||
data: 数据
|
|
||||||
bot_id: 机器人ID
|
bot_id: 机器人ID
|
||||||
session_id: 会话ID
|
message: 消息,消息段数组[{type: str, data: dict[str, Any]}]
|
||||||
session_type: 会话类型
|
raw_message: 原始消息(通常为纯文本的格式)
|
||||||
receive_channel: 接收频道
|
message_type: 消息类型(private, group, other)
|
||||||
|
|
||||||
|
session_id: 会话ID(私聊通常为用户ID,群聊通常为群ID)
|
||||||
|
session_type: 会话类型(private, group)
|
||||||
|
receive_channel: 接收频道(用于回复消息)
|
||||||
|
|
||||||
|
data: 附加数据
|
||||||
"""
|
"""
|
||||||
self.type = type
|
|
||||||
|
if data is None:
|
||||||
|
data = {}
|
||||||
|
self.message_type = message_type
|
||||||
self.data = data
|
self.data = data
|
||||||
self.bot_id = bot_id
|
self.bot_id = bot_id
|
||||||
|
|
||||||
|
self.message = message
|
||||||
|
self.raw_message = raw_message
|
||||||
|
|
||||||
self.session_id = session_id
|
self.session_id = session_id
|
||||||
self.session_type = session_type
|
self.session_type = session_type
|
||||||
|
|
||||||
self.receive_channel = receive_channel
|
self.receive_channel = receive_channel
|
||||||
|
|
||||||
def __str__(self):
|
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})"
|
return (f"Event(message_type={self.message_type}, data={self.data}, bot_id={self.bot_id}, "
|
||||||
|
f"session_id={self.session_id}, session_type={self.session_type})")
|
||||||
|
|
||||||
def reply(self, message: str | dict[str, Any]):
|
def reply(self, message: str | dict[str, Any]):
|
||||||
"""
|
"""
|
||||||
@ -42,8 +67,10 @@ class Event:
|
|||||||
message:
|
message:
|
||||||
Returns:
|
Returns:
|
||||||
"""
|
"""
|
||||||
to_nonebot_event = Event(
|
reply_event = MessageEvent(
|
||||||
type=self.session_type,
|
message_type=self.session_type,
|
||||||
|
message=message,
|
||||||
|
raw_message="",
|
||||||
data={
|
data={
|
||||||
"message": message
|
"message": message
|
||||||
},
|
},
|
||||||
@ -52,5 +79,4 @@ class Event:
|
|||||||
session_type=self.session_type,
|
session_type=self.session_type,
|
||||||
receive_channel="_"
|
receive_channel="_"
|
||||||
)
|
)
|
||||||
print(to_nonebot_event)
|
shared_memory.publish(self.receive_channel, reply_event)
|
||||||
shared_memory.publish(self.receive_channel, to_nonebot_event)
|
|
||||||
|
@ -11,10 +11,10 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|||||||
import traceback
|
import traceback
|
||||||
from typing import Any, TypeAlias, Callable, Coroutine
|
from typing import Any, TypeAlias, Callable, Coroutine
|
||||||
|
|
||||||
from liteyuki import Event
|
from liteyuki.message.event import MessageEvent
|
||||||
from liteyuki.message.rule import Rule
|
from liteyuki.message.rule import Rule
|
||||||
|
|
||||||
EventHandler: TypeAlias = Callable[[Event], Coroutine[None, None, Any]]
|
EventHandler: TypeAlias = Callable[[MessageEvent], Coroutine[None, None, Any]]
|
||||||
|
|
||||||
|
|
||||||
class Matcher:
|
class Matcher:
|
||||||
@ -34,18 +34,19 @@ class Matcher:
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Matcher(rule={self.rule}, priority={self.priority}, block={self.block})"
|
return f"Matcher(rule={self.rule}, priority={self.priority}, block={self.block})"
|
||||||
|
|
||||||
def handle(self, handler: EventHandler) -> EventHandler:
|
def handle(self) -> Callable[[EventHandler], EventHandler]:
|
||||||
"""
|
"""
|
||||||
添加处理函数,装饰器
|
添加处理函数,装饰器
|
||||||
Args:
|
|
||||||
handler:
|
|
||||||
Returns:
|
Returns:
|
||||||
EventHandler
|
装饰器 handler
|
||||||
"""
|
"""
|
||||||
self.handlers.append(handler)
|
def decorator(handler: EventHandler) -> EventHandler:
|
||||||
return handler
|
self.handlers.append(handler)
|
||||||
|
return handler
|
||||||
|
|
||||||
async def run(self, event: Event) -> None:
|
return decorator
|
||||||
|
|
||||||
|
async def run(self, event: MessageEvent) -> None:
|
||||||
"""
|
"""
|
||||||
运行处理函数
|
运行处理函数
|
||||||
Args:
|
Args:
|
||||||
|
@ -8,22 +8,21 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|||||||
@File : on.py
|
@File : on.py
|
||||||
@Software: PyCharm
|
@Software: PyCharm
|
||||||
"""
|
"""
|
||||||
import threading
|
|
||||||
|
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
|
|
||||||
from liteyuki.comm.storage import shared_memory
|
from liteyuki.comm.storage import shared_memory
|
||||||
from liteyuki.log import logger
|
from liteyuki.log import logger
|
||||||
from liteyuki.message.event import Event
|
from liteyuki.message.event import MessageEvent
|
||||||
from liteyuki.message.matcher import Matcher
|
from liteyuki.message.matcher import Matcher
|
||||||
from liteyuki.message.rule import Rule
|
from liteyuki.message.rule import Rule, empty_rule
|
||||||
|
|
||||||
_matcher_list: list[Matcher] = []
|
_matcher_list: list[Matcher] = []
|
||||||
_queue: Queue = Queue()
|
_queue: Queue = Queue()
|
||||||
|
|
||||||
|
|
||||||
@shared_memory.on_subscriber_receive("event_to_liteyuki")
|
@shared_memory.on_subscriber_receive("event_to_liteyuki")
|
||||||
async def _(event: Event):
|
async def _(event: MessageEvent):
|
||||||
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}")
|
||||||
@ -35,7 +34,7 @@ async def _(event: Event):
|
|||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def on_message(rule: Rule = Rule(), priority: int = 0, block: bool = True) -> Matcher:
|
def on_message(rule: Rule = empty_rule, priority: int = 0, block: bool = False) -> Matcher:
|
||||||
matcher = Matcher(rule, priority, block)
|
matcher = Matcher(rule, priority, block)
|
||||||
# 按照优先级插入
|
# 按照优先级插入
|
||||||
for i, m in enumerate(_matcher_list):
|
for i, m in enumerate(_matcher_list):
|
||||||
@ -45,3 +44,10 @@ def on_message(rule: Rule = Rule(), priority: int = 0, block: bool = True) -> Ma
|
|||||||
else:
|
else:
|
||||||
_matcher_list.append(matcher)
|
_matcher_list.append(matcher)
|
||||||
return matcher
|
return matcher
|
||||||
|
|
||||||
|
|
||||||
|
def on_keywords(keywords: list[str], rule=empty_rule, priority: int = 0, block: bool = False) -> Matcher:
|
||||||
|
@Rule
|
||||||
|
async def on_keywords_rule(event: MessageEvent):
|
||||||
|
return any(keyword in event.raw_message for keyword in keywords)
|
||||||
|
return on_message(on_keywords_rule & rule, priority, block)
|
||||||
|
@ -8,26 +8,37 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|||||||
@File : rule.py
|
@File : rule.py
|
||||||
@Software: PyCharm
|
@Software: PyCharm
|
||||||
"""
|
"""
|
||||||
|
import inspect
|
||||||
from typing import Optional, TypeAlias, Callable, Coroutine
|
from typing import Optional, TypeAlias, Callable, Coroutine
|
||||||
|
|
||||||
from liteyuki.message.event import Event
|
from liteyuki.message.event import MessageEvent
|
||||||
|
|
||||||
RuleHandler: TypeAlias = Callable[[Event], Coroutine[None, None, bool]]
|
RuleHandlerFunc: TypeAlias = Callable[[MessageEvent], Coroutine[None, None, bool]]
|
||||||
"""规则函数签名"""
|
"""规则函数签名"""
|
||||||
|
|
||||||
|
|
||||||
class Rule:
|
class Rule:
|
||||||
def __init__(self, handler: Optional[RuleHandler] = None):
|
def __init__(self, handler: RuleHandlerFunc):
|
||||||
self.handler = handler
|
self.handler = handler
|
||||||
|
|
||||||
def __or__(self, other: "Rule") -> "Rule":
|
def __or__(self, other: "Rule") -> "Rule":
|
||||||
return Rule(lambda event: self.handler(event) or other.handler(event))
|
async def combined_handler(event: MessageEvent) -> bool:
|
||||||
|
return await self.handler(event) or await other.handler(event)
|
||||||
|
|
||||||
|
return Rule(combined_handler)
|
||||||
|
|
||||||
def __and__(self, other: "Rule") -> "Rule":
|
def __and__(self, other: "Rule") -> "Rule":
|
||||||
return Rule(lambda event: self.handler(event) and other.handler(event))
|
async def combined_handler(event: MessageEvent) -> bool:
|
||||||
|
return await self.handler(event) and await other.handler(event)
|
||||||
|
|
||||||
async def __call__(self, event: Event) -> bool:
|
return Rule(combined_handler)
|
||||||
|
|
||||||
|
async def __call__(self, event: MessageEvent) -> bool:
|
||||||
if self.handler is None:
|
if self.handler is None:
|
||||||
return True
|
return True
|
||||||
return await self.handler(event)
|
return await self.handler(event)
|
||||||
|
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
async def empty_rule(event: MessageEvent) -> bool:
|
||||||
|
return True
|
||||||
|
@ -33,6 +33,7 @@ class FunctionInfo(BaseModel):
|
|||||||
args: list[tuple[str, str]]
|
args: list[tuple[str, str]]
|
||||||
return_type: str
|
return_type: str
|
||||||
docstring: str
|
docstring: str
|
||||||
|
source_code: str = ""
|
||||||
|
|
||||||
type: DefType
|
type: DefType
|
||||||
"""若为类中def,则有"""
|
"""若为类中def,则有"""
|
||||||
@ -142,7 +143,8 @@ def get_module_info_normal(file_path: str, ignore_private: bool = True) -> Modul
|
|||||||
return_type=ast.unparse(node.returns) if node.returns else "None",
|
return_type=ast.unparse(node.returns) if node.returns else "None",
|
||||||
docstring=function_docstring if function_docstring else "",
|
docstring=function_docstring if function_docstring else "",
|
||||||
type=DefType.FUNCTION,
|
type=DefType.FUNCTION,
|
||||||
is_async=isinstance(node, ast.AsyncFunctionDef)
|
is_async=isinstance(node, ast.AsyncFunctionDef),
|
||||||
|
source_code=ast.unparse(node)
|
||||||
)
|
)
|
||||||
module_info.functions.append(func_info)
|
module_info.functions.append(func_info)
|
||||||
|
|
||||||
@ -176,7 +178,8 @@ def get_module_info_normal(file_path: str, ignore_private: bool = True) -> Modul
|
|||||||
return_type=ast.unparse(class_node.returns) if class_node.returns else "None",
|
return_type=ast.unparse(class_node.returns) if class_node.returns else "None",
|
||||||
docstring=method_docstring if method_docstring else "",
|
docstring=method_docstring if method_docstring else "",
|
||||||
type=def_type,
|
type=def_type,
|
||||||
is_async=isinstance(class_node, ast.AsyncFunctionDef)
|
is_async=isinstance(class_node, ast.AsyncFunctionDef),
|
||||||
|
source_code=ast.unparse(class_node)
|
||||||
))
|
))
|
||||||
# attributes
|
# attributes
|
||||||
elif isinstance(class_node, ast.Assign):
|
elif isinstance(class_node, ast.Assign):
|
||||||
@ -206,13 +209,14 @@ def get_module_info_normal(file_path: str, ignore_private: bool = True) -> Modul
|
|||||||
return module_info
|
return module_info
|
||||||
|
|
||||||
|
|
||||||
def generate_markdown(module_info: ModuleInfo, front_matter=None) -> str:
|
def generate_markdown(module_info: ModuleInfo, front_matter=None, lang: str = "zh-CN") -> str:
|
||||||
"""
|
"""
|
||||||
生成模块的Markdown
|
生成模块的Markdown
|
||||||
你可在此自定义生成的Markdown格式
|
你可在此自定义生成的Markdown格式
|
||||||
Args:
|
Args:
|
||||||
module_info: 模块信息
|
module_info: 模块信息
|
||||||
front_matter: 自定义选项title, index, icon, category
|
front_matter: 自定义选项title, index, icon, category
|
||||||
|
lang: 语言
|
||||||
Returns:
|
Returns:
|
||||||
Markdown 字符串
|
Markdown 字符串
|
||||||
"""
|
"""
|
||||||
@ -223,8 +227,6 @@ def generate_markdown(module_info: ModuleInfo, front_matter=None) -> str:
|
|||||||
|
|
||||||
content += front_matter
|
content += front_matter
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 模块函数
|
# 模块函数
|
||||||
for func in module_info.functions:
|
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]
|
args_with_type = [f"{arg[0]}: {arg[1]}" if arg[1] else arg[0] for arg in func.args]
|
||||||
@ -233,6 +235,9 @@ def generate_markdown(module_info: ModuleInfo, front_matter=None) -> str:
|
|||||||
func.docstring = func.docstring.replace("\n", "\n\n")
|
func.docstring = func.docstring.replace("\n", "\n\n")
|
||||||
content += f"{func.docstring}\n\n"
|
content += f"{func.docstring}\n\n"
|
||||||
|
|
||||||
|
# 函数源代码可展开区域
|
||||||
|
content += f"<details>\n<summary>源代码</summary>\n\n```python\n{func.source_code}\n```\n</details>\n\n"
|
||||||
|
|
||||||
# 类
|
# 类
|
||||||
for cls in module_info.classes:
|
for cls in module_info.classes:
|
||||||
if cls.inherit:
|
if cls.inherit:
|
||||||
@ -256,6 +261,14 @@ def generate_markdown(module_info: ModuleInfo, front_matter=None) -> str:
|
|||||||
|
|
||||||
method.docstring = method.docstring.replace("\n", "\n\n")
|
method.docstring = method.docstring.replace("\n", "\n\n")
|
||||||
content += f" {method.docstring}\n\n"
|
content += f" {method.docstring}\n\n"
|
||||||
|
# 函数源代码可展开区域
|
||||||
|
|
||||||
|
if lang == "zh-CN":
|
||||||
|
TEXT_SOURCE_CODE = "源代码"
|
||||||
|
else:
|
||||||
|
TEXT_SOURCE_CODE = "Source Code"
|
||||||
|
|
||||||
|
content += f"<details>\n<summary>{TEXT_SOURCE_CODE}</summary>\n\n```python\n{method.source_code}\n```\n</details>\n\n"
|
||||||
for attr in cls.attributes:
|
for attr in cls.attributes:
|
||||||
content += f"###   ***attr*** `{attr.name}: {attr.type}`\n\n"
|
content += f"###   ***attr*** `{attr.name}: {attr.type}`\n\n"
|
||||||
|
|
||||||
@ -272,7 +285,7 @@ def generate_markdown(module_info: ModuleInfo, front_matter=None) -> str:
|
|||||||
return content
|
return content
|
||||||
|
|
||||||
|
|
||||||
def generate_docs(module_folder: str, output_dir: str, with_top: bool = False, ignored_paths=None):
|
def generate_docs(module_folder: str, output_dir: str, with_top: bool = False, lang: str = "zh-CN", ignored_paths=None):
|
||||||
"""
|
"""
|
||||||
生成文档
|
生成文档
|
||||||
Args:
|
Args:
|
||||||
@ -280,6 +293,7 @@ def generate_docs(module_folder: str, output_dir: str, with_top: bool = False, i
|
|||||||
output_dir: 输出文件夹
|
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
|
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: 忽略的路径
|
ignored_paths: 忽略的路径
|
||||||
|
lang: 语言
|
||||||
"""
|
"""
|
||||||
if ignored_paths is None:
|
if ignored_paths is None:
|
||||||
ignored_paths = []
|
ignored_paths = []
|
||||||
@ -339,5 +353,5 @@ def generate_docs(module_folder: str, output_dir: str, with_top: bool = False, i
|
|||||||
# 入口脚本
|
# 入口脚本
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# 这里填入你的模块路径
|
# 这里填入你的模块路径
|
||||||
generate_docs('liteyuki', 'docs/dev/api', with_top=False, ignored_paths=["liteyuki/plugins"])
|
generate_docs('liteyuki', 'docs/dev/api', with_top=False, ignored_paths=["liteyuki/plugins"], lang="zh-CN")
|
||||||
generate_docs('liteyuki', 'docs/en/dev/api', with_top=False, ignored_paths=["liteyuki/plugins"])
|
generate_docs('liteyuki', 'docs/en/dev/api', with_top=False, ignored_paths=["liteyuki/plugins"], lang="en")
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import inspect
|
import inspect
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
|
import threading
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Callable, Coroutine
|
from typing import Any, Callable, Coroutine
|
||||||
|
|
||||||
@ -61,6 +62,16 @@ def run_coroutine(*coro: Coroutine):
|
|||||||
# 捕获其他异常,防止协程被重复等待
|
# 捕获其他异常,防止协程被重复等待
|
||||||
logger.error(f"Exception occurred: {e}")
|
logger.error(f"Exception occurred: {e}")
|
||||||
|
|
||||||
|
def run_coroutine_in_thread(*coro: Coroutine):
|
||||||
|
"""
|
||||||
|
在新线程中运行协程
|
||||||
|
Args:
|
||||||
|
coro:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
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:
|
||||||
"""
|
"""
|
||||||
|
@ -25,7 +25,9 @@ from ..utils.base.ly_function import get_function
|
|||||||
|
|
||||||
require("nonebot_plugin_alconna")
|
require("nonebot_plugin_alconna")
|
||||||
require("nonebot_plugin_apscheduler")
|
require("nonebot_plugin_apscheduler")
|
||||||
from nonebot_plugin_alconna import on_alconna, Alconna, Args, Arparma, MultiVar
|
require("nonebot_plugin_htmlrender")
|
||||||
|
from nonebot_plugin_htmlrender import md_to_pic
|
||||||
|
from nonebot_plugin_alconna import UniMessage, on_alconna, Alconna, Args, Arparma, MultiVar
|
||||||
from nonebot_plugin_apscheduler import scheduler
|
from nonebot_plugin_apscheduler import scheduler
|
||||||
|
|
||||||
driver = get_driver()
|
driver = get_driver()
|
||||||
@ -54,7 +56,7 @@ async def _(bot: T_Bot, matcher: Matcher, result: Arparma):
|
|||||||
permission=SUPERUSER
|
permission=SUPERUSER
|
||||||
).handle()
|
).handle()
|
||||||
# Satori OK
|
# Satori OK
|
||||||
async def _(bot: T_Bot, event: T_MessageEvent):
|
async def _(bot: T_Bot, event: T_MessageEvent, matcher: Matcher):
|
||||||
# 使用git pull更新
|
# 使用git pull更新
|
||||||
|
|
||||||
ulang = get_user_lang(str(event.user.id if isinstance(event, satori.event.Event) else event.user_id))
|
ulang = get_user_lang(str(event.user.id if isinstance(event, satori.event.Event) else event.user_id))
|
||||||
@ -64,7 +66,9 @@ async def _(bot: T_Bot, event: T_MessageEvent):
|
|||||||
btn_restart = md.btn_cmd(ulang.get("liteyuki.restart_now"), "reload-liteyuki")
|
btn_restart = md.btn_cmd(ulang.get("liteyuki.restart_now"), "reload-liteyuki")
|
||||||
pip.main(["install", "-r", "requirements.txt"])
|
pip.main(["install", "-r", "requirements.txt"])
|
||||||
reply += f"{ulang.get('liteyuki.update_restart', RESTART=btn_restart)}"
|
reply += f"{ulang.get('liteyuki.update_restart', RESTART=btn_restart)}"
|
||||||
await md.send_md(reply, bot, event=event, at_sender=False)
|
# await md.send_md(reply, bot)
|
||||||
|
img_bytes = await md_to_pic(reply)
|
||||||
|
await UniMessage.send(UniMessage.image(raw=img_bytes))
|
||||||
|
|
||||||
|
|
||||||
@on_alconna(
|
@on_alconna(
|
||||||
|
24
src/liteyuki_plugins/anti_dislink.py
Normal file
24
src/liteyuki_plugins/anti_dislink.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# -*- 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", "严禁断联化"]))
|
@ -10,15 +10,15 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|||||||
"""
|
"""
|
||||||
from liteyuki.plugin import PluginMetadata, PluginType
|
from liteyuki.plugin import PluginMetadata, PluginType
|
||||||
from liteyuki.message.on import on_message
|
from liteyuki.message.on import on_message
|
||||||
from liteyuki.message.event import Event
|
from liteyuki.message.event import MessageEvent
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="你好轻雪",
|
name="你好轻雪",
|
||||||
type=PluginType.TEST
|
type=PluginType.APPLICATION
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@on_message().handle
|
@on_message().handle()
|
||||||
async def _(event: Event):
|
async def _(event: MessageEvent):
|
||||||
if str(event.data["raw_message"]) == "你好轻雪":
|
if str(event.raw_message) == "你好轻雪":
|
||||||
event.reply("你好呀")
|
event.reply("你好呀")
|
||||||
|
26
src/liteyuki_plugins/ts_chan_main.py
Normal file
26
src/liteyuki_plugins/ts_chan_main.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# -*- 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)
|
@ -1,125 +0,0 @@
|
|||||||
import nonebot
|
|
||||||
from nonebot import on_message, require
|
|
||||||
from nonebot.plugin import PluginMetadata
|
|
||||||
|
|
||||||
from src.utils.base.data import Database, LiteModel
|
|
||||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
|
||||||
from src.utils.message.message import MarkdownMessage as md
|
|
||||||
|
|
||||||
require("nonebot_plugin_alconna")
|
|
||||||
from nonebot_plugin_alconna import on_alconna
|
|
||||||
from arclet.alconna import Arparma, Alconna, Args, Option, Subcommand
|
|
||||||
|
|
||||||
|
|
||||||
class Node(LiteModel):
|
|
||||||
TABLE_NAME: str = "node"
|
|
||||||
bot_id: str = ""
|
|
||||||
session_type: str = ""
|
|
||||||
session_id: str = ""
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.bot_id}.{self.session_type}.{self.session_id}"
|
|
||||||
|
|
||||||
|
|
||||||
class Push(LiteModel):
|
|
||||||
TABLE_NAME: str = "push"
|
|
||||||
source: Node = Node()
|
|
||||||
target: Node = Node()
|
|
||||||
inde: int = 0
|
|
||||||
|
|
||||||
|
|
||||||
pushes_db = Database("data/pushes.ldb")
|
|
||||||
pushes_db.auto_migrate(Push(), Node())
|
|
||||||
|
|
||||||
alc = Alconna(
|
|
||||||
"lep",
|
|
||||||
Subcommand(
|
|
||||||
"add",
|
|
||||||
Args["source", str],
|
|
||||||
Args["target", str],
|
|
||||||
Option("bidirectional", Args["bidirectional", bool])
|
|
||||||
),
|
|
||||||
Subcommand(
|
|
||||||
"rm",
|
|
||||||
Args["index", int],
|
|
||||||
|
|
||||||
),
|
|
||||||
Subcommand(
|
|
||||||
"list",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
add_push = on_alconna(alc)
|
|
||||||
|
|
||||||
|
|
||||||
@add_push.handle()
|
|
||||||
async def _(result: Arparma):
|
|
||||||
"""bot_id.session_type.session_id"""
|
|
||||||
if result.subcommands.get("add"):
|
|
||||||
source = result.subcommands["add"].args.get("source")
|
|
||||||
target = result.subcommands["add"].args.get("target")
|
|
||||||
if source and target:
|
|
||||||
source = source.split(".")
|
|
||||||
target = target.split(".")
|
|
||||||
push1 = Push(
|
|
||||||
source=Node(bot_id=source[0], session_type=source[1], session_id=source[2]),
|
|
||||||
target=Node(bot_id=target[0], session_type=target[1], session_id=target[2]),
|
|
||||||
inde=len(pushes_db.where_all(Push(), default=[]))
|
|
||||||
)
|
|
||||||
pushes_db.save(push1)
|
|
||||||
|
|
||||||
if result.subcommands["add"].args.get("bidirectional"):
|
|
||||||
push2 = Push(
|
|
||||||
source=Node(bot_id=target[0], session_type=target[1], session_id=target[2]),
|
|
||||||
target=Node(bot_id=source[0], session_type=source[1], session_id=source[2]),
|
|
||||||
inde=len(pushes_db.where_all(Push(), default=[]))
|
|
||||||
)
|
|
||||||
pushes_db.save(push2)
|
|
||||||
await add_push.finish("添加成功")
|
|
||||||
else:
|
|
||||||
await add_push.finish("参数缺失")
|
|
||||||
elif result.subcommands.get("rm"):
|
|
||||||
index = result.subcommands["rm"].args.get("index")
|
|
||||||
if index is not None:
|
|
||||||
try:
|
|
||||||
pushes_db.delete(Push(), "inde = ?", index)
|
|
||||||
await add_push.finish("删除成功")
|
|
||||||
except IndexError:
|
|
||||||
await add_push.finish("索引错误")
|
|
||||||
else:
|
|
||||||
await add_push.finish("参数缺失")
|
|
||||||
elif result.subcommands.get("list"):
|
|
||||||
await add_push.finish(
|
|
||||||
"\n".join([f"{push.inde} {push.source.bot_id}.{push.source.session_type}.{push.source.session_id} -> "
|
|
||||||
f"{push.target.bot_id}.{push.target.session_type}.{push.target.session_id}" for i, push in
|
|
||||||
enumerate(pushes_db.where_all(Push(), default=[]))]))
|
|
||||||
else:
|
|
||||||
await add_push.finish("参数错误")
|
|
||||||
|
|
||||||
|
|
||||||
@on_message(block=False).handle()
|
|
||||||
async def _(event: T_MessageEvent, bot: T_Bot):
|
|
||||||
for push in pushes_db.where_all(Push(), default=[]):
|
|
||||||
if str(push.source) == f"{bot.self_id}.{event.message_type}.{event.user_id if event.message_type == 'private' else event.group_id}":
|
|
||||||
bot2 = nonebot.get_bot(push.target.bot_id)
|
|
||||||
msg_formatted = ""
|
|
||||||
for line in str(event.message).split("\n"):
|
|
||||||
msg_formatted += f"**{line.strip()}**\n"
|
|
||||||
push_message = (
|
|
||||||
f"> From {event.sender.nickname}@{push.source.session_type}.{push.source.session_id}\n> Bot {bot.self_id}\n\n"
|
|
||||||
f"{msg_formatted}")
|
|
||||||
await md.send_md(push_message, bot2, message_type=push.target.session_type,
|
|
||||||
session_id=push.target.session_id)
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
__author__ = "snowykami"
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
|
||||||
name="轻雪事件推送",
|
|
||||||
description="事件推送插件,支持单向和双向推送,支持跨Bot推送",
|
|
||||||
usage="",
|
|
||||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
|
||||||
extra={
|
|
||||||
"liteyuki": True,
|
|
||||||
}
|
|
||||||
)
|
|
@ -1,52 +0,0 @@
|
|||||||
from nonebot import on_command, require
|
|
||||||
from nonebot.adapters.onebot.v11 import MessageSegment
|
|
||||||
from nonebot.params import CommandArg
|
|
||||||
from nonebot.permission import SUPERUSER
|
|
||||||
from nonebot.plugin import PluginMetadata
|
|
||||||
|
|
||||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent, v11
|
|
||||||
from src.utils.message.message import MarkdownMessage as md, broadcast_to_superusers
|
|
||||||
from src.utils.message.html_tool import *
|
|
||||||
|
|
||||||
md_test = on_command("mdts", permission=SUPERUSER)
|
|
||||||
btn_test = on_command("btnts", permission=SUPERUSER)
|
|
||||||
latex_test = on_command("latex", permission=SUPERUSER)
|
|
||||||
|
|
||||||
|
|
||||||
@md_test.handle()
|
|
||||||
async def _(bot: T_Bot, event: T_MessageEvent, arg: v11.Message = CommandArg()):
|
|
||||||
await md.send_md(
|
|
||||||
v11.utils.unescape(str(arg)),
|
|
||||||
bot,
|
|
||||||
message_type=event.message_type,
|
|
||||||
session_id=event.user_id if event.message_type == "private" else event.group_id
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@btn_test.handle()
|
|
||||||
async def _(bot: T_Bot, event: T_MessageEvent, arg: v11.Message = CommandArg()):
|
|
||||||
await md.send_btn(
|
|
||||||
str(arg),
|
|
||||||
bot,
|
|
||||||
message_type=event.message_type,
|
|
||||||
session_id=event.user_id if event.message_type == "private" else event.group_id
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@latex_test.handle()
|
|
||||||
async def _(bot: T_Bot, event: T_MessageEvent, arg: v11.Message = CommandArg()):
|
|
||||||
latex_text = f"$${v11.utils.unescape(str(arg))}$$"
|
|
||||||
img = await md_to_pic(latex_text)
|
|
||||||
await bot.send(event=event, message=MessageSegment.image(img))
|
|
||||||
|
|
||||||
|
|
||||||
__author__ = "snowykami"
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
|
||||||
name="轻雪Markdown测试",
|
|
||||||
description="用于测试Markdown的插件",
|
|
||||||
usage="",
|
|
||||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
|
||||||
extra={
|
|
||||||
"liteyuki": True,
|
|
||||||
}
|
|
||||||
)
|
|
@ -1,15 +0,0 @@
|
|||||||
from nonebot.plugin import PluginMetadata
|
|
||||||
from .minesweeper import *
|
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
|
||||||
name="轻雪小游戏",
|
|
||||||
description="内置了一些小游戏",
|
|
||||||
usage="",
|
|
||||||
type="application",
|
|
||||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
|
||||||
extra={
|
|
||||||
"liteyuki": True,
|
|
||||||
"toggleable" : True,
|
|
||||||
"default_enable" : True,
|
|
||||||
}
|
|
||||||
)
|
|
@ -1,168 +0,0 @@
|
|||||||
import random
|
|
||||||
from pydantic import BaseModel
|
|
||||||
from src.utils.message.message import MarkdownMessage as md
|
|
||||||
|
|
||||||
class Dot(BaseModel):
|
|
||||||
row: int
|
|
||||||
col: int
|
|
||||||
mask: bool = True
|
|
||||||
value: int = 0
|
|
||||||
flagged: bool = False
|
|
||||||
|
|
||||||
|
|
||||||
class Minesweeper:
|
|
||||||
# 0-8: number of mines around, 9: mine, -1: undefined
|
|
||||||
NUMS = "⓪①②③④⑤⑥⑦⑧🅑⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳"
|
|
||||||
MASK = "🅜"
|
|
||||||
FLAG = "🅕"
|
|
||||||
MINE = "🅑"
|
|
||||||
|
|
||||||
def __init__(self, rows, cols, num_mines, session_type, session_id):
|
|
||||||
assert rows > 0 and cols > 0 and 0 < num_mines < rows * cols
|
|
||||||
self.session_type = session_type
|
|
||||||
self.session_id = session_id
|
|
||||||
self.rows = rows
|
|
||||||
self.cols = cols
|
|
||||||
self.num_mines = num_mines
|
|
||||||
self.board: list[list[Dot]] = [[Dot(row=i, col=j) for j in range(cols)] for i in range(rows)]
|
|
||||||
self.is_first = True
|
|
||||||
|
|
||||||
def reveal(self, row, col) -> bool:
|
|
||||||
"""
|
|
||||||
展开
|
|
||||||
Args:
|
|
||||||
row:
|
|
||||||
col:
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
游戏是否继续
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self.is_first:
|
|
||||||
# 第一次展开,生成地雷
|
|
||||||
self.generate_board(self.board[row][col])
|
|
||||||
self.is_first = False
|
|
||||||
|
|
||||||
if self.board[row][col].value == 9:
|
|
||||||
self.board[row][col].mask = False
|
|
||||||
return False
|
|
||||||
|
|
||||||
if not self.board[row][col].mask:
|
|
||||||
return True
|
|
||||||
|
|
||||||
self.board[row][col].mask = False
|
|
||||||
|
|
||||||
if self.board[row][col].value == 0:
|
|
||||||
self.reveal_neighbors(row, col)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def is_win(self) -> bool:
|
|
||||||
"""
|
|
||||||
是否胜利
|
|
||||||
Returns:
|
|
||||||
"""
|
|
||||||
for row in range(self.rows):
|
|
||||||
for col in range(self.cols):
|
|
||||||
if self.board[row][col].mask and self.board[row][col].value != 9:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def generate_board(self, first_dot: Dot):
|
|
||||||
"""
|
|
||||||
避开第一个点,生成地雷
|
|
||||||
Args:
|
|
||||||
first_dot: 第一个点
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
generate_count = 0
|
|
||||||
while generate_count < self.num_mines:
|
|
||||||
row = random.randint(0, self.rows - 1)
|
|
||||||
col = random.randint(0, self.cols - 1)
|
|
||||||
if self.board[row][col].value == 9 or (row, col) == (first_dot.row, first_dot.col):
|
|
||||||
continue
|
|
||||||
self.board[row][col] = Dot(row=row, col=col, mask=True, value=9)
|
|
||||||
generate_count += 1
|
|
||||||
|
|
||||||
for row in range(self.rows):
|
|
||||||
for col in range(self.cols):
|
|
||||||
if self.board[row][col].value != 9:
|
|
||||||
self.board[row][col].value = self.count_adjacent_mines(row, col)
|
|
||||||
|
|
||||||
def count_adjacent_mines(self, row, col):
|
|
||||||
"""
|
|
||||||
计算周围地雷数量
|
|
||||||
Args:
|
|
||||||
row:
|
|
||||||
col:
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
count = 0
|
|
||||||
for r in range(max(0, row - 1), min(self.rows, row + 2)):
|
|
||||||
for c in range(max(0, col - 1), min(self.cols, col + 2)):
|
|
||||||
if self.board[r][c].value == 9:
|
|
||||||
count += 1
|
|
||||||
return count
|
|
||||||
|
|
||||||
def reveal_neighbors(self, row, col):
|
|
||||||
"""
|
|
||||||
递归展开,使用深度优先搜索
|
|
||||||
Args:
|
|
||||||
row:
|
|
||||||
col:
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
for r in range(max(0, row - 1), min(self.rows, row + 2)):
|
|
||||||
for c in range(max(0, col - 1), min(self.cols, col + 2)):
|
|
||||||
if self.board[r][c].mask:
|
|
||||||
self.board[r][c].mask = False
|
|
||||||
if self.board[r][c].value == 0:
|
|
||||||
self.reveal_neighbors(r, c)
|
|
||||||
|
|
||||||
def mark(self, row, col) -> bool:
|
|
||||||
"""
|
|
||||||
标记
|
|
||||||
Args:
|
|
||||||
row:
|
|
||||||
col:
|
|
||||||
Returns:
|
|
||||||
是否标记成功,如果已经展开则无法标记
|
|
||||||
"""
|
|
||||||
if self.board[row][col].mask:
|
|
||||||
self.board[row][col].flagged = not self.board[row][col].flagged
|
|
||||||
return self.board[row][col].flagged
|
|
||||||
|
|
||||||
def board_markdown(self) -> str:
|
|
||||||
"""
|
|
||||||
打印地雷板
|
|
||||||
Returns:
|
|
||||||
"""
|
|
||||||
dis = " "
|
|
||||||
start = "> " if self.cols >= 10 else ""
|
|
||||||
text = start + self.NUMS[0] + dis*2
|
|
||||||
# 横向两个雷之间的间隔字符
|
|
||||||
# 生成横向索引
|
|
||||||
for i in range(self.cols):
|
|
||||||
text += f"{self.NUMS[i]}" + dis
|
|
||||||
text += "\n\n"
|
|
||||||
for i, row in enumerate(self.board):
|
|
||||||
text += start + f"{self.NUMS[i]}" + dis*2
|
|
||||||
for dot in row:
|
|
||||||
if dot.mask and not dot.flagged:
|
|
||||||
text += md.btn_cmd(self.MASK, f"minesweeper reveal {dot.row} {dot.col}")
|
|
||||||
elif dot.flagged:
|
|
||||||
text += md.btn_cmd(self.FLAG, f"minesweeper mark {dot.row} {dot.col}")
|
|
||||||
else:
|
|
||||||
text += self.NUMS[dot.value]
|
|
||||||
text += dis
|
|
||||||
text += "\n"
|
|
||||||
btn_mark = md.btn_cmd("标记", f"minesweeper mark ", enter=False)
|
|
||||||
btn_end = md.btn_cmd("结束", "minesweeper end", enter=True)
|
|
||||||
text += f" {btn_mark} {btn_end}"
|
|
||||||
return text
|
|
@ -1,103 +0,0 @@
|
|||||||
from nonebot import require
|
|
||||||
|
|
||||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
|
||||||
from src.utils.message.message import MarkdownMessage as md
|
|
||||||
|
|
||||||
require("nonebot_plugin_alconna")
|
|
||||||
from .game import Minesweeper
|
|
||||||
|
|
||||||
from nonebot_plugin_alconna import Alconna, on_alconna, Subcommand, Args, Arparma
|
|
||||||
|
|
||||||
minesweeper = on_alconna(
|
|
||||||
aliases={"扫雷"},
|
|
||||||
command=Alconna(
|
|
||||||
"minesweeper",
|
|
||||||
Subcommand(
|
|
||||||
"start",
|
|
||||||
Args["row", int, 8]["col", int, 8]["mines", int, 10],
|
|
||||||
alias=["开始"],
|
|
||||||
|
|
||||||
),
|
|
||||||
Subcommand(
|
|
||||||
"end",
|
|
||||||
alias=["结束"]
|
|
||||||
),
|
|
||||||
Subcommand(
|
|
||||||
"reveal",
|
|
||||||
Args["row", int]["col", int],
|
|
||||||
alias=["展开"]
|
|
||||||
|
|
||||||
),
|
|
||||||
Subcommand(
|
|
||||||
"mark",
|
|
||||||
Args["row", int]["col", int],
|
|
||||||
alias=["标记"]
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
minesweeper_cache: list[Minesweeper] = []
|
|
||||||
|
|
||||||
|
|
||||||
def get_minesweeper_cache(event: T_MessageEvent) -> Minesweeper | None:
|
|
||||||
for i in minesweeper_cache:
|
|
||||||
if i.session_type == event.message_type:
|
|
||||||
if i.session_id == event.user_id or i.session_id == event.group_id:
|
|
||||||
return i
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
@minesweeper.handle()
|
|
||||||
async def _(event: T_MessageEvent, result: Arparma, bot: T_Bot):
|
|
||||||
game = get_minesweeper_cache(event)
|
|
||||||
if result.subcommands.get("start"):
|
|
||||||
if game:
|
|
||||||
await minesweeper.finish("当前会话不能同时进行多个扫雷游戏")
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
new_game = Minesweeper(
|
|
||||||
rows=result.subcommands["start"].args["row"],
|
|
||||||
cols=result.subcommands["start"].args["col"],
|
|
||||||
num_mines=result.subcommands["start"].args["mines"],
|
|
||||||
session_type=event.message_type,
|
|
||||||
session_id=event.user_id if event.message_type == "private" else event.group_id,
|
|
||||||
)
|
|
||||||
minesweeper_cache.append(new_game)
|
|
||||||
await minesweeper.send("游戏开始")
|
|
||||||
await md.send_md(new_game.board_markdown(), bot, event=event)
|
|
||||||
except AssertionError:
|
|
||||||
await minesweeper.finish("参数错误")
|
|
||||||
elif result.subcommands.get("end"):
|
|
||||||
if game:
|
|
||||||
minesweeper_cache.remove(game)
|
|
||||||
await minesweeper.finish("游戏结束")
|
|
||||||
else:
|
|
||||||
await minesweeper.finish("当前没有扫雷游戏")
|
|
||||||
elif result.subcommands.get("reveal"):
|
|
||||||
if not game:
|
|
||||||
await minesweeper.finish("当前没有扫雷游戏")
|
|
||||||
else:
|
|
||||||
row = result.subcommands["reveal"].args["row"]
|
|
||||||
col = result.subcommands["reveal"].args["col"]
|
|
||||||
if not (0 <= row < game.rows and 0 <= col < game.cols):
|
|
||||||
await minesweeper.finish("参数错误")
|
|
||||||
if not game.reveal(row, col):
|
|
||||||
minesweeper_cache.remove(game)
|
|
||||||
await md.send_md(game.board_markdown(), bot, event=event)
|
|
||||||
await minesweeper.finish("游戏结束")
|
|
||||||
await md.send_md(game.board_markdown(), bot, event=event)
|
|
||||||
if game.is_win():
|
|
||||||
minesweeper_cache.remove(game)
|
|
||||||
await minesweeper.finish("游戏胜利")
|
|
||||||
elif result.subcommands.get("mark"):
|
|
||||||
if not game:
|
|
||||||
await minesweeper.finish("当前没有扫雷游戏")
|
|
||||||
else:
|
|
||||||
row = result.subcommands["mark"].args["row"]
|
|
||||||
col = result.subcommands["mark"].args["col"]
|
|
||||||
if not (0 <= row < game.rows and 0 <= col < game.cols):
|
|
||||||
await minesweeper.finish("参数错误")
|
|
||||||
game.board[row][col].flagged = not game.board[row][col].flagged
|
|
||||||
await md.send_md(game.board_markdown(), bot, event=event)
|
|
||||||
else:
|
|
||||||
await minesweeper.finish("参数错误")
|
|
@ -14,6 +14,7 @@ from nonebot.permission import SUPERUSER
|
|||||||
from nonebot.plugin import Plugin, PluginMetadata
|
from nonebot.plugin import Plugin, PluginMetadata
|
||||||
from nonebot.utils import run_sync
|
from nonebot.utils import run_sync
|
||||||
|
|
||||||
|
|
||||||
from src.utils.base.data_manager import InstalledPlugin
|
from src.utils.base.data_manager import InstalledPlugin
|
||||||
from src.utils.base.language import get_user_lang
|
from src.utils.base.language import get_user_lang
|
||||||
from src.utils.base.ly_typing import T_Bot
|
from src.utils.base.ly_typing import T_Bot
|
||||||
@ -24,8 +25,10 @@ from src.utils.message.tools import clamp
|
|||||||
from .common import *
|
from .common import *
|
||||||
|
|
||||||
require("nonebot_plugin_alconna")
|
require("nonebot_plugin_alconna")
|
||||||
|
require("nonebot_plugin_htmlrender")
|
||||||
|
from nonebot_plugin_htmlrender import md_to_pic
|
||||||
from nonebot_plugin_alconna import (
|
from nonebot_plugin_alconna import (
|
||||||
on_alconna,
|
UniMessage, on_alconna,
|
||||||
Alconna,
|
Alconna,
|
||||||
Args,
|
Args,
|
||||||
Arparma,
|
Arparma,
|
||||||
@ -292,7 +295,8 @@ async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot, npm: Matcher):
|
|||||||
reply += f"\n{ulang.get('npm.too_many_results', HIDE_NUM=len(rs) - max_show)}"
|
reply += f"\n{ulang.get('npm.too_many_results', HIDE_NUM=len(rs) - max_show)}"
|
||||||
else:
|
else:
|
||||||
reply = ulang.get("npm.search_no_result")
|
reply = ulang.get("npm.search_no_result")
|
||||||
await md.send_md(reply, bot, event=event)
|
img_bytes = await md_to_pic(reply)
|
||||||
|
await UniMessage.send(UniMessage.image(raw=img_bytes))
|
||||||
|
|
||||||
elif sc.get("install") and perm_s:
|
elif sc.get("install") and perm_s:
|
||||||
plugin_name: str = result.subcommands["install"].args.get("plugin_name")
|
plugin_name: str = result.subcommands["install"].args.get("plugin_name")
|
||||||
@ -320,7 +324,7 @@ async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot, npm: Matcher):
|
|||||||
info = md.escape(
|
info = md.escape(
|
||||||
ulang.get("npm.install_success", NAME=store_plugin.name)
|
ulang.get("npm.install_success", NAME=store_plugin.name)
|
||||||
) # markdown转义
|
) # markdown转义
|
||||||
await md.send_md(f"{info}\n\n" f"```\n{log}\n```", bot, event=event)
|
await npm.send(f"{info}\n\n" + f"\n{log}\n")
|
||||||
else:
|
else:
|
||||||
await npm.finish(
|
await npm.finish(
|
||||||
ulang.get(
|
ulang.get(
|
||||||
@ -331,12 +335,12 @@ async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot, npm: Matcher):
|
|||||||
info = ulang.get(
|
info = ulang.get(
|
||||||
"npm.load_failed", NAME=plugin_name, HOMEPAGE=homepage_btn
|
"npm.load_failed", NAME=plugin_name, HOMEPAGE=homepage_btn
|
||||||
).replace("_", r"\\_")
|
).replace("_", r"\\_")
|
||||||
await md.send_md(f"{info}\n\n" f"```\n{log}\n```\n", bot, event=event)
|
await npm.finish(f"{info}\n\n" f"```\n{log}\n```\n")
|
||||||
else:
|
else:
|
||||||
info = ulang.get(
|
info = ulang.get(
|
||||||
"npm.install_failed", NAME=plugin_name, HOMEPAGE=homepage_btn
|
"npm.install_failed", NAME=plugin_name, HOMEPAGE=homepage_btn
|
||||||
).replace("_", r"\\_")
|
).replace("_", r"\\_")
|
||||||
await md.send_md(f"{info}\n\n" f"```\n{log}\n```", bot, event=event)
|
await npm.send(f"{info}\n\n" f"```\n{log}\n```")
|
||||||
|
|
||||||
elif sc.get("uninstall") and perm_s:
|
elif sc.get("uninstall") and perm_s:
|
||||||
plugin_name: str = result.subcommands["uninstall"].args.get("plugin_name") # type: ignore
|
plugin_name: str = result.subcommands["uninstall"].args.get("plugin_name") # type: ignore
|
||||||
@ -464,7 +468,8 @@ async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot, npm: Matcher):
|
|||||||
else ulang.get("npm.next_page")
|
else ulang.get("npm.next_page")
|
||||||
)
|
)
|
||||||
reply += f"\n{btn_prev} {page}/{total} {btn_next}"
|
reply += f"\n{btn_prev} {page}/{total} {btn_next}"
|
||||||
await md.send_md(reply, bot, event=event)
|
img_bytes = await md_to_pic(reply)
|
||||||
|
await UniMessage.send(UniMessage.image(raw=img_bytes))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if await SUPERUSER(bot, event):
|
if await SUPERUSER(bot, event):
|
||||||
@ -517,7 +522,8 @@ async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot, npm: Matcher):
|
|||||||
f"\n\n>page为页数,num为每页显示数量"
|
f"\n\n>page为页数,num为每页显示数量"
|
||||||
f"\n\n>*{md.escape('npm list [page] [num]')}*"
|
f"\n\n>*{md.escape('npm list [page] [num]')}*"
|
||||||
)
|
)
|
||||||
await md.send_md(reply, bot, event=event)
|
img_bytes = await md_to_pic(reply)
|
||||||
|
await UniMessage.send(UniMessage.image(raw=img_bytes))
|
||||||
else:
|
else:
|
||||||
|
|
||||||
btn_list = md.btn_cmd(
|
btn_list = md.btn_cmd(
|
||||||
@ -539,7 +545,8 @@ async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot, npm: Matcher):
|
|||||||
f"\n\n>page为页数,num为每页显示数量"
|
f"\n\n>page为页数,num为每页显示数量"
|
||||||
f"\n\n>*{md.escape('npm list [page] [num]')}*"
|
f"\n\n>*{md.escape('npm list [page] [num]')}*"
|
||||||
)
|
)
|
||||||
await md.send_md(reply, bot, event=event)
|
img_bytes = await md_to_pic(reply)
|
||||||
|
await UniMessage.send(UniMessage.image(raw=img_bytes))
|
||||||
|
|
||||||
|
|
||||||
@on_alconna(
|
@on_alconna(
|
||||||
@ -679,7 +686,7 @@ async def _(result: Arparma, matcher: Matcher, event: T_MessageEvent, bot: T_Bot
|
|||||||
else mdc.paragraph(ulang.get("npm.homepage"))
|
else mdc.paragraph(ulang.get("npm.homepage"))
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
await md.send_md(compile_md(reply), bot, event=event)
|
await matcher.finish(compile_md(reply))
|
||||||
else:
|
else:
|
||||||
await matcher.finish(ulang.get("npm.plugin_not_found", NAME=plugin_name))
|
await matcher.finish(ulang.get("npm.plugin_not_found", NAME=plugin_name))
|
||||||
else:
|
else:
|
||||||
|
@ -181,6 +181,6 @@ async def _(bot: T_Bot, event: T_MessageEvent, result: Arparma, matcher: Matcher
|
|||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
if send_as_md:
|
if send_as_md:
|
||||||
await md.send_md(reply, bot, event=event)
|
await matcher.send(reply)
|
||||||
else:
|
else:
|
||||||
await matcher.finish(reply)
|
await matcher.finish(reply)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import nonebot
|
import nonebot
|
||||||
|
|
||||||
from nonebot.message import event_preprocessor
|
from nonebot.message import event_preprocessor
|
||||||
# from nonebot_plugin_alconna.typings import Event
|
|
||||||
from src.utils.base.ly_typing import T_MessageEvent
|
from src.utils.base.ly_typing import T_MessageEvent
|
||||||
from src.utils import satori_utils
|
from src.utils import satori_utils
|
||||||
from nonebot.adapters import satori
|
from nonebot.adapters import satori
|
||||||
|
@ -12,7 +12,9 @@ from .const import representative_timezones_list
|
|||||||
from src.utils import event as event_utils
|
from src.utils import event as event_utils
|
||||||
|
|
||||||
require("nonebot_plugin_alconna")
|
require("nonebot_plugin_alconna")
|
||||||
|
require("nonebot_plugin_htmlrender")
|
||||||
from nonebot_plugin_alconna import Alconna, Args, Arparma, Subcommand, on_alconna
|
from nonebot_plugin_alconna import Alconna, Args, Arparma, Subcommand, on_alconna
|
||||||
|
from nonebot_plugin_htmlrender import md_to_pic
|
||||||
|
|
||||||
profile_alc = on_alconna(
|
profile_alc = on_alconna(
|
||||||
Alconna(
|
Alconna(
|
||||||
@ -65,7 +67,8 @@ async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot):
|
|||||||
# 未输入值,尝试呼出菜单
|
# 未输入值,尝试呼出菜单
|
||||||
menu = get_profile_menu(result.args["key"], ulang)
|
menu = get_profile_menu(result.args["key"], ulang)
|
||||||
if menu:
|
if menu:
|
||||||
await md.send_md(menu, bot, event=event)
|
img_bytes = await md_to_pic(menu)
|
||||||
|
await profile_alc.finish(menu)
|
||||||
else:
|
else:
|
||||||
await profile_alc.finish(ulang.get("user.profile.input_value", ATTR=ulang.get(f"user.profile.{result.args['key']}")))
|
await profile_alc.finish(ulang.get("user.profile.input_value", ATTR=ulang.get(f"user.profile.{result.args['key']}")))
|
||||||
|
|
||||||
@ -97,7 +100,8 @@ async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot):
|
|||||||
reply += (f"\n**{key_text}** **{val}**\n"
|
reply += (f"\n**{key_text}** **{val}**\n"
|
||||||
f"\n> {ulang.get(f'user.profile.{key}.desc')}"
|
f"\n> {ulang.get(f'user.profile.{key}.desc')}"
|
||||||
f"\n> {btn_set} \n\n***\n")
|
f"\n> {btn_set} \n\n***\n")
|
||||||
await md.send_md(reply, bot, event=event)
|
img_bytes = await md_to_pic(reply)
|
||||||
|
await profile_alc.finish(reply)
|
||||||
|
|
||||||
|
|
||||||
def get_profile_menu(key: str, ulang: Language) -> Optional[str]:
|
def get_profile_menu(key: str, ulang: Language) -> Optional[str]:
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
# -*- 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
|
|
||||||
|
|
@ -8,11 +8,13 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|||||||
@File : to_liteyuki.py
|
@File : to_liteyuki.py
|
||||||
@Software: PyCharm
|
@Software: PyCharm
|
||||||
"""
|
"""
|
||||||
|
import asyncio
|
||||||
|
|
||||||
from nonebot import Bot, get_bot, on_message
|
from nonebot import Bot, get_bot, on_message
|
||||||
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.comm.storage import shared_memory
|
from liteyuki.comm.storage import shared_memory
|
||||||
from liteyuki.message.event import Event
|
from liteyuki.message.event import MessageEvent as LiteyukiMessageEvent
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="轻雪物流",
|
name="轻雪物流",
|
||||||
@ -23,8 +25,10 @@ __plugin_meta__ = PluginMetadata(
|
|||||||
|
|
||||||
@on_message().handle()
|
@on_message().handle()
|
||||||
async def _(bot: Bot, event: MessageEvent):
|
async def _(bot: Bot, event: MessageEvent):
|
||||||
liteyuki_event = Event(
|
liteyuki_event = LiteyukiMessageEvent(
|
||||||
type=event.message_type,
|
message_type=event.message_type,
|
||||||
|
message=event.dict()["message"],
|
||||||
|
raw_message=event.raw_message,
|
||||||
data=event.dict(),
|
data=event.dict(),
|
||||||
bot_id=bot.self_id,
|
bot_id=bot.self_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),
|
||||||
@ -35,6 +39,6 @@ async def _(bot: Bot, event: MessageEvent):
|
|||||||
|
|
||||||
|
|
||||||
@shared_memory.on_subscriber_receive("event_to_nonebot")
|
@shared_memory.on_subscriber_receive("event_to_nonebot")
|
||||||
async def _(event: Event):
|
async def _(event: LiteyukiMessageEvent):
|
||||||
bot: Bot = get_bot(event.bot_id)
|
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"])
|
await bot.send_msg(message_type=event.message_type, user_id=int(event.session_id), group_id=int(event.session_id), message=event.data["message"])
|
||||||
|
35
src/nonebot_plugins/ts_chan_sub.py
Normal file
35
src/nonebot_plugins/ts_chan_sub.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# -*- 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
|
@ -1,18 +1,15 @@
|
|||||||
import base64
|
import base64
|
||||||
import io
|
import io
|
||||||
|
from typing import Any
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
||||||
import aiofiles
|
import aiofiles
|
||||||
from PIL import Image
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import nonebot
|
import nonebot
|
||||||
|
from PIL import Image
|
||||||
from nonebot import require
|
from nonebot import require
|
||||||
from nonebot.adapters import satori
|
from nonebot.adapters import satori
|
||||||
from nonebot.adapters.onebot import v11
|
from nonebot.adapters.onebot import v11
|
||||||
from typing import Any, Type
|
|
||||||
|
|
||||||
from nonebot.internal.adapter import MessageSegment
|
|
||||||
from nonebot.internal.adapter.message import TM
|
|
||||||
|
|
||||||
from .. import load_from_yaml
|
from .. import load_from_yaml
|
||||||
from ..base.ly_typing import T_Bot, T_Message, T_MessageEvent
|
from ..base.ly_typing import T_Bot, T_Message, T_MessageEvent
|
||||||
@ -22,12 +19,6 @@ from nonebot_plugin_htmlrender import md_to_pic
|
|||||||
|
|
||||||
config = load_from_yaml("config.yml")
|
config = load_from_yaml("config.yml")
|
||||||
|
|
||||||
can_send_markdown = {} # 用于存储机器人是否支持发送markdown消息,id->bool
|
|
||||||
|
|
||||||
|
|
||||||
class TencentBannedMarkdownError(BaseException):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
async def broadcast_to_superusers(message: str | T_Message, markdown: bool = False):
|
async def broadcast_to_superusers(message: str | T_Message, markdown: bool = False):
|
||||||
"""广播消息给超级用户"""
|
"""广播消息给超级用户"""
|
||||||
@ -45,10 +36,7 @@ class MarkdownMessage:
|
|||||||
markdown: str,
|
markdown: str,
|
||||||
bot: T_Bot, *,
|
bot: T_Bot, *,
|
||||||
message_type: str = None,
|
message_type: str = None,
|
||||||
session_id: str | int = None,
|
session_id: str | int = None
|
||||||
event: T_MessageEvent = None,
|
|
||||||
retry_as_image: bool = True,
|
|
||||||
**kwargs
|
|
||||||
) -> dict[str, Any] | None:
|
) -> dict[str, Any] | None:
|
||||||
"""
|
"""
|
||||||
发送Markdown消息,支持自动转为图片发送
|
发送Markdown消息,支持自动转为图片发送
|
||||||
@ -57,89 +45,22 @@ class MarkdownMessage:
|
|||||||
bot:
|
bot:
|
||||||
message_type:
|
message_type:
|
||||||
session_id:
|
session_id:
|
||||||
event:
|
|
||||||
retry_as_image: 发送失败后是否尝试以图片形式发送,否则失败返回None
|
|
||||||
**kwargs:
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
formatted_md = v11.unescape(markdown).replace("\n", r"\n").replace('"', r'\\\"')
|
plain_markdown = markdown.replace("[🔗", "[")
|
||||||
if event is not None and message_type is None:
|
md_image_bytes = await md_to_pic(
|
||||||
if isinstance(event, satori.event.Event):
|
md=plain_markdown,
|
||||||
message_type = "private" if event.guild is None else "group"
|
width=540,
|
||||||
group_id = event.guild.id if event.guild is not None else None
|
device_scale_factor=4
|
||||||
else:
|
)
|
||||||
assert event is not None
|
print(md_image_bytes)
|
||||||
message_type = event.message_type
|
data = await bot.send_msg(
|
||||||
group_id = event.group_id if message_type == "group" else None
|
message_type=message_type,
|
||||||
user_id = event.user.id if isinstance(event, satori.event.Event) else event.user_id
|
group_id=session_id,
|
||||||
session_id = user_id if message_type == "private" else group_id
|
user_id=session_id,
|
||||||
else:
|
message=v11.MessageSegment.image(md_image_bytes),
|
||||||
pass
|
)
|
||||||
try:
|
|
||||||
raise TencentBannedMarkdownError("Tencent banned markdown")
|
|
||||||
forward_id = await bot.call_api(
|
|
||||||
"send_private_forward_msg",
|
|
||||||
messages=[
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"data": {
|
|
||||||
"content": [
|
|
||||||
{
|
|
||||||
"data": {
|
|
||||||
"content": "{\"content\":\"%s\"}" % formatted_md,
|
|
||||||
},
|
|
||||||
"type": "markdown"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "[]",
|
|
||||||
"uin": bot.self_id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
user_id=bot.self_id
|
|
||||||
|
|
||||||
)
|
|
||||||
data = await bot.send_msg(
|
|
||||||
user_id=session_id,
|
|
||||||
group_id=session_id,
|
|
||||||
message_type=message_type,
|
|
||||||
message=[
|
|
||||||
{
|
|
||||||
"type": "longmsg",
|
|
||||||
"data": {
|
|
||||||
"id": forward_id
|
|
||||||
}
|
|
||||||
},
|
|
||||||
],
|
|
||||||
**kwargs
|
|
||||||
)
|
|
||||||
except BaseException as e:
|
|
||||||
nonebot.logger.error(f"send markdown error, retry as image: {e}")
|
|
||||||
# 发送失败,渲染为图片发送
|
|
||||||
# if not retry_as_image:
|
|
||||||
# return None
|
|
||||||
|
|
||||||
plain_markdown = markdown.replace("[🔗", "[")
|
|
||||||
md_image_bytes = await md_to_pic(
|
|
||||||
md=plain_markdown,
|
|
||||||
width=540,
|
|
||||||
device_scale_factor=4
|
|
||||||
)
|
|
||||||
if isinstance(bot, satori.Bot):
|
|
||||||
msg_seg = satori.MessageSegment.image(raw=md_image_bytes,mime="image/png")
|
|
||||||
data = await bot.send(
|
|
||||||
event=event,
|
|
||||||
message=msg_seg
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
data = await bot.send_msg(
|
|
||||||
message_type=message_type,
|
|
||||||
group_id=session_id,
|
|
||||||
user_id=session_id,
|
|
||||||
message=v11.MessageSegment.image(md_image_bytes),
|
|
||||||
)
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -156,48 +77,36 @@ class MarkdownMessage:
|
|||||||
Args:
|
Args:
|
||||||
image: 图片字节流或图片本地路径,链接请使用Markdown.image_async方法获取后通过send_md发送
|
image: 图片字节流或图片本地路径,链接请使用Markdown.image_async方法获取后通过send_md发送
|
||||||
bot: bot instance
|
bot: bot instance
|
||||||
message_type: message type
|
message_type: message message_type
|
||||||
session_id: session id
|
session_id: session id
|
||||||
event: event
|
event: event
|
||||||
kwargs: other arguments
|
kwargs: other arguments
|
||||||
Returns:
|
Returns:
|
||||||
dict: response data
|
dict: response data
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if isinstance(image, str):
|
if isinstance(image, str):
|
||||||
async with aiofiles.open(image, "rb") as f:
|
async with aiofiles.open(image, "rb") as f:
|
||||||
image = await f.read()
|
image = await f.read()
|
||||||
method = 2
|
method = 2
|
||||||
# 1.轻雪图床方案
|
|
||||||
# if method == 1:
|
|
||||||
# image_url = await liteyuki_api.upload_image(image)
|
|
||||||
# image_size = Image.open(io.BytesIO(image)).size
|
|
||||||
# image_md = Markdown.image(image_url, image_size)
|
|
||||||
# data = await Markdown.send_md(image_md, bot, message_type=message_type, session_id=session_id, event=event,
|
|
||||||
# retry_as_image=False,
|
|
||||||
# **kwargs)
|
|
||||||
|
|
||||||
# Lagrange.OneBot方案
|
|
||||||
if method == 2:
|
if method == 2:
|
||||||
base64_string = base64.b64encode(image).decode("utf-8")
|
base64_string = base64.b64encode(image).decode("utf-8")
|
||||||
data = await bot.call_api("upload_image", file=f"base64://{base64_string}")
|
data = await bot.call_api("upload_image", file=f"base64://{base64_string}")
|
||||||
await MarkdownMessage.send_md(MarkdownMessage.image(data, Image.open(io.BytesIO(image)).size), bot,
|
await MarkdownMessage.send_md(MarkdownMessage.image(data, Image.open(io.BytesIO(image)).size), bot,
|
||||||
event=event, message_type=message_type,
|
message_type=message_type,
|
||||||
session_id=session_id, **kwargs)
|
session_id=session_id)
|
||||||
|
|
||||||
# 其他实现端方案
|
# 其他实现端方案
|
||||||
else:
|
else:
|
||||||
image_message_id = (await bot.send_private_msg(
|
image_message_id = (await bot.send_private_msg(
|
||||||
user_id=bot.self_id,
|
user_id=bot.self_id,
|
||||||
message=[
|
message=[
|
||||||
v11.MessageSegment.image(file=image)
|
v11.MessageSegment.image(file=image)
|
||||||
]
|
]
|
||||||
))["message_id"]
|
))["message_id"]
|
||||||
image_url = (await bot.get_msg(message_id=image_message_id))["message"][0]["data"]["url"]
|
image_url = (await bot.get_msg(message_id=image_message_id))["message"][0]["data"]["url"]
|
||||||
image_size = Image.open(io.BytesIO(image)).size
|
image_size = Image.open(io.BytesIO(image)).size
|
||||||
image_md = MarkdownMessage.image(image_url, image_size)
|
image_md = MarkdownMessage.image(image_url, image_size)
|
||||||
return await MarkdownMessage.send_md(image_md, bot, message_type=message_type, session_id=session_id,
|
return await MarkdownMessage.send_md(image_md, bot, message_type=message_type, session_id=session_id)
|
||||||
event=event, **kwargs)
|
|
||||||
|
|
||||||
if data is None:
|
if data is None:
|
||||||
data = await bot.send_msg(
|
data = await bot.send_msg(
|
||||||
|
Reference in New Issue
Block a user