package main import ( "errors" "log/slog" "git.vakhrushev.me/av/transcriber/internal/adapter/telegram" "git.vakhrushev.me/av/transcriber/internal/contract" "git.vakhrushev.me/av/transcriber/internal/metrics" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" ) // buildTelegram заводит вход Telegram при старте. Клиент собирается **один // раз** и достаётся обоим — отправителю ответов и транспорту бота. Пока его // строили порознь, два пути одного старта разошлись: один ронял процесс на // негодном токене, другой терпел, и согласовывать их приходилось руками. func buildTelegram(botToken string, logger *slog.Logger) (*tgbotapi.BotAPI, contract.TelegramMessageSender, error) { bot, err := telegram.NewBot(botToken, logger) return telegramFromBot(bot, err, logger) } // telegramFromBot решает, чем обернулась сборка клиента, и это решение — // единственное содержательное здесь. Оно отделено от самого обращения к // Telegram намеренно: обращение ходит в сеть и в проверке недоступно, а // разрез проверять надо, иначе его молча вернут к прежнему виду. // // Разрез проходит по тому, **ответил ли Telegram**, и это решение владельца // от 2026-08-13: недоступность Telegram на старт сервиса не влияет. // // - токен не задан — законный отказ от входа: сервис работает оставшимся; // - Telegram ответил отказом — ошибка настройки: бота по этому токену не // существует, ждать нечего, и старт роняется. Молча потерянный бот // перестаёт отвечать отправителям, а узнать об этом было бы неоткуда; // - до Telegram не дошли — недоступность: сеть, DNS, авария Bot API, // истёкший срок ожидания. Сервис поднимается без Telegram, потому что // основной вход у него другой, и класть его из-за чужой аварии нельзя. // // Ядро во всех трёх случаях получает непустого отправителя: необязательная // зависимость, доехавшая до него нулём, уронила бы первую же задачу из // Telegram. func telegramFromBot( bot *tgbotapi.BotAPI, err error, logger *slog.Logger, ) (*tgbotapi.BotAPI, contract.TelegramMessageSender, error) { var apiErr *tgbotapi.Error switch { case errors.Is(err, telegram.ErrEmptyToken): metrics.IntakeUpGauge.WithLabelValues("telegram").Set(0) logger.Warn("Telegram bot is not started", "reason", "bot token is not set in configuration") return nil, telegram.NewAbsentMessageSender(), nil case errors.As(err, &apiErr): // Отказ токена не несёт: его чистит единая точка `telegram.NewBot`. return nil, nil, err case err != nil: metrics.IntakeUpGauge.WithLabelValues("telegram").Set(0) logger.Warn("Telegram bot is not started", "reason", "telegram is unreachable", "error", err) return nil, telegram.NewAbsentMessageSender(), nil default: metrics.IntakeUpGauge.WithLabelValues("telegram").Set(1) return bot, telegram.NewTelegramMessageSender(bot, logger), nil } }