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
- Interpreter: Configure Python interpreter to use
./venv/bin/python
- Code Style: Import Black formatter configuration
- Database: Configure PostgreSQL connection in Database tool
- 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 featurefix
: Bug fixdocs
: Documentation changesstyle
: Code style changes (formatting, etc.)refactor
: Code refactoringtest
: Adding or updating testschore
: 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
- Fork the repository and create a feature branch
- Make your changes following coding standards
- Add tests for new functionality
- Update documentation as needed
- Run the test suite and ensure all tests pass
- 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.