Files
cellbot/internal/plugins/welcome/welcome.go
lafay fb5fae1524 chore: update project structure and enhance plugin functionality
- Added new entries to .gitignore for database files.
- Updated go.mod and go.sum to include new indirect dependencies for database and ORM support.
- Refactored event handling to improve message reply functionality in the protocol.
- Enhanced the dispatcher to allow for better event processing and logging.
- Removed outdated plugin documentation and unnecessary files to streamline the codebase.
- Improved welcome message formatting and screenshot options for better user experience.
2026-01-05 05:14:31 +08:00

348 lines
9.4 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: 1200, // 增加宽度,确保内容有足够空间
Height: 800, // 增加高度,确保内容完整显示
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
}