Files
rmser/internal/transport/telegram/bot.go

163 lines
4.8 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{}
webAppURL string
}
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,
webAppURL: cfg.WebAppURL,
}
// Если в конфиге пусто, ставим заглушку, чтобы не падало, но предупреждаем
if bot.webAppURL == "" {
logger.Log.Warn("Telegram WebAppURL не задан в конфиге! Кнопки работать не будут.")
bot.webAppURL = "http://example.com"
}
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("⏳ Обрабатываю чек: создаю черновик и распознаю...")
// 2. Отправляем в сервис (добавили ID чата)
ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second) // Чуть увеличим таймаут
defer cancel()
draft, err := bot.ocrService.ProcessReceiptImage(ctx, c.Chat().ID, imgData)
if err != nil {
logger.Log.Error("OCR processing failed", zap.Error(err))
return c.Send("❌ Ошибка обработки: " + err.Error())
}
// 3. Анализ результатов для сообщения
matchedCount := 0
for _, item := range draft.Items {
if item.IsMatched {
matchedCount++
}
}
// Формируем URL. Для Mini App это должен быть https URL вашего фронтенда.
// Фронтенд должен уметь роутить /invoice/:id
baseURL := strings.TrimRight(bot.webAppURL, "/")
fullURL := fmt.Sprintf("%s/invoice/%s", baseURL, draft.ID.String())
// Формируем текст сообщения
var msgText string
if matchedCount == len(draft.Items) {
msgText = fmt.Sprintf("✅ <b>Успех!</b> Все позиции (%d) распознаны.\n\nПереходите к созданию накладной.", len(draft.Items))
} else {
msgText = fmt.Sprintf("⚠️ <b>Внимание!</b> Распознано %d из %d позиций.\n\nНекоторые товары требуют ручного сопоставления. Нажмите кнопку ниже, чтобы исправить.", matchedCount, len(draft.Items))
}
menu := &tele.ReplyMarkup{}
// Используем WebApp, а не URL
btnOpen := menu.WebApp("📝 Открыть накладную", &tele.WebApp{
URL: fullURL,
})
menu.Inline(
menu.Row(btnOpen),
)
return c.Send(msgText, menu, tele.ModeHTML)
}