fix(bot): проверка дубликата BOT_TOKEN при старте (409 Conflict → exit 1)
All checks were successful
Release: multi-arch Docker images / build-push (push) Successful in 5m53s

Перед запуском polling делаем пробный getUpdates(timeout:0). Если Telegram
возвращает 409 'Conflict: terminated by other getUpdates request' (токен уже
используется другой нодой/процессом) — печатаем '🚫 ОБНАРУЖЕН ДУБЛИКАТ ТОКЕНА
ДЛЯ ЗАПУСКА' и завершаемся с кодом 1: контейнер не стартует, рестарт-цикл
останавливается (вместо молчаливого конфликта long poll между нодами).
Версия v1.0.1.
This commit is contained in:
NW
2026-08-23 01:57:16 +01:00
parent 1d083e564c
commit d2324db105
2 changed files with 48 additions and 2 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "telegram-shop-bot",
"version": "1.0.0",
"version": "1.0.1",
"type": "module",
"scripts": {
"test": "vitest run",

View File

@@ -15,7 +15,53 @@ const initBot = async () => {
}
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
const instance = new TelegramBot(config.BOT_TOKEN, {polling: true});
const instance = new TelegramBot(config.BOT_TOKEN, {polling: false});
// ── Проверка на ДУБЛИКАТ токена ─────────────────────────────
// Перед запуском polling делаем разовый getUpdates. Если токен уже
// используется другой нодой/процессом — Telegram возвращает 409
// "Conflict: terminated by other getUpdates request" (или webhook).
// Такой бот НЕ должен стартовать: он будет конфликтовать, терять
// обновления и «прыгать» между серверами (issue: дубликат BotFather
// токена на нескольких нодах).
const dupCheck = await new Promise((resolve, reject) => {
const timeout = setTimeout(() => resolve(null), 10000);
instance
.getUpdates({ timeout: 0, allowed_updates: [] })
.then((updates) => {
clearTimeout(timeout);
// допустимые значения — любой успешный ответ ([] или список)
resolve({ ok: true, updates });
})
.catch((error) => {
clearTimeout(timeout);
const status = error?.response?.statusCode ?? error?.response?.status;
const description = error?.response?.body?.description ?? error?.message ?? String(error);
const is409 = status === 409 || /conflict/i.test(description);
resolve({ ok: false, status, description, is409 });
});
});
if (!dupCheck?.ok) {
if (dupCheck?.is409) {
console.error(
'\n====================================================\n' +
'🚫 ОБНАРУЖЕН ДУБЛИКАТ ТОКЕНА ДЛЯ ЗАПУСКА\n' +
' Telegram вернул 409 Conflict — этот BOT_TOKEN уже\n' +
' используется другим экземпляром/нодой.\n' +
` (${dupCheck.description})\n` +
' Запуск бота остановлен. Создайте новый бот в @BotFather\n' +
' или замените BOT_TOKEN в настройках сервиса.\n' +
'====================================================\n'
);
process.exit(1);
}
if (dupCheck?.status === 404 || (dupCheck?.description || '').includes('Not Found')) {
// неверный токен — пробуем дальше; ниже полноценная валидация
logger.warn('validate: getUpdates 404 (invalid token?)');
}
}
// Реальная инициализация: включаем polling и проверяем getMe
await instance.startPolling();
await new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
instance.stopPolling();