Files
transcriber/internal/controller/tg/tg.go

329 lines
12 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 tg
import (
"fmt"
"io"
"log/slog"
"net/http"
"os"
"strings"
"time"
"git.vakhrushev.me/av/transcriber/internal/contract"
"git.vakhrushev.me/av/transcriber/internal/service"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
type TelegramController struct {
bot *tgbotapi.BotAPI
transcribeService *service.TranscribeService
jobRepo contract.TranscriptJobRepository
logger *slog.Logger
}
func NewTelegramController(transcribeService *service.TranscribeService, jobRepo contract.TranscriptJobRepository, logger *slog.Logger) (*TelegramController, error) {
botToken := os.Getenv("TELEGRAM_BOT_TOKEN")
if botToken == "" {
return nil, &EmptyBotTokenError{}
}
bot, err := tgbotapi.NewBotAPI(botToken)
if err != nil {
return nil, err
}
controller := &TelegramController{
bot: bot,
transcribeService: transcribeService,
jobRepo: jobRepo,
logger: logger,
}
return controller, nil
}
func (c *TelegramController) Start() {
c.logger.Info("Telegram bot started", "username", c.bot.Self.UserName)
u := tgbotapi.NewUpdate(0)
u.Timeout = 60
updates := c.bot.GetUpdatesChan(u)
for update := range updates {
if update.Message == nil { // ignore any non-Message updates
continue
}
// Handle commands
if update.Message.IsCommand() {
// Extract the command from the Message
switch update.Message.Command() {
case "start":
c.handleStartCommand(update.Message)
case "help":
c.handleHelpCommand(update.Message)
}
continue
}
// Handle audio messages and files
if update.Message.Audio != nil {
c.handleAudioMessage(update.Message)
} else if update.Message.Voice != nil {
c.handleVoiceMessage(update.Message)
} else if update.Message.Document != nil {
c.handleDocumentMessage(update.Message)
}
}
}
func (c *TelegramController) Stop() {
c.logger.Info("Telegram bot stopped")
}
func (c *TelegramController) handleStartCommand(message *tgbotapi.Message) {
msg := tgbotapi.NewMessage(message.Chat.ID, "Привет! Я бот для расшифровки аудиосообщений. Отправь мне голосовое сообщение или аудиофайл, и я пришлю тебе текст.")
msg.ReplyToMessageID = message.MessageID
c.bot.Send(msg)
}
func (c *TelegramController) handleHelpCommand(message *tgbotapi.Message) {
helpText := `Я бот для расшифровки аудиосообщений и аудиофайлов.
Просто отправь мне:
- Голосовое сообщение
- Аудиофайл (mp3, wav, ogg и др.)
Я пришлю тебе текст расшифровки.
Команды:
/start - Начало работы с ботом
/help - Показать эту справку`
msg := tgbotapi.NewMessage(message.Chat.ID, helpText)
msg.ReplyToMessageID = message.MessageID
c.bot.Send(msg)
}
func (c *TelegramController) handleAudioMessage(message *tgbotapi.Message) {
// Отправляем сообщение о начале обработки
progressMsg := tgbotapi.NewMessage(message.Chat.ID, "Обрабатываю аудиофайл...")
progressMsg.ReplyToMessageID = message.MessageID
sentProgressMsg, err := c.bot.Send(progressMsg)
if err != nil {
c.logger.Error("Failed to send progress message", "error", err)
return
}
// Скачиваем файл
fileReader, fileName, err := c.downloadAudioFile(message.Audio.FileID)
if err != nil {
c.logger.Error("Failed to download audio file", "error", err)
errorMsg := tgbotapi.NewMessage(message.Chat.ID, "Ошибка при скачивании аудиофайла. Попробуйте еще раз.")
c.bot.Send(errorMsg)
return
}
defer fileReader.Close()
// Обрабатываем файл
job, err := c.transcribeService.CreateTranscribeJob(fileReader, fileName)
if err != nil {
c.logger.Error("Failed to create transcribe job", "error", err)
errorMsg := tgbotapi.NewMessage(message.Chat.ID, "Ошибка при создании задачи на расшифровку. Попробуйте еще раз.")
c.bot.Send(errorMsg)
return
}
// Отправляем сообщение об успешном создании задачи
successMsg := tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("Задача на расшифровку создана. ID задачи: %s", job.Id))
successMsg.ReplyToMessageID = message.MessageID
c.bot.Send(successMsg)
// Отправляем результат расшифровки (асинхронно)
go c.sendTranscriptionResult(message.Chat.ID, job.Id, sentProgressMsg.MessageID)
}
func (c *TelegramController) handleVoiceMessage(message *tgbotapi.Message) {
// Отправляем сообщение о начале обработки
progressMsg := tgbotapi.NewMessage(message.Chat.ID, "Обрабатываю голосовое сообщение...")
progressMsg.ReplyToMessageID = message.MessageID
sentProgressMsg, err := c.bot.Send(progressMsg)
if err != nil {
c.logger.Error("Failed to send progress message", "error", err)
return
}
// Скачиваем файл
fileReader, fileName, err := c.downloadAudioFile(message.Voice.FileID)
if err != nil {
c.logger.Error("Failed to download voice file", "error", err)
errorMsg := tgbotapi.NewMessage(message.Chat.ID, "Ошибка при скачивании голосового сообщения. Попробуйте еще раз.")
c.bot.Send(errorMsg)
return
}
defer fileReader.Close()
// Обрабатываем файл
job, err := c.transcribeService.CreateTranscribeJob(fileReader, fileName)
if err != nil {
c.logger.Error("Failed to create transcribe job", "error", err)
errorMsg := tgbotapi.NewMessage(message.Chat.ID, "Ошибка при создании задачи на расшифровку. Попробуйте еще раз.")
c.bot.Send(errorMsg)
return
}
// Отправляем сообщение об успешном создании задачи
successMsg := tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("Задача на расшифровку создана. ID задачи: %s", job.Id))
successMsg.ReplyToMessageID = message.MessageID
c.bot.Send(successMsg)
// Отправляем результат расшифровки (асинхронно)
go c.sendTranscriptionResult(message.Chat.ID, job.Id, sentProgressMsg.MessageID)
}
func (c *TelegramController) handleDocumentMessage(message *tgbotapi.Message) {
// Проверяем, является ли документ аудиофайлом
if !c.isAudioDocument(message.Document) {
return
}
// Отправляем сообщение о начале обработки
progressMsg := tgbotapi.NewMessage(message.Chat.ID, "Обрабатываю аудиофайл...")
progressMsg.ReplyToMessageID = message.MessageID
sentProgressMsg, err := c.bot.Send(progressMsg)
if err != nil {
c.logger.Error("Failed to send progress message", "error", err)
return
}
// Скачиваем файл
fileReader, fileName, err := c.downloadAudioFile(message.Document.FileID)
if err != nil {
c.logger.Error("Failed to download document file", "error", err)
errorMsg := tgbotapi.NewMessage(message.Chat.ID, "Ошибка при скачивании аудиофайла. Попробуйте еще раз.")
c.bot.Send(errorMsg)
return
}
defer fileReader.Close()
// Обрабатываем файл
job, err := c.transcribeService.CreateTranscribeJob(fileReader, fileName)
if err != nil {
c.logger.Error("Failed to create transcribe job", "error", err)
errorMsg := tgbotapi.NewMessage(message.Chat.ID, "Ошибка при создании задачи на расшифровку. Попробуйте еще раз.")
c.bot.Send(errorMsg)
return
}
// Отправляем сообщение об успешном создании задачи
successMsg := tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("Задача на расшифровку создана. ID задачи: %s", job.Id))
successMsg.ReplyToMessageID = message.MessageID
c.bot.Send(successMsg)
// Отправляем результат расшифровки (асинхронно)
go c.sendTranscriptionResult(message.Chat.ID, job.Id, sentProgressMsg.MessageID)
}
func (c *TelegramController) downloadAudioFile(fileID string) (io.ReadCloser, string, error) {
// Получаем информацию о файле
file, err := c.bot.GetFile(tgbotapi.FileConfig{FileID: fileID})
if err != nil {
return nil, "", fmt.Errorf("failed to get file info: %w", err)
}
// Скачиваем файл
fileURL := file.Link(c.bot.Token)
resp, err := http.Get(fileURL)
if err != nil {
return nil, "", fmt.Errorf("failed to download file: %w", err)
}
// Получаем имя файла из URL
fileName := file.FilePath
if fileName == "" {
fileName = "audio.ogg"
}
return resp.Body, fileName, nil
}
func (c *TelegramController) sendTranscriptionResult(chatID int64, jobID string, progressMessageID int) {
// Периодически проверяем статус задачи
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
timeout := time.After(10 * time.Minute) // Максимальное время ожидания 10 минут
for {
select {
case <-ticker.C:
// Проверяем статус задачи
job, err := c.jobRepo.GetByID(jobID)
if err != nil {
c.logger.Error("Failed to get job", "job_id", jobID, "error", err)
continue
}
switch job.State {
case "done":
// Отправляем результат
if job.TranscriptionText != nil {
resultMsg := tgbotapi.NewMessage(chatID, *job.TranscriptionText)
resultMsg.ReplyToMessageID = progressMessageID
c.bot.Send(resultMsg)
} else {
resultMsg := tgbotapi.NewMessage(chatID, "Расшифровка завершена, но текст пуст.")
resultMsg.ReplyToMessageID = progressMessageID
c.bot.Send(resultMsg)
}
return
case "failed":
// Отправляем сообщение об ошибке
var errorMsg string
if job.ErrorText != nil {
errorMsg = fmt.Sprintf("Ошибка при расшифровке: %s", *job.ErrorText)
} else {
errorMsg = "Ошибка при расшифровке аудиофайла."
}
resultMsg := tgbotapi.NewMessage(chatID, errorMsg)
resultMsg.ReplyToMessageID = progressMessageID
c.bot.Send(resultMsg)
return
}
case <-timeout:
// Время ожидания истекло
resultMsg := tgbotapi.NewMessage(chatID, "Время ожидания результата расшифровки истекло. Попробуйте позже проверить статус задачи.")
resultMsg.ReplyToMessageID = progressMessageID
c.bot.Send(resultMsg)
return
}
}
}
func (c *TelegramController) isAudioDocument(document *tgbotapi.Document) bool {
// Проверяем MIME-тип документа
if document.MimeType != "" {
return strings.HasPrefix(document.MimeType, "audio/") || strings.HasPrefix(document.MimeType, "video/")
}
// Проверяем расширение файла
audioExtensions := []string{".mp3", ".wav", ".ogg", ".flac", ".m4a", ".aac", ".wma"}
filename := document.FileName
for _, ext := range audioExtensions {
if len(filename) >= len(ext) && strings.ToLower(filename[len(filename)-len(ext):]) == ext {
return true
}
}
return false
}
type EmptyBotTokenError struct{}
func (e *EmptyBotTokenError) Error() string {
return "telegram bot token is empty"
}