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

370 lines
8.5 KiB
Markdown

# 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`)
```python
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`)
```python
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:
```python
@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
```python
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
```python
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
```python
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
```python
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.