Files
cellbot/docs/plugin_guide.md
lafay d16261e6bd feat: add rate limiting and improve event handling
- Introduced rate limiting configuration in config.toml with options for enabling, requests per second (RPS), and burst capacity.
- Enhanced event handling in the OneBot11 adapter to ignore messages sent by the bot itself.
- Updated the dispatcher to register rate limit middleware based on configuration settings.
- Refactored WebSocket message handling to support flexible JSON parsing and improved event type detection.
- Removed deprecated echo plugin and associated tests to streamline the codebase.
2026-01-05 01:00:38 +08:00

307 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CellBot 插件开发指南
## 快速开始
CellBot 提供了类似 ZeroBot 风格的插件注册方式,可以在一个包内注册多个处理函数。
### ZeroBot 风格(推荐)
`init` 函数中使用 `OnXXX().Handle()` 注册处理函数,一个包内可以注册多个:
```go
package echo
import (
"context"
"cellbot/internal/engine"
"cellbot/internal/protocol"
"go.uber.org/zap"
)
func init() {
// 处理私聊消息
engine.OnPrivateMessage().
Handle(func(ctx context.Context, event protocol.Event, botManager *protocol.BotManager, logger *zap.Logger) error {
data := event.GetData()
message := data["message"]
userID := data["user_id"]
// 获取 bot 实例
bot, _ := botManager.Get(event.GetSelfID())
// 发送回复
action := &protocol.BaseAction{
Type: protocol.ActionTypeSendPrivateMessage,
Params: map[string]interface{}{
"user_id": userID,
"message": message,
},
}
return bot.SendAction(ctx, action)
})
// 可以继续注册更多处理函数
engine.OnGroupMessage().
Handle(func(ctx context.Context, event protocol.Event, botManager *protocol.BotManager, logger *zap.Logger) error {
// 处理群消息
return nil
})
// 处理命令
engine.OnCommand("/help").
Handle(func(ctx context.Context, event protocol.Event, botManager *protocol.BotManager, logger *zap.Logger) error {
// 处理 /help 命令
return nil
})
}
```
**注意**:需要在 `internal/di/providers.go` 中导入插件包以触发 `init` 函数:
```go
import (
_ "cellbot/internal/plugins/echo" // 导入插件以触发 init
)
```
### 方式二:传统方式
实现 `protocol.EventHandler` 接口:
```go
package myplugin
import (
"context"
"cellbot/internal/protocol"
"go.uber.org/zap"
)
type MyPlugin struct {
logger *zap.Logger
botManager *protocol.BotManager
}
func NewMyPlugin(logger *zap.Logger, botManager *protocol.BotManager) *MyPlugin {
return &MyPlugin{
logger: logger.Named("my-plugin"),
botManager: botManager,
}
}
func (p *MyPlugin) Name() string {
return "MyPlugin"
}
func (p *MyPlugin) Description() string {
return "我的插件"
}
func (p *MyPlugin) Priority() int {
return 100
}
func (p *MyPlugin) Match(event protocol.Event) bool {
return event.GetType() == protocol.EventTypeMessage
}
func (p *MyPlugin) Handle(ctx context.Context, event protocol.Event) error {
// 处理逻辑
return nil
}
```
## 内置匹配器
提供了以下便捷的匹配器函数:
- `engine.OnPrivateMessage()` - 匹配私聊消息
- `engine.OnGroupMessage()` - 匹配群消息
- `engine.OnMessage()` - 匹配所有消息
- `engine.OnNotice()` - 匹配通知事件
- `engine.OnRequest()` - 匹配请求事件
- `engine.OnCommand(prefix)` - 匹配命令(以指定前缀开头)
- `engine.OnPrefix(prefix)` - 匹配以指定前缀开头的消息
- `engine.OnSuffix(suffix)` - 匹配以指定后缀结尾的消息
- `engine.OnKeyword(keyword)` - 匹配包含指定关键词的消息
- `engine.On(matchFunc)` - 自定义匹配器
### 自定义匹配器
```go
func init() {
engine.On(func(event protocol.Event) bool {
// 自定义匹配逻辑
if event.GetType() != protocol.EventTypeMessage {
return false
}
data := event.GetData()
message, ok := data["raw_message"].(string)
if !ok {
return false
}
// 只匹配以 "/" 开头的消息
return len(message) > 0 && message[0] == '/'
}).
Priority(50). // 可以设置优先级
Handle(func(ctx context.Context, event protocol.Event, botManager *protocol.BotManager, logger *zap.Logger) error {
// 处理命令
return nil
})
}
```
## 注册插件
插件通过 `init` 函数自动注册,只需在 `internal/di/providers.go` 中导入插件包:
```go
import (
_ "cellbot/internal/plugins/echo" // 导入插件以触发 init
_ "cellbot/internal/plugins/other" // 可以导入多个插件包
)
```
插件会在应用启动时自动加载,无需手动注册。
## 插件优先级
优先级数值越小,越先执行。建议:
- 0-50: 高优先级(预处理、权限检查等)
- 51-100: 中等优先级(普通功能插件)
- 101-200: 低优先级(日志记录、统计等)
## 完整示例
### 示例 1关键词回复插件
```go
package keyword
import (
"context"
"cellbot/internal/engine"
"cellbot/internal/protocol"
"go.uber.org/zap"
)
func init() {
keywords := map[string]string{
"你好": "你好呀!",
"再见": "再见~",
"帮助": "发送 /help 查看帮助",
}
engine.OnMessage().
Priority(80).
Handle(func(ctx context.Context, event protocol.Event, botManager *protocol.BotManager, logger *zap.Logger) error {
data := event.GetData()
message, ok := data["raw_message"].(string)
if !ok {
return nil
}
// 检查关键词
reply, found := keywords[message]
if !found {
return nil
}
// 获取 bot 和用户信息
bot, _ := botManager.Get(event.GetSelfID())
userID := data["user_id"]
// 发送回复
action := &protocol.BaseAction{
Type: protocol.ActionTypeSendPrivateMessage,
Params: map[string]interface{}{
"user_id": userID,
"message": reply,
},
}
_, err := bot.SendAction(ctx, action)
return err
})
}
```
### 示例 2命令插件
```go
func RegisterCommandPlugin(registry *engine.PluginRegistry, botManager *protocol.BotManager, logger *zap.Logger) {
plugin := engine.NewPlugin("CommandPlugin").
Description("命令处理插件").
Priority(50).
Match(func(event protocol.Event) bool {
if event.GetType() != protocol.EventTypeMessage {
return false
}
data := event.GetData()
message, ok := data["raw_message"].(string)
return ok && len(message) > 0 && message[0] == '/'
}).
Handle(func(ctx context.Context, event protocol.Event) error {
data := event.GetData()
message := data["raw_message"].(string)
userID := data["user_id"]
bot, _ := botManager.Get(event.GetSelfID())
var reply string
switch message {
case "/help":
reply = "可用命令:\n/help - 显示帮助\n/ping - 测试连接\n/time - 显示时间"
case "/ping":
reply = "pong!"
case "/time":
reply = time.Now().Format("2006-01-02 15:04:05")
default:
reply = "未知命令,发送 /help 查看帮助"
}
action := &protocol.BaseAction{
Type: protocol.ActionTypeSendPrivateMessage,
Params: map[string]interface{}{
"user_id": userID,
"message": reply,
},
}
_, err := bot.SendAction(ctx, action)
return err
}).
Build()
registry.Register(plugin)
}
```
## 最佳实践
1. **使用简化方式**:对于简单插件,使用 `engine.NewPlugin` 构建器
2. **使用传统方式**:对于复杂插件(需要状态管理、配置等),使用传统方式
3. **合理设置优先级**:确保插件按正确顺序执行
4. **错误处理**:在 Handle 函数中妥善处理错误
5. **日志记录**:使用 logger 记录关键操作
6. **避免阻塞**Handle 函数应快速返回,耗时操作应使用 goroutine
## 插件生命周期
插件在应用启动时注册,在应用运行期间持续监听事件。目前不支持热重载。
## 调试技巧
1. 使用 `logger.Debug` 记录调试信息
2. 在 Match 函数中添加日志,确认匹配逻辑
3. 检查事件数据结构,确保字段存在
4. 使用 `zap.Any` 打印完整事件数据
```go
logger.Debug("Event data", zap.Any("data", event.GetData()))
```