105 lines
2.8 KiB
Go
105 lines
2.8 KiB
Go
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 <zone_id>\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)
|
||
}
|
||
} |