import { config } from "./util/config.js"; import { logger } from "./util/logger.js"; import { db, pool } from "./db/client.js"; import { createBotClient, closeBotClient, onBotUpdate } from "./tdlib/client.js"; import { startSendListener, stopSendListener } from "./send-listener.js"; import { handleMessage } from "./commands.js"; import { mkdir } from "fs/promises"; const log = logger.child({ module: "main" }); async function main(): Promise { log.info("DragonsStash Telegram Bot starting"); if (!config.botToken) { log.fatal("BOT_TOKEN environment variable is required"); process.exit(1); } if (!config.telegramApiId || !config.telegramApiHash) { log.fatal("TELEGRAM_API_ID and TELEGRAM_API_HASH are required"); process.exit(1); } // Ensure TDLib state directory exists await mkdir(config.tdlibStateDir, { recursive: true }); await mkdir(`${config.tdlibStateDir}/bot`, { recursive: true }); await mkdir(`${config.tdlibStateDir}/bot_files`, { recursive: true }); // Initialize TDLib bot client await createBotClient(); // Start pg_notify listener for send requests and new package notifications await startSendListener(); // Listen for incoming messages from Telegram users onBotUpdate((update) => { if (update._ === "updateNewMessage") { const message = update.message as Record; const content = message.content as Record; const chatId = message.chat_id as number; const senderId = message.sender_id as Record | undefined; // Only handle text messages from users (not channels or service messages) if ( content?._ === "messageText" && senderId?._ === "messageSenderUser" ) { const text = (content.text as Record)?.text as string; const userId = senderId.user_id as number; if (text && userId) { // Get user info for display name (async but fire-and-forget for perf) handleMessage({ chatId: BigInt(chatId), userId: BigInt(userId), text, firstName: "User", // TDLib provides this via a separate getUser call username: undefined, }).catch((err) => { log.error({ err, chatId, userId }, "Failed to handle message"); }); } } } }); log.info("Bot is running and listening for messages"); } // Graceful shutdown function shutdown(signal: string): void { log.info({ signal }, "Shutdown signal received"); stopSendListener(); Promise.all([closeBotClient(), db.$disconnect(), pool.end()]) .then(() => { log.info("Shutdown complete"); process.exit(0); }) .catch((err) => { log.error({ err }, "Error during shutdown"); process.exit(1); }); } process.on("SIGTERM", () => shutdown("SIGTERM")); process.on("SIGINT", () => shutdown("SIGINT")); main().catch((err) => { log.fatal({ err }, "Bot failed to start"); process.exit(1); });