DEV Community

Cover image for Boilerplate for bots in 2k25
Maksym
Maksym

Posted on

Boilerplate for bots in 2k25

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
Enter fullscreen mode Exit fullscreen mode

What's happening here:

  • Standard library imports for file operations, logging, async programming
  • ABC and abstractmethod 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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]
    )
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Why this design:

  • Inheritance: All bot types share common functionality
  • Abstract methods: Forces every bot type to implement start() and stop()
  • 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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Command System:

@self.bot.command(name='hello')
async def hello(ctx):
    await ctx.send(f'Hello {ctx.author.mention}!')
Enter fullscreen mode Exit fullscreen mode

Error handling:

@self.bot.event
async def on_command_error(ctx, error):
    if isinstance(error, commands.CommandNotFound):
        await ctx.send("Command not found!")
Enter fullscreen mode Exit fullscreen mode

5. Telegram Bot Implementation

class TelegramBot(BaseBot):
    def __init__(self, config: BotConfig):
        super().__init__(config)
        self.application = Application.builder().token(config.token).build()
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

Periodic tasks:

async def periodic_task(self):
    while self.is_running:
        await self.log_message("Running task...")
        await asyncio.sleep(60)  # Every minute
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

Execution:

if __name__ == "__main__":
    asyncio.run(main())
Enter fullscreen mode Exit fullscreen mode

9. How It All Works Together

Step-by-step execution:

  1. Load config from environment variables
  2. Determine bot type from BOT_TYPE env var
  3. Create appropriate bot instance (Discord/Telegram/Web)
  4. Bot inherits from BaseBot so it has common functionality
  5. Bot sets up its specific handlers/events
  6. Bot starts and begins listening for messages/events
  7. When message received, appropriate handler processes it
  8. Logging happens throughout via inherited methods
  9. 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
Enter fullscreen mode Exit fullscreen mode

Running:

python bot.py
Enter fullscreen mode Exit fullscreen mode

What happens:

  1. Loads Discord token from .env
  2. Creates DiscordBot instance
  3. Sets up commands like !hello, !ping, !info
  4. Starts listening for Discord messages
  5. 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)