From 769251df0d375d61ddbe3e8a8699150763b47c30 Mon Sep 17 00:00:00 2001 From: Mow910 Date: Sun, 3 May 2026 18:05:50 +0200 Subject: [PATCH] Enhance moderation commands and ProtonDB integration in Discord bot - Introduced a new slash command `/say` for sending messages in specific channels, improving moderation capabilities. - Updated the ProtonDB command to include an alias `/pdb`, streamlining user interaction. - Refactored command descriptions and improved error handling for better user experience. --- discordbot/__init__.py | 6 ++- discordbot/moderation.py | 58 +++++++++++++++++++++++++++-- discordbot/protondb_discord.py | 68 +++++++++++++++------------------- 3 files changed, 89 insertions(+), 43 deletions(-) diff --git a/discordbot/__init__.py b/discordbot/__init__.py index a520c62..91c7670 100644 --- a/discordbot/__init__.py +++ b/discordbot/__init__.py @@ -27,12 +27,13 @@ from discordbot.moderation import ( moderation_ctx_ban_author, moderation_ctx_kick_author, moderation_ctx_timeout_author, + moderation_slash_say, ) from discordbot.welcome import sendWelcomeMessage, sendLeaveMessage, updateInviteCache from discordbot.patreon import checkPatreonPosts from discordbot.youtube import checkYouTubeVideos from discordbot.auto_rooms import on_voice_state_update_auto_rooms, on_raw_reaction_add_auto_rooms, on_message_auto_rooms, cleanup_orphaned_auto_rooms -from discordbot.protondb_discord import protondb_slash_command, protondb_message_context_menu +from discordbot.protondb_discord import protondb_slash_command, pdb_slash_command class DiscordBot(discord.Client): def __init__(self, *, intents: discord.Intents): @@ -49,8 +50,9 @@ class DiscordBot(discord.Client): moderation_ctx_ban_author, moderation_ctx_kick_author, moderation_ctx_timeout_author, + moderation_slash_say, protondb_slash_command, - protondb_message_context_menu, + pdb_slash_command, ): self.tree.add_command(cmd) logging.info("Commandes d'application (transfert, modération, ProtonDB) ajoutées au CommandTree") diff --git a/discordbot/moderation.py b/discordbot/moderation.py index 1dcbe01..fe4a279 100644 --- a/discordbot/moderation.py +++ b/discordbot/moderation.py @@ -910,9 +910,8 @@ async def handle_staff_help_command(message: Message, bot): if ConfigurationHelper().getValue('proton_db_enable_enable'): public_commands.append( "**🎮 ProtonDB**\n" - "• `/protondb` — recherche par nom de jeu\n" - "• Clic droit sur un message → Applications → **Rechercher sur ProtonDB**\n" - "Ex. `/protondb` avec le paramètre *jeu* : Elden Ring" + "• `/protondb` ou `/pdb` — recherche par nom de jeu\n" + "Ex. `/pdb` avec le paramètre *jeu* : Elden Ring" ) from database.models import Commande @@ -998,6 +997,7 @@ async def handle_staff_help_command(message: Message, bot): embed.add_field( name="💬 Autres", value=( + "• `/say` — salon + message (équivalent de `!say`)\n" "• `!say #channel message`\n" " Envoie un message en tant que bot\n" " Ex: `!say #annonces Nouvelle fonctionnalité !`\n\n" @@ -1979,6 +1979,58 @@ async def moderation_slash_timeout( ephemeral=True, ) +@app_commands.command(name="say", description="Envoie un message dans un salon ou un fil (équivalent de !say).") +@app_commands.guild_only() +@app_commands.default_permissions(manage_messages=True) +@app_commands.describe(canal="Salon ou fil cible", message="Contenu du message à envoyer") +async def moderation_slash_say( + interaction: discord.Interaction, + canal: discord.TextChannel | discord.Thread, + message: str, +): + if not _interaction_must_be_staff_member(interaction): + await interaction.response.send_message( + "❌ Vous n'avez pas les permissions nécessaires pour utiliser cette commande.", + ephemeral=True, + ) + return + if interaction.guild and getattr(canal, "guild", None) and canal.guild.id != interaction.guild.id: + await interaction.response.send_message( + "❌ Vous ne pouvez cibler qu'un salon de ce serveur.", + ephemeral=True, + ) + return + text = message.strip() + if not text: + await interaction.response.send_message("❌ Le message ne peut pas être vide.", ephemeral=True) + return + if len(text) > 2000: + await interaction.response.send_message( + "❌ Le message dépasse la limite de 2000 caractères.", + ephemeral=True, + ) + return + try: + await canal.send(text) + except discord.Forbidden: + await interaction.response.send_message( + "❌ Je n'ai pas les permissions pour écrire dans ce canal.", + ephemeral=True, + ) + return + except Exception as e: + logging.error(f"Slash say : {e}") + await interaction.response.send_message( + f"❌ Impossible d'envoyer le message : {e}", + ephemeral=True, + ) + return + try: + mention = canal.mention + except Exception: + mention = f"#{canal.name}" + await interaction.response.send_message(f"✅ Message envoyé dans {mention}.", ephemeral=True) + @app_commands.context_menu(name="Bannir l'auteur") @app_commands.guild_only() @app_commands.default_permissions(ban_members=True) diff --git a/discordbot/protondb_discord.py b/discordbot/protondb_discord.py index 6ecf511..65d182b 100644 --- a/discordbot/protondb_discord.py +++ b/discordbot/protondb_discord.py @@ -61,42 +61,40 @@ def _build_protondb_embed(games: List[Any]) -> discord.Embed: async def _protondb_search_followup(interaction: discord.Interaction, query: str) -> None: + # Une seule réponse éditée (pas de followups en chaîne) : évite les fils « message introuvable » + # et supprime le besoin d’un message séparé « Recherche en cours… ». await interaction.response.defer() - search_msg = None - try: - search_msg = await interaction.followup.send(f"🔍 Recherche en cours pour **{query}**...", wait=True) - except Exception as e: - logging.error(f"ProtonDB : message de recherche : {e}") - try: games = searhProtonDb(query) except Exception as e: logging.error(f"ProtonDB : searhProtonDb : {e}") games = [] - if search_msg: - try: - await search_msg.delete() - except Exception: - pass - if len(games) == 0: - await interaction.followup.send( - f"{interaction.user.mention} Je n'ai pas trouvé de jeux correspondant à **{query}**. Es-tu sûr que le jeu est disponible sur Steam ?", - suppress_embeds=True, - ) + try: + await interaction.edit_original_response( + content=( + f"{interaction.user.mention} Je n'ai pas trouvé de jeux correspondant à **{query}**. " + "Es-tu sûr que le jeu est disponible sur Steam ?" + ), + embed=None, + ) + except Exception as e: + logging.error(f"ProtonDB : edit_original_response (vide) : {e}") return embed = _build_protondb_embed(games) try: - await interaction.followup.send(embed=embed) + await interaction.edit_original_response(content=None, embed=embed) except Exception as e: - logging.error(f"ProtonDB : envoi embed : {e}") + logging.error(f"ProtonDB : edit_original_response (embed) : {e}") + try: + await interaction.followup.send(embed=embed) + except Exception as e2: + logging.error(f"ProtonDB : followup de secours : {e2}") -@app_commands.command(name="protondb", description="Recherche un jeu sur ProtonDB (compatibilité Linux / Steam).") -@app_commands.describe(jeu="Nom du jeu (ex. Elden Ring)") -async def protondb_slash_command(interaction: discord.Interaction, jeu: str): +async def _protondb_slash_impl(interaction: discord.Interaction, jeu: str, exemple: str) -> None: if not ConfigurationHelper().getValue('proton_db_enable_enable'): await interaction.response.send_message( "❌ La commande ProtonDB n'est pas activée.", @@ -106,26 +104,20 @@ async def protondb_slash_command(interaction: discord.Interaction, jeu: str): query = jeu.strip() if not query: await interaction.response.send_message( - "⚠️ Indique le nom d'un jeu.\nExemple : `/protondb jeu:Elden Ring`", + f"⚠️ Indique le nom d'un jeu.\nExemple : `{exemple}`", ephemeral=True, ) return await _protondb_search_followup(interaction, query) -@app_commands.context_menu(name="Rechercher sur ProtonDB") -async def protondb_message_context_menu(interaction: discord.Interaction, message: discord.Message): - if not ConfigurationHelper().getValue('proton_db_enable_enable'): - await interaction.response.send_message( - "❌ La commande ProtonDB n'est pas activée.", - ephemeral=True, - ) - return - query = (message.clean_content or "").strip() - if not query: - await interaction.response.send_message( - "❌ Ce message n'a pas de texte exploitable pour une recherche (ou seulement des pièces jointes / mentions vides).", - ephemeral=True, - ) - return - await _protondb_search_followup(interaction, query) +@app_commands.command(name="protondb", description="Recherche un jeu sur ProtonDB (compatibilité Linux / Steam).") +@app_commands.describe(jeu="Nom du jeu (ex. Elden Ring)") +async def protondb_slash_command(interaction: discord.Interaction, jeu: str): + await _protondb_slash_impl(interaction, jeu, "/protondb jeu:Elden Ring") + + +@app_commands.command(name="pdb", description="Alias de /protondb — recherche un jeu sur ProtonDB.") +@app_commands.describe(jeu="Nom du jeu (ex. Elden Ring)") +async def pdb_slash_command(interaction: discord.Interaction, jeu: str): + await _protondb_slash_impl(interaction, jeu, "/pdb jeu:Elden Ring")