-
Notifications
You must be signed in to change notification settings - Fork 0
07. Developer Guide
Complete guide for developers who want to contribute to UtilsBot+ or understand its internal architecture.
- Development Environment Setup
- Project Structure
- Code Architecture
- Adding New Commands
- Database Development
- Testing
- Code Style Guidelines
- Contributing Workflow
- Debugging
- Python 3.11+ (Python 3.12 recommended)
- Git for version control
- Code editor (VS Code recommended with Python extensions)
- Discord Bot Token for testing
- Google Gemini API Key for AI features
# Clone the repository
git clone https://github.com/ad1107/utils-bot-plus.git
cd utils-bot-plus
# Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install development dependencies
pip install -r requirements-dev.txt
pip install -r requirements.txt
# Run setup script
chmod +x setup.sh
./setup.sh
# Initialize database
python migrations/init_db.py
Create a .env
file for development:
# Development configuration
DEBUG=true
LOG_LEVEL=DEBUG
# Required credentials
BOT_TOKEN=your_dev_bot_token
DEV_IDS=your_discord_id
DEV_GUILD_ID=your_test_server_id
GEMINI_API_KEY=your_api_key
SECRET_KEY=generated_secret
# Development features
AUTO_SYNC_COMMANDS=true
ENABLE_HOT_RELOAD=true
# Start with hot reload
make run-dev
# Format code
make format
# Type checking
make type-check
# Run tests
make test
# Check code quality
make lint
utils-bot-plus/
├── main.py # Application entry point
├── pyproject.toml # Modern Python packaging
├── requirements.txt # Production dependencies
├── requirements-dev.txt # Development dependencies
├── Makefile # Development commands
│
├── config/ # Configuration management
│ ├── __init__.py
│ └── settings.py # Pydantic settings with validation
│
├── core/ # Core bot functionality
│ ├── __init__.py
│ ├── bot.py # Main UtilsBotPlus class
│ └── logger.py # Structured logging setup
│
├── cogs/ # Feature modules (slash commands)
│ ├── ai.py # Google Gemini integration
│ ├── games.py # Interactive games (Wordle)
│ ├── info.py # Bot info & help system
│ ├── network.py # Network utilities
│ ├── system.py # Developer commands
│ └── tools.py # Security & utility tools
│
├── models/ # Data models & database
│ ├── __init__.py
│ └── database.py # SQLAlchemy models & management
│
├── utils/ # Utility functions
│ ├── __init__.py
│ ├── checks.py # Permission decorators
│ ├── embeds.py # Discord embed helpers
│ ├── health.py # Health monitoring
│ └── screenshot.py # Screenshot service
│
├── assets/ # Static resources
│ └── games/
│ └── wordle_words.txt
│
├── migrations/ # Database setup scripts
│ ├── init_db.py # Database initialization
│ └── populate_data.py # Sample data population
│
├── data/ # SQLite database storage
├── logs/ # Application logs
└── tests/ # Test files
- Slash Commands Only - No prefix commands for better UX
- Async First - Full async/await architecture
- Type Safety - Complete type hints and validation
- Modular Design - Feature separation via cogs
- Error Resilience - Comprehensive error handling
- Security First - Permission-based access control
The main bot class extends commands.Bot
:
class UtilsBotPlus(commands.Bot):
def __init__(self):
super().__init__(
command_prefix="$disabled$", # Unused
intents=discord.Intents.default(),
help_command=None
)
# Initialize components
self.db = Database()
self.logger = get_logger(__name__)
self.start_time = datetime.now()
Key Features:
- Automatic cog loading from
cogs/
directory - Database initialization and management
- Global error handling for slash commands
- Command syncing (global and guild-specific)
- Uptime and performance tracking
Commands are organized into cogs (modules) for better organization:
from discord.ext import commands
from discord import app_commands
class ExampleCog(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.logger = get_logger(__name__)
@app_commands.command(name="example", description="Example command")
async def example_command(self, interaction: discord.Interaction):
await interaction.response.send_message("Hello!")
async def setup(bot):
await bot.add_cog(ExampleCog(bot))
SQLAlchemy 2.0 with async support:
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
class Database:
def __init__(self, database_url: str = None):
self.engine = create_async_engine(database_url or settings.database_url)
self.async_session = sessionmaker(
self.engine, class_=AsyncSession, expire_on_commit=False
)
async def get_user(self, discord_id: int) -> Optional[User]:
async with self.async_session() as session:
result = await session.get(User, discord_id)
return result
Determine which cog your command belongs to:
-
ai.py
: AI and machine learning features -
games.py
: Interactive games and entertainment -
info.py
: Bot information and help commands -
network.py
: Network and web utilities -
system.py
: Developer and system commands -
tools.py
: Security and utility tools
@app_commands.command(name="mycommand", description="Description of my command")
@app_commands.describe(
param1="Description of parameter 1",
param2="Description of parameter 2"
)
@requires_whitelist() # Add appropriate permission checks
@cooldown(rate=5, per=60) # Add rate limiting
async def my_command(
self,
interaction: discord.Interaction,
param1: str,
param2: Optional[int] = None
):
"""
Command implementation with proper type hints.
Args:
interaction: Discord interaction object
param1: Required string parameter
param2: Optional integer parameter
"""
try:
# Log command usage
self.logger.info(
f"Command executed",
extra={
"user_id": interaction.user.id,
"guild_id": interaction.guild_id if interaction.guild else None,
"command": "mycommand"
}
)
# Command logic here
result = await some_async_operation(param1, param2)
# Create success response
embed = create_success_embed(
title="Success",
description=f"Operation completed: {result}"
)
await interaction.response.send_message(embed=embed)
except ValueError as e:
# Handle specific errors
embed = create_error_embed("Invalid Input", str(e))
await interaction.response.send_message(embed=embed, ephemeral=True)
except Exception as e:
# Log unexpected errors
self.logger.error(f"Command error: {e}", exc_info=True)
embed = create_error_embed(
"Command Error",
"An unexpected error occurred. Please try again."
)
await interaction.response.send_message(embed=embed, ephemeral=True)
Use the standard error handling pattern:
from utils.embeds import create_success_embed, create_error_embed
# Success response
embed = create_success_embed("Title", "Description")
await interaction.response.send_message(embed=embed)
# Error response
embed = create_error_embed("Error Title", "Error description")
await interaction.response.send_message(embed=embed, ephemeral=True)
Use decorators for permission control:
from utils.checks import dev_only, requires_whitelist
@dev_only() # Developer only
@requires_whitelist() # Requires whitelist (if beta mode)
@cooldown(rate=3, per=60) # Rate limiting
- Add command to this wiki's Commands Reference
- Update the main README.md
- Add docstrings and type hints
Define models using SQLAlchemy:
from sqlalchemy import Column, Integer, String, Boolean, DateTime
from sqlalchemy.ext.declarative import declarative_base
from datetime import datetime
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
discord_id = Column(String(20), unique=True, nullable=False)
username = Column(String(100))
is_whitelisted = Column(Boolean, default=False)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# Create user
async def create_user(self, discord_id: int, username: str) -> User:
async with self.async_session() as session:
user = User(discord_id=discord_id, username=username)
session.add(user)
await session.commit()
await session.refresh(user)
return user
# Update user
async def update_user(self, discord_id: int, **kwargs) -> Optional[User]:
async with self.async_session() as session:
user = await session.get(User, discord_id)
if user:
for key, value in kwargs.items():
setattr(user, key, value)
user.updated_at = datetime.utcnow()
await session.commit()
return user
return None
For database schema changes:
# migrations/add_new_table.py
from models.database import Base, engine
async def migrate():
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
if __name__ == "__main__":
import asyncio
asyncio.run(migrate())
import pytest
from unittest.mock import AsyncMock, MagicMock
from cogs.example import ExampleCog
@pytest.mark.asyncio
async def test_example_command():
# Setup
bot = MagicMock()
cog = ExampleCog(bot)
interaction = AsyncMock()
# Execute
await cog.example_command(interaction, "test_input")
# Assert
interaction.response.send_message.assert_called_once()
@pytest.mark.asyncio
async def test_database_integration():
# Test database operations
db = Database(":memory:") # In-memory SQLite
await db.initialize()
user = await db.create_user(123456789, "testuser")
assert user.discord_id == 123456789
retrieved = await db.get_user(123456789)
assert retrieved.username == "testuser"
# Run all tests
make test
# Run specific test file
pytest tests/test_commands.py -v
# Run with coverage
pytest --cov=cogs tests/
Follow PEP 8 and use modern Python features:
# Type hints
from typing import Optional, List, Dict, Any, Union
async def example_function(
param1: str,
param2: Optional[int] = None,
param3: List[str] = None
) -> Dict[str, Any]:
"""Function with proper type hints and docstring."""
if param3 is None:
param3 = []
return {"result": param1, "count": param2, "items": param3}
Use proper async/await patterns:
# Good: Proper async context
async with self.bot.db.async_session() as session:
result = await session.execute(query)
# Good: Async error handling
try:
result = await async_operation()
except asyncio.TimeoutError:
await interaction.response.send_message("Operation timed out")
Use structured logging:
self.logger.info(
"User action completed",
extra={
"user_id": interaction.user.id,
"action": "command_execution",
"command": "example",
"success": True
}
)
Document all public functions:
async def process_data(self, data: List[str]) -> Dict[str, int]:
"""
Process a list of strings and return statistics.
Args:
data: List of strings to process
Returns:
Dictionary with processing statistics
Raises:
ValueError: If data is empty
ProcessingError: If processing fails
"""
# Fork the repository on GitHub
git clone https://github.com/utils-bot/utils-bot-plus.git
cd utils-bot-plus
git remote add upstream https://github.com/utils-bot/utils-bot-plus.git
git checkout -b feature/your-feature-name
- Follow code style guidelines
- Add tests for new features
- Update documentation
- Ensure all tests pass
git add .
git commit -m "feat: add new command for example feature
- Implement /example command with proper validation
- Add tests and documentation
- Update command reference
"
git push origin feature/your-feature-name
# Create PR on GitHub
Use conventional commits:
feat: add new feature
fix: resolve bug in command
docs: update documentation
test: add test coverage
refactor: improve code structure
# Enable debug mode
echo "DEBUG=true" >> .env
echo "LOG_LEVEL=DEBUG" >> .env
# Run with detailed logging
python main.py
# Check logs
tail -f logs/bot.log
Use the /eval
command for live debugging:
# Check bot state
/eval code:"len(bot.guilds)"
# Inspect user data
/eval code:"await bot.db.get_user(interaction.user.id)"
# Test functions
/eval code:"import math; math.sqrt(16)"
Commands Not Syncing:
# Check command tree
/eval code:"[cmd.name for cmd in bot.tree.get_commands()]"
# Manual sync
/sync scope:"guild"
Database Issues:
# Test database connection
/eval code:"await bot.db.get_user(123456789)"
# Check table existence
/eval code:"bot.db.engine.table_names()"
# Add timing to commands
import time
start_time = time.time()
# Command logic
execution_time = (time.time() - start_time) * 1000
self.logger.info(f"Command executed in {execution_time:.2f}ms")
- Always validate user input
- Use ephemeral responses for sensitive data
- Implement proper rate limiting
- Never log sensitive information
- Use async/await properly
- Implement connection pooling for external APIs
- Cache frequently accessed data
- Monitor memory usage
- Provide user-friendly error messages
- Log detailed errors for debugging
- Use appropriate HTTP status codes for APIs
- Implement retry logic for external services
- Keep cogs focused on specific functionality
- Use utility functions for common operations
- Separate business logic from Discord interface
- Write comprehensive tests
Navigation: ← Commands Reference | Database Schema →
Related Pages: Architecture Overview | API Integrations | Security Guide