Skip to content

tux.cogs.admin.dev

Classes:

Name Description
Dev

Classes

Dev(bot: Tux)

Bases: Cog

Methods:

Name Description
dev

Dev related commands.

sync_tree

Syncs the app command tree.

clear_tree

Clears the app command tree.

emoji

Emoji management commands.

sync_emojis

Synchronize emojis from the local assets directory to the application.

resync_emoji

Resync a specific emoji from the local assets directory.

delete_all_emojis

Delete all application emojis that match names from the emoji assets directory.

list_emojis

List all emojis currently in the emoji manager's cache.

load_cog

Loads a cog into the bot.

unload_cog

Unloads a cog from the bot.

reload_cog

Reloads a cog in the bot.

stop

Stops the bot. If Tux is running with Docker Compose, this will restart the container.

Source code in tux/cogs/admin/dev.py
Python
def __init__(self, bot: Tux) -> None:
    self.bot = bot
    self.sync_tree.usage = generate_usage(self.sync_tree)
    self.clear_tree.usage = generate_usage(self.clear_tree)
    self.load_cog.usage = generate_usage(self.load_cog)
    self.unload_cog.usage = generate_usage(self.unload_cog)
    self.reload_cog.usage = generate_usage(self.reload_cog)
    self.stop.usage = generate_usage(self.stop)
    self.sync_emojis.usage = generate_usage(self.sync_emojis)
    self.resync_emoji.usage = generate_usage(self.resync_emoji)
    self.delete_all_emojis.usage = generate_usage(self.delete_all_emojis)

Functions

dev(ctx: commands.Context[Tux]) -> None async

Dev related commands.

Parameters:

Name Type Description Default
ctx Context[Tux]

The context object for the command.

required

Raises:

Type Description
MissingPermissions

If the user does not have the required permissions

CommandInvokeError

If the subcommand is not found.

Source code in tux/cogs/admin/dev.py
Python
@commands.hybrid_group(
    name="dev",
    aliases=["d"],
)
@commands.guild_only()
@checks.has_pl(8)
async def dev(self, ctx: commands.Context[Tux]) -> None:
    """
    Dev related commands.

    Parameters
    ----------
    ctx : commands.Context[Tux]
        The context object for the command.

    Raises
    ------
    commands.MissingPermissions
        If the user does not have the required permissions
    commands.CommandInvokeError
        If the subcommand is not found.
    """

    if ctx.invoked_subcommand is None:
        await ctx.send_help("dev")
sync_tree(ctx: commands.Context[Tux], guild: discord.Guild) -> None async

Syncs the app command tree.

Parameters:

Name Type Description Default
ctx Context

The context in which the command is being invoked.

required
guild Guild

The guild to sync application commands to.

required

Raises:

Type Description
MissingRequiredArgument

If a guild is not specified.

Source code in tux/cogs/admin/dev.py
Python
@dev.command(
    name="sync_tree",
    aliases=["st", "sync", "s"],
)
@commands.guild_only()
@checks.has_pl(8)
async def sync_tree(self, ctx: commands.Context[Tux], guild: discord.Guild) -> None:
    """
    Syncs the app command tree.

    Parameters
    ----------
    ctx : commands.Context
        The context in which the command is being invoked.
    guild : discord.Guild
        The guild to sync application commands to.

    Raises
    ------
    commands.MissingRequiredArgument
        If a guild is not specified.
    """

    assert ctx.guild

    # Copy the global tree to the guild
    self.bot.tree.copy_global_to(guild=ctx.guild)
    # Sync the guild tree
    await self.bot.tree.sync(guild=ctx.guild)
    await ctx.send("Application command tree synced.")
clear_tree(ctx: commands.Context[Tux]) -> None async

Clears the app command tree.

Parameters:

Name Type Description Default
ctx Context

The context in which the command is being invoked.

required

Raises:

Type Description
MissingPermissions

If the user does not have the required permissions.

Source code in tux/cogs/admin/dev.py
Python
@dev.command(
    name="clear_tree",
    aliases=["ct", "clear", "c"],
)
@commands.guild_only()
@checks.has_pl(8)
async def clear_tree(self, ctx: commands.Context[Tux]) -> None:
    """
    Clears the app command tree.

    Parameters
    ----------
    ctx : commands.Context
        The context in which the command is being invoked.

    Raises
    ------
    commands.MissingPermissions
        If the user does not have the required permissions.
    """

    assert ctx.guild

    # Clear the slash command tree for the guild.
    self.bot.tree.clear_commands(guild=ctx.guild)
    # Copy the global slash commands to the guild.
    self.bot.tree.copy_global_to(guild=ctx.guild)
    # Sync the slash command tree for the guild.
    await self.bot.tree.sync(guild=ctx.guild)

    await ctx.send("Slash command tree cleared.")
emoji(ctx: commands.Context[Tux]) -> None async

Emoji management commands.

Parameters:

Name Type Description Default
ctx Context[Tux]

The context object for the command.

required
Source code in tux/cogs/admin/dev.py
Python
@dev.group(
    name="emoji",
    aliases=["em"],
)
@commands.guild_only()
@checks.has_pl(8)
async def emoji(self, ctx: commands.Context[Tux]) -> None:
    """
    Emoji management commands.

    Parameters
    ----------
    ctx : commands.Context[Tux]
        The context object for the command.
    """
    if ctx.invoked_subcommand is None:
        await ctx.send_help("dev emoji")
sync_emojis(ctx: commands.Context[Tux]) -> None async

Synchronize emojis from the local assets directory to the application.

This command: 1. Scans the emoji assets directory 2. Uploads any missing emojis to the application 3. Reports which emojis were created and which were skipped

Parameters:

Name Type Description Default
ctx Context[Tux]

The context object for the command.

required
Source code in tux/cogs/admin/dev.py
Python
@emoji.command(
    name="sync",
    aliases=["s"],
)
@commands.guild_only()
@checks.has_pl(8)
async def sync_emojis(self, ctx: commands.Context[Tux]) -> None:
    """
    Synchronize emojis from the local assets directory to the application.

    This command:
    1. Scans the emoji assets directory
    2. Uploads any missing emojis to the application
    3. Reports which emojis were created and which were skipped

    Parameters
    ----------
    ctx : commands.Context[Tux]
        The context object for the command.
    """
    try:
        async with ctx.typing():
            created, skipped = await self.bot.emoji_manager.sync_emojis()

            created_count = len(created)
            skipped_count = len(skipped)

            embed = discord.Embed(
                title="Emoji Synchronization Results",
                color=discord.Color.green() if created_count > 0 else discord.Color.blue(),
            )

            embed.add_field(
                name="Status",
                value=f"✅ Created: **{created_count}**\n⏭️ Skipped/Failed: **{skipped_count}**",
                inline=False,
            )

            if created_count > 0:
                created_names = [e.name for e in created]
                created_str = ", ".join(created_names[:10])
                if len(created_names) > 10:
                    created_str += f" and {len(created_names) - 10} more"
                embed.add_field(
                    name="Created Emojis",
                    value=created_str,
                    inline=False,
                )

        await ctx.send(embed=embed)
    except Exception as e:
        logger.error(f"Error in sync_emojis command: {e}")
        await ctx.send(f"Error synchronizing emojis: {e}")
resync_emoji(ctx: commands.Context[Tux], emoji_name: str) -> None async

Resync a specific emoji from the local assets directory.

This command: 1. Deletes the existing emoji with the given name (if it exists) 2. Creates a new emoji using the local file with the same name 3. Reports the results

Parameters:

Name Type Description Default
ctx Context[Tux]

The context object for the command.

required
emoji_name str

The name of the emoji to resync.

required
Source code in tux/cogs/admin/dev.py
Python
@emoji.command(
    name="resync",
    aliases=["r"],
)
@commands.guild_only()
@checks.has_pl(8)
async def resync_emoji(self, ctx: commands.Context[Tux], emoji_name: str) -> None:
    """
    Resync a specific emoji from the local assets directory.

    This command:
    1. Deletes the existing emoji with the given name (if it exists)
    2. Creates a new emoji using the local file with the same name
    3. Reports the results

    Parameters
    ----------
    ctx : commands.Context[Tux]
        The context object for the command.
    emoji_name : str
        The name of the emoji to resync.
    """
    try:
        async with ctx.typing():
            new_emoji = await self.bot.emoji_manager.resync_emoji(emoji_name)

            if new_emoji:
                embed = discord.Embed(
                    title="Emoji Resync Successful",
                    description=f"Emoji `{emoji_name}` has been resynced successfully!",
                    color=discord.Color.green(),
                )
                embed.add_field(name="Emoji", value=str(new_emoji))
                embed.set_thumbnail(url=new_emoji.url)
            else:
                embed = discord.Embed(
                    title="Emoji Resync Failed",
                    description=f"Failed to resync emoji `{emoji_name}`. Check logs for details.",
                    color=discord.Color.red(),
                )

        await ctx.send(embed=embed)
    except Exception as e:
        logger.error(f"Error in resync_emoji command: {e}")
        await ctx.send(f"Error resyncing emoji: {e}")
delete_all_emojis(ctx: commands.Context[Tux]) -> None async

Delete all application emojis that match names from the emoji assets directory.

This command: 1. Scans the emoji assets directory for valid emoji names 2. Deletes all application emojis with matching names 3. Reports which emojis were deleted and which failed

Parameters:

Name Type Description Default
ctx Context[Tux]

The context object for the command.

required
Source code in tux/cogs/admin/dev.py
Python
@emoji.command(
    name="delete_all",
    aliases=["da", "clear"],
)
@commands.guild_only()
@checks.has_pl(8)
async def delete_all_emojis(self, ctx: commands.Context[Tux]) -> None:
    """
    Delete all application emojis that match names from the emoji assets directory.

    This command:
    1. Scans the emoji assets directory for valid emoji names
    2. Deletes all application emojis with matching names
    3. Reports which emojis were deleted and which failed

    Parameters
    ----------
    ctx : commands.Context[Tux]
        The context object for the command.
    """
    # Ask for confirmation before proceeding
    await ctx.send(
        "⚠️ **WARNING**: This will delete all application emojis matching the emoji assets directory.\n"
        "Are you sure you want to continue? (yes/no)",
    )

    def check(m: discord.Message) -> bool:
        return m.author == ctx.author and m.channel == ctx.channel and m.content.lower() in ["yes", "no"]

    try:
        response = await self.bot.wait_for("message", check=check, timeout=30.0)

        if response.content.lower() != "yes":
            await ctx.send("Operation cancelled.")
            return

        async with ctx.typing():
            deleted, failed = await self.bot.emoji_manager.delete_all_emojis()

            deleted_count = len(deleted)
            failed_count = len(failed)

            embed = discord.Embed(
                title="Emoji Deletion Results",
                color=discord.Color.orange(),
            )

            embed.add_field(
                name="Status",
                value=f"🗑️ Deleted: **{deleted_count}**\n❌ Failed/Not Found: **{failed_count}**",
                inline=False,
            )

            if deleted_count > 0:
                deleted_str = ", ".join(deleted[:10])
                if len(deleted) > 10:
                    deleted_str += f" and {len(deleted) - 10} more"
                embed.add_field(
                    name="Deleted Emojis",
                    value=deleted_str,
                    inline=False,
                )

            if failed_count > 0:
                failed_str = ", ".join(failed[:10])
                if len(failed) > 10:
                    failed_str += f" and {len(failed) - 10} more"
                embed.add_field(
                    name="Failed Emoji Deletions",
                    value=failed_str,
                    inline=False,
                )

        await ctx.send(embed=embed)
    except TimeoutError:
        await ctx.send("Confirmation timed out. Operation cancelled.")
    except Exception as e:
        logger.error(f"Error in delete_all_emojis command: {e}")
        await ctx.send(f"Error deleting emojis: {e}")
list_emojis(ctx: commands.Context[Tux]) -> None async

List all emojis currently in the emoji manager's cache.

This command: 1. Shows all emojis in the bot's emoji cache 2. Displays emoji count and names

Parameters:

Name Type Description Default
ctx Context[Tux]

The context object for the command.

required
Source code in tux/cogs/admin/dev.py
Python
@emoji.command(
    name="list",
    aliases=["ls", "l"],
)
@commands.guild_only()
@checks.has_pl(8)
async def list_emojis(self, ctx: commands.Context[Tux]) -> None:
    """
    List all emojis currently in the emoji manager's cache.

    This command:
    1. Shows all emojis in the bot's emoji cache
    2. Displays emoji count and names

    Parameters
    ----------
    ctx : commands.Context[Tux]
        The context object for the command.
    """
    try:
        # Check if emoji manager is initialized by examining the cache
        if len(self.bot.emoji_manager.cache) == 0:
            await ctx.send("Emoji manager cache is empty. It might not be initialized yet.")
            return

        # Get all emojis and sort them by name
        emojis = sorted(self.bot.emoji_manager.cache.values(), key=lambda e: e.name)
        emoji_count = len(emojis)

        if emoji_count == 0:
            await ctx.send("No emojis found in the emoji manager's cache.")
            return

        # Create a ViewMenu for pagination
        from reactionmenu import ViewButton, ViewMenu

        menu = ViewMenu(
            ctx,
            menu_type=ViewMenu.TypeEmbed,
            all_can_click=True,
            delete_on_timeout=True,
        )

        # Paginate emojis
        emojis_per_page = 10

        for i in range(0, emoji_count, emojis_per_page):
            page_emojis = emojis[i : i + emojis_per_page]

            embed = discord.Embed(
                title="Application Emojis",
                description=f"Found **{emoji_count}** emojis in the emoji manager's cache.",
                color=discord.Color.blue(),
            )

            # Add server info and footer
            if ctx.guild and ctx.guild.icon:
                embed.set_author(name=ctx.guild.name, icon_url=ctx.guild.icon.url)

            embed.set_footer(
                text=f"Page {i // emojis_per_page + 1}/{(emoji_count + emojis_per_page - 1) // emojis_per_page} • Requested by {ctx.author}",
                icon_url=ctx.author.display_avatar.url,
            )

            # Create a table-like format with headers
            table_header = "\n**Emoji**\u2003\u2002**Reference**\n"
            embed.description = f"Found **{emoji_count}** emojis in the emoji manager's cache.{table_header}"

            for emoji in page_emojis:
                # Format with consistent spacing (using unicode spaces for alignment)
                emoji_display = str(emoji)
                emoji_name = emoji.name
                emoji_id = emoji.id

                # Create copyable reference format
                is_animated = getattr(emoji, "animated", False)
                emoji_ref = f"<{'a' if is_animated else ''}:{emoji_name}:{emoji_id}>"

                embed.description += f"{emoji_display}\u2003\u2003\u2003`{emoji_ref}`\n"

            menu.add_page(embed)

        # Add navigation buttons
        menu_buttons = [
            ViewButton(
                style=discord.ButtonStyle.secondary,
                custom_id=ViewButton.ID_GO_TO_FIRST_PAGE,
                emoji="⏮️",
            ),
            ViewButton(
                style=discord.ButtonStyle.secondary,
                custom_id=ViewButton.ID_PREVIOUS_PAGE,
                emoji="⏪",
            ),
            ViewButton(
                style=discord.ButtonStyle.secondary,
                custom_id=ViewButton.ID_NEXT_PAGE,
                emoji="⏩",
            ),
            ViewButton(
                style=discord.ButtonStyle.secondary,
                custom_id=ViewButton.ID_GO_TO_LAST_PAGE,
                emoji="⏭️",
            ),
        ]

        menu.add_buttons(menu_buttons)

        # Start the menu
        await menu.start()

    except Exception as e:
        logger.error(f"Error in list_emojis command: {e}")
        await ctx.send(f"Error listing emojis: {e}")
load_cog(ctx: commands.Context[Tux], *, cog: str) -> None async

Loads a cog into the bot.

Parameters:

Name Type Description Default
ctx Context

The context in which the command is being invoked.

required
cog str

The name of the cog to load.

required

Raises:

Type Description
MissingRequiredArgument

If an cog is not specified.

ExtensionAlreadyLoaded

If the specified cog is already loaded.

ExtensionNotFound

If the specified cog is not found.

ExtensionFailed

If the cog failed to load.

NoEntryPointError

If the specified cog does not have a setup function.

Source code in tux/cogs/admin/dev.py
Python
@dev.command(
    name="load_cog",
    aliases=["lc", "load", "l"],
)
@commands.guild_only()
@checks.has_pl(8)
async def load_cog(self, ctx: commands.Context[Tux], *, cog: str) -> None:
    """
    Loads a cog into the bot.

    Parameters
    ----------
    ctx : commands.Context
        The context in which the command is being invoked.
    cog : str
        The name of the cog to load.

    Raises
    ------
    commands.MissingRequiredArgument
        If an cog is not specified.
    commands.ExtensionAlreadyLoaded
        If the specified cog is already loaded.
    commands.ExtensionNotFound
        If the specified cog is not found.
    commands.ExtensionFailed
        If the cog failed to load.
    commands.NoEntryPointError
        If the specified cog does not have a setup function.
    """

    try:
        await self.bot.load_extension(cog)
    except Exception as error:
        await ctx.send(f"Failed to load cog {cog}: {error}")
        logger.error(f"Failed to load cog {cog}: {error}")
    else:
        await ctx.send(f"Cog {cog} loaded.")
        logger.info(f"Cog {cog} loaded.")
unload_cog(ctx: commands.Context[Tux], *, cog: str) -> None async

Unloads a cog from the bot.

Parameters:

Name Type Description Default
ctx Context

The context in which the command is being invoked.

required
cog str

The name of the cog to unload.

required

Raises:

Type Description
MissingRequiredArgument

If an extension name is not specified.

ExtensionNotLoaded

If the specified extension is not loaded or doesn't exist.

Source code in tux/cogs/admin/dev.py
Python
@dev.command(
    name="unload_cog",
    aliases=["uc", "unload", "u"],
)
@commands.guild_only()
@checks.has_pl(8)
async def unload_cog(self, ctx: commands.Context[Tux], *, cog: str) -> None:
    """
    Unloads a cog from the bot.

    Parameters
    ----------
    ctx : commands.Context
        The context in which the command is being invoked.
    cog : str
        The name of the cog to unload.

    Raises
    ------
    commands.MissingRequiredArgument
        If an extension name is not specified.
    commands.ExtensionNotLoaded
        If the specified extension is not loaded or doesn't exist.
    """

    try:
        await self.bot.unload_extension(cog)
    except Exception as error:
        logger.error(f"Failed to unload cog {cog}: {error}")
        await ctx.send(f"Failed to unload cog {cog}: {error}", ephemeral=True, delete_after=30)
    else:
        logger.info(f"Cog {cog} unloaded.")
        await ctx.send(f"Cog {cog} unloaded.", ephemeral=True, delete_after=30)
reload_cog(ctx: commands.Context[Tux], *, cog: str) -> None async

Reloads a cog in the bot.

Parameters:

Name Type Description Default
ctx Context

The context in which the command is being invoked.

required
cog str

The name of the cog to reload.

required

Raises:

Type Description
MissingRequiredArgument

If an extension is not specified.

ExtensionNotLoaded

If the specified extension is not loaded or doesn't exist.

Source code in tux/cogs/admin/dev.py
Python
@dev.command(
    name="reload_cog",
    aliases=["rc", "reload", "r"],
)
@commands.guild_only()
@checks.has_pl(8)
async def reload_cog(self, ctx: commands.Context[Tux], *, cog: str) -> None:
    """
    Reloads a cog in the bot.

    Parameters
    ----------
    ctx : commands.Context
        The context in which the command is being invoked.
    cog : str
        The name of the cog to reload.

    Raises
    ------
    commands.MissingRequiredArgument
        If an extension is not specified.
    commands.ExtensionNotLoaded
        If the specified extension is not loaded or doesn't exist.
    """

    try:
        await self.bot.unload_extension(cog)
        await self.bot.load_extension(cog)
    except Exception as error:
        await ctx.send(f"Failed to reload cog {cog}: {error}", ephemeral=True, delete_after=30)
        logger.error(f"Failed to reload cog {cog}: {error}")
    else:
        await ctx.send(f"Cog {cog} reloaded.", ephemeral=True, delete_after=30)
        logger.info(f"Cog {cog} reloaded.")
stop(ctx: commands.Context[Tux]) -> None async

Stops the bot. If Tux is running with Docker Compose, this will restart the container.

Parameters:

Name Type Description Default
ctx Context

The context in which the command is being invoked.

required
Source code in tux/cogs/admin/dev.py
Python
@dev.command(
    name="stop",
)
@commands.guild_only()
@checks.has_pl(8)
async def stop(self, ctx: commands.Context[Tux]) -> None:
    """
    Stops the bot. If Tux is running with Docker Compose, this will restart the container.

    Parameters
    ----------
    ctx : commands.Context
        The context in which the command is being invoked.
    """

    await ctx.send(
        "Stopping the bot...\n-# Note: if Tux is running with Docker Compose, this will restart the container.",
    )

    await self.bot.shutdown()

Functions