The ZygnalBot Extensions System allows you to create and load custom cogs (modules) without restarting your bot. This powerful feature enables you to:
First, create a folder named "Extensions" in your bot's root directory. This is where all your custom cogs will be stored.
ZygnalBot/
├── data/
├── cogs/
├── Extensions/ <-- Create this folder
├── Main_bot.py
└── other files...
The bot will automatically create this folder if it doesn't exist when you try to load an extension.
Each extension is a Python file with a specific structure. Here's a template for a basic extension:
import discord
from discord.ext import commands
import asyncio # Required for handling async add_cog
class MyCustomCog(commands.Cog):
def __init__(self, bot):
self.bot = bot
@commands.command(name="hello")
async def hello_command(self, ctx):
"""A simple hello command"""
await ctx.send("Hello from my custom extension!")
def setup(bot):
# Create the cog instance
cog = MyCustomCog(bot)
# Get the event loop and schedule the async add_cog
loop = asyncio.get_event_loop()
loop.create_task(bot.add_cog(cog))
# Return the cog for potential use by the loader
return cog
setup(bot)
function that adds your cog to the bot. In ZygnalBot, the add_cog
method is asynchronous, but the extension system expects a synchronous setup
function. The pattern above handles this by scheduling the async task in the event loop.
Save your extension file in the Extensions folder with a descriptive name and the .py extension.
For example: Extensions/greetings.py
Loads an extension from the Extensions folder. You can specify the filename with or without the .py extension.
Example: !load greetings
or !load greetings.py
To load your extension, use the !load
command followed by the filename (without the path):
!load greetings
The bot will search for greetings.py
in the Extensions folder and attempt to load it.
If successful, you'll see a confirmation message with details about the loaded cog and any new commands that are now available.
ZygnalBot provides several commands to manage your extensions:
When you make changes to an extension file, you can apply those changes without restarting the bot:
!reload CogName
to reload the cog with your changesFor example, if you updated your MyCustomCog class in greetings.py:
!reload MyCustomCog
!listcogs
first.
Here's an example of a utility extension with multiple commands:
import discord
from discord.ext import commands
import random
import datetime
import asyncio # Required for handling async add_cog
class UtilityCommands(commands.Cog):
def __init__(self, bot):
self.bot = bot
@commands.command(name="roll")
async def roll_dice(self, ctx, dice: str = "1d6"):
"""Roll dice in NdN format"""
try:
rolls, limit = map(int, dice.split('d'))
if rolls > 25:
await ctx.send("I can't roll more than 25 dice at once!")
return
results = [random.randint(1, limit) for _ in range(rolls)]
embed = discord.Embed(
title="🎲 Dice Roll",
description=f"Rolling {dice}...",
color=discord.Color.blue()
)
embed.add_field(name="Results", value=str(results))
embed.add_field(name="Sum", value=str(sum(results)))
await ctx.send(embed=embed)
except Exception as e:
await ctx.send(f"Error: {str(e)}\nFormat should be NdN (e.g., 3d6)")
@commands.command(name="time")
async def current_time(self, ctx):
"""Shows the current time in different formats"""
now = datetime.datetime.now()
embed = discord.Embed(
title="⏰ Current Time",
color=discord.Color.gold()
)
embed.add_field(
name="Standard Format",
value=now.strftime("%Y-%m-%d %H:%M:%S"),
inline=False
)
embed.add_field(
name="12-Hour Format",
value=now.strftime("%I:%M:%S %p"),
inline=True
)
embed.add_field(
name="Date Only",
value=now.strftime("%B %d, %Y"),
inline=True
)
await ctx.send(embed=embed)
def setup(bot):
# Create the cog instance
cog = UtilityCommands(bot)
# Get the event loop and schedule the async add_cog
loop = asyncio.get_event_loop()
loop.create_task(bot.add_cog(cog))
# Return the cog for potential use by the loader
return cog
Save this as Extensions/utility.py
and load it with !load utility
.
Here's an example of a fun commands extension:
import discord
from discord.ext import commands
import random
import asyncio
class FunCommands(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.magic_responses = [
"It is certain.",
"It is decidedly so.",
"Without a doubt.",
"Yes, definitely.",
"You may rely on it.",
"As I see it, yes.",
"Most likely.",
"Outlook good.",
"Yes.",
"Signs point to yes.",
"Reply hazy, try again.",
"Ask again later.",
"Better not tell you now.",
"Cannot predict now.",
"Concentrate and ask again.",
"Don't count on it.",
"My reply is no.",
"My sources say no.",
"Outlook not so good.",
"Very doubtful."
]
@commands.command(name="8ball")
async def magic_8ball(self, ctx, *, question: str):
"""Ask the magic 8-ball a question"""
if not question.endswith('?'):
question += '?'
embed = discord.Embed(
title="🔮 Magic 8-Ball",
description=f"**Question:** {question}",
color=discord.Color.purple()
)
# Add suspense with typing indicator
async with ctx.typing():
await asyncio.sleep(1.5)
embed.add_field(
name="Answer",
value=random.choice(self.magic_responses),
inline=False
)
await ctx.send(embed=embed)
@commands.command(name="choose")
async def choose(self, ctx, *, options: str):
"""Choose between multiple options (separated by commas)"""
option_list = [opt.strip() for opt in options.split(',')]
if len(option_list) < 2:
await ctx.send("Please provide at least two options separated by commas!")
return
embed = discord.Embed(
title="🤔 Making a Choice",
description=f"Choosing from {len(option_list)} options...",
color=discord.Color.green()
)
# Add suspense with typing indicator
async with ctx.typing():
await asyncio.sleep(1)
chosen = random.choice(option_list)
embed.add_field(name="I choose", value=f"**{chosen}**", inline=False)
await ctx.send(embed=embed)
def setup(bot):
# Create the cog instance
cog = FunCommands(bot)
# Get the event loop and schedule the async add_cog
loop = asyncio.get_event_loop()
loop.create_task(bot.add_cog(cog))
# Return the cog for potential use by the loader
return cog
Save this as Extensions/fun.py
and load it with !load fun
.
If you get an error about a missing setup function, make sure your extension includes:
def setup(bot):
# Create the cog instance
cog = YourCogName(bot)
# Get the event loop and schedule the async add_cog
loop = asyncio.get_event_loop()
loop.create_task(bot.add_cog(cog))
# Return the cog for potential use by the loader
return cog
If you see a warning like RuntimeWarning: coroutine 'BotBase.add_cog' was never awaited
, it means you're not properly handling the asynchronous nature of the add_cog
method. Make sure you're using the pattern shown above in your setup
function.
If you're getting import errors, check that:
asyncio
module for handling async tasksIf the bot can't find your extension:
!load
command are designed to be helpful. They'll often tell you exactly what's wrong and how to fix it.
If your extension loads but doesn't work correctly:
Example of adding error handling to a command:
@commands.command(name="debug_example")
async def debug_example(self, ctx, *, text: str = None):
"""Example command with error handling"""
try:
if text is None:
await ctx.send("You need to provide some text!")
return
# Your command logic here
result = text.upper() # Just an example operation
await ctx.send(f"Result: {result}")
except Exception as e:
# Log the error to console
print(f"Error in debug_example command: {e}")
# Send error message to Discord
await ctx.send(f"An error occurred: {type(e).__name__}: {str(e)}")
# You could also send more detailed error info to a specific channel
error_channel = self.bot.get_channel(YOUR_ERROR_CHANNEL_ID)
if error_channel:
await error_channel.send(f"Error in {ctx.command} used by {ctx.author}:\n```py\n{type(e).__name__}: {str(e)}\n```")
Here's a simple test extension you can use to verify that your extension system is working correctly:
import discord
from discord.ext import commands
import asyncio
class TestExtension(commands.Cog):
def __init__(self, bot):
self.bot = bot
print("TestExtension loaded successfully!")
@commands.Cog.listener()
async def on_ready(self):
print("TestExtension is ready!")
@commands.command(name="testinfo")
async def test_info(self, ctx):
"""Shows information about this test extension"""
embed = discord.Embed(
title="✅ Test Extension Information",
description="This is a simple test extension for ZygnalBot.",
color=discord.Color.green()
)
embed.add_field(
name="Purpose",
value="This extension demonstrates how to create a custom cog that can be loaded without restarting the bot.",
inline=False
)
embed.add_field(
name="Commands",
value="• `!testinfo` - Shows this information message",
inline=False
)
embed.add_field(
name="Extension Management",
value=(
"• `!listcogs` - Verify this extension is loaded\n"
"• `!reload TestExtension` - Test reloading this extension\n"
"• `!unload TestExtension` - Test unloading this extension"
),
inline=False
)
embed.set_footer(text="ZygnalBot Made By TheHolyoneZ")
await ctx.send(embed=embed)
def setup(bot):
# Create a new cog instance
cog = TestExtension(bot)
# Get the event loop
loop = asyncio.get_event_loop()
# Schedule the add_cog coroutine to run
loop.create_task(bot.add_cog(cog))
# Return the cog for potential use by the loader
return cog
Save this as Extensions/Test_Extension.py
and load it with !load Test_Extension
.
The ZygnalBot Extensions System provides a powerful way to extend your bot's functionality without restarting. By creating modular, well-designed extensions, you can:
Remember that extensions are loaded from the Extensions folder and must include a proper setup function. Use the management commands to load, unload, and reload your extensions as needed.