feat(plugin): 新增极简插件开发模式
新增 SimplePlugin 基类,提供面向新手的极简插件开发方式 添加相关示例代码和文档说明
This commit is contained in:
217
core/plugin.py
Normal file
217
core/plugin.py
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
import inspect
|
||||||
|
import functools
|
||||||
|
from typing import Optional, Union, Any, Callable
|
||||||
|
from core.managers.command_manager import matcher as command_manager
|
||||||
|
from core.permission import Permission
|
||||||
|
from models.events.message import MessageEvent
|
||||||
|
|
||||||
|
class Plugin:
|
||||||
|
"""
|
||||||
|
插件基类,提供类风格的插件编写方式。
|
||||||
|
通过继承此类,可以使用装饰器在类方法上注册命令和事件处理器。
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
self._register_handlers()
|
||||||
|
|
||||||
|
def _register_handlers(self):
|
||||||
|
"""
|
||||||
|
自动注册带有装饰器的方法。
|
||||||
|
"""
|
||||||
|
# 遍历实例的所有方法
|
||||||
|
for name, method in inspect.getmembers(self, predicate=inspect.ismethod):
|
||||||
|
# 检查是否有命令元数据
|
||||||
|
if hasattr(method, "_command_meta"):
|
||||||
|
meta = method._command_meta
|
||||||
|
# 调用 command_manager 的装饰器来注册绑定后的方法
|
||||||
|
command_manager.command(
|
||||||
|
*meta['names'],
|
||||||
|
permission=meta.get('permission'),
|
||||||
|
override_permission_check=meta.get('override_permission_check', False)
|
||||||
|
)(method)
|
||||||
|
|
||||||
|
# 检查是否有消息处理元数据
|
||||||
|
if hasattr(method, "_on_message_meta"):
|
||||||
|
command_manager.on_message()(method)
|
||||||
|
|
||||||
|
# 检查是否有通知处理元数据
|
||||||
|
if hasattr(method, "_on_notice_meta"):
|
||||||
|
meta = method._on_notice_meta
|
||||||
|
command_manager.on_notice(notice_type=meta.get('notice_type'))(method)
|
||||||
|
|
||||||
|
# 检查是否有请求处理元数据
|
||||||
|
if hasattr(method, "_on_request_meta"):
|
||||||
|
meta = method._on_request_meta
|
||||||
|
command_manager.on_request(request_type=meta.get('request_type'))(method)
|
||||||
|
|
||||||
|
async def send(self, event: MessageEvent, message: Union[str, Any]):
|
||||||
|
"""
|
||||||
|
发送消息的基础逻辑。
|
||||||
|
"""
|
||||||
|
if hasattr(event, 'reply'):
|
||||||
|
await event.reply(message)
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def reply(self, event: MessageEvent, message: Union[str, Any]):
|
||||||
|
"""
|
||||||
|
回复消息。
|
||||||
|
"""
|
||||||
|
await self.send(event, message)
|
||||||
|
|
||||||
|
class SimplePlugin(Plugin):
|
||||||
|
"""
|
||||||
|
面向新手的简化插件基类。
|
||||||
|
|
||||||
|
特性:
|
||||||
|
1. 自动将公共方法(不以_开头)注册为指令。
|
||||||
|
2. 指令名默认为方法名。
|
||||||
|
3. 自动解析参数类型。
|
||||||
|
4. 支持直接返回字符串来回复消息。
|
||||||
|
"""
|
||||||
|
def _register_handlers(self):
|
||||||
|
# 先处理带装饰器的方法
|
||||||
|
super()._register_handlers()
|
||||||
|
|
||||||
|
# 扫描普通方法并注册为指令
|
||||||
|
for name, method in inspect.getmembers(self, predicate=inspect.ismethod):
|
||||||
|
if name.startswith("_"):
|
||||||
|
continue
|
||||||
|
if hasattr(method, "_command_meta"):
|
||||||
|
continue # 已经处理过
|
||||||
|
if hasattr(method, "_on_message_meta"):
|
||||||
|
continue
|
||||||
|
if hasattr(method, "_on_notice_meta"):
|
||||||
|
continue
|
||||||
|
if hasattr(method, "_on_request_meta"):
|
||||||
|
continue
|
||||||
|
if name in dir(Plugin):
|
||||||
|
continue # 忽略基类方法
|
||||||
|
|
||||||
|
self._register_method_as_command(name, method)
|
||||||
|
|
||||||
|
def _register_method_as_command(self, name: str, method: Callable):
|
||||||
|
# 获取方法的签名
|
||||||
|
sig = inspect.signature(method)
|
||||||
|
|
||||||
|
# 包装函数
|
||||||
|
@functools.wraps(method)
|
||||||
|
async def wrapper(event: MessageEvent, args: list[str]):
|
||||||
|
try:
|
||||||
|
# 准备调用参数
|
||||||
|
call_args: list[Any] = []
|
||||||
|
|
||||||
|
# 跳过 self,第一个参数应该是 event
|
||||||
|
params = list(sig.parameters.values())
|
||||||
|
if not params:
|
||||||
|
# 方法没有参数?这不应该发生,至少要有 event
|
||||||
|
await method()
|
||||||
|
return
|
||||||
|
|
||||||
|
# 绑定 event
|
||||||
|
call_args.append(event)
|
||||||
|
|
||||||
|
# 处理剩余参数
|
||||||
|
method_params = params[1:] # 除去 event
|
||||||
|
|
||||||
|
if not method_params:
|
||||||
|
# 方法不需要额外参数
|
||||||
|
pass
|
||||||
|
elif len(method_params) == 1:
|
||||||
|
# 只有一个参数,把所有 args 拼起来传给它
|
||||||
|
param = method_params[0]
|
||||||
|
if args:
|
||||||
|
str_val = " ".join(args)
|
||||||
|
val: Any = str_val
|
||||||
|
# 类型转换
|
||||||
|
if param.annotation is int:
|
||||||
|
val = int(str_val)
|
||||||
|
elif param.annotation is float:
|
||||||
|
val = float(str_val)
|
||||||
|
call_args.append(val)
|
||||||
|
elif param.default is not inspect.Parameter.empty:
|
||||||
|
call_args.append(param.default)
|
||||||
|
else:
|
||||||
|
await event.reply(f"缺少参数: {param.name}")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
# 多个参数,尝试一一对应
|
||||||
|
if len(args) < len([p for p in method_params if p.default is inspect.Parameter.empty]):
|
||||||
|
# 必填参数不足
|
||||||
|
usage = " ".join([f"<{p.name}>" for p in method_params])
|
||||||
|
await event.reply(f"参数不足。用法: /{name} {usage}")
|
||||||
|
return
|
||||||
|
|
||||||
|
for i, param in enumerate(method_params):
|
||||||
|
if i < len(args):
|
||||||
|
arg_str = args[i]
|
||||||
|
arg_val: Any = arg_str
|
||||||
|
# 简单的类型转换
|
||||||
|
try:
|
||||||
|
if param.annotation is int:
|
||||||
|
arg_val = int(arg_str)
|
||||||
|
elif param.annotation is float:
|
||||||
|
arg_val = float(arg_str)
|
||||||
|
except ValueError:
|
||||||
|
await event.reply(f"参数 {param.name} 类型错误,应为 {param.annotation.__name__}")
|
||||||
|
return
|
||||||
|
call_args.append(arg_val)
|
||||||
|
else:
|
||||||
|
call_args.append(param.default)
|
||||||
|
|
||||||
|
# 调用方法
|
||||||
|
result = await method(*call_args)
|
||||||
|
|
||||||
|
# 如果有返回值,自动回复
|
||||||
|
if result is not None:
|
||||||
|
await event.reply(str(result))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
await event.reply(f"执行命令时发生错误: {str(e)}")
|
||||||
|
|
||||||
|
# 注册命令
|
||||||
|
command_manager.command(name)(wrapper)
|
||||||
|
|
||||||
|
|
||||||
|
def command(name: str, *aliases: str, permission: Optional[Permission] = None, override_permission_check: bool = False):
|
||||||
|
"""
|
||||||
|
装饰器:标记方法为命令处理器。
|
||||||
|
"""
|
||||||
|
def decorator(func):
|
||||||
|
func._command_meta = {
|
||||||
|
"names": (name,) + aliases,
|
||||||
|
"permission": permission,
|
||||||
|
"override_permission_check": override_permission_check
|
||||||
|
}
|
||||||
|
return func
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def on_message():
|
||||||
|
"""
|
||||||
|
装饰器:标记方法为通用消息处理器。
|
||||||
|
"""
|
||||||
|
def decorator(func):
|
||||||
|
func._on_message_meta = {}
|
||||||
|
return func
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def on_notice(notice_type: Optional[str] = None):
|
||||||
|
"""
|
||||||
|
装饰器:标记方法为通知处理器。
|
||||||
|
"""
|
||||||
|
def decorator(func):
|
||||||
|
func._on_notice_meta = {
|
||||||
|
"notice_type": notice_type
|
||||||
|
}
|
||||||
|
return func
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def on_request(request_type: Optional[str] = None):
|
||||||
|
"""
|
||||||
|
装饰器:标记方法为请求处理器。
|
||||||
|
"""
|
||||||
|
def decorator(func):
|
||||||
|
func._on_request_meta = {
|
||||||
|
"request_type": request_type
|
||||||
|
}
|
||||||
|
return func
|
||||||
|
return decorator
|
||||||
@@ -73,6 +73,13 @@ Bot 应该会回复你:“你好,[你的昵称]!”
|
|||||||
|
|
||||||
就这么简单,一个最基础的插件就写完了。
|
就这么简单,一个最基础的插件就写完了。
|
||||||
|
|
||||||
|
## 极简插件开发(推荐新手)
|
||||||
|
|
||||||
|
如果你觉得上面的装饰器写法太复杂,或者只是想快速写几个简单的指令,我们提供了一种**极简模式**。
|
||||||
|
你只需要定义一个类,写几个方法,它们就会自动变成指令!
|
||||||
|
|
||||||
|
- [查看极简插件开发指南](./simple-plugin.md)
|
||||||
|
|
||||||
## 进阶阅读
|
## 进阶阅读
|
||||||
|
|
||||||
- [指令处理](./command-handling.md): 了解如何处理参数、获取用户输入。
|
- [指令处理](./command-handling.md): 了解如何处理参数、获取用户输入。
|
||||||
|
|||||||
127
docs/plugin-development/simple-plugin.md
Normal file
127
docs/plugin-development/simple-plugin.md
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
# 极简插件开发指南
|
||||||
|
|
||||||
|
如果你是 Python 新手,或者只是想快速写一些简单的指令,那么 `SimplePlugin` 是你的最佳选择。它让你无需理解复杂的装饰器和事件处理机制,只需要写普通的 Python 方法即可。
|
||||||
|
|
||||||
|
## 1. 快速开始
|
||||||
|
|
||||||
|
在 `plugins/` 目录下创建一个新文件,例如 `my_simple_plugin.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from core.plugin import SimplePlugin
|
||||||
|
from models.events.message import MessageEvent
|
||||||
|
|
||||||
|
class MyPlugin(SimplePlugin):
|
||||||
|
|
||||||
|
async def hello(self, event: MessageEvent):
|
||||||
|
"""
|
||||||
|
发送 /hello 即可调用
|
||||||
|
"""
|
||||||
|
return "你好!这是极简插件。"
|
||||||
|
|
||||||
|
async def echo(self, event: MessageEvent, msg: str):
|
||||||
|
"""
|
||||||
|
发送 /echo <内容> 即可调用
|
||||||
|
"""
|
||||||
|
return f"你说了: {msg}"
|
||||||
|
|
||||||
|
# 必须实例化插件以生效
|
||||||
|
plugin = MyPlugin()
|
||||||
|
```
|
||||||
|
|
||||||
|
就是这么简单!现在你可以发送 `/hello` 和 `/echo 测试` 来测试你的插件了。
|
||||||
|
|
||||||
|
## 2. 核心特性
|
||||||
|
|
||||||
|
### 方法即指令
|
||||||
|
|
||||||
|
在 `SimplePlugin` 的子类中,任何**不以下划线开头**的方法都会自动注册为指令。
|
||||||
|
指令名称就是方法名。
|
||||||
|
|
||||||
|
例如:
|
||||||
|
- `async def ping(self, ...)` -> 注册为 `/ping`
|
||||||
|
- `async def help_me(self, ...)` -> 注册为 `/help_me`
|
||||||
|
|
||||||
|
### 自动参数解析
|
||||||
|
|
||||||
|
框架会根据你定义的参数类型,自动解析用户输入的参数。
|
||||||
|
|
||||||
|
#### 字符串参数
|
||||||
|
```python
|
||||||
|
async def greet(self, event: MessageEvent, name: str):
|
||||||
|
return f"你好, {name}"
|
||||||
|
```
|
||||||
|
- 发送 `/greet Neo` -> `name` 参数为 `"Neo"`
|
||||||
|
|
||||||
|
#### 数字参数 (自动转换类型)
|
||||||
|
```python
|
||||||
|
async def add(self, event: MessageEvent, a: int, b: int):
|
||||||
|
return f"{a} + {b} = {a + b}"
|
||||||
|
```
|
||||||
|
- 发送 `/add 10 20` -> `a` 为 `10` (int), `b` 为 `20` (int)
|
||||||
|
- 如果用户输入非数字(如 `/add a b`),框架会自动提示参数类型错误。
|
||||||
|
|
||||||
|
#### 捕获剩余文本
|
||||||
|
如果你的方法只有一个参数(除了 `event`),那么该参数会捕获指令后的所有文本。
|
||||||
|
```python
|
||||||
|
async def broadcast(self, event: MessageEvent, content: str):
|
||||||
|
return f"广播内容: {content}"
|
||||||
|
```
|
||||||
|
- 发送 `/broadcast 这是一个 很长 的消息` -> `content` 为 `"这是一个 很长 的消息"`
|
||||||
|
|
||||||
|
### 自动回复
|
||||||
|
|
||||||
|
如果你的方法返回了字符串(`str`),框架会自动将其作为回复发送给用户。
|
||||||
|
如果返回 `None`(即没有 return 语句),则不发送回复。
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def silent(self, event: MessageEvent):
|
||||||
|
# 执行一些操作,但不回复
|
||||||
|
print("Silent command executed")
|
||||||
|
# 也可以手动调用 reply
|
||||||
|
await event.reply("手动回复")
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. 进阶用法
|
||||||
|
|
||||||
|
### 访问事件对象
|
||||||
|
|
||||||
|
所有方法的第一个参数(除了 `self`)必须是 `event`。通过 `event` 对象,你可以获取更多信息:
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def whoami(self, event: MessageEvent):
|
||||||
|
user_id = event.user_id
|
||||||
|
nickname = event.sender.nickname
|
||||||
|
return f"你是 {nickname} ({user_id})"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 混合使用装饰器
|
||||||
|
|
||||||
|
虽然 `SimplePlugin` 旨在简化开发,但你仍然可以使用装饰器来处理更复杂的场景,例如权限控制或监听非指令消息。
|
||||||
|
|
||||||
|
```python
|
||||||
|
from core.plugin import SimplePlugin, command, on_message
|
||||||
|
from core.permission import Permission
|
||||||
|
|
||||||
|
class AdvancedPlugin(SimplePlugin):
|
||||||
|
|
||||||
|
# 普通指令
|
||||||
|
async def normal(self, event: MessageEvent):
|
||||||
|
return "普通指令"
|
||||||
|
|
||||||
|
# 使用装饰器添加权限控制
|
||||||
|
@command("admin_only", permission=Permission.ADMIN)
|
||||||
|
async def admin_op(self, event: MessageEvent, args: list[str]):
|
||||||
|
return "只有管理员能看到这个"
|
||||||
|
|
||||||
|
# 监听所有消息
|
||||||
|
@on_message()
|
||||||
|
async def handle_all(self, event: MessageEvent):
|
||||||
|
if "敏感词" in event.raw_message:
|
||||||
|
await event.reply("检测到敏感词!")
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. 注意事项
|
||||||
|
|
||||||
|
1. **方法名**:不要使用以 `_` 开头的方法名作为指令,这些方法会被忽略。
|
||||||
|
2. **参数类型**:目前支持 `str`, `int`, `float` 的自动转换。
|
||||||
|
3. **实例化**:不要忘记在文件末尾实例化你的类(`plugin = MyPlugin()`),否则插件不会生效。
|
||||||
38
plugins/class_style_example.py
Normal file
38
plugins/class_style_example.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
from core.plugin import Plugin, command, on_message
|
||||||
|
from models.events.message import MessageEvent
|
||||||
|
from core.permission import Permission
|
||||||
|
|
||||||
|
# 插件元信息
|
||||||
|
__plugin_meta__ = {
|
||||||
|
"name": "类风格插件示例",
|
||||||
|
"description": "演示如何使用类风格编写插件",
|
||||||
|
"usage": "/hello - 打招呼\n/echo <msg> - 复读消息",
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyPlugin(Plugin):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
# 可以在这里初始化一些状态
|
||||||
|
self.count = 0
|
||||||
|
|
||||||
|
@command("hello")
|
||||||
|
async def hello(self, event: MessageEvent, args: list[str]):
|
||||||
|
self.count += 1
|
||||||
|
await self.reply(event, f"Hello from class-based plugin! (Called {self.count} times)")
|
||||||
|
|
||||||
|
@command("echo", permission=Permission.USER)
|
||||||
|
async def echo(self, event: MessageEvent, args: list[str]):
|
||||||
|
if args:
|
||||||
|
await self.reply(event, " ".join(args))
|
||||||
|
else:
|
||||||
|
await self.reply(event, "请输入要复读的内容。")
|
||||||
|
|
||||||
|
@on_message()
|
||||||
|
async def handle_message(self, event: MessageEvent):
|
||||||
|
# 这是一个通用的消息处理器,会处理所有消息
|
||||||
|
# 注意:这可能会与命令冲突,通常需要过滤
|
||||||
|
if "特定关键词" in event.raw_message:
|
||||||
|
await self.reply(event, "检测到特定关键词!")
|
||||||
|
|
||||||
|
# 实例化插件以注册
|
||||||
|
plugin = MyPlugin()
|
||||||
41
plugins/simple_style_example.py
Normal file
41
plugins/simple_style_example.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
from core.plugin import SimplePlugin
|
||||||
|
from models.events.message import MessageEvent
|
||||||
|
|
||||||
|
# 插件元信息
|
||||||
|
__plugin_meta__ = {
|
||||||
|
"name": "极简插件示例",
|
||||||
|
"description": "演示面向新手的极简插件写法",
|
||||||
|
"usage": "/ping - 测试\n/add <a> <b> - 加法\n/greet <name> - 问候",
|
||||||
|
}
|
||||||
|
|
||||||
|
class MySimplePlugin(SimplePlugin):
|
||||||
|
|
||||||
|
async def ping(self, event: MessageEvent):
|
||||||
|
"""
|
||||||
|
发送 /ping 即可调用
|
||||||
|
"""
|
||||||
|
return "Pong! (来自极简插件)"
|
||||||
|
|
||||||
|
async def greet(self, event: MessageEvent, name: str):
|
||||||
|
"""
|
||||||
|
发送 /greet Neo 即可调用
|
||||||
|
"""
|
||||||
|
return f"你好, {name}!"
|
||||||
|
|
||||||
|
async def add(self, event: MessageEvent, a: int, b: int):
|
||||||
|
"""
|
||||||
|
发送 /add 10 20 即可调用
|
||||||
|
自动处理类型转换
|
||||||
|
"""
|
||||||
|
return f"{a} + {b} = {a + b}"
|
||||||
|
|
||||||
|
async def echo_all(self, event: MessageEvent, msg: str):
|
||||||
|
"""
|
||||||
|
只有一个参数时,会自动捕获所有剩余文本
|
||||||
|
发送 /echo_all 这是一个 测试 消息
|
||||||
|
msg 将会是 "这是一个 测试 消息"
|
||||||
|
"""
|
||||||
|
return f"复读: {msg}"
|
||||||
|
|
||||||
|
# 实例化插件以生效
|
||||||
|
plugin = MySimplePlugin()
|
||||||
Reference in New Issue
Block a user