diff --git a/command_context.py b/command_context.py deleted file mode 100644 index bc5ce95..0000000 --- a/command_context.py +++ /dev/null @@ -1,131 +0,0 @@ -""" -Utility module for handling command contexts in Discord.py. - -This module provides functions for setting up commands with different contexts, -allowing them to work in DMs, private channels, guilds, or any combination. -""" - -import discord -from discord import app_commands -from enum import Enum, auto -from typing import Optional, List, Dict, Any, Union, Callable, Awaitable - -class AppCommandContext(Enum): - """ - Enum representing the allowed contexts for application commands. - - This enum defines where commands can be used: - - GUILD: Commands can only be used in guilds (servers) - - GUILD_INSTALL: Commands can only be used in guilds where the app is installed - - PRIVATE: Commands can only be used in private contexts (DMs and private channels) - - ALL: Commands can be used in both guilds and private contexts - """ - GUILD = auto() - GUILD_INSTALL = auto() - PRIVATE = auto() - ALL = auto() - -def set_command_context( - command: app_commands.Command, - context: AppCommandContext = AppCommandContext.ALL -) -> app_commands.Command: - """ - Set the context for a command, determining where it can be used. - - Args: - command: The command to modify - context: The context to set for the command - - Returns: - The modified command - """ - # Set guild_only and dm_permission based on the context - if context == AppCommandContext.GUILD or context == AppCommandContext.GUILD_INSTALL: - # Guild-only commands - command.guild_only = True - # dm_permission is automatically set to False when guild_only is True - elif context == AppCommandContext.PRIVATE: - # Private-only commands (DMs and private channels) - command.guild_only = False - # Set dm_permission to True - if not hasattr(command, "extras"): - command.extras = {} - command.extras["dm_permission"] = True - # Disable in guilds by setting default_member_permissions to "0" - command.extras["default_member_permissions"] = "0" - elif context == AppCommandContext.ALL: - # Commands that work everywhere - command.guild_only = False - # Set dm_permission to True - if not hasattr(command, "extras"): - command.extras = {} - command.extras["dm_permission"] = True - # Allow default permissions in guilds - - return command - -def create_global_command( - bot_tree: app_commands.CommandTree, - name: str, - description: str, - callback: Callable[[discord.Interaction], Awaitable[None]], - context: AppCommandContext = AppCommandContext.ALL, - **kwargs -) -> app_commands.Command: - """ - Create a command that can be used globally with the specified context. - - Args: - bot_tree: The command tree to add the command to - name: The name of the command - description: The description of the command - callback: The function to call when the command is used - context: The context to set for the command - **kwargs: Additional arguments to pass to the command constructor - - Returns: - The created command - """ - # Create the command - command = app_commands.Command( - name=name, - description=description, - callback=callback, - **kwargs - ) - - # Set the context - set_command_context(command, context) - - # Add the command to the tree - bot_tree.add_command(command) - - return command - -def sync_commands( - bot_tree: app_commands.CommandTree, - guild_specific: bool = True, - global_commands: bool = True -) -> Awaitable[List[app_commands.Command]]: - """ - Sync commands with Discord. - - Args: - bot_tree: The command tree to sync - guild_specific: Whether to sync guild-specific commands - global_commands: Whether to sync global commands - - Returns: - A coroutine that resolves to the list of synced commands - """ - if guild_specific and not global_commands: - # Only sync guild-specific commands - # This is handled by command_customization.register_all_guild_commands - return None - elif not guild_specific and global_commands: - # Only sync global commands - return bot_tree.sync() - else: - # Sync both guild-specific and global commands - # This is the default behavior of bot_tree.sync() - return bot_tree.sync() diff --git a/command_customization.py b/command_customization.py index 8d46ce9..a5bfc6d 100644 --- a/command_customization.py +++ b/command_customization.py @@ -203,26 +203,17 @@ async def register_guild_commands(bot, guild: discord.Guild) -> List[app_command return await syncer.sync_guild_commands(guild) -async def register_all_guild_commands(bot, global_command_names=None) -> Dict[int, List[app_commands.Command]]: +async def register_all_guild_commands(bot) -> Dict[int, List[app_commands.Command]]: """ Register commands for all guilds with customizations. Returns a dictionary mapping guild IDs to lists of registered commands. - - Args: - bot: The bot instance - global_command_names: Optional list of command names that are already registered globally - (not used in this implementation, kept for compatibility) """ syncer = GuildCommandSyncer(bot) results = {} for guild in bot.guilds: try: - # Sync all commands to this guild - log.info(f"Syncing commands for guild {guild.id}") results[guild.id] = await syncer.sync_guild_commands(guild) - log.info(f"Synced {len(results[guild.id])} commands for guild {guild.id}") - except Exception as e: log.error(f"Failed to sync commands for guild {guild.id}: {e}") results[guild.id] = [] diff --git a/command_sync_utils.py b/command_sync_utils.py deleted file mode 100644 index cafd420..0000000 --- a/command_sync_utils.py +++ /dev/null @@ -1,156 +0,0 @@ -""" -Utility module for syncing commands with Discord. - -This module provides functions for syncing both global and guild-specific commands, -allowing for more flexibility in command registration and supporting commands in -both guild and private contexts (DMs and private channels). -""" - -import discord -from discord import app_commands -from discord.ext import commands -import logging -import command_customization -from typing import Dict, List, Optional, Union, Any -from command_context import AppCommandContext - -# Configure logging -logging.basicConfig(level=logging.INFO) -log = logging.getLogger(__name__) - -async def sync_global_and_guild_commands( - bot: commands.Bot, - global_only: bool = False, - guild_only: bool = False, - private_command_names: List[str] = None -) -> Dict[str, Any]: - """ - Sync commands with Discord, first to guilds and then globally. - - This function implements the following strategy: - 1. First, sync guild-specific commands to every guild (as before) - 2. Then, sync global commands with dm_permission=True and guild_only=False - - Args: - bot: The bot instance - global_only: If True, only sync global commands - guild_only: If True, only sync guild-specific commands - private_command_names: List of command names that should work in private contexts - - Returns: - A dictionary containing the results of the sync operations - """ - results = { - "global": [], - "guild": {} - } - - try: - # Get all commands from the command tree - all_commands = bot.tree.get_commands() - command_names = [cmd.name for cmd in all_commands] - log.info(f"Found {len(all_commands)} commands in tree: {', '.join(command_names)}") - - # If private_command_names is not provided, use an empty list - if private_command_names is None: - private_command_names = [] - - # First, sync guild-specific commands if requested - if not global_only: - log.info("Syncing guild-specific commands to all guilds...") - # Use the command_customization module to handle guild-specific commands - guild_syncs = await command_customization.register_all_guild_commands(bot) - results["guild"] = guild_syncs - - total_guild_syncs = sum(len(cmds) for cmds in guild_syncs.values()) - log.info(f"Synced commands for {len(guild_syncs)} guilds with a total of {total_guild_syncs} customized commands") - - # Now, prepare global commands with appropriate context settings - if not guild_only and private_command_names: - log.info("Preparing global commands for private contexts...") - - # Clear the command tree for global commands - bot.tree.clear_commands() - - # Add back only the commands that should work in private contexts - from command_context import AppCommandContext, set_command_context - for cmd_name in private_command_names: - # Find the command in the original list - cmd = next((c for c in all_commands if c.name == cmd_name), None) - if cmd: - log.info(f"Setting command {cmd_name} to work in private contexts") - # Set the command to work in private contexts - set_command_context(cmd, AppCommandContext.ALL) - # Add the command back to the tree - bot.tree.add_command(cmd) - else: - log.warning(f"Command {cmd_name} not found in command tree") - - # Sync the global commands - log.info("Syncing global commands for private contexts...") - global_synced = await bot.tree.sync() - results["global"] = global_synced - - # List the synced commands - global_command_names = [cmd.name for cmd in global_synced] - log.info(f"Synced {len(global_synced)} global command(s) for private contexts: {', '.join(global_command_names)}") - - # Restore the original command tree - bot.tree.clear_commands() - for cmd in all_commands: - bot.tree.add_command(cmd) - - except Exception as e: - log.error(f"Failed to sync commands: {e}") - import traceback - traceback.print_exc() - - return results - -def register_global_command( - bot: commands.Bot, - name: str, - description: str, - callback: Any, - context: AppCommandContext = AppCommandContext.ALL, - **kwargs -) -> app_commands.Command: - """ - Register a global command with the specified context. - - Args: - bot: The bot instance - name: The name of the command - description: The description of the command - callback: The function to call when the command is used - context: The context to set for the command - **kwargs: Additional arguments to pass to the command constructor - - Returns: - The created command - """ - from command_context import create_global_command - - return create_global_command( - bot.tree, - name=name, - description=description, - callback=callback, - context=context, - **kwargs - ) - -def set_command_contexts(bot: commands.Bot, contexts: Dict[str, AppCommandContext]) -> None: - """ - Set the contexts for multiple commands. - - Args: - bot: The bot instance - contexts: A dictionary mapping command names to contexts - """ - from command_context import set_command_context - - for cmd in bot.tree.get_commands(): - if cmd.name in contexts: - set_command_context(cmd, contexts[cmd.name]) - log.info(f"Set context for command '{cmd.name}' to {contexts[cmd.name]}") diff --git a/example_global_commands.py b/example_global_commands.py deleted file mode 100644 index 1e43384..0000000 --- a/example_global_commands.py +++ /dev/null @@ -1,97 +0,0 @@ -""" -Example module demonstrating how to create global commands that work in DMs and private channels. - -This module shows how to use the AppCommandContext system to create commands -that can be used in both guild and private contexts. -""" - -import discord -from discord import app_commands -from discord.ext import commands -from command_context import AppCommandContext, create_global_command -from typing import Optional - -class GlobalCommandsCog(commands.Cog): - """A cog that demonstrates global commands that work in DMs and private channels.""" - - def __init__(self, bot: commands.Bot): - self.bot = bot - print("GlobalCommandsCog initialized!") - - # Register global commands - self.register_global_commands() - - def register_global_commands(self): - """Register global commands that work in DMs and private channels.""" - - # Create a help command that works in DMs and private channels - create_global_command( - self.bot.tree, - name="dmhelp", - description="Get help with bot commands (works in DMs)", - callback=self.dm_help_callback, - context=AppCommandContext.ALL - ) - - # Create a ping command that works in DMs and private channels - create_global_command( - self.bot.tree, - name="dmping", - description="Check if the bot is responsive (works in DMs)", - callback=self.dm_ping_callback, - context=AppCommandContext.ALL - ) - - # Create a command that only works in private contexts - create_global_command( - self.bot.tree, - name="privateonly", - description="This command only works in DMs and private channels", - callback=self.private_only_callback, - context=AppCommandContext.PRIVATE - ) - - print("GlobalCommandsCog: Registered global commands") - - async def dm_help_callback(self, interaction: discord.Interaction): - """Callback for the /dmhelp command.""" - is_dm = isinstance(interaction.channel, discord.DMChannel) - is_private = not interaction.guild - - help_text = ( - "# Bot Help\n\n" - "This help command works in both DMs and servers!\n\n" - f"**Current context:** {'DM' if is_dm else 'Private Channel' if is_private else 'Server'}\n\n" - "## Available Commands\n" - "- `/dmhelp` - This help command\n" - "- `/dmping` - Check if the bot is responsive\n" - "- `/privateonly` - Only works in DMs and private channels\n" - ) - - await interaction.response.send_message(help_text, ephemeral=True) - - async def dm_ping_callback(self, interaction: discord.Interaction): - """Callback for the /dmping command.""" - is_dm = isinstance(interaction.channel, discord.DMChannel) - is_private = not interaction.guild - - await interaction.response.send_message( - f"🏓 Pong! Bot is responsive.\n" - f"**Current context:** {'DM' if is_dm else 'Private Channel' if is_private else 'Server'}", - ephemeral=True - ) - - async def private_only_callback(self, interaction: discord.Interaction): - """Callback for the /privateonly command.""" - is_dm = isinstance(interaction.channel, discord.DMChannel) - - await interaction.response.send_message( - f"This command only works in private contexts.\n" - f"**Current context:** {'DM' if is_dm else 'Private Channel'}", - ephemeral=True - ) - -async def setup(bot: commands.Bot): - """Add the GlobalCommandsCog to the bot.""" - await bot.add_cog(GlobalCommandsCog(bot)) - print("GlobalCommandsCog setup complete.") diff --git a/gurt_bot.py b/gurt_bot.py index 638f594..8878ef5 100644 --- a/gurt_bot.py +++ b/gurt_bot.py @@ -28,42 +28,8 @@ async def on_ready(): # Sync commands try: print("Starting command sync process...") - - # Import our command sync utilities - try: - import command_sync_utils - - # Define commands that should work in private channels and DMs - private_commands = [ - # Add command names that should work in private contexts here - "gurtmood", # Allow mood command in DMs - ] - - # First sync guild-specific commands to all guilds, then sync global commands for private contexts - print("First syncing commands to all guilds, then syncing global commands for private contexts...") - sync_results = await command_sync_utils.sync_global_and_guild_commands( - bot, - private_command_names=private_commands - ) - - # Report results - global_syncs = sync_results["global"] - guild_syncs = sync_results["guild"] - - # Report guild syncs - if guild_syncs: - total_guild_syncs = sum(len(cmds) for cmds in guild_syncs.values()) - print(f"Synced commands for {len(guild_syncs)} guilds with a total of {total_guild_syncs} commands") - - # Report global syncs - print(f"Synced {len(global_syncs)} global command(s) for private contexts") - - except ImportError: - # Fall back to regular sync if the utility modules aren't available - print("Command sync utilities not available, falling back to regular sync...") - synced = await bot.tree.sync() - print(f"Synced {len(synced)} command(s)") - + synced = await bot.tree.sync() + print(f"Synced {len(synced)} command(s)") except Exception as e: print(f"Failed to sync commands: {e}") import traceback diff --git a/main.py b/main.py index 9e5b9cf..357b698 100644 --- a/main.py +++ b/main.py @@ -230,57 +230,19 @@ async def on_ready(): commands_before = [cmd.name for cmd in bot.tree.get_commands()] print(f"Commands before sync: {commands_before}") - # Import our new command sync utilities - import command_sync_utils + # Skip global command sync to avoid duplication + print("Skipping global command sync to avoid command duplication...") - # First sync guild-specific commands to all guilds, then sync global commands for private contexts - print("First syncing commands to all guilds, then syncing global commands for private contexts...") - sync_results = await command_sync_utils.sync_global_and_guild_commands( - bot, - private_command_names=private_commands - ) + # Only sync guild-specific commands with customizations + print("Syncing guild-specific command customizations...") + guild_syncs = await command_customization.register_all_guild_commands(bot) - # Report results - global_syncs = sync_results["global"] - guild_syncs = sync_results["guild"] - - # Report guild syncs - if guild_syncs: - total_guild_syncs = sum(len(cmds) for cmds in guild_syncs.values()) - print(f"Synced commands for {len(guild_syncs)} guilds with a total of {total_guild_syncs} commands") - - # Report global syncs - print(f"Synced {len(global_syncs)} global command(s) for private contexts") + total_guild_syncs = sum(len(cmds) for cmds in guild_syncs.values()) + print(f"Synced commands for {len(guild_syncs)} guilds with a total of {total_guild_syncs} customized commands") # List commands after sync commands_after = [cmd.name for cmd in bot.tree.get_commands()] - print(f"Commands registered in command tree: {', '.join(commands_after)}") - - # Define commands that should work in private channels and DMs - private_commands = [ - # Add command names that should work in private contexts here - "dmhelp", - "dmping", - "privateonly", - ] - - # Load the example global commands cog - try: - await bot.load_extension("example_global_commands") - print("Loaded example_global_commands cog") - except Exception as e: - print(f"Failed to load example_global_commands cog: {e}") - - # Sync both global and guild-specific commands, avoiding duplicates - print("Re-syncing commands with private context settings...") - sync_results = await command_sync_utils.sync_global_and_guild_commands( - bot, - private_command_names=private_commands - ) - - # Report results - global_syncs = sync_results["global"] - print(f"Re-synced {len(global_syncs)} global command(s) with private context settings") + print(f"Commands registered in command tree: {commands_after}") except Exception as e: print(f"Failed to sync commands: {e}") diff --git a/wheatley_bot.py b/wheatley_bot.py index a019cfa..5e1b526 100644 --- a/wheatley_bot.py +++ b/wheatley_bot.py @@ -28,42 +28,8 @@ async def on_ready(): # Sync commands try: print("Starting command sync process...") - - # Import our command sync utilities - try: - import command_sync_utils - - # Define commands that should work in private channels and DMs - private_commands = [ - # Add command names that should work in private contexts here - "wheatleymemory", # Allow memory command in DMs - ] - - # First sync guild-specific commands to all guilds, then sync global commands for private contexts - print("First syncing commands to all guilds, then syncing global commands for private contexts...") - sync_results = await command_sync_utils.sync_global_and_guild_commands( - bot, - private_command_names=private_commands - ) - - # Report results - global_syncs = sync_results["global"] - guild_syncs = sync_results["guild"] - - # Report guild syncs - if guild_syncs: - total_guild_syncs = sum(len(cmds) for cmds in guild_syncs.values()) - print(f"Synced commands for {len(guild_syncs)} guilds with a total of {total_guild_syncs} commands") - - # Report global syncs - print(f"Synced {len(global_syncs)} global command(s) for private contexts") - - except ImportError: - # Fall back to regular sync if the utility modules aren't available - print("Command sync utilities not available, falling back to regular sync...") - synced = await bot.tree.sync() - print(f"Synced {len(synced)} command(s)") - + synced = await bot.tree.sync() + print(f"Synced {len(synced)} command(s)") except Exception as e: print(f"Failed to sync commands: {e}") import traceback