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.
This commit is contained in:
306
docs/plugin_guide.md
Normal file
306
docs/plugin_guide.md
Normal file
@@ -0,0 +1,306 @@
|
||||
# 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()))
|
||||
```
|
||||
Reference in New Issue
Block a user