Parcourir la source

feat: 实现 Telegram 群组管理和红包发送功能

## 主要功能

### 1. Telegram 群组管理
- 添加 /groupinfo 命令,支持在群组中查看群组信息(ID、名称、类型等)
- 实现群组信息自动同步:Bot 收到群组消息时自动保存群组信息到数据库
- 新增 magic_tg_group 数据表模型
- 实现群组同步 API:从数据库获取已记录的群组列表

### 2. 后台红包发送
- 新增手动发送红包 API(magic_admin)
- 支持直接发送红包到指定群组
- 与 magic_server 集成,通过签名验证调用红包发送接口

### 3. Bot 命令增强
- /groupinfo - 查看群组详细信息(群组ID可点击复制)
- 支持 HTML 格式消息发送

### 4. 系统配置修复
- 修复签名配置表名:sys_sign_config -> ams_sign_config
- 更新 .gitignore,忽略编译后的二进制文件

## 技术细节

- 使用 Keccak256 + ECDSA 签名验证服务间通信
- 自动同步机制:消息处理时异步更新群组信息
- HTTP 标准库重构:移除对自定义 utils 的依赖
urban il y a 3 semaines
Parent
commit
ac6bf76f6a

+ 6 - 0
.gitignore

@@ -90,3 +90,9 @@ docs/
 # Project specific
 .specstory
 conf/app.local.yml
+
+magic_admin/magic_admin
+magic_admin/go_server
+magic_server/magic_server
+magic_server/app
+后台手动发送红包功能说明.md

+ 154 - 3
magic_admin/service/app/tg_group.go

@@ -1,13 +1,19 @@
 package app
 
 import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
 	"time"
 
 	"github.com/demdxx/gocast"
 	"github.com/gin-gonic/gin"
+	"go_server/base/core"
 	"go_server/model/biz_modules/app"
 	"go_server/model/common/response"
 	"go_server/service/base"
+	"go_server/utils"
 )
 
 type TgGroupService struct {
@@ -221,7 +227,152 @@ func (s *TgGroupService) Delete(c *gin.Context) {
 // SyncFromBot 从 Telegram Bot 同步群组信息
 func (s *TgGroupService) SyncFromBot(c *gin.Context) {
 	s.SetDbAlias("app")
-	// TODO: 实现从 Telegram Bot API 获取群组列表并同步到数据库
-	// 这个功能需要调用 magic_server 的接口来获取 bot 加入的所有群组
-	response.Resp(c, "功能开发中")
+
+	// 调用 magic_server API 同步群组信息
+	groups, err := s.callMagicServerSyncGroups()
+	if err != nil {
+		core.Log.Errorf("同步群组失败: %v", err)
+		response.Resp(c, fmt.Sprintf("同步失败: %v", err))
+		return
+	}
+
+	// 统计新增和更新的数量
+	newCount := 0
+	updateCount := 0
+
+	// 遍历群组,保存或更新到数据库
+	for _, groupData := range groups {
+		// 检查群组是否已存在
+		var existingGroup app.TgGroup
+		err := s.DB().Where("chat_id = ?", groupData.ChatId).First(&existingGroup).Error
+
+		now := time.Now().Unix()
+
+		if err != nil {
+			// 群组不存在,创建新记录
+			newGroup := &app.TgGroup{
+				ChatId:      groupData.ChatId,
+				ChatType:    groupData.ChatType,
+				Title:       groupData.Title,
+				Username:    groupData.Username,
+				Description: groupData.Description,
+				MemberCount: groupData.MemberCount,
+				Status:      1, // 默认正常状态
+				BotJoinedAt: groupData.BotJoinedAt,
+				CreatedAt:   now,
+				UpdatedAt:   now,
+			}
+
+			if err := s.DB().Create(newGroup).Error; err != nil {
+				core.Log.Errorf("创建群组记录失败: %v", err)
+				continue
+			}
+			newCount++
+		} else {
+			// 群组已存在,更新信息
+			updates := map[string]interface{}{
+				"title":        groupData.Title,
+				"username":     groupData.Username,
+				"description":  groupData.Description,
+				"member_count": groupData.MemberCount,
+				"updated_at":   now,
+			}
+
+			if err := s.DB().Model(&existingGroup).Updates(updates).Error; err != nil {
+				core.Log.Errorf("更新群组记录失败: %v", err)
+				continue
+			}
+			updateCount++
+		}
+	}
+
+	core.Log.Infof("群组同步完成: 新增 %d 个, 更新 %d 个", newCount, updateCount)
+
+	response.Resp(c, map[string]interface{}{
+		"message":     "同步成功",
+		"totalCount":  len(groups),
+		"newCount":    newCount,
+		"updateCount": updateCount,
+	})
+}
+
+// callMagicServerSyncGroups 调用 magic_server API 同步群组
+func (s *TgGroupService) callMagicServerSyncGroups() ([]GroupData, error) {
+	core.Log.Info("========== 开始同步群组 ==========")
+
+	// magic_server API 地址
+	apiURL := "http://localhost:2011/api/v1/adi/telegram/groups/sync"
+
+	// 生成签名
+	core.Log.Info("正在生成签名...")
+	signMessage, err := utils.BuildSignMessage()
+	if err != nil {
+		core.Log.Errorf("生成签名失败: %v", err)
+		return nil, fmt.Errorf("生成签名失败: %v", err)
+	}
+	core.Log.Infof("签名生成成功: %s", signMessage[:50]+"...")
+
+	// 发送 HTTP 请求
+	req, err := http.NewRequest("POST", apiURL, nil)
+	if err != nil {
+		return nil, fmt.Errorf("创建 HTTP 请求失败: %v", err)
+	}
+
+	req.Header.Set("Content-Type", "application/json")
+	req.Header.Set("sign", signMessage)
+
+	core.Log.Infof("开始调用 magic_server API: %s", apiURL)
+
+	client := &http.Client{Timeout: 10 * time.Second}
+	resp, err := client.Do(req)
+	if err != nil {
+		return nil, fmt.Errorf("发送 HTTP 请求失败: %v", err)
+	}
+	defer resp.Body.Close()
+
+	core.Log.Infof("收到响应,状态码: %d", resp.StatusCode)
+
+	// 解析响应
+	var result struct {
+		Code    int    `json:"code"`
+		Msg     string `json:"msg"`     // magic_server 使用 msg 而不是 message
+		Message string `json:"message"` // 兼容两种格式
+		Data    struct {
+			Groups []GroupData `json:"groups"`
+			Count  int         `json:"count"`
+		} `json:"data"`
+	}
+
+	body, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return nil, fmt.Errorf("读取响应失败: %v", err)
+	}
+
+	// 打印原始响应用于调试
+	core.Log.Infof("API 响应: %s", string(body))
+
+	if err := json.Unmarshal(body, &result); err != nil {
+		return nil, fmt.Errorf("解析响应失败: %v, 原始响应: %s", err, string(body))
+	}
+
+	if result.Code != 0 && result.Code != 200 {
+		errMsg := result.Message
+		if errMsg == "" {
+			errMsg = result.Msg
+		}
+		return nil, fmt.Errorf("API 返回错误(code=%d): %s", result.Code, errMsg)
+	}
+
+	return result.Data.Groups, nil
+}
+
+// GroupData 群组数据结构(用于与 magic_server 交互)
+type GroupData struct {
+	ChatId      int64  `json:"chatId"`
+	ChatType    string `json:"chatType"`
+	Title       string `json:"title"`
+	Username    string `json:"username"`
+	Description string `json:"description"`
+	MemberCount int    `json:"memberCount"`
+	BotJoinedAt int64  `json:"botJoinedAt"`
 }

+ 93 - 24
magic_admin/service/app/tg_red_packet_send.go

@@ -1,16 +1,19 @@
 package app
 
 import (
+	"bytes"
+	"encoding/json"
 	"fmt"
-	"go_server/base/core"
-	model "go_server/model/biz_modules/app"
-	"go_server/model/common/response"
-	"go_server/service/base"
-	"go_server/utils"
+	"io"
+	"net/http"
 	"time"
 
 	"github.com/gin-gonic/gin"
 	"github.com/shopspring/decimal"
+	"go_server/base/core"
+	model "go_server/model/biz_modules/app"
+	"go_server/model/common/response"
+	"go_server/service/base"
 )
 
 type TgRedPacketSendService struct {
@@ -126,34 +129,100 @@ func (s *TgRedPacketSendService) SendDirect(c *gin.Context) {
 	})
 }
 
-// executeSendRedPacket 执行发送红包的核心逻辑
+// ExecuteSendRedPacket 执行发送红包的核心逻辑
 func (s *TgRedPacketSendService) ExecuteSendRedPacket(config *model.TgRedPacketConfig) (string, error) {
-	// 生成红包编号
-	packetNo := fmt.Sprintf("RP%d%s", time.Now().Unix(), utils.GenStrUuid()[0:8])
-
-	// TODO: 这里需要调用 magic_server 的红包服务
-	// 暂时先记录到数据库,实际发送需要通过 HTTP 调用 magic_server 的 API
-	// 或者直接在这里调用 Telegram Bot API
+	// 调用 magic_server API 发送红包
+	packetNo, err := s.callMagicServerSendRedPacket(config)
+	if err != nil {
+		core.Log.Errorf("调用 magic_server 发送红包失败: %v", err)
+		return "", err
+	}
 
-	core.Log.Infof("准备发送红包: 群组=%s, 类型=%d, 金额=%v, 个数=%d, 编号=%s",
+	core.Log.Infof("成功发送红包: 群组=%s, 类型=%d, 金额=%v, 个数=%d, 编号=%s",
 		config.GroupId, config.PacketType, config.TotalAmount, config.TotalCount, packetNo)
 
-	// 调用 magic_server API 发送红包
-	// 这里需要配置 magic_server 的地址
-	// err := s.callMagicServerSendRedPacket(packetNo, config)
-	// if err != nil {
-	//     return "", err
-	// }
-
 	return packetNo, nil
 }
 
+// callMagicServerSendRedPacket 调用 magic_server API 发送红包
+func (s *TgRedPacketSendService) callMagicServerSendRedPacket(config *model.TgRedPacketConfig) (string, error) {
+	// magic_server API 地址
+	apiURL := "http://localhost:2011/api/v1/redpacket/send"
+
+	// 构造请求参数
+	payload := map[string]interface{}{
+		"groupId":       config.GroupId,
+		"totalAmount":   config.TotalAmount,
+		"totalCount":    config.TotalCount,
+		"packetType":    config.PacketType,
+		"symbol":        config.Symbol,
+		"blessingWords": config.BlessingWords,
+	}
+
+	jsonData, err := json.Marshal(payload)
+	if err != nil {
+		return "", fmt.Errorf("序列化请求参数失败: %v", err)
+	}
+
+	// 发送 HTTP 请求
+	req, err := http.NewRequest("POST", apiURL, bytes.NewBuffer(jsonData))
+	if err != nil {
+		return "", fmt.Errorf("创建 HTTP 请求失败: %v", err)
+	}
+
+	req.Header.Set("Content-Type", "application/json")
+	// TODO: 添加认证 Token
+	// req.Header.Set("Authorization", "Bearer "+token)
+
+	client := &http.Client{Timeout: 10 * time.Second}
+	resp, err := client.Do(req)
+	if err != nil {
+		return "", fmt.Errorf("发送 HTTP 请求失败: %v", err)
+	}
+	defer resp.Body.Close()
+
+	// 解析响应
+	var result struct {
+		Code    int    `json:"code"`
+		Message string `json:"message"`
+		Data    struct {
+			PacketNo string `json:"packetNo"`
+			PacketId int64  `json:"packetId"`
+		} `json:"data"`
+	}
+
+	body, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return "", fmt.Errorf("读取响应失败: %v", err)
+	}
+
+	if err := json.Unmarshal(body, &result); err != nil {
+		return "", fmt.Errorf("解析响应失败: %v", err)
+	}
+
+	if result.Code != 0 && result.Code != 200 {
+		return "", fmt.Errorf("API 返回错误: %s", result.Message)
+	}
+
+	if result.Data.PacketNo == "" {
+		return "", fmt.Errorf("红包编号为空")
+	}
+
+	return result.Data.PacketNo, nil
+}
+
 // GetGroups 获取机器人所在的群组列表
 func (s *TgRedPacketSendService) GetGroups(c *gin.Context) {
-	// TODO: 这里需要调用 Telegram Bot API 获取群组列表
-	// 或者从数据库中获取已记录的群组
+	s.SetDbAlias("app")
+	// 从数据库中获取已记录的群组
+	var groups []model.TgGroup
+	if err := s.DB().Where("status = 1").Find(&groups).Error; err != nil {
+		response.Resp(c, err.Error())
+		return
+	}
+
 	response.Resp(c, map[string]interface{}{
-		"message": "获取群组列表",
-		"groups":  []interface{}{},
+		"message": "获取群组列表成功",
+		"groups":  groups,
 	})
 }

+ 5 - 0
magic_server/apis/admin/routers.go

@@ -43,4 +43,9 @@ func (h Router) Register(group *gin.RouterGroup) {
 	group.GET("node/banner/get", server().GetNodeBanner)        // 获取节点Banner详情
 	group.POST("node/banner/find", server().FindNodeBanner)     // 分页查询节点Banner
 
+	// Telegram 群组管理
+	group.GET("telegram/groups", server().GetTelegramGroups)       // 获取 Bot 加入的群组列表
+	group.POST("telegram/groups/sync", server().SyncTelegramGroups) // 同步群组信息
+	group.GET("telegram/chat/info", server().GetChatInfo)          // 获取群组详细信息
+
 }

+ 165 - 0
magic_server/apis/admin/telegram_groups.go

@@ -0,0 +1,165 @@
+package admin
+
+import (
+	"app/commons/core"
+	"app/telegram"
+	"fmt"
+	"github.com/gin-gonic/gin"
+	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
+	"time"
+)
+
+// GetTelegramGroups 获取 Bot 加入的群组列表
+func (s *Server) GetTelegramGroups(ctx *gin.Context) {
+	c := s.FromContext(ctx)
+
+	// 检查 Bot 是否启用
+	if !telegram.IsEnabled() {
+		c.Fail("Telegram Bot 未启用")
+		return
+	}
+
+	bot := telegram.GetBot()
+	if bot == nil {
+		c.Fail("无法获取 Bot 实例")
+		return
+	}
+
+	// 获取群组列表
+	// 注意: Telegram Bot API 没有直接获取所有群组的方法
+	// 这里我们需要从 getUpdates 中提取群组信息
+	// 或者从数据库中查询已记录的群组信息
+
+	type GroupInfo struct {
+		ChatId      int64  `json:"chatId"`
+		ChatType    string `json:"chatType"`
+		Title       string `json:"title"`
+		Username    string `json:"username"`
+		MemberCount int    `json:"memberCount"`
+		BotJoinedAt int64  `json:"botJoinedAt"`
+	}
+
+	// 这里简化处理,返回一个提示
+	// 实际应该从数据库查询已记录的群组
+	c.Resp(gin.H{
+		"message": "请通过 Bot 添加到群组后,群组信息会自动记录",
+		"groups":  []GroupInfo{},
+	})
+}
+
+// SyncTelegramGroups 同步 Bot 加入的群组到数据库
+// 从数据库获取已自动记录的群组信息
+func (s *Server) SyncTelegramGroups(ctx *gin.Context) {
+	c := s.FromContext(ctx)
+
+	// 检查 Bot 是否启用
+	if !telegram.IsEnabled() {
+		c.Fail("Telegram Bot 未启用")
+		return
+	}
+
+	// 从数据库获取已自动记录的群组
+	dbGroups, err := telegram.GetAllGroupsFromDB()
+	if err != nil {
+		core.Log.Errorf("从数据库获取群组失败: %v", err)
+		c.Fail(fmt.Sprintf("获取群组失败: %v", err))
+		return
+	}
+
+	// 转换为 GroupData 格式
+	groups := make([]GroupData, 0, len(dbGroups))
+	for _, g := range dbGroups {
+		groups = append(groups, GroupData{
+			ChatId:      g.ChatId,
+			ChatType:    g.ChatType,
+			Title:       g.Title,
+			Username:    g.Username,
+			Description: g.Description,
+			MemberCount: g.MemberCount,
+			BotJoinedAt: g.BotJoinedAt,
+		})
+	}
+
+	core.Log.Infof("从数据库获取了 %d 个群组", len(groups))
+
+	c.Resp(gin.H{
+		"message": fmt.Sprintf("成功同步 %d 个群组", len(groups)),
+		"groups":  groups,
+		"count":   len(groups),
+	})
+}
+
+// GetChatInfo 获取指定群组的详细信息
+func (s *Server) GetChatInfo(ctx *gin.Context) {
+	c := s.FromContext(ctx)
+
+	type request struct {
+		ChatId int64 `json:"chatId" form:"chatId" binding:"required"`
+	}
+
+	req := new(request)
+	if err := c.ShouldBindQuery(req); err != nil {
+		c.Fail("参数错误")
+		return
+	}
+
+	// 检查 Bot 是否启用
+	if !telegram.IsEnabled() {
+		c.Fail("Telegram Bot 未启用")
+		return
+	}
+
+	bot := telegram.GetBot()
+	if bot == nil {
+		c.Fail("无法获取 Bot 实例")
+		return
+	}
+
+	// 获取群组信息
+	chat, err := bot.GetChat(tgbotapi.ChatInfoConfig{
+		ChatConfig: tgbotapi.ChatConfig{
+			ChatID: req.ChatId,
+		},
+	})
+
+	if err != nil {
+		core.Log.Errorf("获取群组信息失败: %v", err)
+		c.Fail(fmt.Sprintf("获取群组信息失败: %v", err))
+		return
+	}
+
+	// 获取成员数量
+	memberCount, err := bot.GetChatMembersCount(tgbotapi.ChatMemberCountConfig{
+		ChatConfig: tgbotapi.ChatConfig{
+			ChatID: req.ChatId,
+		},
+	})
+
+	if err != nil {
+		core.Log.Warnf("获取成员数量失败: %v", err)
+		memberCount = 0
+	}
+
+	groupInfo := GroupData{
+		ChatId:      chat.ID,
+		ChatType:    chat.Type,
+		Title:       chat.Title,
+		Username:    chat.UserName,
+		Description: chat.Description,
+		MemberCount: memberCount,
+		BotJoinedAt: time.Now().Unix(),
+	}
+
+	c.Resp(groupInfo)
+}
+
+// GroupData 群组数据结构
+type GroupData struct {
+	ChatId      int64  `json:"chatId"`
+	ChatType    string `json:"chatType"`
+	Title       string `json:"title"`
+	Username    string `json:"username"`
+	Description string `json:"description"`
+	MemberCount int    `json:"memberCount"`
+	BotJoinedAt int64  `json:"botJoinedAt"`
+}

+ 30 - 2
magic_server/apis/redpacket/send.go

@@ -2,6 +2,9 @@ package redpacket
 
 import (
 	"app/commons/constant"
+	"app/commons/core"
+	"app/commons/model/entity"
+	"app/telegram"
 	"fmt"
 	"github.com/gin-gonic/gin"
 	"github.com/shopspring/decimal"
@@ -54,8 +57,12 @@ func (s *Server) SendRedPacket(ctx *gin.Context) {
 		return
 	}
 
-	// TODO: 发送到 Telegram 群组
-	// go telegram.SendRedPacketToGroup(packet)
+	// 发送到 Telegram 群组
+	go func() {
+		// 异步发送到 Telegram
+		// 使用系统发送者名称
+		sendToTelegram(packet, "System")
+	}()
 
 	c.Resp(gin.H{
 		"packetId": packet.Id,
@@ -63,3 +70,24 @@ func (s *Server) SendRedPacket(ctx *gin.Context) {
 		"message":  "红包发送成功",
 	})
 }
+
+// sendToTelegram 发送红包到Telegram群组
+func sendToTelegram(packet *entity.TgRedPacket, senderName string) {
+	if packet == nil {
+		return
+	}
+
+	// 调用 telegram 包的发送函数
+	telegram.SendRedPacketToGroup(
+		packet.GroupId,
+		packet.PacketNo,
+		senderName,
+		packet.TotalAmount.InexactFloat64(),
+		packet.TotalCount,
+		packet.PacketType,
+		packet.BlessingWords,
+	)
+
+	core.Log.Infof("红包已发送到Telegram群组 - PacketNo: %s, GroupID: %s",
+		packet.PacketNo, packet.GroupId)
+}

+ 1 - 1
magic_server/commons/model/entity/model.go

@@ -4,7 +4,7 @@ import "gorm.io/gorm"
 
 const (
 	ModelPrefix    = "magic_"
-	SysModelPrefix = "sys_"
+	SysModelPrefix = "ams_"
 )
 
 type Member struct {

+ 22 - 0
magic_server/commons/model/entity/tg_group.go

@@ -0,0 +1,22 @@
+package entity
+
+// TgGroup Telegram 群组信息
+type TgGroup struct {
+	MysqlBaseModel
+	ChatId      int64  `json:"chatId" gorm:"column:chat_id;unique;comment:'群组ID'"`
+	ChatType    string `json:"chatType" gorm:"column:chat_type;type:varchar(20);comment:'群组类型:group/supergroup'"`
+	Title       string `json:"title" gorm:"column:title;type:varchar(255);comment:'群组名称'"`
+	Username    string `json:"username" gorm:"column:username;type:varchar(255);comment:'群组用户名'"`
+	Description string `json:"description" gorm:"column:description;type:text;comment:'群组描述'"`
+	MemberCount int    `json:"memberCount" gorm:"column:member_count;comment:'成员数量'"`
+	Status      int    `json:"status" gorm:"column:status;default:1;comment:'状态:0-禁用,1-启用'"`
+	BotJoinedAt int64  `json:"botJoinedAt" gorm:"column:bot_joined_at;comment:'Bot加入时间'"`
+}
+
+func (*TgGroup) TableName() string {
+	return ModelPrefix + "tg_group"
+}
+
+func (*TgGroup) Comment() string {
+	return "Telegram群组表"
+}

+ 48 - 1
magic_server/telegram/commands.go

@@ -24,6 +24,8 @@ func handleCommand(msg *tgbotapi.Message) {
 		handleBalanceCommand(msg)
 	case "records":
 		handleRecordsCommand(msg)
+	case "groupinfo":
+		handleGroupInfoCommand(msg)
 	default:
 		sendTextMessage(msg.Chat.ID, "未知命令,使用 /help 查看帮助")
 	}
@@ -74,11 +76,13 @@ func handleHelpCommand(msg *tgbotapi.Message) {
 ℹ️ 其他:
 /help - 显示此帮助
 /start - 显示欢迎消息
+/groupinfo - 查看群组信息(仅限群组)
 
 💡 提示:
 • 抢红包需要先绑定账户
 • 每个红包只能抢一次
-• 红包24小时内有效`
+• 红包24小时内有效
+• 使用 /groupinfo 可以查看群组ID`
 
 	sendTextMessage(msg.Chat.ID, helpText)
 }
@@ -120,6 +124,49 @@ func handleRecordsCommand(msg *tgbotapi.Message) {
 	sendTextMessage(msg.Chat.ID, "📊 记录查询功能开发中...")
 }
 
+// handleGroupInfoCommand 处理 /groupinfo 命令
+func handleGroupInfoCommand(msg *tgbotapi.Message) {
+	// 检查是否在群组中
+	if msg.Chat.Type != "group" && msg.Chat.Type != "supergroup" {
+		sendTextMessage(msg.Chat.ID, "⚠️ 此命令只能在群组中使用")
+		return
+	}
+
+	// 获取群组信息
+	chatType := msg.Chat.Type
+	if chatType == "group" {
+		chatType = "普通群组"
+	} else if chatType == "supergroup" {
+		chatType = "超级群组"
+	}
+
+	username := msg.Chat.UserName
+	if username == "" {
+		username = "无"
+	} else {
+		username = "@" + username
+	}
+
+	infoText := fmt.Sprintf(`📊 群组信息
+
+🆔 群组ID: <code>%d</code>
+📝 群组名称: %s
+🔗 用户名: %s
+📂 群组类型: %s
+
+💡 提示:
+• 点击群组ID可以复制
+• 管理员可以使用此ID配置红包发送`,
+		msg.Chat.ID,
+		msg.Chat.Title,
+		username,
+		chatType,
+	)
+
+	// 使用 HTML 解析模式发送消息,这样可以点击复制
+	sendHTMLMessage(msg.Chat.ID, infoText)
+}
+
 // generateBindURL 生成绑定链接
 func generateBindURL(telegramID int64) string {
 	// TODO: 生成真实的绑定链接

+ 101 - 0
magic_server/telegram/group_sync.go

@@ -0,0 +1,101 @@
+package telegram
+
+import (
+	"app/commons/core"
+	"app/commons/model/entity"
+	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
+	"time"
+)
+
+// SyncGroupInfo 同步群组信息到数据库
+// 当收到群组消息时自动调用
+func SyncGroupInfo(chat *tgbotapi.Chat) {
+	if chat == nil {
+		return
+	}
+
+	// 只处理群组
+	if chat.Type != "group" && chat.Type != "supergroup" {
+		return
+	}
+
+	// 查询数据库中是否已存在该群组
+	var existingGroup entity.TgGroup
+	db := core.MainDb()
+
+	err := db.Where("chat_id = ?", chat.ID).First(&existingGroup).Error
+
+	now := time.Now().Unix()
+
+	if err != nil {
+		// 群组不存在,创建新记录
+		newGroup := &entity.TgGroup{
+			ChatId:      chat.ID,
+			ChatType:    chat.Type,
+			Title:       chat.Title,
+			Username:    chat.UserName,
+			Description: chat.Description,
+			Status:      1, // 默认启用
+			BotJoinedAt: now,
+		}
+
+		// 尝试获取成员数量
+		if bot != nil {
+			memberCount, err := bot.GetChatMembersCount(tgbotapi.ChatMemberCountConfig{
+				ChatConfig: tgbotapi.ChatConfig{
+					ChatID: chat.ID,
+				},
+			})
+			if err == nil {
+				newGroup.MemberCount = memberCount
+			}
+		}
+
+		if err := db.Create(newGroup).Error; err != nil {
+			core.Log.Errorf("创建群组记录失败: %v", err)
+			return
+		}
+
+		core.Log.Infof("✅ 自动记录新群组: %s (ID: %d)", chat.Title, chat.ID)
+	} else {
+		// 群组已存在,更新信息
+		updates := map[string]interface{}{
+			"title":       chat.Title,
+			"username":    chat.UserName,
+			"description": chat.Description,
+			"updated_at":  now,
+		}
+
+		// 尝试更新成员数量
+		if bot != nil {
+			memberCount, err := bot.GetChatMembersCount(tgbotapi.ChatMemberCountConfig{
+				ChatConfig: tgbotapi.ChatConfig{
+					ChatID: chat.ID,
+				},
+			})
+			if err == nil {
+				updates["member_count"] = memberCount
+			}
+		}
+
+		if err := db.Model(&existingGroup).Updates(updates).Error; err != nil {
+			core.Log.Errorf("更新群组记录失败: %v", err)
+			return
+		}
+
+		core.Log.Debugf("更新群组信息: %s (ID: %d)", chat.Title, chat.ID)
+	}
+}
+
+// GetAllGroupsFromDB 从数据库获取所有群组
+func GetAllGroupsFromDB() ([]entity.TgGroup, error) {
+	var groups []entity.TgGroup
+	db := core.MainDb()
+
+	err := db.Where("status = 1").Order("created_at DESC").Find(&groups).Error
+	if err != nil {
+		return nil, err
+	}
+
+	return groups, nil
+}

+ 5 - 0
magic_server/telegram/handlers.go

@@ -15,6 +15,11 @@ func HandleMessage(msg *tgbotapi.Message) {
 	core.Log.Infof("收到消息 - Chat: %d, User: %s, Text: %s",
 		msg.Chat.ID, msg.From.UserName, msg.Text)
 
+
+	// 自动同步群组信息到数据库
+	if msg.Chat.Type == "group" || msg.Chat.Type == "supergroup" {
+		go SyncGroupInfo(msg.Chat)
+	}
 	// 处理新成员加入
 	if msg.NewChatMembers != nil {
 		handleNewMembers(msg)

+ 15 - 0
magic_server/telegram/messages.go

@@ -21,6 +21,21 @@ func sendTextMessage(chatID int64, text string) {
 	}
 }
 
+// sendHTMLMessage 发送 HTML 格式的消息
+func sendHTMLMessage(chatID int64, text string) {
+	if !IsEnabled() {
+		return
+	}
+
+	msg := tgbotapi.NewMessage(chatID, text)
+	msg.ParseMode = "HTML"
+
+	_, err := bot.Send(msg)
+	if err != nil {
+		core.Log.Errorf("发送消息失败: %v", err)
+	}
+}
+
 // sendMessageWithKeyboard 发送带按钮的消息
 func sendMessageWithKeyboard(chatID int64, text string, keyboard tgbotapi.InlineKeyboardMarkup) {
 	if !IsEnabled() {