diff --git a/src/config/config.js b/src/config/config.js index b738c29..9291968 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -1,8 +1,7 @@ import logger from '../utils/logger.js'; if (!process.env.BOT_TOKEN) { - logger.fatal('BOT_TOKEN environment variable is required'); - process.exit(1); + logger.warn('BOT_TOKEN not set. Bot will not start. Admin panel will continue to work.'); } if (!process.env.ENCRYPTION_KEY || process.env.ENCRYPTION_KEY.length < 32) { diff --git a/src/context/bot.js b/src/context/bot.js index 5f2c871..86e2ccd 100644 --- a/src/context/bot.js +++ b/src/context/bot.js @@ -2,17 +2,45 @@ import TelegramBot from "node-telegram-bot-api"; import config from "../config/config.js"; import logger from "../utils/logger.js"; -const initBot = () => { - try { - const bot = new TelegramBot(config.BOT_TOKEN, {polling: true}); - logger.info('Bot initialized successfully'); - return bot; - } catch (error) { - logger.error({ err: error }, 'Failed to initialize bot'); - process.exit(1); +const MAX_RETRIES = 5; +const RETRY_DELAY_MS = 5000; + +let bot = null; +let botAvailable = false; + +const initBot = async () => { + 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; \ No newline at end of file diff --git a/src/index.js b/src/index.js index f1572d5..038dbcf 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,7 @@ import 'dotenv/config'; import { runMigrations, cleanUpInvalidForeignKeys } from './migrations/runner.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 logger from './utils/logger.js'; import userHandler from './handlers/userHandlers/userHandler.js'; @@ -16,62 +16,60 @@ await cleanUpInvalidForeignKeys(); await initStates(); registerRoutes(); -const logDebug = (action, functionName) => { - logger.debug({ action, functionName }, 'Button Press'); -}; +if (bot && botAvailable) { + 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) => { - logDebug('/start', 'handleStart'); - 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(/\/admin/, async (msg) => { + try { + await adminHandler.handleAdminCommand(msg); + } catch (error) { + await ErrorHandler.handleError(bot, msg.chat.id, error, 'admin command'); + } + }); -bot.onText(/\/admin/, async (msg) => { - logDebug('/admin', 'handleAdminCommand'); - try { - await adminHandler.handleAdminCommand(msg); - } catch (error) { - await ErrorHandler.handleError(bot, msg.chat.id, error, 'admin command'); - } -}); + bot.on('message', async (msg) => { + if (msg.text?.toLowerCase() === '/start') return; + const canUse = await userHandler.canUseBot(msg); + if (!canUse) return; + try { + await messageRouter.dispatch(msg); + } catch (error) { + await ErrorHandler.handleError(bot, msg.chat.id, error, 'message handler'); + } + }); -bot.on('message', async (msg) => { - if (msg.text?.toLowerCase() === '/start') return; - const canUse = await userHandler.canUseBot(msg); - if (!canUse) return; - try { - await messageRouter.dispatch(msg); - } catch (error) { - await ErrorHandler.handleError(bot, msg.chat.id, error, 'message handler'); - } -}); + bot.on('callback_query', async (callbackQuery) => { + 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('callback_query', async (callbackQuery) => { - 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); -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) => { logger.error({ err: error }, 'Unhandled promise rejection'); }); -logger.info('Bot is running...'); - import { startAdminPanel } from './admin/server.js'; -startAdminPanel(); +startAdminPanel(); \ No newline at end of file diff --git a/src/utils/errorHandler.js b/src/utils/errorHandler.js index 87aa908..bd78f6b 100644 --- a/src/utils/errorHandler.js +++ b/src/utils/errorHandler.js @@ -1,5 +1,8 @@ import logger from './logger.js'; +let pollingRetries = 0; +const MAX_POLLING_RETRIES = 5; + export default class ErrorHandler { static async handleError(bot, chatId, error, context) { logger.error({ err: error, context }, 'Error in handler'); @@ -8,6 +11,7 @@ export default class ErrorHandler { ? `Error: ${error.message}` : 'An error occurred. Please try again later.'; + if (!bot) return; try { await bot.sendMessage(chatId, errorMessage); } catch (sendError) { @@ -17,8 +21,32 @@ export default class ErrorHandler { static handlePollingError(error) { 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'); - process.exit(1); } else { logger.error({ err: error }, 'Polling error'); }