Let's break down Python bot boilerplate step by step, explaining each component and how they work together.
1. Imports and Setup
import os
import logging
import asyncio
import json
from abc import ABC, abstractmethod
from typing import Dict, Any, Optional
from dataclasses import dataclass
from datetime import datetime
What's happening here:
- Standard library imports for file operations, logging, async programming
-
ABC
andabstractmethod
for creating base classes that other classes must implement - Type hints for better code documentation and IDE support
-
dataclass
for easy configuration management
Conditional imports:
try:
import discord
DISCORD_AVAILABLE = True
except ImportError:
DISCORD_AVAILABLE = False
Why this pattern:
- Allows the bot to work even if you don't have all libraries installed
- You only install what you need (Discord OR Telegram OR just web functionality)
- Graceful fallback when dependencies are missing
2. Configuration System
@dataclass
class BotConfig:
token: str
prefix: str = "!"
admin_ids: list = None
debug: bool = False
database_url: Optional[str] = None
What this does:
-
@dataclass
automatically creates__init__
,__repr__
etc. - Stores all bot settings in one place
- Default values mean you only specify what you need
- Type hints help catch errors early
Loading config from environment:
def load_config() -> BotConfig:
return BotConfig(
token=os.getenv("BOT_TOKEN", ""),
prefix=os.getenv("BOT_PREFIX", "!"),
admin_ids=[int(x) for x in os.getenv("ADMIN_IDS", "").split(",") if x]
)
3. Base Bot Class (Abstract)
class BaseBot(ABC):
def __init__(self, config: BotConfig):
self.config = config
self.logger = logging.getLogger(self.__class__.__name__)
self.is_running = False
@abstractmethod
async def start(self):
pass
@abstractmethod
async def stop(self):
pass
Why this design:
- Inheritance: All bot types share common functionality
-
Abstract methods: Forces every bot type to implement
start()
andstop()
- Polymorphism: You can treat all bots the same way regardless of type
- DRY principle: Common code (logging, admin checks) written once
Shared functionality:
def is_admin(self, user_id: int) -> bool:
return user_id in self.config.admin_ids
async def log_message(self, message: str, level: str = "info"):
# Centralized logging with timestamps
4. Discord Bot Implementation
class DiscordBot(BaseBot):
def __init__(self, config: BotConfig):
super().__init__(config) # Call parent constructor
intents = discord.Intents.default()
intents.message_content = True # Required for reading messages
self.bot = commands.Bot(command_prefix=config.prefix, intents=intents)
Key concepts:
Event System:
@self.bot.event
async def on_ready():
await self.log_message(f'{self.bot.user} has connected!')
@self.bot.event
async def on_message(message):
if message.author == self.bot.user:
return # Don't respond to self
await self.bot.process_commands(message)
Command System:
@self.bot.command(name='hello')
async def hello(ctx):
await ctx.send(f'Hello {ctx.author.mention}!')
Error handling:
@self.bot.event
async def on_command_error(ctx, error):
if isinstance(error, commands.CommandNotFound):
await ctx.send("Command not found!")
5. Telegram Bot Implementation
class TelegramBot(BaseBot):
def __init__(self, config: BotConfig):
super().__init__(config)
self.application = Application.builder().token(config.token).build()
Handler Pattern:
async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text('Hello!')
# Register the handler
self.application.add_handler(CommandHandler("start", start_command))
Different from Discord:
- Telegram uses handlers instead of decorators
- Update object contains message info
- Different API structure but same async patterns
6. Web Bot Implementation
class WebBot(BaseBot):
def __init__(self, config: BotConfig):
super().__init__(config)
self.session = None # HTTP session
self.tasks = [] # Background tasks
HTTP requests:
async def make_request(self, method: str, url: str, **kwargs):
async with self.session.request(method, url, **kwargs) as response:
return await response.json()
Periodic tasks:
async def periodic_task(self):
while self.is_running:
await self.log_message("Running task...")
await asyncio.sleep(60) # Every minute
7. Bot Manager (Multi-Bot Support)
class BotManager:
def __init__(self):
self.bots = {}
async def start_all(self):
tasks = []
for name, bot in self.bots.items():
tasks.append(asyncio.create_task(bot.start()))
await asyncio.gather(*tasks) # Run all bots simultaneously
Why this is useful:
- Run multiple bots at once (Discord + Telegram)
- Centralized management
- Graceful shutdown of all bots
8. Main Execution Flow
async def main():
config = load_config()
if not config.token:
logger.error("BOT_TOKEN required!")
return
bot_type = os.getenv("BOT_TYPE", "discord").lower()
if bot_type == "discord":
bot = DiscordBot(config)
elif bot_type == "telegram":
bot = TelegramBot(config)
# ...
await bot.start()
Execution:
if __name__ == "__main__":
asyncio.run(main())
9. How It All Works Together
Step-by-step execution:
- Load config from environment variables
- Determine bot type from BOT_TYPE env var
- Create appropriate bot instance (Discord/Telegram/Web)
- Bot inherits from BaseBot so it has common functionality
- Bot sets up its specific handlers/events
- Bot starts and begins listening for messages/events
- When message received, appropriate handler processes it
- Logging happens throughout via inherited methods
- Graceful shutdown when interrupted
10. Usage Example
Environment setup (.env file):
BOT_TOKEN=your_discord_bot_token
BOT_TYPE=discord
BOT_PREFIX=!
ADMIN_IDS=123456789,987654321
Running:
python bot.py
What happens:
- Loads Discord token from .env
- Creates DiscordBot instance
- Sets up commands like
!hello
,!ping
,!info
- Starts listening for Discord messages
- Responds to commands automatically
11. Key Design Patterns Used
- Abstract Factory: BaseBot defines interface, specific bots implement it
- Strategy Pattern: Different bot types use different strategies (Discord API vs Telegram API)
- Template Method: BaseBot provides common structure, subclasses fill in details
- Dependency Injection: Configuration passed to bots rather than hardcoded
This design makes the code:
- Modular: Easy to add new bot types
- Maintainable: Changes to common functionality affect all bots
- Testable: Each component can be tested independently
- Scalable: Easy to add features like databases, plugins, etc.
The boilerplate handles the complex setup so you can focus on adding your specific bot functionality!
Top comments (0)