- 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.
8.5 KiB
8.5 KiB
CellBot 插件开发指南
快速开始
CellBot 提供了类似 ZeroBot 风格的插件注册方式,可以在一个包内注册多个处理函数。
ZeroBot 风格(推荐)
在 init 函数中使用 OnXXX().Handle() 注册处理函数,一个包内可以注册多个:
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 函数:
import (
_ "cellbot/internal/plugins/echo" // 导入插件以触发 init
)
方式二:传统方式
实现 protocol.EventHandler 接口:
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)- 自定义匹配器
自定义匹配器
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 中导入插件包:
import (
_ "cellbot/internal/plugins/echo" // 导入插件以触发 init
_ "cellbot/internal/plugins/other" // 可以导入多个插件包
)
插件会在应用启动时自动加载,无需手动注册。
插件优先级
优先级数值越小,越先执行。建议:
- 0-50: 高优先级(预处理、权限检查等)
- 51-100: 中等优先级(普通功能插件)
- 101-200: 低优先级(日志记录、统计等)
完整示例
示例 1:关键词回复插件
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:命令插件
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)
}
最佳实践
- 使用简化方式:对于简单插件,使用
engine.NewPlugin构建器 - 使用传统方式:对于复杂插件(需要状态管理、配置等),使用传统方式
- 合理设置优先级:确保插件按正确顺序执行
- 错误处理:在 Handle 函数中妥善处理错误
- 日志记录:使用 logger 记录关键操作
- 避免阻塞:Handle 函数应快速返回,耗时操作应使用 goroutine
插件生命周期
插件在应用启动时注册,在应用运行期间持续监听事件。目前不支持热重载。
调试技巧
- 使用
logger.Debug记录调试信息 - 在 Match 函数中添加日志,确认匹配逻辑
- 检查事件数据结构,确保字段存在
- 使用
zap.Any打印完整事件数据
logger.Debug("Event data", zap.Any("data", event.GetData()))