Skip to content

Latest commit

Β 

History

History
867 lines (687 loc) Β· 22.3 KB

File metadata and controls

867 lines (687 loc) Β· 22.3 KB

RatArt Music Bot - Developer Documentation

πŸ“‹ Table of Contents


πŸ—οΈ Architecture Overview

Core Design Principles

  • Modular Architecture: Clean separation of concerns with independent modules
  • Database-Driven: SQLite with async operations for persistence
  • Permission-Based: Hierarchical permission system with cross-server support
  • Event-Driven: Background tasks for monitoring and automation
  • Error-Resilient: Comprehensive error handling and logging

Technology Stack

β”œβ”€β”€ Discord.py 2.3.2+     # Discord API wrapper
β”œβ”€β”€ Wavelink 3.4.1+       # Lavalink client for music
β”œβ”€β”€ aiosqlite 0.19.0+     # Async SQLite operations
β”œβ”€β”€ python-dotenv 1.0.0+  # Environment configuration
β”œβ”€β”€ aiohttp 3.9.0+        # HTTP client for API calls
└── typing-extensions      # Enhanced type hints

High-Level Flow

graph TD
    A[Discord Event] --> B[Command Processing]
    B --> C[Permission Check]
    C --> D[Module Router]
    D --> E[Business Logic]
    E --> F[Database Operation]
    F --> G[Response Generation]
    G --> H[Discord Response]
Loading

πŸ“ Project Structure

RatArt/
β”œβ”€β”€ main.py                 # Application entry point
β”œβ”€β”€ requirements.txt        # Python dependencies
β”œβ”€β”€ .env                   # Environment configuration
β”œβ”€β”€ config/
β”‚   β”œβ”€β”€ __init__.py
β”‚   └── settings.py        # Configuration management
β”œβ”€β”€ database/
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ models.py          # Database models and operations
β”‚   └── ratart.db          # SQLite database (auto-created)
β”œβ”€β”€ modules/
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ base_module.py     # Abstract base module
β”‚   β”œβ”€β”€ admin/
β”‚   β”‚   β”œβ”€β”€ __init__.py
β”‚   β”‚   └── admin_module.py # Admin commands
β”‚   β”œβ”€β”€ core/
β”‚   β”‚   β”œβ”€β”€ __init__.py
β”‚   β”‚   └── help_module.py  # Help system
β”‚   └── music/
β”‚       β”œβ”€β”€ __init__.py
β”‚       └── music_module.py # Music playback
β”œβ”€β”€ utils/
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ decorators.py      # Permission decorators
β”‚   β”œβ”€β”€ embeds.py          # Unified embed system
β”‚   └── logging_config.py  # Logging setup
└── docs/
    β”œβ”€β”€ user-guide.html    # End-user documentation
    └── DEVELOPER_GUIDE.md # This file

πŸ—„οΈ Database Schema

Core Tables

users - Admin Permission System

CREATE TABLE users (
    user_id INTEGER PRIMARY KEY,           -- Discord user ID
    username TEXT,                         -- Display name for reference
    permission_level INTEGER DEFAULT 0,    -- 0=User, 1=Mod, 2=Admin, 3=Owner
    added_by INTEGER,                      -- Who granted permissions
    added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    is_active BOOLEAN DEFAULT 1           -- Soft delete flag
);

guild_settings - Server Configuration

CREATE TABLE guild_settings (
    guild_id INTEGER PRIMARY KEY,         -- Discord guild ID
    guild_name TEXT,                       -- Server name for reference
    prefix TEXT DEFAULT '!',              -- Command prefix
    auto_disconnect_minutes INTEGER DEFAULT 10, -- Auto-disconnect timer
    max_queue_size INTEGER DEFAULT 100,   -- Queue size limit
    volume INTEGER DEFAULT 100,           -- Default volume
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

blacklisted_users - User Blacklist System

CREATE TABLE blacklisted_users (
    user_id INTEGER PRIMARY KEY,          -- Discord user ID
    username TEXT,                         -- Display name
    reason TEXT,                          -- Blacklist reason
    blacklisted_by INTEGER,              -- Admin who blacklisted
    blacklisted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    is_active BOOLEAN DEFAULT 1          -- Soft delete flag
);

tracked_users - Activity Tracking System

CREATE TABLE tracked_users (
    guild_id INTEGER PRIMARY KEY,         -- One tracked user per guild
    user_id INTEGER NOT NULL,             -- Discord user ID being tracked
    username TEXT,                         -- Display name
    tracked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    is_active BOOLEAN DEFAULT 1          -- Active tracking flag
);

Database Operations

User Management

# Add/update user permissions
await db.add_user(user_id: int, username: str, level: PermissionLevel, added_by: int) -> bool

# Get user permission level
await db.get_user_permission(user_id: int) -> PermissionLevel

# Remove user permissions
await db.remove_user(user_id: int) -> bool

# List all admin users
await db.get_admin_users() -> List[Dict]

Blacklist Management

# Blacklist a user
await db.add_blacklisted_user(user_id: int, username: str, reason: str, blacklisted_by: int) -> bool

# Check if user is blacklisted
await db.is_user_blacklisted(user_id: int) -> bool

# Remove from blacklist
await db.remove_blacklisted_user(user_id: int) -> bool

# Get all blacklisted users
await db.get_all_blacklisted_users() -> List[Dict]

Activity Tracking

# Set tracked user for guild
await db.set_tracked_user(guild_id: int, user_id: int, username: str) -> bool

# Get currently tracked user
await db.get_tracked_user(guild_id: int) -> Dict | None

# Remove tracking
await db.remove_tracked_user(guild_id: int) -> bool

πŸ”§ Module System

Base Module Architecture

All modules inherit from BaseModule:

from modules.base_module import BaseModule
from abc import abstractmethod

class BaseModule(commands.Cog):
    def __init__(self, bot):
        self.bot = bot
        self.logger = logging.getLogger(f'modules.{self.__class__.__name__}')

    @property
    @abstractmethod
    def module_name(self) -> str:
        """Human-readable module name"""
        pass

    @property
    @abstractmethod
    def description(self) -> str:
        """Module description"""
        pass

    async def _setup(self):
        """Override for module-specific setup"""
        pass

    async def _teardown(self):
        """Override for module-specific cleanup"""
        pass

Module Registration

Modules are automatically loaded in main.py:

class RatArtBot(commands.Bot):
    async def load_modules(self):
        """Load all bot modules"""
        modules = [
            ('modules.admin.admin_module', 'AdminModule'),
            ('modules.core.help_module', 'HelpModule'),
            ('modules.music.music_module', 'MusicModule'),
        ]

        for module_path, class_name in modules:
            try:
                await self.load_extension(module_path)
                self.logger.info(f"Loaded module: {class_name}")
            except Exception as e:
                self.logger.error(f"Failed to load {class_name}: {e}")

Creating New Modules

  1. Create module directory: modules/your_module/
  2. Create __init__.py: Empty file for Python package
  3. Create module file: your_module_module.py
  4. Implement BaseModule:
from modules.base_module import BaseModule
from discord.ext import commands

class YourModule(BaseModule):
    @property
    def module_name(self) -> str:
        return "Your Module"

    @property
    def description(self) -> str:
        return "Module description"

    async def _setup(self):
        """Module initialization"""
        pass

    @commands.command()
    async def your_command(self, ctx):
        """Your command implementation"""
        pass

async def setup(bot):
    await bot.add_cog(YourModule(bot))
  1. Register in main.py: Add to modules list
  2. Update help system: Add commands to help_module.py

βš™οΈ Configuration Management

Environment Variables (.env)

# Discord Configuration
DISCORD_TOKEN=your_bot_token_here
DISCORD_APPLICATION_ID=your_application_id
DISCORD_PREFIX=!

# Lavalink Configuration
LAVALINK_URI=http://localhost:2333
LAVALINK_PASSWORD=youshallnotpass

# Database Configuration
DATABASE_PATH=database/ratart.db

# Feature Flags
FEATURES_AUTO_DISCONNECT=true
FEATURES_ACTIVITY_TRACKING=true

# Music Configuration
MUSIC_AUTO_DISCONNECT_MINUTES=10
MUSIC_MAX_QUEUE_SIZE=100
MUSIC_DEFAULT_VOLUME=100
MUSIC_TRACKS_PER_PAGE=10

Configuration Loading

The BotConfig class in config/settings.py handles configuration:

class BotConfig:
    def __init__(self):
        load_dotenv()
        self.config = {}
        self._load_config()

    def get(self, key: str, default=None):
        """Get configuration value with dot notation"""
        keys = key.split('.')
        value = self.config

        for k in keys:
            if isinstance(value, dict) and k in value:
                value = value[k]
            else:
                return default
        return value

    @property
    def discord_token(self) -> str:
        return os.getenv('DISCORD_TOKEN')

    @property
    def lavalink_uri(self) -> str:
        return os.getenv('LAVALINK_URI', 'http://localhost:2333')

    # ... additional properties

Adding New Configuration

  1. Add to .env: Define the environment variable
  2. Add property to BotConfig: Create getter method
  3. Update validation: Add to _validate_config()
  4. Document: Update this guide and user docs

πŸš€ Deployment & Setup

Prerequisites

  • Python 3.11+
  • Discord Bot Token
  • Lavalink Server
  • SQLite (included with Python)

Installation Steps

  1. Clone repository:
git clone <repository-url>
cd RatArt
  1. Create virtual environment:
python -m venv venv
# Windows
venv\Scripts\activate
# Linux/Mac
source venv/bin/activate
  1. Install dependencies:
pip install -r requirements.txt
  1. Configure environment:
cp .env.example .env
# Edit .env with your configuration
  1. Set up Lavalink:

    • Download Lavalink.jar
    • Configure application.yml
    • Start Lavalink server
  2. Run the bot:

python main.py

Discord Bot Setup

  1. Create Application: Discord Developer Portal
  2. Create Bot: Get bot token
  3. Set Intents: Enable required intents
    • Message Content Intent
    • Server Members Intent
    • Presence Intent
  4. Invite Bot: Generate invite URL with required permissions

Required Bot Permissions

β”œβ”€β”€ View Channels
β”œβ”€β”€ Send Messages
β”œβ”€β”€ Embed Links
β”œβ”€β”€ Read Message History
β”œβ”€β”€ Use External Emojis
β”œβ”€β”€ Add Reactions
β”œβ”€β”€ Connect (Voice)
β”œβ”€β”€ Speak (Voice)
β”œβ”€β”€ Use Voice Activity
└── Manage Roles (for cross-server role management)

Lavalink Configuration

Create application.yml for Lavalink:

server:
  port: 2333
  address: 0.0.0.0

lavalink:
  plugins:
    - dependency: "com.github.topi314.lavasrc:lavasrc-plugin:4.0.1"
      repository: "https://maven.topi.wtf/releases"

  server:
    password: "youshallnotpass"
    sources:
      youtube: true
      bandcamp: true
      soundcloud: true
      twitch: true
      vimeo: true
      http: true
      local: false

plugins:
  lavasrc:
    providers:
      - "ytsearch:\"%ISRC%\""
      - "ytsearch:%QUERY%"
    sources:
      spotify: true
      applemusic: false
      deezer: false
      yandexmusic: false
    spotify:
      clientId: "your_spotify_client_id"
      clientSecret: "your_spotify_client_secret"

πŸ“ Development Guidelines

Code Style

  • PEP 8 compliance: Use consistent formatting
  • Type hints: Always provide type annotations
  • Docstrings: Document all public methods
  • Error handling: Comprehensive try-catch blocks
  • Logging: Use structured logging for debugging

Example Command Implementation

@commands.command(name='example')
@not_blacklisted  # Always check blacklist
async def example_command(self, ctx: commands.Context, argument: str = None):
    """
    Example command with proper structure

    Args:
        ctx: Discord context
        argument: Optional command argument
    """
    try:
        # Input validation
        if not argument:
            embed = MusicEmbed.error(
                "Missing Argument",
                "Please provide an argument for this command."
            )
            await ctx.send(embed=embed)
            return

        # Permission check (if needed)
        user_permission = await db.get_user_permission(ctx.author.id)
        if user_permission.value < PermissionLevel.MODERATOR.value:
            embed = MusicEmbed.error(
                "Insufficient Permissions",
                "This command requires moderator permissions."
            )
            await ctx.send(embed=embed)
            return

        # Business logic
        result = await self.process_example(argument)

        # Success response
        embed = MusicEmbed.success(
            "Command Success",
            f"Processed: {result}"
        )
        await ctx.send(embed=embed)

    except Exception as e:
        # Error logging and user feedback
        self.logger.error(f"Error in example command: {e}")
        embed = MusicEmbed.error("Error", f"Command failed: {str(e)}")
        await ctx.send(embed=embed)

Database Operations

Always use async database operations:

async def example_database_operation(self, user_id: int):
    """Example of proper database usage"""
    try:
        # Use database connection
        async with self._connection.cursor() as cursor:
            await cursor.execute(
                'SELECT * FROM users WHERE user_id = ?',
                (user_id,)
            )
            result = await cursor.fetchone()

            if result:
                # Process result
                return {'user_id': result[0], 'username': result[1]}
            return None

    except Exception as e:
        self.logger.error(f"Database error: {e}")
        raise

Background Tasks

Use discord.py tasks for periodic operations:

from discord.ext import tasks

@tasks.loop(minutes=5)  # Run every 5 minutes
async def background_task(self):
    """Example background task"""
    try:
        # Your periodic logic here
        self.logger.debug("Background task executed")
    except Exception as e:
        self.logger.error(f"Background task error: {e}")

@background_task.before_loop
async def before_background_task(self):
    """Wait for bot to be ready"""
    await self.bot.wait_until_ready()

# Start in module setup
async def _setup(self):
    self.background_task.start()

# Stop in teardown
async def _teardown(self):
    self.background_task.cancel()

πŸ“š API References

Permission System

PermissionLevel Enum

class PermissionLevel(IntEnum):
    USER = 0        # Default user
    MODERATOR = 1   # Basic moderation
    ADMIN = 2       # Role management, user management
    OWNER = 3       # Full bot control

Decorators

@owner_only          # Requires OWNER level
@admin_only          # Requires ADMIN level or higher
@moderator_only      # Requires MODERATOR level or higher
@not_blacklisted     # Blocks blacklisted users

Embed System

MusicEmbed Methods

# Success message (green)
MusicEmbed.success(title: str, description: str, thumbnail: Optional[str] = None)

# Error message (red)
MusicEmbed.error(title: str, description: str)

# Info message (blue)
MusicEmbed.info(title: str, description: str, thumbnail: Optional[str] = None)

# Warning message (orange)
MusicEmbed.warning(title: str, description: str, thumbnail: Optional[str] = None)

# Music-specific embeds
MusicEmbed.music_playing(title: str, url: str, duration: str, requester: str, thumbnail: Optional[str] = None)
MusicEmbed.queue_added(title: str, url: str, position: int, requester: str, thumbnail: Optional[str] = None)
MusicEmbed.playlist_added(playlist_name: str, track_count: int, requester: str, thumbnail: Optional[str] = None)

Music System

Wavelink Player Access

# Get voice client
if not ctx.voice_client:
    # Handle no connection
    return

vc: wavelink.Player = ctx.voice_client

# Player operations
await vc.play(track)         # Play track
await vc.pause(True/False)   # Pause/unpause
await vc.stop()              # Stop playback
await vc.disconnect()        # Leave voice channel

# Player properties
vc.playing                   # Is playing
vc.paused                   # Is paused
vc.current                  # Current track
vc.queue                    # Queue object
vc.volume                   # Volume level
vc.position                 # Current position

Activity Tracking

User Activity Detection

# Check if user is playing games
for activity in user.activities:
    if activity.type == discord.ActivityType.playing:
        # User is playing a game
        game_name = activity.name
        break

Tracking System Integration

# Set tracked user
await db.set_tracked_user(guild_id, user_id, username)

# Check tracked user
tracked = await db.get_tracked_user(guild_id)
if tracked:
    user_id = tracked['user_id']
    # Process tracking logic

# Remove tracking
await db.remove_tracked_user(guild_id)

πŸ› Troubleshooting

Common Issues

Database Errors

# Issue: Database locked
# Solution: Ensure proper async context usage
async with self._connection.cursor() as cursor:
    # All operations here

# Issue: Table doesn't exist
# Solution: Check _create_tables() method
await self._create_tables()

Lavalink Connection Issues

# Issue: Cannot connect to Lavalink
# Check: Lavalink server running on correct port
# Check: application.yml configuration
# Check: Firewall/network connectivity

# Debug connection
node = wavelink.Node(uri=config.lavalink_uri, password=config.lavalink_password)
try:
    await wavelink.Pool.connect(client=self.bot, nodes=[node])
    self.logger.info("Connected to Lavalink")
except Exception as e:
    self.logger.error(f"Lavalink connection failed: {e}")

Discord Intent Issues

# Issue: Cannot access member data
# Solution: Enable intents in main.py and Discord Developer Portal

intents = discord.Intents.default()
intents.message_content = True    # For command processing
intents.members = True            # For member data access
intents.presences = True          # For activity tracking

Permission Errors

# Issue: Commands not working for admins
# Check: User has proper permissions in database
user_permission = await db.get_user_permission(user_id)
print(f"User permission level: {user_permission}")

# Check: Decorator hierarchy
@admin_only  # This requires ADMIN level or higher
@moderator_only  # This requires MODERATOR level or higher

Debugging Tools

Enable Debug Logging

# In logging_config.py
logging.basicConfig(level=logging.DEBUG)

# Module-specific debugging
self.logger.debug(f"Processing command: {ctx.command}")
self.logger.debug(f"User permission: {user_permission}")

Database Inspection

# Open SQLite database
sqlite3 database/ratart.db

# Check tables
.tables

# View user permissions
SELECT * FROM users;

# Check blacklisted users
SELECT * FROM blacklisted_users WHERE is_active = 1;

# View tracking data
SELECT * FROM tracked_users;

Bot Status Command

Use !debug command to get comprehensive bot status including:

  • Voice client status
  • Queue information
  • Connection details
  • Guild information

πŸ”„ Maintenance Tasks

Regular Maintenance

  1. Database Cleanup: Remove old inactive records
  2. Log Rotation: Archive or clean old log files
  3. Dependency Updates: Keep packages up to date
  4. Lavalink Updates: Update Lavalink server when needed

Database Migrations

When adding new features requiring database changes:

  1. Create migration script:
async def migrate_database():
    """Apply database migrations"""
    async with db._connection.cursor() as cursor:
        # Add new column
        await cursor.execute('ALTER TABLE users ADD COLUMN new_field TEXT')
        # Create new table
        await cursor.execute('CREATE TABLE new_table (...)')
        await db._connection.commit()
  1. Version tracking: Track applied migrations
  2. Backup first: Always backup database before migrations
  3. Test thoroughly: Test migrations on copy first

Performance Optimization

  • Connection pooling: Optimize database connections
  • Caching: Cache frequently accessed data
  • Query optimization: Use indexes and efficient queries
  • Memory management: Monitor memory usage patterns

πŸš€ Future Enhancements

Planned Features

  1. Web Dashboard: Browser-based configuration interface
  2. Playlist Management: Save and load custom playlists
  3. Analytics: Usage statistics and reporting
  4. Multi-Language: Internationalization support
  5. Plugin System: Third-party plugin architecture

Extension Points

The modular architecture makes it easy to add:

  • New command modules
  • Additional music sources
  • Custom permission systems
  • Enhanced activity tracking
  • Integration with other services

πŸ“ž Support & Contributing

Getting Help

  1. Check logs: Most issues show up in console logs
  2. Use debug command: !debug provides system status
  3. Check configuration: Verify .env and Lavalink setup
  4. Database inspection: Use SQLite tools to check data

Contributing Guidelines

  1. Fork repository: Create your own fork
  2. Feature branches: Create branches for new features
  3. Follow conventions: Match existing code style
  4. Test thoroughly: Test all new functionality
  5. Document changes: Update both user and developer docs
  6. Submit PR: Create pull request with clear description

Code Review Checklist

  • Follows PEP 8 style guide
  • Includes type hints
  • Has comprehensive error handling
  • Includes appropriate logging
  • Updates relevant documentation
  • Tested across different scenarios
  • No security vulnerabilities
  • Performance considerations addressed

This documentation is maintained alongside the codebase. Last updated: 2025-09-13