Telegram bot server
Introduction#
BotServer: DI wiring, command handlers (/start, /help, /stop, /status, /placement_year, /stats, /noticestats, /web; admin /userstats), registration, subscriptions, admin commands. Polling lifecycle, HTML/Markdown replies, and how tokens stay out of logs.
Project structure#
The Telegram Bot Server resides in the servers package and integrates with services and clients for database, notifications, and Telegram API communication. The main entry point orchestrates server startup and daemon mode.
graph TB subgraph "Entry Point" MAIN["main.py"] end subgraph "Bot Server" BOT["BotServer<br/>app/servers/bot_server.py"] HANDLERS["Command Handlers<br/>/start, /help, /stop, /status, /placement_year,<br/>/stats, /noticestats, /web; admin /userstats"] end subgraph "Services" DB["DatabaseService<br/>app/services/database_service.py"] NOTIF["NotificationService<br/>app/services/notification_service.py"] TELEGRAM["TelegramService<br/>app/services/telegram_service.py"] ADMIN["AdminTelegramService<br/>app/services/admin_telegram_service.py"] STATS["PlacementStatsCalculatorService<br/>app/services/placement_stats_calculator_service.py"] end subgraph "Clients" DBC["DBClient<br/>app/clients/db_client.py"] TGCLI["TelegramClient<br/>app/clients/telegram_client.py"] end subgraph "Core" CFG["Settings & Logging<br/>app/core/config.py"] DMN["Daemon Utilities<br/>app/core/daemon.py"] end MAIN --> BOT BOT --> HANDLERS BOT --> DB BOT --> NOTIF BOT --> ADMIN BOT --> STATS DB --> DBC NOTIF --> TELEGRAM TELEGRAM --> TGCLI BOT --> CFG MAIN --> DMN
Core components#
- BotServer: Implements the Telegram bot server with DI support, command handlers, lifecycle management, and admin command integration.
- DatabaseService: Provides MongoDB operations for notices, jobs, placement offers, users, policies, and official placement data.
- NotificationService: Aggregates multiple notification channels and orchestrates sending unsent notices.
- TelegramService: Wraps Telegram Bot API interactions, message formatting, and broadcasting to users.
- AdminTelegramService: Handles administrative commands with authentication and operational controls.
- PlacementStatsCalculatorService: Computes detailed placement statistics from placement offers.
- DBClient: Manages MongoDB connection and exposes collections.
- TelegramClient: Low-level Telegram API client with retry logic and rate-limit handling.
- Settings and Logging: Centralized configuration and logging setup with daemon mode support.
Architecture overview#
The Telegram Bot Server follows a service-oriented architecture with dependency injection. The BotServer initializes services, registers command handlers, and manages the asynchronous lifecycle. Services encapsulate domain logic and integrate with clients for external APIs and databases.
classDiagram
class BotServer {
+Settings settings
+bool daemon_mode
+Any db_service
+Any notification_service
+Any admin_service
+Any stats_service
+Application application
+run_async() void
+run() void
+shutdown() void
+setup_handlers(Application) void
+start_command(Update, Context) void
+help_command(Update, Context) void
+stop_command(Update, Context) void
+status_command(Update, Context) void
+placement_year_command(Update, Context) void
+stats_command(Update, Context) void
+notice_stats_command(Update, Context) void
+user_stats_command(Update, Context) void
+web_command(Update, Context) void
}
class DatabaseService {
+DBClient db_client
+add_user(...) (bool, str)
+deactivate_user(int) bool
+get_user_by_id(int) Dict
+get_users_stats() Dict
+get_notice_stats() Dict
+get_all_offers(int) List
}
class NotificationService {
+List channels
+broadcast(str, List[str], ...) Dict
+send_unsent_notices(bool, bool) Dict
+send_new_posts_to_all_users(bool, bool) Dict
}
class TelegramService {
+TelegramClient client
+send_message(str, str, ...) bool
+send_to_user(any, str, str, ...) bool
+broadcast_to_all_users(str, str, ...) Dict
+convert_markdown_to_html(str) str
+split_long_message(str, int) str[]
}
class AdminTelegramService {
+Settings settings
+DatabaseService db_service
+TelegramService telegram_service
+users_command(Update, Context) void
+broadcast_command(Update, Context) void
+scrape_command(Update, Context) void
+kill_scheduler_command(Update, Context) void
+logs_command(Update, Context) void
}
class PlacementStatsCalculatorService {
+DatabaseService db_service
+calculate_all_stats(List) Dict
}
class DBClient {
+MongoClient client
+connect() void
+close_connection() void
+notices_collection
+jobs_collection
+placement_offers_collection
+users_collection
+policies_collection
+official_placement_data_collection
}
class TelegramClient {
+send_message(str, str, str, bool, int, float) bool
+test_connection() bool
}
BotServer --> DatabaseService : "uses"
BotServer --> NotificationService : "uses"
BotServer --> AdminTelegramService : "uses"
BotServer --> PlacementStatsCalculatorService : "uses"
DatabaseService --> DBClient : "uses"
NotificationService --> TelegramService : "uses"
TelegramService --> TelegramClient : "uses"Detailed component analysis#
BotServer: dependency injection and lifecycle#
- Dependency Injection: BotServer accepts Settings, DatabaseService, NotificationService, AdminTelegramService, and PlacementStatsCalculatorService instances. Daemon mode is configurable too.
- Command Handlers Registration: setup_handlers registers
/start,/help,/stop,/status,/placement_year,/stats,/noticestats,/web, and admin/userstats. Admin scrape/broadcast/log/kill commands are registered when AdminTelegramService is provided. - Asynchronous Lifecycle:
- run_async builds the Application, sets up logging, initializes and starts the application, and keeps the loop running while self.running is True.
- run blocks and catches KeyboardInterrupt to trigger graceful shutdown.
- shutdown stops the updater, application, and performs cleanup.
- Factory Function: create_bot_server constructs DBClient, DatabaseService, TelegramService, NotificationService, AdminTelegramService, and PlacementStatsCalculatorService, then returns a BotServer instance.
sequenceDiagram participant CLI as "CLI (main.py)" participant Factory as "create_bot_server()" participant Bot as "BotServer" participant App as "Application" participant DB as "DatabaseService" participant Notif as "NotificationService" participant Admin as "AdminTelegramService" participant Stats as "PlacementStatsCalculatorService" CLI->>Factory : create_bot_server(daemon_mode) Factory->>DB : DBClient + DatabaseService Factory->>Bot : TelegramService Factory->>Notif : NotificationService(channels=[TelegramService], db_service=DB) Factory->>Admin : AdminTelegramService(settings, db_service=DB, telegram_service=TelegramService) Factory->>Stats : PlacementStatsCalculatorService(db_service=DB) Factory-->>CLI : BotServer(db_service=DB, notification_service=Notif, admin_service=Admin, stats_service=Stats) CLI->>Bot : run() Bot->>Bot : setup_logging(settings) Bot->>App : Application.builder().token(token).build() Bot->>App : setup_handlers(Application) Bot->>App : initialize() + start() App->>App : start_polling(drop_pending_updates=True) Bot->>Bot : loop while running
Command handler system#
- /start: Registers or reactivates a user via DatabaseService and responds with HTML-formatted welcome message including commands and helpful links.
- /help: Responds with Markdown-formatted help text listing available commands.
- /stop: Deactivates a user via DatabaseService and informs the user.
- /status: Retrieves user data from DatabaseService and formats a status message indicating subscription state and placement year.
- /placement_year: Inline keyboard of configured years. Callback writes
set_user_placement_year. - /stats: Calculates placement statistics via PlacementStatsCalculatorService and replies with Markdown-formatted statistics.
- /noticestats: Retrieves notice statistics from DatabaseService and replies with Markdown-formatted stats.
- /userstats: Retrieves user statistics from DatabaseService and replies with Markdown-formatted stats (admin-only via AdminTelegramService).
- /web: Replies with HTML-formatted links to JIIT suite tools.
sequenceDiagram participant User as "Telegram User" participant Bot as "BotServer" participant DB as "DatabaseService" participant Stats as "PlacementStatsCalculatorService" User->>Bot : /start Bot->>DB : add_user(user_id, chat_id, username, first_name, last_name) DB-->>Bot : (success, message) Bot-->>User : HTML welcome message User->>Bot : /status Bot->>DB : get_user_by_id(user_id) DB-->>Bot : user_data Bot-->>User : Status message User->>Bot : /stats Bot->>Stats : calculate_all_stats() Stats-->>Bot : stats Bot-->>User : Markdown stats
User registration and management workflows#
- Registration: On /start, BotServer calls DatabaseService.add_user with user identity and chat metadata. Responses vary depending on whether the user is newly registered, reactivated, or already active.
- Subscription Management: /stop triggers DatabaseService.deactivate_user to mark the user as inactive. /status retrieves user data and indicates active/inactive state.
- Placement year:
/placement_yearstores a preference used by/statsand/noticestats. - User Statistics:
/userstatsis admin-only (ADMIN_TELEGRAM_USER_IDS) and uses DatabaseService.get_users_stats.
flowchart TD
Start(["/start received"]) --> Validate["Validate update.message and user/chat"]
Validate --> HasDB{"db_service available?"}
HasDB --> |No| ReplyStartup["Reply: Bot starting up"]
HasDB --> |Yes| AddUser["Call add_user(user_id, chat_id, username, first_name, last_name)"]
AddUser --> Result{"Success?"}
Result --> |Yes| WelcomeMsg["Build HTML welcome message<br/>Reactivate/Welcome text + commands + links"]
Result --> |No| AlreadyActive{"Already active?"}
AlreadyActive --> |Yes| AlreadyMsg["Build HTML message: already registered"]
AlreadyActive --> |No| ErrorMsg["Build HTML message: error"]
WelcomeMsg --> Send["reply_text with parse_mode=HTML"]
AlreadyMsg --> Send
ErrorMsg --> Send
ReplyStartup --> Send
Send --> End(["Done"])Subscription handling#
- Deactivation: /stop calls DatabaseService.deactivate_user and informs the user accordingly.
- Status Checking: /status fetches user data and formats a readable status message, including registration date and user ID when available.
Admin command processing#
- Authentication: AdminTelegramService verifies that the chat ID matches the configured admin chat ID.
- /users: Lists all users with status and identifiers.
- /boo: Broadcasts messages either to all users or a specific user.
- /fu or /scrapyyy: Triggers a legacy update workflow executed in a thread to avoid blocking the event loop.
- /kill: Stops the scheduler daemon.
- /logs: Reads and returns the last lines of the specified log file, HTML-escaped and truncated to fit Telegram limits.
sequenceDiagram
participant Admin as "Admin User"
participant AdminSvc as "AdminTelegramService"
participant DB as "DatabaseService"
participant Tele as "TelegramService"
participant Main as "main.py legacy workflow"
Admin->>AdminSvc : /users
AdminSvc->>DB : get_all_users()
DB-->>AdminSvc : users[]
AdminSvc-->>Admin : HTML list (chunked if needed)
Admin->>AdminSvc : /boo broadcast <msg>
AdminSvc->>Tele : broadcast_to_all_users(msg)
Tele-->>AdminSvc : results
AdminSvc-->>Admin : Result summary
Admin->>AdminSvc : /fu
AdminSvc->>Main : run legacy workflow (thread)
Main-->>AdminSvc : results
AdminSvc-->>Admin : Results summary
Admin->>AdminSvc : /kill
AdminSvc->>AdminSvc : stop_daemon("scheduler")
AdminSvc-->>Admin : Success/failure message
Admin->>AdminSvc : /logs [bot|scheduler]
AdminSvc->>AdminSvc : read last 100 lines
AdminSvc-->>Admin : HTML-escaped log previewAsynchronous bot lifecycle#
- Initialization: create_bot_server constructs dependencies and returns a BotServer instance.
- Polling Mode: run_async initializes and starts the Application, then starts polling with drop_pending_updates to avoid replaying old updates.
- Graceful Shutdown: run catches KeyboardInterrupt and calls shutdown to stop the updater and application cleanly.
sequenceDiagram participant App as "Application" participant Updater as "Updater" participant Bot as "BotServer" Bot->>App : initialize() Bot->>App : start() App->>Updater : start_polling(drop_pending_updates=True) loop While running Bot->>Bot : sleep(1) end Bot->>Bot : running=False Bot->>Updater : stop() Bot->>App : stop() Bot->>App : shutdown()
Message formatting with HTML and Markdown#
- HTML: Used for /start welcome message, /web links, and status messages. TelegramService.convert_markdown_to_html transforms Markdown to HTML for Telegram.
- Markdown: Used for /help and statistics commands. TelegramService.convert_markdown_to_telegram adapts Markdown for Telegram’s MarkdownV2.
- Long Messages: TelegramService.split_long_message splits messages exceeding 4000 characters and sends them sequentially with delays.
Integration with DatabaseService and NotificationService#
- DatabaseService: Provides user management, notice operations, job operations, placement offers, and statistics retrieval. Used by BotServer for user registration/status and by AdminTelegramService for user listing and stats.
- NotificationService: Orchestrates sending unsent notices to Telegram and Web Push channels. It fetches unsent notices from DatabaseService and broadcasts via TelegramService.
Security considerations for bot token management#
- Token Storage: BotServer reads TELEGRAM_BOT_TOKEN from Settings. TelegramClient validates presence of bot token before sending messages.
- Admin Authentication: AdminTelegramService compares the incoming chat ID with the configured admin chat ID to restrict sensitive commands.
- Daemon Mode: safe_print suppresses stdout in daemon mode and logs to files instead.
Dependency analysis#
The BotServer depends on DatabaseService, NotificationService, AdminTelegramService, and PlacementStatsCalculatorService. DatabaseService depends on DBClient. NotificationService depends on TelegramService. TelegramService depends on TelegramClient. The main entry point creates the BotServer and runs it, optionally in daemon mode.
graph TB Bot["BotServer"] DB["DatabaseService"] Notif["NotificationService"] Admin["AdminTelegramService"] Stats["PlacementStatsCalculatorService"] DBC["DBClient"] Tele["TelegramService"] TGCLI["TelegramClient"] Main["main.py"] Main --> Bot Bot --> DB Bot --> Notif Bot --> Admin Bot --> Stats DB --> DBC Notif --> Tele Tele --> TGCLI
Performance considerations#
- Rate Limiting: TelegramService applies small delays between broadcasts to Telegram users to avoid rate limits.
- Message chunking: TelegramService.split_long_message keeps messages under Telegram’s character limit.
- Asynchronous Polling: BotServer uses asynchronous polling to keep the event loop responsive.
- Daemon Mode: Reduces console output overhead in production environments.
[No sources needed since this section provides general guidance]
Troubleshooting guide#
- Bot not starting: Ensure TELEGRAM_BOT_TOKEN is configured; create_bot_server raises if token is missing.
- Database connectivity: Verify MONGO_CONNECTION_STR; DBClient.connect tests the connection and logs errors.
- Admin commands failing: Confirm admin chat ID matches the configured TELEGRAM_CHAT_ID; AdminTelegramService enforces authentication.
- Logs: Use /logs admin command to retrieve recent log entries; logs are written to files configured by Settings.
Conclusion#
BotServer is DI, command handlers, and an async polling loop. It talks to DatabaseService and NotificationService. Daemon mode is how you leave it running without a terminal attached.