EveCogs/support/TECHNICAL.md
Omega Launcher 26b1bf9253 feat(support): add modernized contact/dm system replacing Red core commands
Add comprehensive Support cog that automatically disables and replaces Red's
built-in contact and dm commands with enhanced, feature-rich versions.

Features:
- Command override mechanism (disables core commands on load, restores on unload)
- Multi-category support system with dedicated channels
- Custom emojis, greetings, and descriptions per category
- User blocking/unblocking system
- Comprehensive logging to dedicated log channel
- Anonymous mode for staff replies
- Enhanced embed formatting
- Category selection dropdown UI
- Staff control buttons (Close Thread, Block User)
- Reply modal for staff responses
- Background thread cleanup task

Commands:
- [p]contact - Enhanced contact command (DM only)
- [p]dm - Enhanced DM command with logging
- [p]supportset - 15 admin configuration commands

Documentation:
- README.md - Full documentation (7.9K)
- QUICKSTART.md - Quick setup guide (2.5K)
- EXAMPLES.md - Configuration examples (8.1K)
- TECHNICAL.md - Technical docs (8.6K)
- CHANGELOG.md - Version history
- test_override.py - Override mechanism reference

Technical:
- 625 lines of code
- 33 async functions
- Full type hints
- Config-based data storage
- Red 3.5.0+ compatible
- Follows NexusCore patterns

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
2026-06-12 17:45:13 +01:00

8.5 KiB

Support Cog - Technical Documentation

Command Override Mechanism

How It Works

This cog replaces Red-DiscordBot's built-in contact and dm commands by disabling them when the cog loads and re-enabling them when it unloads.

Implementation Details

On Load (cog_load)

async def _disable_core_commands(self):
    """Disable Red's built-in contact and dm commands."""
    core_cog = self.bot.get_cog("Core")
    if not core_cog:
        return

    commands_to_disable = ["contact", "dm"]

    for cmd_name in commands_to_disable:
        cmd = self.bot.get_command(cmd_name)
        if cmd and cmd.cog_name == "Core":
            cmd.enabled = False
            self._disabled_core_commands.append(cmd_name)

What happens:

  1. Gets the Core cog from Red
  2. Finds the contact and dm commands
  3. Verifies they belong to Core (not another cog)
  4. Sets enabled = False on each command
  5. Tracks which commands were disabled

On Unload (cog_unload)

async def _enable_core_commands(self):
    """Re-enable Red's core commands when this cog unloads."""
    for cmd_name in self._disabled_core_commands:
        cmd = self.bot.get_command(cmd_name)
        if cmd:
            cmd.enabled = True
    self._disabled_core_commands.clear()

What happens:

  1. Iterates through disabled commands
  2. Finds each command
  3. Sets enabled = True to restore them
  4. Clears the tracking list

Command Registration

The cog registers its own versions of these commands with the same names:

@commands.command(name="contact")
@commands.dm_only()
async def contact(self, ctx: commands.Context, *, message: str):
    """Enhanced contact command"""
    # ... implementation

@commands.command(name="dm")
@commands.guild_only()
@commands.admin_or_permissions(manage_guild=True)
async def dm_command(self, ctx: commands.Context, user: discord.User, *, message: str):
    """Enhanced dm command"""
    # ... implementation

Why This Approach?

Pros:

  • ✅ Clean override without modifying Red's core
  • ✅ Automatic restoration when unloaded
  • ✅ No conflicts with other cogs
  • ✅ Preserves command names users are familiar with
  • ✅ Works with Red's permission system

Alternatives considered:

  • ❌ Command name collision (Red would error)
  • ❌ Monkey-patching (fragile and unsafe)
  • ❌ Different command names (breaks user familiarity)

Architecture

Data Storage (Config)

Guild Settings

DEFAULT_GUILD = {
    "enabled": False,
    "categories": {},  # name -> {channel_id, description, emoji, roles, greeting}
    "default_category": None,
    "log_channel": None,
    "mod_roles": [],
    "reply_with_embed": True,
    "show_author_info": True,
    "anonymous_mode": False,
    "thread_mode": False,
    "dm_on_reply": True,
    "active_threads": {},  # channel_id -> {user_id, category, started_at, message_count}
    "thread_timeout": 3600,
    "custom_greeting": "",
    "require_category": False,
}

User Settings

DEFAULT_USER = {
    "blocked": False,
    "preference_category": None,
    "last_contact": 0,
}

Message Flow

User → Staff

User (DM) → [p]contact message
    ↓
Check blocked status
    ↓
Select category (if multiple)
    ↓
Get category channel
    ↓
Send to staff channel (embed with user info)
    ↓
Log to log channel (if configured)
    ↓
Send confirmation to user

Staff → User

Staff (Guild) → [p]dm @user message
    ↓
Check permissions
    ↓
Format message (embed/plain, anonymous/named)
    ↓
Send DM to user
    ↓
Log to log channel (if configured)
    ↓
Confirm to staff

UI Components

CategorySelectView

  • Discord UI Select dropdown
  • Shows up to 25 categories
  • Includes emoji, name, and description
  • 180 second timeout
  • Returns selected category

SupportControlView

  • Persistent view (timeout=None)
  • Custom IDs for persistence across restarts
  • Buttons: Close Thread, Block User
  • Staff-only controls

ReplyModal

  • Text input for staff replies
  • Paragraph style (multi-line)
  • 1900 character limit
  • Sends reply on submit

Background Tasks

Thread Cleanup Loop

async def _thread_cleanup_loop(self):
    """Background task to clean up inactive threads."""
    # Runs every 5 minutes
    # Checks thread_timeout setting
    # Removes stale threads from active_threads

Purpose: Clean up inactive support threads based on guild settings

Interval: 5 minutes

Action: Removes threads older than thread_timeout seconds

Security Considerations

Permission Checks

  1. contact command:

    • DM-only (@commands.dm_only())
    • Checks user blocked status
    • Validates guild membership
  2. dm command:

    • Guild-only (@commands.guild_only())
    • Requires Manage Guild permission
    • Validates target user exists
  3. supportset commands:

    • Guild-only
    • Requires Manage Guild permission
    • Validates input data

Data Privacy

  • User IDs stored (not usernames - they can change)
  • Message content not stored (only transmitted)
  • Last contact timestamp for rate limiting
  • Blocked status for abuse prevention

Error Handling

  • discord.Forbidden - User has DMs disabled
  • discord.HTTPException - Network/API errors
  • Missing channels - Graceful degradation
  • Invalid config - Falls back to defaults

Performance

Optimizations

  1. Config Caching:

    • Red's Config system caches values
    • Only writes on changes
  2. Async Operations:

    • All I/O is async
    • Background tasks don't block
  3. View Persistence:

    • Views registered once at load
    • Reused across all interactions
  4. Minimal Database Writes:

    • Bulk operations where possible
    • Async context managers for safety

Scalability

  • Small servers (<100 members): No issues
  • Medium servers (100-1000 members): Optimized for this
  • Large servers (1000+ members): Thread cleanup may need tuning

Recommendations for large servers:

  • Increase thread_timeout to reduce cleanup frequency
  • Use separate log channel (not same as support channel)
  • Consider rate limiting (future feature)

Compatibility

Red-DiscordBot Versions

  • Minimum: 3.5.0
  • Tested on: 3.5.0+
  • Max: Latest

Python Versions

  • Minimum: 3.8
  • Recommended: 3.9+

Discord.py Version

  • Matches Red's discord.py version
  • Uses discord.py 2.x features (UI components)

Debugging

Enable Debug Logging

import logging
logging.getLogger("red.evecogs.support").setLevel(logging.DEBUG)

Common Issues

"Commands not found after loading"

  • Check if Core cog is loaded
  • Verify [p]load support succeeded
  • Try [p]reload support

"Core commands still work"

  • Check cog load order
  • Ensure no errors in console during load
  • Try [p]unload core then [p]load core and [p]load support

"Messages not showing up"

  • Verify channel exists: [p]supportset list
  • Check bot permissions in target channel
  • Ensure category is properly configured

"Can't send DMs"

  • User likely has DMs disabled
  • Check bot can DM users (not always possible)
  • Verify user hasn't blocked the bot

Testing

Manual Testing Checklist

  • Load cog successfully
  • Core commands disabled
  • [p]contact works in DMs
  • Category selection appears (if multiple categories)
  • Messages reach staff channel
  • User info displays correctly
  • [p]dm sends to users
  • Logging works (if configured)
  • Blocking works
  • Unload restores core commands

Test Cases

  1. Single Category:

    • Create one category
    • Contact should go directly there
    • No category selection shown
  2. Multiple Categories:

    • Create 2+ categories
    • Contact should show selection dropdown
    • Messages route to correct channels
  3. Blocked User:

    • Block a user
    • User cannot contact
    • Error message shown
  4. Anonymous Mode:

    • Enable anonymous mode
    • Staff replies hide names
    • Shows "Server Name Staff" instead

Future Enhancements

Potential features for future versions:

  • Rate limiting (X messages per hour)
  • Priority queues
  • Auto-responses
  • Ticket numbers/IDs
  • Thread persistence
  • Multi-server support (user chooses)
  • Attachment support
  • Transcript generation
  • Staff assignments
  • SLA tracking
  • Analytics dashboard

Contributing

When contributing to this cog:

  1. Maintain async/await patterns
  2. Use type hints
  3. Follow existing code style
  4. Test with multiple guilds
  5. Ensure commands still override properly
  6. Update documentation

License

Follows EveCogs repository license.