- Telegram Bot + QQ Bot (WebSocket) 双平台支持 - 150+ 预设分类关键词,jieba 智能分词 - Web 管理后台(记录查看/删除/CSV导出) - 金额精确存储(分/int64) - 版本信息嵌入(编译时注入) - Docker 支持 - 优雅关闭(context + signal)
123 lines
3.4 KiB
Go
123 lines
3.4 KiB
Go
package bot
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"log"
|
||
"strings"
|
||
"time"
|
||
|
||
"xiaji-go/internal/service"
|
||
|
||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||
)
|
||
|
||
type TGBot struct {
|
||
api *tgbotapi.BotAPI
|
||
finance *service.FinanceService
|
||
}
|
||
|
||
func NewTGBot(token string, finance *service.FinanceService) (*TGBot, error) {
|
||
bot, err := tgbotapi.NewBotAPI(token)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return &TGBot{api: bot, finance: finance}, 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
|
||
}
|
||
b.handleMessage(update.Message)
|
||
}
|
||
}
|
||
}
|
||
|
||
func (b *TGBot) handleMessage(msg *tgbotapi.Message) {
|
||
text := msg.Text
|
||
chatID := msg.Chat.ID
|
||
userID := msg.From.ID
|
||
|
||
var reply string
|
||
|
||
switch {
|
||
case text == "/start":
|
||
reply = "🦞 欢迎使用虾记记账!\n\n直接发送消费描述即可记账,例如:\n• 午饭 25元\n• 打车 ¥30\n• 买咖啡15块\n\n命令:\n/list - 查看最近记录\n/help - 帮助"
|
||
|
||
case text == "/help":
|
||
reply = "📖 使用说明:\n\n直接发送带金额的文本即可自动记账。\n系统会自动识别金额和消费分类。\n\n支持格式:\n• 午饭 25元\n• ¥30 打车\n• 买水果15块\n\n命令:\n/list - 最近10条记录\n/today - 今日汇总\n/start - 欢迎信息"
|
||
|
||
case text == "/today":
|
||
today := time.Now().Format("2006-01-02")
|
||
items, err := b.finance.GetTransactionsByDate(userID, today)
|
||
if err != nil {
|
||
reply = "❌ 查询失败"
|
||
} else if len(items) == 0 {
|
||
reply = fmt.Sprintf("📭 %s 暂无消费记录", today)
|
||
} else {
|
||
var sb strings.Builder
|
||
var total int64
|
||
sb.WriteString(fmt.Sprintf("📊 今日(%s)消费:\n\n", today))
|
||
for _, item := range items {
|
||
sb.WriteString(fmt.Sprintf("• %s:%.2f元\n", item.Category, item.AmountYuan()))
|
||
total += item.Amount
|
||
}
|
||
sb.WriteString(fmt.Sprintf("\n💰 共 %d 笔,合计 %.2f 元", len(items), float64(total)/100.0))
|
||
reply = sb.String()
|
||
}
|
||
|
||
case text == "/list":
|
||
items, err := b.finance.GetTransactions(userID, 10)
|
||
if err != nil {
|
||
reply = "❌ 查询失败"
|
||
} else if len(items) == 0 {
|
||
reply = "📭 暂无记录"
|
||
} else {
|
||
var sb strings.Builder
|
||
sb.WriteString("📋 最近记录:\n\n")
|
||
for _, item := range items {
|
||
sb.WriteString(fmt.Sprintf("• [%s] %s:%.2f元\n", item.Date, item.Category, item.AmountYuan()))
|
||
}
|
||
reply = sb.String()
|
||
}
|
||
|
||
case strings.HasPrefix(text, "/"):
|
||
reply = "❓ 未知命令,输入 /help 查看帮助"
|
||
|
||
default:
|
||
// 记账逻辑
|
||
amount, category, err := b.finance.AddTransaction(userID, text)
|
||
if err != nil {
|
||
reply = "❌ 记账失败,请稍后重试"
|
||
log.Printf("记账失败 user=%d: %v", userID, err)
|
||
} else if amount == 0 {
|
||
reply = "📍 没看到金额,这笔花了多少钱?"
|
||
} else {
|
||
amountYuan := float64(amount) / 100.0
|
||
reply = fmt.Sprintf("✅ 已记入【%s】:%.2f元\n📝 备注:%s", category, amountYuan, text)
|
||
}
|
||
}
|
||
|
||
m := tgbotapi.NewMessage(chatID, reply)
|
||
if _, err := b.api.Send(m); err != nil {
|
||
log.Printf("发送消息失败 chat=%d: %v", chatID, err)
|
||
}
|
||
}
|