Developer Guide - openguard-bot/openguard GitHub Wiki

Developer Guide

This comprehensive guide covers everything developers need to know to contribute to AIMod, including setup, architecture, coding standards, and contribution workflows.

๐Ÿš€ Getting Started

Development Environment Setup

Prerequisites

  • Python 3.11+ with pip and venv
  • Node.js 18+ with npm
  • PostgreSQL 13+
  • Redis 6.0+
  • Git for version control
  • IDE (VS Code, PyCharm, or similar)

Quick Setup

# Clone the repository
git clone https://github.com/discordaimod/aimod.git
cd aimod

# Create and activate virtual environment
python3.11 -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install development dependencies
pip install -r requirements.txt
pip install -r requirements-dev.txt

# Set up pre-commit hooks
pre-commit install

# Copy environment template
cp .env.example .env.dev

Development Dependencies

# requirements-dev.txt
pytest>=7.4.0
pytest-asyncio>=0.21.0
pytest-cov>=4.1.0
black>=23.7.0
pylint>=2.17.0
mypy>=1.5.0
pre-commit>=3.3.0
bandit>=1.7.0
safety>=2.3.0
isort>=5.12.0
flake8>=6.0.0

IDE Configuration

VS Code Setup

// .vscode/settings.json
{
    "python.defaultInterpreterPath": "./venv/bin/python",
    "python.linting.enabled": true,
    "python.linting.pylintEnabled": true,
    "python.linting.mypyEnabled": true,
    "python.formatting.provider": "black",
    "python.formatting.blackArgs": ["--line-length", "88"],
    "python.sortImports.args": ["--profile", "black"],
    "editor.formatOnSave": true,
    "editor.codeActionsOnSave": {
        "source.organizeImports": true
    },
    "files.exclude": {
        "**/__pycache__": true,
        "**/*.pyc": true,
        "**/node_modules": true,
        "**/.pytest_cache": true
    }
}
// .vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Bot",
            "type": "python",
            "request": "launch",
            "program": "bot.py",
            "console": "integratedTerminal",
            "envFile": "${workspaceFolder}/.env.dev",
            "cwd": "${workspaceFolder}"
        },
        {
            "name": "Python: Tests",
            "type": "python",
            "request": "launch",
            "module": "pytest",
            "args": ["-v"],
            "console": "integratedTerminal",
            "envFile": "${workspaceFolder}/.env.dev"
        }
    ]
}

PyCharm Setup

  1. Interpreter: Configure Python interpreter to use ./venv/bin/python
  2. Code Style: Import Black formatter configuration
  3. Database: Configure PostgreSQL connection in Database tool
  4. Run Configurations: Create configurations for bot.py and tests

Environment Configuration

Development Environment (.env.dev)

# Discord Configuration
DISCORD_TOKEN=your_dev_bot_token
DISCORD_CLIENT_ID=your_dev_client_id
DISCORD_CLIENT_SECRET=your_dev_client_secret

# Database Configuration
DATABASE_URL=postgresql://aimod_user:password@localhost:5432/aimod_bot_dev
REDIS_URL=redis://localhost:6379/1

# AI Provider Configuration
OPENROUTER_API_KEY=your_dev_api_key

# Development Settings
ENVIRONMENT=development
DEBUG=true
LOG_LEVEL=DEBUG

# Testing
TEST_GUILD_ID=your_test_guild_id
TEST_USER_ID=your_test_user_id

๐Ÿ—๏ธ Project Structure

Directory Layout

aimod/
โ”œโ”€โ”€ bot.py                      # Main bot entry point
โ”œโ”€โ”€ cogs/                       # Bot functionality modules
โ”‚   โ”œโ”€โ”€ core_ai_cog.py         # AI moderation core
โ”‚   โ”œโ”€โ”€ config_cog.py          # Configuration management
โ”‚   โ”œโ”€โ”€ human_moderation_cog.py # Manual moderation tools
โ”‚   โ”œโ”€โ”€ logging_cog.py         # Event logging
โ”‚   โ”œโ”€โ”€ statistics.py          # Analytics and stats
โ”‚   โ”œโ”€โ”€ botdetect.py           # Bot detection
โ”‚   โ”œโ”€โ”€ raiddefence.py         # Raid protection
โ”‚   โ”œโ”€โ”€ appeal_cog.py          # Appeal system
โ”‚   โ””โ”€โ”€ aimod_helpers/         # Helper modules
โ”‚       โ”œโ”€โ”€ config_manager.py  # Configuration utilities
โ”‚       โ”œโ”€โ”€ litellm_config.py  # AI client configuration
โ”‚       โ”œโ”€โ”€ media_processor.py # Image/media processing
โ”‚       โ”œโ”€โ”€ system_prompt.py   # AI prompt templates
โ”‚       โ”œโ”€โ”€ utils.py           # General utilities
โ”‚       โ””โ”€โ”€ ui.py              # Discord UI components
โ”œโ”€โ”€ database/                   # Database layer
โ”‚   โ”œโ”€โ”€ __init__.py
โ”‚   โ”œโ”€โ”€ connection.py          # Database connections
โ”‚   โ”œโ”€โ”€ models.py              # Data models
โ”‚   โ”œโ”€โ”€ operations.py          # Database operations
โ”‚   โ”œโ”€โ”€ cache.py               # Redis caching
โ”‚   โ””โ”€โ”€ migrations/            # Database migrations
โ”œโ”€โ”€ dashboard/                  # Web dashboard
โ”‚   โ”œโ”€โ”€ backend/               # FastAPI backend
โ”‚   โ”‚   โ”œโ”€โ”€ main.py           # FastAPI application
โ”‚   โ”‚   โ””โ”€โ”€ app/              # Application modules
โ”‚   โ”‚       โ”œโ”€โ”€ api.py        # API endpoints
โ”‚   โ”‚       โ”œโ”€โ”€ schemas.py    # Pydantic models
โ”‚   โ”‚       โ”œโ”€โ”€ crud.py       # Database operations
โ”‚   โ”‚       โ””โ”€โ”€ db.py         # Database connection
โ”‚   โ””โ”€โ”€ frontend/             # React frontend
โ”‚       โ”œโ”€โ”€ src/              # Source code
โ”‚       โ”œโ”€โ”€ public/           # Static assets
โ”‚       โ””โ”€โ”€ package.json      # Dependencies
โ”œโ”€โ”€ tests/                     # Test suite
โ”‚   โ”œโ”€โ”€ test_core_ai.py       # AI moderation tests
โ”‚   โ”œโ”€โ”€ test_database.py      # Database tests
โ”‚   โ”œโ”€โ”€ test_config.py        # Configuration tests
โ”‚   โ””โ”€โ”€ fixtures/             # Test fixtures
โ”œโ”€โ”€ scripts/                   # Utility scripts
โ”‚   โ”œโ”€โ”€ deploy_backend.sh     # Backend deployment
โ”‚   โ”œโ”€โ”€ deploy_frontend.sh    # Frontend deployment
โ”‚   โ””โ”€โ”€ setup_dev.sh          # Development setup
โ”œโ”€โ”€ docs/                      # Documentation
โ”œโ”€โ”€ requirements.txt           # Production dependencies
โ”œโ”€โ”€ requirements-dev.txt       # Development dependencies
โ”œโ”€โ”€ pyproject.toml            # Project configuration
โ”œโ”€โ”€ .pre-commit-config.yaml   # Pre-commit hooks
โ””โ”€โ”€ README.md                 # Project overview

Code Organization Principles

Separation of Concerns

  • Cogs: Discrete functionality modules
  • Database: Data persistence layer
  • Helpers: Utility functions and classes
  • Dashboard: Web interface (separate from bot)

Dependency Management

  • Core Dependencies: Essential for bot operation
  • Optional Dependencies: Feature-specific (e.g., image processing)
  • Development Dependencies: Testing and code quality tools

๐Ÿ“ Coding Standards

Python Style Guide

Code Formatting

# Use Black formatter with 88-character line length
# Example of properly formatted code

from typing import Dict, List, Optional, Union
import asyncio
import discord
from discord.ext import commands


class ExampleCog(commands.Cog, name="Example"):
    """Example cog demonstrating coding standards."""

    def __init__(self, bot: commands.Bot) -> None:
        self.bot = bot
        self.cache: Dict[int, str] = {}

    async def process_data(
        self, 
        guild_id: int, 
        data: List[Dict[str, Union[str, int]]]
    ) -> Optional[str]:
        """Process data with proper type hints and documentation.
        
        Args:
            guild_id: The Discord guild ID
            data: List of data dictionaries to process
            
        Returns:
            Processed result string or None if processing failed
            
        Raises:
            ValueError: If guild_id is invalid
            ProcessingError: If data processing fails
        """
        if guild_id <= 0:
            raise ValueError("Guild ID must be positive")
        
        try:
            # Process data here
            result = await self._internal_processing(data)
            return result
        except Exception as e:
            logger.error(f"Processing failed for guild {guild_id}: {e}")
            return None

    async def _internal_processing(self, data: List[Dict]) -> str:
        """Internal processing method (private)."""
        # Implementation details
        pass

Naming Conventions

# Variables and functions: snake_case
user_id = 123456789
guild_config = {}

async def get_user_data(user_id: int) -> dict:
    pass

# Classes: PascalCase
class UserManager:
    pass

class AIConfigurationError(Exception):
    pass

# Constants: UPPER_SNAKE_CASE
DEFAULT_TIMEOUT_DURATION = 3600
MAX_MESSAGE_LENGTH = 2000

# Private methods: leading underscore
def _internal_helper(self) -> None:
    pass

# Discord.py conventions
@commands.command(name="example")
async def example_command(self, ctx: commands.Context) -> None:
    pass

@app_commands.command(name="example")
async def example_slash(self, interaction: discord.Interaction) -> None:
    pass

Type Hints

from typing import Dict, List, Optional, Union, Any, Callable, Awaitable
from discord.ext import commands
import discord

# Function signatures
async def get_guild_config(
    guild_id: int, 
    key: str, 
    default: Optional[Any] = None
) -> Any:
    pass

# Class attributes
class ConfigManager:
    cache: Dict[str, Any]
    bot: commands.Bot
    
    def __init__(self, bot: commands.Bot) -> None:
        self.bot = bot
        self.cache = {}

# Complex types
UserData = Dict[str, Union[str, int, bool]]
ConfigCallback = Callable[int, str, Any], Awaitable[None](/openguard-bot/openguard/wiki/int,-str,-Any],-Awaitable[None)

async def process_users(
    users: List[UserData],
    callback: Optional[ConfigCallback] = None
) -> List[str]:
    pass

Documentation Standards

Docstring Format

def complex_function(
    param1: str,
    param2: int,
    param3: Optional[bool] = None
) -> Dict[str, Any]:
    """Brief description of the function.
    
    Longer description explaining the function's purpose,
    behavior, and any important implementation details.
    
    Args:
        param1: Description of the first parameter
        param2: Description of the second parameter
        param3: Optional parameter with default value
        
    Returns:
        Dictionary containing the processed results with keys:
        - 'status': Processing status string
        - 'data': Processed data
        - 'errors': List of any errors encountered
        
    Raises:
        ValueError: If param1 is empty or param2 is negative
        ProcessingError: If data processing fails
        
    Example:
        >>> result = complex_function("test", 42, True)
        >>> print(result['status'])
        'success'
    """
    pass

Code Comments

# Good comments explain WHY, not WHAT
class MessageProcessor:
    def __init__(self):
        # Use deque for efficient FIFO operations on recent decisions
        self.recent_decisions = collections.deque(maxlen=5)
        
        # Cache AI responses to avoid redundant API calls for identical content
        self.response_cache = TTLCache(maxsize=1000, ttl=300)
    
    async def process_message(self, message: discord.Message) -> None:
        # Skip processing if user is whitelisted to avoid false positives
        if await self.is_whitelisted(message.author):
            return
        
        # TODO: Implement rate limiting for AI API calls
        # See issue #123 for implementation details
        result = await self.analyze_with_ai(message.content)

Error Handling

Exception Handling Patterns

import logging
from typing import Optional

logger = logging.getLogger(__name__)

# Specific exception handling
async def get_user_data(user_id: int) -> Optional[dict]:
    """Get user data with proper error handling."""
    try:
        async with get_connection() as conn:
            result = await conn.fetchrow(
                "SELECT * FROM user_data WHERE user_id = $1", 
                user_id
            )
            return dict(result) if result else None
            
    except asyncpg.PostgresError as e:
        logger.error(f"Database error getting user {user_id}: {e}")
        return None
    except Exception as e:
        logger.error(f"Unexpected error getting user {user_id}: {e}")
        return None

# Discord.py error handling
@commands.command()
async def example_command(self, ctx: commands.Context) -> None:
    """Example command with error handling."""
    try:
        # Command implementation
        await ctx.send("Success!")
        
    except discord.Forbidden:
        await ctx.send("โŒ I don't have permission to do that.")
    except discord.HTTPException as e:
        logger.error(f"Discord API error in command: {e}")
        await ctx.send("โŒ Something went wrong. Please try again.")
    except Exception as e:
        logger.error(f"Unexpected error in command: {e}")
        await ctx.send("โŒ An unexpected error occurred.")

# Custom exceptions
class AIModError(Exception):
    """Base exception for AIMod-specific errors."""
    pass

class ConfigurationError(AIModError):
    """Raised when configuration is invalid."""
    pass

class AIProviderError(AIModError):
    """Raised when AI provider API fails."""
    pass

๐Ÿงช Testing

Test Structure

Test Organization

tests/
โ”œโ”€โ”€ conftest.py                # Pytest configuration and fixtures
โ”œโ”€โ”€ test_core_ai.py           # AI moderation tests
โ”œโ”€โ”€ test_database.py          # Database operation tests
โ”œโ”€โ”€ test_config.py            # Configuration tests
โ”œโ”€โ”€ test_security.py          # Security feature tests
โ”œโ”€โ”€ test_dashboard.py         # Dashboard API tests
โ”œโ”€โ”€ integration/              # Integration tests
โ”‚   โ”œโ”€โ”€ test_bot_integration.py
โ”‚   โ””โ”€โ”€ test_api_integration.py
โ”œโ”€โ”€ fixtures/                 # Test data
โ”‚   โ”œโ”€โ”€ sample_messages.json
โ”‚   โ”œโ”€โ”€ test_config.json
โ”‚   โ””โ”€โ”€ mock_responses.json
โ””โ”€โ”€ utils/                    # Test utilities
    โ”œโ”€โ”€ mock_discord.py       # Discord.py mocks
    โ””โ”€โ”€ test_helpers.py       # Test helper functions

Test Configuration (conftest.py)

import pytest
import asyncio
import asyncpg
from unittest.mock import AsyncMock, MagicMock
import discord
from discord.ext import commands

@pytest.fixture(scope="session")
def event_loop():
    """Create an instance of the default event loop for the test session."""
    loop = asyncio.get_event_loop_policy().new_event_loop()
    yield loop
    loop.close()

@pytest.fixture
async def test_db():
    """Create a test database connection."""
    conn = await asyncpg.connect("postgresql://test_user:password@localhost/test_db")
    yield conn
    await conn.close()

@pytest.fixture
def mock_bot():
    """Create a mock Discord bot."""
    bot = AsyncMock(spec=commands.Bot)
    bot.user = MagicMock()
    bot.user.id = 123456789
    return bot

@pytest.fixture
def mock_guild():
    """Create a mock Discord guild."""
    guild = MagicMock(spec=discord.Guild)
    guild.id = 987654321
    guild.name = "Test Guild"
    return guild

@pytest.fixture
def mock_message(mock_guild):
    """Create a mock Discord message."""
    message = MagicMock(spec=discord.Message)
    message.id = 111222333
    message.content = "Test message content"
    message.guild = mock_guild
    message.author = MagicMock()
    message.author.id = 444555666
    message.author.bot = False
    return message

Unit Test Examples

# test_core_ai.py
import pytest
from unittest.mock import AsyncMock, patch
from cogs.core_ai_cog import CoreAICog

class TestCoreAICog:
    """Test suite for Core AI Cog."""
    
    @pytest.fixture
    async def ai_cog(self, mock_bot):
        """Create AI cog instance for testing."""
        cog = CoreAICog(mock_bot)
        await cog.cog_load()
        return cog
    
    async def test_message_analysis(self, ai_cog, mock_message):
        """Test AI message analysis functionality."""
        # Mock AI response
        mock_response = {
            "action": "WARN",
            "rule_violated": "Rule 1",
            "reasoning": "Test violation",
            "confidence": 85
        }
        
        with patch.object(ai_cog, 'analyze_message_with_ai', return_value=mock_response):
            result = await ai_cog.analyze_message_with_ai(
                mock_message, 
                "Test rules", 
                "test-model"
            )
            
        assert result["action"] == "WARN"
        assert result["confidence"] == 85
    
    async def test_global_ban_enforcement(self, ai_cog, mock_message):
        """Test global ban enforcement."""
        # Add user to global ban list
        with patch('cogs.core_ai_cog.GLOBAL_BANS', {mock_message.author.id}):
            # Mock guild ban method
            mock_message.guild.ban = AsyncMock()
            
            await ai_cog.message_listener(mock_message)
            
            # Verify ban was called
            mock_message.guild.ban.assert_called_once()

# test_database.py
import pytest
from database.operations import get_guild_config, set_guild_config

class TestDatabaseOperations:
    """Test suite for database operations."""
    
    async def test_guild_config_operations(self, test_db):
        """Test guild configuration CRUD operations."""
        guild_id = 123456789
        key = "TEST_KEY"
        value = "test_value"
        
        # Test setting configuration
        await set_guild_config(guild_id, key, value)
        
        # Test getting configuration
        result = await get_guild_config(guild_id, key)
        assert result == value
        
        # Test default value
        result = await get_guild_config(guild_id, "NONEXISTENT", "default")
        assert result == "default"

Running Tests

Basic Test Execution

# Run all tests
pytest

# Run with verbose output
pytest -v

# Run specific test file
pytest tests/test_core_ai.py

# Run specific test method
pytest tests/test_core_ai.py::TestCoreAICog::test_message_analysis

# Run with coverage
pytest --cov=. --cov-report=html

# Run only fast tests (skip integration tests)
pytest -m "not integration"

Test Configuration (pyproject.toml)

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
    "--strict-markers",
    "--strict-config",
    "--disable-warnings",
]
markers = [
    "integration: marks tests as integration tests",
    "slow: marks tests as slow",
    "requires_db: marks tests that require database",
    "requires_redis: marks tests that require Redis",
]

๐Ÿ”„ Development Workflow

Git Workflow

Branch Strategy

# Main branches
main          # Production-ready code
develop       # Integration branch for features

# Feature branches
feature/ai-improvements
feature/dashboard-redesign
feature/new-security-features

# Release branches
release/v2.1.0

# Hotfix branches
hotfix/critical-bug-fix

Commit Message Format

type(scope): brief description

Longer description explaining the change in detail.
Include motivation for the change and contrast with
previous behavior.

Fixes #123
Closes #456

Commit Types:

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation changes
  • style: Code style changes (formatting, etc.)
  • refactor: Code refactoring
  • test: Adding or updating tests
  • chore: Maintenance tasks

Examples:

feat(ai): add support for image analysis in moderation

- Implement OCR text extraction from images
- Add content classification for NSFW detection
- Update system prompt to include image context

Closes #234

fix(database): resolve connection pool exhaustion

The connection pool was not properly releasing connections
after failed queries, leading to pool exhaustion under load.

- Add proper connection cleanup in error handlers
- Increase default pool size from 10 to 20
- Add connection pool monitoring

Fixes #456

Pre-commit Hooks

Configuration (.pre-commit-config.yaml)

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.4.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-added-large-files
      - id: check-merge-conflict

  - repo: https://github.com/psf/black
    rev: 23.7.0
    hooks:
      - id: black
        language_version: python3.11

  - repo: https://github.com/pycqa/isort
    rev: 5.12.0
    hooks:
      - id: isort
        args: ["--profile", "black"]

  - repo: https://github.com/pycqa/flake8
    rev: 6.0.0
    hooks:
      - id: flake8
        args: ["--max-line-length=88", "--extend-ignore=E203,W503"]

  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.5.0
    hooks:
      - id: mypy
        additional_dependencies: [types-all]

  - repo: https://github.com/PyCQA/bandit
    rev: 1.7.5
    hooks:
      - id: bandit
        args: ["-r", ".", "-x", "tests/"]

Pull Request Process

Creating a Pull Request

  1. Fork the repository and create a feature branch
  2. Make your changes following coding standards
  3. Add tests for new functionality
  4. Update documentation as needed
  5. Run the test suite and ensure all tests pass
  6. Submit a pull request with detailed description

PR Template

## Description
Brief description of the changes made.

## Type of Change
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Documentation update

## Testing
- [ ] Tests pass locally
- [ ] New tests added for new functionality
- [ ] Manual testing completed

## Checklist
- [ ] Code follows the project's coding standards
- [ ] Self-review of code completed
- [ ] Documentation updated
- [ ] No new warnings introduced

Code Review Guidelines

For Reviewers:

  • Check code quality and adherence to standards
  • Verify test coverage for new features
  • Ensure documentation is updated
  • Test functionality manually if needed
  • Provide constructive feedback

For Contributors:

  • Respond to feedback promptly
  • Make requested changes
  • Keep PR scope focused and manageable
  • Rebase on latest main before merging

๐Ÿš€ Contributing Guidelines

Getting Help

  • Discord Server: Join our community for real-time help
  • GitHub Issues: Report bugs and request features
  • Documentation: Check existing docs before asking questions
  • Code Review: Learn from feedback on your PRs

Contribution Areas

  • Core Bot Features: AI moderation, security, commands
  • Dashboard: Frontend and backend improvements
  • Documentation: Guides, API docs, tutorials
  • Testing: Unit tests, integration tests, performance tests
  • Infrastructure: Deployment, monitoring, optimization

Recognition

Contributors are recognized in:

  • CONTRIBUTORS.md file
  • Release notes for significant contributions
  • Discord server contributor roles
  • Annual contributor highlights

This completes the comprehensive AIMod documentation wiki with detailed coverage of all major systems, components, and development processes.