feat: enhance event handling and add scheduling capabilities

- Introduced a new scheduler to manage timed tasks within the event dispatcher.
- Updated the dispatcher to support the new scheduler, allowing for improved event processing.
- Enhanced action serialization in the OneBot11 adapter to convert message chains to the appropriate format.
- Added new dependencies for cron scheduling and other indirect packages in go.mod and go.sum.
- Improved logging for event publishing and handler matching, providing better insights during execution.
- Refactored plugin loading to include scheduled job management.
This commit is contained in:
lafay
2026-01-05 04:33:30 +08:00
parent d16261e6bd
commit 64cd81b7f1
14 changed files with 2130 additions and 27 deletions

View File

@@ -12,8 +12,9 @@ import (
func init() {
// 在 init 函数中注册多个处理函数(类似 ZeroBot 风格)
// 处理私聊消息
// 处理私聊消息(使用 OnlyPrivate 中间件,虽然 OnPrivateMessage 已经匹配私聊,这里作为示例)
engine.OnPrivateMessage().
Use(engine.OnlyPrivate()). // 只在私聊中响应
Handle(func(ctx context.Context, event protocol.Event, botManager *protocol.BotManager, logger *zap.Logger) error {
// 获取消息内容
data := event.GetData()

View File

@@ -0,0 +1,347 @@
package welcome
import (
"context"
"fmt"
"time"
"cellbot/internal/engine"
"cellbot/internal/protocol"
"cellbot/pkg/utils"
"go.uber.org/zap"
)
func init() {
// 监听群成员增加通知事件
engine.OnNotice("group_increase").
Priority(50). // 设置较高优先级,确保及时响应
Handle(handleWelcomeEvent)
}
// handleWelcomeEvent 处理群成员加入欢迎事件
func handleWelcomeEvent(ctx context.Context, event protocol.Event, botManager *protocol.BotManager, logger *zap.Logger) error {
logger.Info("Welcome event received",
zap.String("event_type", string(event.GetType())),
zap.String("detail_type", event.GetDetailType()),
zap.String("sub_type", event.GetSubType()),
zap.String("self_id", event.GetSelfID()))
// 注意:中间件已经过滤了 detail_type这里可以简化检查
data := event.GetData()
logger.Debug("Event data", zap.Any("data", data))
// 获取群ID和用户ID
groupID, ok := data["group_id"]
if !ok {
logger.Warn("Missing group_id in event data")
return nil
}
userID, ok := data["user_id"]
if !ok {
logger.Warn("Missing user_id in event data")
return nil
}
logger.Info("Processing welcome event",
zap.Any("group_id", groupID),
zap.Any("user_id", userID))
// 获取操作者ID邀请者或审批者
var operatorID interface{}
if opID, exists := data["operator_id"]; exists {
operatorID = opID
}
// 获取子类型approve: 管理员同意, invite: 邀请)
subType := event.GetSubType()
// 获取 bot 实例
selfID := event.GetSelfID()
bot, ok := botManager.Get(selfID)
if !ok {
bots := botManager.GetAll()
if len(bots) == 0 {
logger.Error("No bot instance available")
return nil
}
bot = bots[0]
}
// 构建欢迎消息链使用HTML模板渲染图片
logger.Info("Building welcome message",
zap.Any("user_id", userID),
zap.Any("operator_id", operatorID),
zap.String("sub_type", subType))
welcomeChain, err := buildWelcomeMessage(ctx, userID, operatorID, subType, logger)
if err != nil {
logger.Error("Failed to build welcome message",
zap.Any("user_id", userID),
zap.Any("operator_id", operatorID),
zap.String("sub_type", subType),
zap.Error(err))
return err
}
logger.Info("Welcome message built successfully",
zap.Int("chain_length", len(welcomeChain)))
// 发送群消息
logger.Info("Sending welcome message",
zap.Any("group_id", groupID),
zap.Any("user_id", userID))
action := &protocol.BaseAction{
Type: protocol.ActionTypeSendGroupMessage,
Params: map[string]interface{}{
"group_id": groupID,
"message": welcomeChain,
},
}
result, err := bot.SendAction(ctx, action)
if err != nil {
logger.Error("Failed to send welcome message",
zap.Any("group_id", groupID),
zap.Any("user_id", userID),
zap.Any("action_type", action.Type),
zap.Error(err))
return err
}
logger.Info("Welcome message sent successfully",
zap.Any("group_id", groupID),
zap.Any("user_id", userID),
zap.Any("result", result))
logger.Info("Welcome message sent",
zap.Any("group_id", groupID),
zap.Any("user_id", userID),
zap.String("sub_type", subType))
return nil
}
// welcomeTemplate HTML欢迎消息模板
const welcomeTemplate = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 40px 20px;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.container {
background: white;
border-radius: 20px;
padding: 40px;
max-width: 600px;
width: 100%;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.header {
text-align: center;
margin-bottom: 30px;
}
.welcome-icon {
font-size: 64px;
margin-bottom: 10px;
}
.title {
font-size: 32px;
font-weight: bold;
color: #333;
margin-bottom: 10px;
}
.subtitle {
font-size: 18px;
color: #666;
}
.content {
margin: 30px 0;
}
.user-info {
background: #f8f9fa;
border-radius: 10px;
padding: 20px;
margin: 20px 0;
}
.info-item {
display: flex;
justify-content: space-between;
padding: 10px 0;
border-bottom: 1px solid #e9ecef;
}
.info-item:last-child {
border-bottom: none;
}
.info-label {
font-weight: 600;
color: #495057;
}
.info-value {
color: #212529;
}
.join-type {
text-align: center;
padding: 15px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 10px;
margin: 20px 0;
font-weight: 600;
}
.tips {
background: #e7f3ff;
border-left: 4px solid #2196F3;
padding: 15px;
border-radius: 5px;
margin: 20px 0;
}
.tips-title {
font-weight: 600;
color: #1976D2;
margin-bottom: 10px;
}
.tips-list {
list-style: none;
padding-left: 0;
}
.tips-list li {
padding: 5px 0;
color: #424242;
}
.tips-list li:before {
content: "• ";
color: #2196F3;
font-weight: bold;
}
.footer {
text-align: center;
margin-top: 30px;
color: #999;
font-size: 14px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="welcome-icon">🎉</div>
<div class="title">欢迎加入!</div>
<div class="subtitle">Welcome to the Group</div>
</div>
<div class="content">
<div class="user-info">
<div class="info-item">
<span class="info-label">用户ID</span>
<span class="info-value">{{.UserID}}</span>
</div>
{{if .OperatorID}}
<div class="info-item">
<span class="info-label">{{.OperatorLabel}}</span>
<span class="info-value">{{.OperatorID}}</span>
</div>
{{end}}
</div>
{{if .JoinType}}
<div class="join-type">
{{.JoinType}}
</div>
{{end}}
<div class="tips">
<div class="tips-title">💡 温馨提示</div>
<ul class="tips-list">
<li>请遵守群规,文明交流</li>
<li>如有问题可以@管理员</li>
<li>发送 /help 查看帮助</li>
</ul>
</div>
</div>
<div class="footer">
希望你在群里玩得开心!
</div>
</div>
</body>
</html>
`
// buildWelcomeMessage 构建欢迎消息链使用HTML模板渲染图片
func buildWelcomeMessage(ctx context.Context, userID, operatorID interface{}, subType string, logger *zap.Logger) (protocol.MessageChain, error) {
logger.Debug("Starting to build welcome message",
zap.Any("user_id", userID),
zap.Any("operator_id", operatorID),
zap.String("sub_type", subType))
// 准备模板数据
data := map[string]interface{}{
"UserID": fmt.Sprintf("%v", userID),
}
logger.Debug("Template data prepared", zap.Any("data", data))
// 根据加入方式设置不同的信息
switch subType {
case "approve":
data["JoinType"] = "✅ 管理员审批通过"
if operatorID != nil {
data["OperatorID"] = fmt.Sprintf("%v", operatorID)
data["OperatorLabel"] = "审批管理员"
}
case "invite":
data["JoinType"] = "👥 被邀请加入"
if operatorID != nil {
data["OperatorID"] = fmt.Sprintf("%v", operatorID)
data["OperatorLabel"] = "邀请人"
}
default:
data["JoinType"] = "🎊 加入群聊"
}
// 配置截图选项
opts := &utils.ScreenshotOptions{
Width: 800,
Height: 600,
Timeout: 60 * time.Second, // 增加超时时间到60秒
WaitTime: 3 * time.Second, // 增加等待时间,确保页面完全加载
FullPage: false,
Quality: 90,
Logger: logger,
}
// 渲染模板并截图
logger.Info("Rendering template and taking screenshot",
zap.Int("width", opts.Width),
zap.Int("height", opts.Height),
zap.Duration("timeout", opts.Timeout))
chain, err := utils.ScreenshotTemplateToMessageChain(ctx, welcomeTemplate, data, opts)
if err != nil {
logger.Error("Failed to render welcome template",
zap.Any("data", data),
zap.Error(err))
return nil, fmt.Errorf("failed to render welcome template: %w", err)
}
logger.Info("Template rendered and screenshot taken successfully",
zap.Int("chain_length", len(chain)))
return chain, nil
}