Files
rmser/internal/transport/telegram/bot.go
2025-11-29 08:40:24 +03:00

142 lines
3.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package telegram
import (
"context"
"fmt"
"io"
"net/http"
"strings"
"time"
"go.uber.org/zap"
tele "gopkg.in/telebot.v3"
"gopkg.in/telebot.v3/middleware"
"rmser/config"
"rmser/internal/services/ocr"
"rmser/pkg/logger"
)
type Bot struct {
b *tele.Bot
ocrService *ocr.Service
adminIDs map[int64]struct{}
}
func NewBot(cfg config.TelegramConfig, ocrService *ocr.Service) (*Bot, error) {
pref := tele.Settings{
Token: cfg.Token,
Poller: &tele.LongPoller{Timeout: 10 * time.Second},
OnError: func(err error, c tele.Context) {
logger.Log.Error("Telegram error", zap.Error(err))
},
}
b, err := tele.NewBot(pref)
if err != nil {
return nil, err
}
admins := make(map[int64]struct{})
for _, id := range cfg.AdminIDs {
admins[id] = struct{}{}
}
bot := &Bot{
b: b,
ocrService: ocrService,
adminIDs: admins,
}
bot.initHandlers()
return bot, nil
}
func (bot *Bot) Start() {
logger.Log.Info("Запуск Telegram бота...")
bot.b.Start()
}
func (bot *Bot) Stop() {
bot.b.Stop()
}
// Middleware для проверки прав (только админы)
func (bot *Bot) authMiddleware(next tele.HandlerFunc) tele.HandlerFunc {
return func(c tele.Context) error {
if len(bot.adminIDs) > 0 {
if _, ok := bot.adminIDs[c.Sender().ID]; !ok {
return c.Send("⛔ У вас нет доступа к этому боту.")
}
}
return next(c)
}
}
func (bot *Bot) initHandlers() {
bot.b.Use(middleware.Logger())
bot.b.Use(bot.authMiddleware)
bot.b.Handle("/start", func(c tele.Context) error {
return c.Send("👋 Привет! Я RMSER Bot.\nОтправь мне фото накладной или чека, и я попробую его распознать.")
})
bot.b.Handle(tele.OnPhoto, bot.handlePhoto)
}
func (bot *Bot) handlePhoto(c tele.Context) error {
// 1. Скачиваем фото
photo := c.Message().Photo
// Берем файл самого высокого качества (последний в массиве, но telebot дает удобный доступ)
file, err := bot.b.FileByID(photo.FileID)
if err != nil {
return c.Send("Ошибка доступа к файлу.")
}
// Читаем тело файла
fileURL := fmt.Sprintf("https://api.telegram.org/file/bot%s/%s", bot.b.Token, file.FilePath)
resp, err := http.Get(fileURL)
if err != nil {
return c.Send("Ошибка скачивания файла.")
}
defer resp.Body.Close()
imgData, err := io.ReadAll(resp.Body)
if err != nil {
return c.Send("Ошибка чтения файла.")
}
c.Send("⏳ Обрабатываю чек через OCR...")
// 2. Отправляем в сервис
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
items, err := bot.ocrService.ProcessReceiptImage(ctx, imgData)
if err != nil {
logger.Log.Error("OCR processing failed", zap.Error(err))
return c.Send("❌ Ошибка распознавания: " + err.Error())
}
// 3. Формируем отчет
var sb strings.Builder
sb.WriteString(fmt.Sprintf("🧾 <b>Результат (%d поз.):</b>\n\n", len(items)))
matchedCount := 0
for _, item := range items {
if item.IsMatched {
matchedCount++
sb.WriteString(fmt.Sprintf("✅ %s\n └ <code>%s</code> x %s = %s\n",
item.RawName, item.Amount, item.Price, item.Sum))
} else {
sb.WriteString(fmt.Sprintf("❓ <b>%s</b>\n └ Нет привязки!\n", item.RawName))
}
}
sb.WriteString(fmt.Sprintf("\nРаспознано: %d/%d", matchedCount, len(items)))
// Тут можно добавить кнопки, если что-то не распознано
// Но для начала просто текст
return c.Send(sb.String(), tele.ModeHTML)
}