Files
cellbot/internal/plugins/welcome/welcome.go
lafay 64cd81b7f1 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.
2026-01-05 04:33:30 +08:00

348 lines
9.3 KiB
Go
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.

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
}