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