McDis-RCON’s addon system (mdaddons) provides global functionality that runs independently of individual server processes. Addons are active whenever McDis-RCON is connected to Discord.
What Are MDAddons?
MDAddons are Python scripts that extend McDis-RCON globally :
Global scope : Affect all processes and the entire McDis-RCON instance
Persistent : Run while McDis-RCON is connected to Discord
Discord-focused : React to Discord events only (no console access)
Reloadable : Can be reloaded with !!adreload
Addons are different from plugins. See Plugins Overview for process-specific extensions.
Addons vs Plugins
Feature MDAddons MDPlugins Scope Global (all processes) Single process Lifecycle McDis-RCON start/stop Process start/stop Location McDis/.mdaddons/McDis/<process>/.mdplugins/Console access No Yes (via listener_events) Discord events Yes (direct) Yes (with listener_ prefix) Reload command !!adreload!!mdreload <process>
When to Use Addons
Use addons for:
Cross-server features Functionality that spans multiple servers (global announcements, status tracking)
Discord management Custom Discord commands, role management, moderation tools
Independent services Background tasks, scheduled jobs, API integrations
Global monitoring Track metrics across all servers, centralized logging
Use plugins for:
Server console monitoring
Process-specific logic
Per-server chat bridging
Individual server events
Addon Architecture
File Structure
Addons are stored in the global .mdaddons folder:
McDis/
├── .mdaddons/
│ ├── global_announcer.py ← Single-file addon
│ ├── status_tracker.py ← Single-file addon
│ └── advanced_bot/ ← Folder-based addon
│ ├── __init__.py ← Must contain mdaddon class
│ ├── commands.py
│ ├── utils.py
│ └── config.json
├── server 1/
├── server 2/
└── network 1/
Addon Types
McDis-RCON supports two addon structures:
1. Single-file addons (.py files):
class mdaddon :
def __init__ ( self , client : McDisClient):
self .client = client
2. Folder-based addons (with __init__.py):
advanced_bot/
├── __init__.py ← Contains mdaddon class
├── helpers.py
└── data.json
For folder-based addons, the __init__.py file must contain the mdaddon class.
Addon Class Structure
Every addon must define an mdaddon class:
from mcdis_rcon.classes import McDisClient
import discord
class mdaddon :
def __init__ ( self , client : McDisClient):
"""Called when addon is loaded"""
self .client = client
# Initialization code here
async def on_message ( self , message : discord.Message):
"""React to Discord messages"""
# No 'listener_' prefix needed for addons
pass
def unload ( self ):
"""Called when addon is unloaded (optional)"""
# Cleanup code here
pass
Unlike plugins, addon event methods do NOT use the listener_ prefix. Use on_message, not listener_on_message.
Addon Capabilities
1. Discord Event Listeners
Addons can react to any Discord event:
import discord
from mcdis_rcon.classes import McDisClient
class mdaddon :
def __init__ ( self , client : McDisClient):
self .client = client
async def on_message ( self , message : discord.Message):
"""React to all Discord messages"""
if message.author.bot:
return
if message.channel.id == self .client.panel.id:
await message.add_reaction( '✅' )
2. Access to All Processes
Addons can interact with all configured servers:
class mdaddon :
def __init__ ( self , client : McDisClient):
self .client = client
async def on_message ( self , message : discord.Message):
if message.content == "!stopall" :
# Stop all processes
for process in self .client.processes:
if process.is_running():
process.stop()
3. Panel Interaction
Addons have full access to the Discord panel:
class mdaddon :
def __init__ ( self , client : McDisClient):
self .client = client
self .panel = client.panel # Discord panel channel
async def on_ready ( self ):
"""Called when bot connects to Discord"""
await self .panel.send( "🟢 McDis-RCON addon loaded!" )
4. Background Tasks
Addons can run background tasks:
import asyncio
class mdaddon :
def __init__ ( self , client : McDisClient):
self .client = client
# Start background task
asyncio.create_task( self .periodic_status())
async def periodic_status ( self ):
"""Send status updates every hour"""
while True :
await asyncio.sleep( 3600 ) # 1 hour
running = [p.name for p in self .client.processes if p.is_running()]
await self .client.panel.send( f "Running servers: { ', ' .join(running) } " )
Addon Lifecycle
Loading Process
McDis-RCON connects
When the bot connects to Discord
Addon discovery
McDis-RCON scans McDis/.mdaddons/ for:
.py files with mdaddon class
Folders with __init__.py containing mdaddon class
Addon instantiation
Each addon is imported and instantiated: addon_instance = mod.mdaddon( self ) # 'self' is McDisClient
Registration
Addons are registered in the global addon registry: self .addons[ "global_announcer" ] = addon_instance
Ready
Addons are now active and listening for Discord events
Unloading Process
Unload trigger
!!adreload command is executed
Cleanup hook
If the addon has an unload() method, it’s called: class mdaddon :
def unload ( self ):
# Cleanup: cancel tasks, close connections, etc.
pass
Deregistration
Addons are removed from the registry:
Reload
New addon instances are created from updated files
Available Discord Events
Addons can listen to all Discord.py events without the listener_ prefix:
# Message events
async def on_message ( self , message ):
async def on_message_edit ( self , before , after ):
async def on_message_delete ( self , message ):
# Reaction events
async def on_reaction_add ( self , reaction , user ):
async def on_reaction_remove ( self , reaction , user ):
# Member events
async def on_member_join ( self , member ):
async def on_member_remove ( self , member ):
# Ready event
async def on_ready ( self ):
# And many more...
See discord.py documentation for the complete list.
Client Object Reference
The client object passed to addons is the McDisClient instance:
self .client.panel # Discord panel channel
self .client.config # md_config.yml contents
self .client.processes # List of all processes
self .client.servers # List of server processes
self .client.networks # List of network processes
self .client.prefix # Command prefix (!!)
self .client.cwd # McDis working directory
self .client.addons # Dictionary of loaded addons
# Methods
await self .client.error_report(title, error) # Create error report
await self .client.call_addons(function, args) # Call method on all addons
Example: Simple Addon
Here’s a complete example that reacts to panel messages:
import discord
from mcdis_rcon.classes import McDisClient
class mdaddon :
def __init__ ( self , client : McDisClient):
self .client = client
async def on_message ( self , message : discord.Message):
"""Add reaction to panel messages"""
if message.author.bot:
return # Ignore messages from bots
if message.channel.id == self .client.panel.id:
await message.add_reaction( '✅' )
Place this file in McDis/.mdaddons/reactor.py and reload:
Unlike plugins, this addon reacts to messages even when no servers are running.
Use Cases
Global Commands Custom Discord commands that work across all servers
Status Dashboard Track and display status of all processes in Discord
Scheduled Tasks Automated backups, announcements, restarts
API Integration Connect to external APIs, webhooks, databases
Role Management Automated Discord role assignment based on events
Logging & Analytics Centralized logging, metrics collection
Best Practices
Error Handling
Always handle errors gracefully:
async def on_message ( self , message : discord.Message):
try :
# Your code here
pass
except Exception as e:
await self .client.error_report(
title = "Addon Error" ,
error = str (e)
)
Resource Management
Clean up resources in the unload() method:
import aiohttp
class mdaddon :
def __init__ ( self , client : McDisClient):
self .client = client
self .session = aiohttp.ClientSession()
def unload ( self ):
# Close HTTP session
asyncio.create_task( self .session.close())
Background Task Cancellation
Cancel background tasks on unload:
class mdaddon :
def __init__ ( self , client : McDisClient):
self .client = client
self .task = asyncio.create_task( self .background_work())
async def background_work ( self ):
while True :
await asyncio.sleep( 60 )
# Do work
def unload ( self ):
self .task.cancel()
Async Everywhere
All Discord event handlers must be async:
# Correct
async def on_message ( self , message ): # Async
await message.channel.send( "Hello" )
# Wrong
def on_message ( self , message ): # Sync - won't work
await message.channel.send( "Hello" )
Folder-Based Addons
For complex addons, use the folder structure:
from mcdis_rcon.classes import McDisClient
import discord
from .helpers import format_status
from .commands import handle_command
class mdaddon :
def __init__ ( self , client : McDisClient):
self .client = client
async def on_message ( self , message : discord.Message):
if message.content.startswith( "!" ):
await handle_command( self .client, message)
def format_status ( processes ):
"""Format process status for display"""
running = [p.name for p in processes if p.is_running()]
stopped = [p.name for p in processes if not p.is_running()]
return f "Running: { ', ' .join(running) } \n Stopped: { ', ' .join(stopped) } "
import discord
from .helpers import format_status
async def handle_command ( client , message : discord.Message):
if message.content == "!status" :
status = format_status(client.processes)
await message.channel.send(status)
Folder-based addons allow better code organization for complex functionality.
Advanced Examples
For production-ready addon implementations, check out:
AeternumBot - Advanced event system and commands
EnigmaBot (Coming soon) - Additional automation examples
Next Steps
Creating Addons Learn how to write custom addons with detailed examples
Plugins Overview Understand process-specific plugins for console monitoring