307 lines
10 KiB
Go
307 lines
10 KiB
Go
package tg
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"slices"
|
|
"strings"
|
|
|
|
"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 {
|
|
// deps
|
|
transcribeService *service.TranscribeService
|
|
jobRepo contract.TranscriptJobRepository
|
|
logger *slog.Logger
|
|
// params
|
|
bot *tgbotapi.BotAPI
|
|
userWhiteList []string
|
|
updateTimeout int
|
|
}
|
|
|
|
type TelegramConfig struct {
|
|
BotToken string
|
|
UpdateTimeout int
|
|
UserWhiteList []string
|
|
}
|
|
|
|
func NewTelegramController(
|
|
config TelegramConfig,
|
|
transcribeService *service.TranscribeService,
|
|
jobRepo contract.TranscriptJobRepository,
|
|
logger *slog.Logger,
|
|
) (*TelegramController, error) {
|
|
botToken := config.BotToken
|
|
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,
|
|
updateTimeout: config.UpdateTimeout,
|
|
userWhiteList: config.UserWhiteList,
|
|
}
|
|
|
|
return controller, nil
|
|
}
|
|
|
|
func (c *TelegramController) Start() {
|
|
c.logger.Info("Telegram bot started", "username", c.bot.Self.UserName)
|
|
|
|
u := tgbotapi.NewUpdate(0)
|
|
u.Timeout = c.updateTimeout
|
|
|
|
updates := c.bot.GetUpdatesChan(u)
|
|
|
|
for update := range updates {
|
|
if update.Message == nil { // ignore any non-Message updates
|
|
continue
|
|
}
|
|
|
|
author := update.Message.From.String()
|
|
c.logger.Info("New incoming message", "author", author)
|
|
|
|
if !slices.Contains(c.userWhiteList, author) {
|
|
c.logger.Info("User is not in white list, reject", "author", author)
|
|
c.handleForbiddenUser(update.Message)
|
|
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.bot.StopReceivingUpdates()
|
|
}
|
|
|
|
func (c *TelegramController) send(chattable tgbotapi.Chattable) (tgbotapi.Message, error) {
|
|
msg, err := c.bot.Send(chattable)
|
|
if err != nil {
|
|
c.logger.Error("Failed to send message to tg bot", "error", err)
|
|
}
|
|
return msg, err
|
|
}
|
|
|
|
func (c *TelegramController) handleStartCommand(message *tgbotapi.Message) {
|
|
msg := tgbotapi.NewMessage(message.Chat.ID, "Привет! Я бот для расшифровки аудиосообщений. Отправь мне голосовое сообщение или аудиофайл, и я пришлю тебе текст.")
|
|
msg.ReplyToMessageID = message.MessageID
|
|
|
|
c.send(msg)
|
|
}
|
|
|
|
func (c *TelegramController) handleForbiddenUser(message *tgbotapi.Message) {
|
|
msg := tgbotapi.NewMessage(message.Chat.ID, "Извини, тебе нельзя пользоваться этим ботом. Обратись к владельцу бота.")
|
|
msg.ReplyToMessageID = message.MessageID
|
|
|
|
c.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.send(msg)
|
|
}
|
|
|
|
func (c *TelegramController) handleAudioMessage(message *tgbotapi.Message) {
|
|
// Отправляем сообщение о начале обработки
|
|
progressMsg := tgbotapi.NewMessage(message.Chat.ID, "Обрабатываю аудиофайл...")
|
|
progressMsg.ReplyToMessageID = message.MessageID
|
|
sentProgressMsg, err := c.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.send(errorMsg)
|
|
return
|
|
}
|
|
defer fileReader.Close()
|
|
|
|
// Обрабатываем файл
|
|
job, err := c.transcribeService.CreateJobFromTelegram(fileReader, fileName, message.Chat.ID, sentProgressMsg.MessageID)
|
|
if err != nil {
|
|
c.logger.Error("Failed to create transcribe job", "error", err)
|
|
errorMsg := tgbotapi.NewMessage(message.Chat.ID, "Ошибка при создании задачи на расшифровку. Попробуйте еще раз.")
|
|
c.send(errorMsg)
|
|
return
|
|
}
|
|
|
|
// Отправляем сообщение об успешном создании задачи
|
|
successMsg := tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("Задача на расшифровку создана. ID задачи: %s", job.Id))
|
|
successMsg.ReplyToMessageID = message.MessageID
|
|
c.send(successMsg)
|
|
}
|
|
|
|
func (c *TelegramController) handleVoiceMessage(message *tgbotapi.Message) {
|
|
// Отправляем сообщение о начале обработки
|
|
progressMsg := tgbotapi.NewMessage(message.Chat.ID, "Обрабатываю голосовое сообщение...")
|
|
progressMsg.ReplyToMessageID = message.MessageID
|
|
sentProgressMsg, err := c.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.send(errorMsg)
|
|
return
|
|
}
|
|
defer fileReader.Close()
|
|
|
|
// Обрабатываем файл
|
|
job, err := c.transcribeService.CreateJobFromTelegram(fileReader, fileName, message.Chat.ID, sentProgressMsg.MessageID)
|
|
if err != nil {
|
|
c.logger.Error("Failed to create transcribe job", "error", err)
|
|
errorMsg := tgbotapi.NewMessage(message.Chat.ID, "Ошибка при создании задачи на расшифровку. Попробуйте еще раз.")
|
|
c.send(errorMsg)
|
|
return
|
|
}
|
|
|
|
// Отправляем сообщение об успешном создании задачи
|
|
successMsg := tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("Задача на расшифровку создана. ID задачи: %s", job.Id))
|
|
successMsg.ReplyToMessageID = message.MessageID
|
|
c.send(successMsg)
|
|
}
|
|
|
|
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.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.send(errorMsg)
|
|
return
|
|
}
|
|
defer fileReader.Close()
|
|
|
|
// Обрабатываем файл
|
|
job, err := c.transcribeService.CreateJobFromTelegram(fileReader, fileName, message.Chat.ID, sentProgressMsg.MessageID)
|
|
if err != nil {
|
|
c.logger.Error("Failed to create transcribe job", "error", err)
|
|
errorMsg := tgbotapi.NewMessage(message.Chat.ID, "Ошибка при создании задачи на расшифровку. Попробуйте еще раз.")
|
|
c.send(errorMsg)
|
|
return
|
|
}
|
|
|
|
// Отправляем сообщение об успешном создании задачи
|
|
successMsg := tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("Задача на расшифровку создана. ID задачи: %s", job.Id))
|
|
successMsg.ReplyToMessageID = message.MessageID
|
|
c.send(successMsg)
|
|
}
|
|
|
|
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) 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"
|
|
}
|