package bot import ( "context" "fmt" "log" "strings" "time" "ops-assistant/internal/core/ops" "ops-assistant/models" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" "gorm.io/gorm" ) // DefaultUserID 统一用户ID const DefaultUserID int64 = 1 type TGBot struct { api *tgbotapi.BotAPI db *gorm.DB opsSvc *ops.Service } func NewTGBot(db *gorm.DB, token string, opsSvc *ops.Service) (*TGBot, error) { bot, err := tgbotapi.NewBotAPI(token) if err != nil { return nil, err } return &TGBot{api: bot, db: db, opsSvc: opsSvc}, nil } func (b *TGBot) Start(ctx context.Context) { u := tgbotapi.NewUpdate(0) u.Timeout = 60 updates := b.api.GetUpdatesChan(u) log.Println("🚀 Telegram Bot 已启动") for { select { case <-ctx.Done(): log.Println("⏳ Telegram Bot 正在停止...") b.api.StopReceivingUpdates() return case update, ok := <-updates: if !ok { return } if update.Message == nil || update.Message.Text == "" { continue } eventID := fmt.Sprintf("tg:%d", update.UpdateID) if b.isDuplicate(eventID) { continue } log.Printf("📩 inbound platform=telegram event=%s chat=%d user=%d text=%q", eventID, update.Message.Chat.ID, update.Message.From.ID, strings.TrimSpace(update.Message.Text)) b.handleMessage(update.Message) } } } func (b *TGBot) isDuplicate(eventID string) bool { if b.db == nil || strings.TrimSpace(eventID) == "" { return false } var existed models.MessageDedup if err := b.db.Where("platform = ? AND event_id = ?", "telegram", eventID).First(&existed).Error; err == nil { return true } _ = b.db.Create(&models.MessageDedup{Platform: "telegram", EventID: eventID, ProcessedAt: time.Now()}).Error return false } func (b *TGBot) handleMessage(msg *tgbotapi.Message) { text := msg.Text chatID := msg.Chat.ID if b.opsSvc != nil { if handled, out := b.opsSvc.Handle(DefaultUserID, text); handled { m := tgbotapi.NewMessage(chatID, out) if _, err := b.api.Send(m); err != nil { log.Printf("发送OPS消息失败 chat=%d: %v", chatID, err) } return } } reply := "" switch text { case "/start": reply = "🛠️ 欢迎使用 Ops-Assistant!\n\n模块化运维命令助手,支持:\n• /cpa - CPA 管理\n• /cf - Cloudflare 管理\n• /cfdav - cfdav 管理\n• /sub - 订阅管理\n• /mail - 邮箱管理\n\n/help - 使用说明" case "/help": reply = "📖 使用说明:\n\n直接发送 OPS 命令即可,例如:\n• /cpa status\n• /cf zones\n• /cf dns list \n• /cfdav users\n• /sub list\n\n模块入口默认关闭,可在 Web 后台「模块设置」中启用。" default: reply = "❓ 无法识别,输入 /help 查看支持的命令" } m := tgbotapi.NewMessage(chatID, reply) if _, err := b.api.Send(m); err != nil { log.Printf("发送消息失败 chat=%d: %v", chatID, err) } }