fix: bot no longer crashes container on invalid token
- bot.js: 5 retries with 5s delay on init, graceful fallback to null - errorHandler.js: 5 retries on 404 (invalid token), stops polling after max retries but keeps process alive for admin panel - config.js: BOT_TOKEN missing logs warning instead of process.exit - index.js: bot handlers only registered when bot is available, admin panel always starts regardless of bot status
This commit is contained in:
@@ -1,8 +1,7 @@
|
|||||||
import logger from '../utils/logger.js';
|
import logger from '../utils/logger.js';
|
||||||
|
|
||||||
if (!process.env.BOT_TOKEN) {
|
if (!process.env.BOT_TOKEN) {
|
||||||
logger.fatal('BOT_TOKEN environment variable is required');
|
logger.warn('BOT_TOKEN not set. Bot will not start. Admin panel will continue to work.');
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!process.env.ENCRYPTION_KEY || process.env.ENCRYPTION_KEY.length < 32) {
|
if (!process.env.ENCRYPTION_KEY || process.env.ENCRYPTION_KEY.length < 32) {
|
||||||
|
|||||||
@@ -2,17 +2,45 @@ import TelegramBot from "node-telegram-bot-api";
|
|||||||
import config from "../config/config.js";
|
import config from "../config/config.js";
|
||||||
import logger from "../utils/logger.js";
|
import logger from "../utils/logger.js";
|
||||||
|
|
||||||
const initBot = () => {
|
const MAX_RETRIES = 5;
|
||||||
try {
|
const RETRY_DELAY_MS = 5000;
|
||||||
const bot = new TelegramBot(config.BOT_TOKEN, {polling: true});
|
|
||||||
logger.info('Bot initialized successfully');
|
let bot = null;
|
||||||
return bot;
|
let botAvailable = false;
|
||||||
} catch (error) {
|
|
||||||
logger.error({ err: error }, 'Failed to initialize bot');
|
const initBot = async () => {
|
||||||
process.exit(1);
|
if (!config.BOT_TOKEN) {
|
||||||
|
logger.warn('No BOT_TOKEN configured. Running in admin-only mode.');
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
||||||
|
try {
|
||||||
|
const instance = new TelegramBot(config.BOT_TOKEN, {polling: true});
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
instance.stopPolling();
|
||||||
|
reject(new Error('Bot initialization timeout'));
|
||||||
|
}, 15000);
|
||||||
|
instance.getMe().then(() => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
resolve();
|
||||||
|
}).catch(reject);
|
||||||
|
});
|
||||||
|
logger.info({ attempt }, 'Bot initialized successfully');
|
||||||
|
botAvailable = true;
|
||||||
|
return instance;
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn({ attempt, maxRetries: MAX_RETRIES, err: error.message }, 'Bot initialization failed');
|
||||||
|
if (attempt < MAX_RETRIES) {
|
||||||
|
await new Promise(r => setTimeout(r, RETRY_DELAY_MS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.error({ maxRetries: MAX_RETRIES }, 'All bot initialization attempts failed. Running in admin-only mode.');
|
||||||
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const bot = initBot();
|
bot = await initBot();
|
||||||
|
|
||||||
|
export { botAvailable };
|
||||||
export default bot;
|
export default bot;
|
||||||
96
src/index.js
96
src/index.js
@@ -1,7 +1,7 @@
|
|||||||
import 'dotenv/config';
|
import 'dotenv/config';
|
||||||
import { runMigrations, cleanUpInvalidForeignKeys } from './migrations/runner.js';
|
import { runMigrations, cleanUpInvalidForeignKeys } from './migrations/runner.js';
|
||||||
import { registerRoutes } from './router/routes.js';
|
import { registerRoutes } from './router/routes.js';
|
||||||
import bot from './context/bot.js';
|
import bot, { botAvailable } from './context/bot.js';
|
||||||
import ErrorHandler from './utils/errorHandler.js';
|
import ErrorHandler from './utils/errorHandler.js';
|
||||||
import logger from './utils/logger.js';
|
import logger from './utils/logger.js';
|
||||||
import userHandler from './handlers/userHandlers/userHandler.js';
|
import userHandler from './handlers/userHandlers/userHandler.js';
|
||||||
@@ -16,62 +16,60 @@ await cleanUpInvalidForeignKeys();
|
|||||||
await initStates();
|
await initStates();
|
||||||
registerRoutes();
|
registerRoutes();
|
||||||
|
|
||||||
const logDebug = (action, functionName) => {
|
if (bot && botAvailable) {
|
||||||
logger.debug({ action, functionName }, 'Button Press');
|
bot.onText(/\/start/, async (msg) => {
|
||||||
};
|
const canUse = await userHandler.canUseBot(msg);
|
||||||
|
if (!canUse) return;
|
||||||
|
try {
|
||||||
|
await userHandler.handleStart(msg);
|
||||||
|
} catch (error) {
|
||||||
|
await ErrorHandler.handleError(bot, msg.chat.id, error, 'start command');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
bot.onText(/\/start/, async (msg) => {
|
bot.onText(/\/admin/, async (msg) => {
|
||||||
logDebug('/start', 'handleStart');
|
try {
|
||||||
const canUse = await userHandler.canUseBot(msg);
|
await adminHandler.handleAdminCommand(msg);
|
||||||
if (!canUse) return;
|
} catch (error) {
|
||||||
try {
|
await ErrorHandler.handleError(bot, msg.chat.id, error, 'admin command');
|
||||||
await userHandler.handleStart(msg);
|
}
|
||||||
} catch (error) {
|
});
|
||||||
await ErrorHandler.handleError(bot, msg.chat.id, error, 'start command');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
bot.onText(/\/admin/, async (msg) => {
|
bot.on('message', async (msg) => {
|
||||||
logDebug('/admin', 'handleAdminCommand');
|
if (msg.text?.toLowerCase() === '/start') return;
|
||||||
try {
|
const canUse = await userHandler.canUseBot(msg);
|
||||||
await adminHandler.handleAdminCommand(msg);
|
if (!canUse) return;
|
||||||
} catch (error) {
|
try {
|
||||||
await ErrorHandler.handleError(bot, msg.chat.id, error, 'admin command');
|
await messageRouter.dispatch(msg);
|
||||||
}
|
} catch (error) {
|
||||||
});
|
await ErrorHandler.handleError(bot, msg.chat.id, error, 'message handler');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
bot.on('message', async (msg) => {
|
bot.on('callback_query', async (callbackQuery) => {
|
||||||
if (msg.text?.toLowerCase() === '/start') return;
|
const canUse = await userHandler.canUseBot(callbackQuery);
|
||||||
const canUse = await userHandler.canUseBot(msg);
|
if (!canUse) {
|
||||||
if (!canUse) return;
|
await bot.answerCallbackQuery(callbackQuery.id);
|
||||||
try {
|
return;
|
||||||
await messageRouter.dispatch(msg);
|
}
|
||||||
} catch (error) {
|
try {
|
||||||
await ErrorHandler.handleError(bot, msg.chat.id, error, 'message handler');
|
await callbackRouter.dispatch(callbackQuery);
|
||||||
}
|
await bot.answerCallbackQuery(callbackQuery.id);
|
||||||
});
|
} catch (error) {
|
||||||
|
await ErrorHandler.handleError(bot, callbackQuery.message.chat.id, error, 'callback query');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
bot.on('callback_query', async (callbackQuery) => {
|
bot.on('polling_error', ErrorHandler.handlePollingError);
|
||||||
const canUse = await userHandler.canUseBot(callbackQuery);
|
|
||||||
if (!canUse) {
|
|
||||||
await bot.answerCallbackQuery(callbackQuery.id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await callbackRouter.dispatch(callbackQuery);
|
|
||||||
await bot.answerCallbackQuery(callbackQuery.id);
|
|
||||||
} catch (error) {
|
|
||||||
await ErrorHandler.handleError(bot, callbackQuery.message.chat.id, error, 'callback query');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
bot.on('polling_error', ErrorHandler.handlePollingError);
|
logger.info('Bot is running...');
|
||||||
|
} else {
|
||||||
|
logger.warn('Bot is not available. Running in admin-only mode. Admin panel will continue to work.');
|
||||||
|
}
|
||||||
|
|
||||||
process.on('unhandledRejection', (error) => {
|
process.on('unhandledRejection', (error) => {
|
||||||
logger.error({ err: error }, 'Unhandled promise rejection');
|
logger.error({ err: error }, 'Unhandled promise rejection');
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.info('Bot is running...');
|
|
||||||
|
|
||||||
import { startAdminPanel } from './admin/server.js';
|
import { startAdminPanel } from './admin/server.js';
|
||||||
startAdminPanel();
|
startAdminPanel();
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
import logger from './logger.js';
|
import logger from './logger.js';
|
||||||
|
|
||||||
|
let pollingRetries = 0;
|
||||||
|
const MAX_POLLING_RETRIES = 5;
|
||||||
|
|
||||||
export default class ErrorHandler {
|
export default class ErrorHandler {
|
||||||
static async handleError(bot, chatId, error, context) {
|
static async handleError(bot, chatId, error, context) {
|
||||||
logger.error({ err: error, context }, 'Error in handler');
|
logger.error({ err: error, context }, 'Error in handler');
|
||||||
@@ -8,6 +11,7 @@ export default class ErrorHandler {
|
|||||||
? `Error: ${error.message}`
|
? `Error: ${error.message}`
|
||||||
: 'An error occurred. Please try again later.';
|
: 'An error occurred. Please try again later.';
|
||||||
|
|
||||||
|
if (!bot) return;
|
||||||
try {
|
try {
|
||||||
await bot.sendMessage(chatId, errorMessage);
|
await bot.sendMessage(chatId, errorMessage);
|
||||||
} catch (sendError) {
|
} catch (sendError) {
|
||||||
@@ -17,8 +21,32 @@ export default class ErrorHandler {
|
|||||||
|
|
||||||
static handlePollingError(error) {
|
static handlePollingError(error) {
|
||||||
if (error.code === 'ETELEGRAM') {
|
if (error.code === 'ETELEGRAM') {
|
||||||
|
const statusCode = error.response?.statusCode;
|
||||||
|
const description = error.response?.body?.description || '';
|
||||||
|
|
||||||
|
if (statusCode === 404 || description.includes('Not Found')) {
|
||||||
|
pollingRetries++;
|
||||||
|
logger.warn({
|
||||||
|
attempt: pollingRetries,
|
||||||
|
maxRetries: MAX_POLLING_RETRIES,
|
||||||
|
statusCode,
|
||||||
|
description
|
||||||
|
}, 'Invalid bot token (404). Will retry up to %d times.', MAX_POLLING_RETRIES);
|
||||||
|
|
||||||
|
if (pollingRetries >= MAX_POLLING_RETRIES) {
|
||||||
|
logger.error({ maxRetries: MAX_POLLING_RETRIES },
|
||||||
|
'Bot token is invalid after %d retries. Stopping polling. Admin panel continues running.',
|
||||||
|
MAX_POLLING_RETRIES);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statusCode === 401 || statusCode === 403) {
|
||||||
|
logger.error({ statusCode }, 'Authentication error. Stopping polling. Admin panel continues running.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
logger.error({ err: error }, 'Telegram API Error');
|
logger.error({ err: error }, 'Telegram API Error');
|
||||||
process.exit(1);
|
|
||||||
} else {
|
} else {
|
||||||
logger.error({ err: error }, 'Polling error');
|
logger.error({ err: error }, 'Polling error');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user