diff --git a/README.md b/README.md index f7ac8d2..454febf 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Once it's ready, you'll have to link the **GitCord** bot to your own repository, - **Version-Controlled Configuration**: Store your Discord server configuration in a Git repository - **Automatic Sync**: Webhook-based synchronization when configuration changes are pushed to GitHub - **Manual Pull**: Use `/gitcord pull` command to manually sync configuration changes +- **Command Syncing**: Use `/synccommands` (slash) or `!synccommands` (prefix) to manually synchronize slash commands - **Category and Channel Management**: Organize your server structure through YAML configuration files ## Project Status diff --git a/README_MODULAR.md b/README_MODULAR.md index f9cf642..871f7e2 100644 --- a/README_MODULAR.md +++ b/README_MODULAR.md @@ -40,7 +40,7 @@ gitcord/ ### Enhanced Features - **Rich Embeds**: Beautiful Discord embeds for responses - **Structured Logging**: Proper logging with formatting -- **Command Syncing**: Automatic slash command synchronization +- **Command Syncing**: Use the `/synccommands` (slash) or `!synccommands` (prefix) command to synchronize application commands - **Error Recovery**: Graceful error handling and recovery ## 📦 Installation diff --git a/src/gitcord/bot.py b/src/gitcord/bot.py index e629d60..4b61e09 100644 --- a/src/gitcord/bot.py +++ b/src/gitcord/bot.py @@ -38,8 +38,7 @@ async def setup_hook(self) -> None: # Load cogs await self._load_cogs() - # Sync command tree - await self.tree.sync() + # Command syncing is now done manually using the `/synccommands` slash command. logger.info("Bot setup completed") diff --git a/src/gitcord/cogs/general.py b/src/gitcord/cogs/general.py index 6a85beb..1a988ed 100644 --- a/src/gitcord/cogs/general.py +++ b/src/gitcord/cogs/general.py @@ -312,6 +312,75 @@ async def slashping(self, interaction: discord.Interaction) -> None: ) await interaction.response.send_message(embed=embed) + @app_commands.command(name="synccommands", description="Manually sync slash commands") + @app_commands.checks.has_permissions(administrator=True) + async def synccommands(self, interaction: discord.Interaction) -> None: + """Synchronize application commands for this guild.""" + await interaction.response.defer(thinking=True, ephemeral=True) + try: + synced = await self.bot.tree.sync(guild=interaction.guild) + await interaction.followup.send( + f"Synced {len(synced)} command(s).", + ephemeral=True + ) + logger.info( + "Manually synced %d command(s) in guild: %s", + len(synced), + interaction.guild.name if interaction.guild else "N/A" + ) + except discord.DiscordException as e: + logger.error("Failed to sync commands: %s", e) + await interaction.followup.send( + f"Failed to sync commands: {e}", ephemeral=True + ) + + @commands.command(name='synccommands') + @commands.has_permissions(administrator=True) + async def synccommands_prefix(self, ctx: commands.Context) -> None: + """Prefix command to manually sync slash commands.""" + try: + # Send initial response + await ctx.send("🔄 Syncing slash commands...") + + synced = await self.bot.tree.sync(guild=ctx.guild) + + embed = create_embed( + title="✅ Commands Synced", + description=f"Successfully synced **{len(synced)}** command(s) to this guild.", + color=discord.Color.green() + ) + + await ctx.send(embed=embed) + + logger.info( + "Manually synced %d command(s) in guild: %s via prefix command", + len(synced), + ctx.guild.name if ctx.guild else "N/A" + ) + except discord.DiscordException as e: + embed = create_embed( + title="❌ Sync Failed", + description=f"Failed to sync commands: {str(e)}", + color=discord.Color.red() + ) + await ctx.send(embed=embed) + logger.error("Failed to sync commands via prefix command: %s", e) + + @synccommands_prefix.error + async def synccommands_prefix_error(self, ctx: commands.Context, + error: commands.CommandError) -> None: + """Handle errors for the synccommands prefix command.""" + if isinstance(error, commands.MissingPermissions): + embed = create_embed( + title="❌ Permission Denied", + description="You need the 'Administrator' permission to use this command.", + color=discord.Color.red() + ) + await ctx.send(embed=embed) + else: + logger.error("Error in synccommands prefix command: %s", error) + await ctx.send(f"An error occurred: {error}") + @app_commands.command(name="createcategory", description="Create a category and its channels from YAML configuration") @app_commands.describe(yaml_path="Path to the category YAML file (optional)") async def createcategory_slash(self, interaction: discord.Interaction, yaml_path: str = None) -> None: diff --git a/src/gitcord/events.py b/src/gitcord/events.py index 9178fd7..350b546 100644 --- a/src/gitcord/events.py +++ b/src/gitcord/events.py @@ -30,8 +30,8 @@ async def on_ready(self) -> None: # Send restart message to guilds await self._send_restart_messages() - # Sync slash commands - await self._sync_commands() + # Slash commands are no longer automatically synced here. + # Use the `/synccommands` command to sync manually if needed. async def _send_restart_messages(self) -> None: """Send restart messages to available text channels."""