config: включение Telegram разведено с ключом доступа
- в секции [telegram] заведён обязательный ключ enabled: умолчания у него нет, файл без него негоден; bot_token стал только ключом доступа и при enabled = false не читается вовсе, а пустой при enabled = true роняет старт - выключенный вход даёт подъём одним входом без единого обращения к Telegram и записью INFO вместо прежнего WARN: это выбор владельца, а не отклонение - отказ разбора файла настроек больше не пересказывает toml — её ParseError несёт в тексте разбираемое значение, и оборванная строка секретного ключа уносила его в журнал; теперь называются путь, строка, столбец и последний ключ
This commit is contained in:
+47
-14
@@ -5,6 +5,7 @@ import (
|
||||
"log/slog"
|
||||
|
||||
"git.vakhrushev.me/av/transcriber/internal/adapter/telegram"
|
||||
"git.vakhrushev.me/av/transcriber/internal/config"
|
||||
"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"
|
||||
@@ -14,8 +15,38 @@ import (
|
||||
// раз** и достаётся обоим — отправителю ответов и транспорту бота. Пока его
|
||||
// строили порознь, два пути одного старта разошлись: один ронял процесс на
|
||||
// негодном токене, другой терпел, и согласовывать их приходилось руками.
|
||||
func buildTelegram(botToken string, logger *slog.Logger) (*tgbotapi.BotAPI, contract.TelegramMessageSender, error) {
|
||||
bot, err := telegram.NewBot(botToken, logger)
|
||||
func buildTelegram(cfg config.TelegramConfig, logger *slog.Logger) (*tgbotapi.BotAPI, contract.TelegramMessageSender, error) {
|
||||
return telegramFromConfig(cfg, telegram.NewBot, logger)
|
||||
}
|
||||
|
||||
// telegramFromConfig решает, поднимать ли вход вообще, и делает это **до**
|
||||
// всякого обращения к Telegram. Намерение объявляет признак включения; ключ
|
||||
// доступа при выключенном входе не смотрится вовсе.
|
||||
//
|
||||
// Сборка клиента приходит параметром, и это не украшение. Главное утверждение
|
||||
// выключенного входа — «обращения не уходит ни одного», — иначе непроверяемо:
|
||||
// адрес Bot API живёт внутри `telegram.NewBot`, и проверка, судящая по исходу,
|
||||
// осталась бы зелёной и тогда, когда ветка выключенного входа встала **после**
|
||||
// обращения. Прогон с заполненным ключом ушёл бы в живой Telegram боевым
|
||||
// токеном, а заметить это было бы нечем.
|
||||
//
|
||||
// Шов — подпись, а не интерфейс: реализация у него одна, и заводить тип ради
|
||||
// неё значит заводить понятие там, где хватает функции.
|
||||
func telegramFromConfig(
|
||||
cfg config.TelegramConfig,
|
||||
newBot func(string, *slog.Logger) (*tgbotapi.BotAPI, error),
|
||||
logger *slog.Logger,
|
||||
) (*tgbotapi.BotAPI, contract.TelegramMessageSender, error) {
|
||||
if !cfg.Enabled {
|
||||
metrics.IntakeUpGauge.WithLabelValues("telegram").Set(0)
|
||||
// Уровень «к сведению», а не «может стать проблемой»: это выбор
|
||||
// владельца, а не отклонение. Предупреждение остаётся за тем, чего
|
||||
// владелец не выбирал, — недоступностью Telegram.
|
||||
logger.Info("Telegram bot is not started", "reason", "telegram intake is disabled in configuration")
|
||||
return nil, telegram.NewAbsentMessageSender(), nil
|
||||
}
|
||||
|
||||
bot, err := newBot(cfg.BotToken, logger)
|
||||
return telegramFromBot(bot, err, logger)
|
||||
}
|
||||
|
||||
@@ -25,17 +56,24 @@ func buildTelegram(botToken string, logger *slog.Logger) (*tgbotapi.BotAPI, cont
|
||||
// разрез проверять надо, иначе его молча вернут к прежнему виду.
|
||||
//
|
||||
// Разрез проходит по тому, **ответил ли Telegram**, и это решение владельца
|
||||
// от 2026-08-13: недоступность Telegram на старт сервиса не влияет.
|
||||
// от 2026-08-13: недоступность Telegram на старт сервиса не влияет. Сюда
|
||||
// доходит только включённый вход: выключенный отсеян выше, до обращения.
|
||||
//
|
||||
// - токен не задан — законный отказ от входа: сервис работает оставшимся;
|
||||
// - Telegram ответил отказом — ошибка настройки: бота по этому токену не
|
||||
// существует, ждать нечего, и старт роняется. Молча потерянный бот
|
||||
// перестаёт отвечать отправителям, а узнать об этом было бы неоткуда;
|
||||
// - Telegram ответил отказом либо ключ доступа пуст — ошибка настройки: бота
|
||||
// по такому ключу не существует, ждать нечего, и старт роняется. Молча
|
||||
// потерянный бот перестаёт отвечать отправителям, а узнать об этом было бы
|
||||
// неоткуда;
|
||||
// - до Telegram не дошли — недоступность: сеть, DNS, авария Bot API,
|
||||
// истёкший срок ожидания. Сервис поднимается без Telegram, потому что
|
||||
// основной вход у него другой, и класть его из-за чужой аварии нельзя.
|
||||
//
|
||||
// Ядро во всех трёх случаях получает непустого отправителя: необязательная
|
||||
// Ветка пустого ключа по построению недостижима — его ловит проверка настроек
|
||||
// раньше, — но исход у неё **тот же**, что у проверки, и потому она оставлена.
|
||||
// Убрав её, мы отправили бы пустой ключ в общий случай `err != nil`, то есть в
|
||||
// «недоступность»: обход проверки настроек дал бы тихий подъём без бота — ровно
|
||||
// то, против чего написано это изменение.
|
||||
//
|
||||
// Ядро в обоих мягких случаях получает непустого отправителя: необязательная
|
||||
// зависимость, доехавшая до него нулём, уронила бы первую же задачу из
|
||||
// Telegram.
|
||||
func telegramFromBot(
|
||||
@@ -46,12 +84,7 @@ func telegramFromBot(
|
||||
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):
|
||||
case errors.Is(err, telegram.ErrEmptyToken), errors.As(err, &apiErr):
|
||||
// Отказ токена не несёт: его чистит единая точка `telegram.NewBot`.
|
||||
return nil, nil, err
|
||||
|
||||
|
||||
Reference in New Issue
Block a user