From f901db1a7db307da8f758aca181d3be7a21ace59 Mon Sep 17 00:00:00 2001 From: Mow910 Date: Sun, 3 May 2026 14:48:16 +0200 Subject: [PATCH] Implement ProtonDB command integration in Discord bot - Replaced the deprecated ProtonDB command handling with a new slash command and context menu for improved user experience. - Updated moderation command descriptions to reflect the new ProtonDB functionalities. - Introduced a new module for ProtonDB interactions, enhancing the bot's capability to search and display game compatibility information. --- discordbot/__init__.py | 103 +------------------------- discordbot/moderation.py | 6 +- discordbot/protondb_discord.py | 131 +++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+), 102 deletions(-) create mode 100644 discordbot/protondb_discord.py diff --git a/discordbot/__init__.py b/discordbot/__init__.py index 0affe89..a520c62 100644 --- a/discordbot/__init__.py +++ b/discordbot/__init__.py @@ -32,7 +32,7 @@ from discordbot.welcome import sendWelcomeMessage, sendLeaveMessage, updateInvit 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 protondb import searhProtonDb +from discordbot.protondb_discord import protondb_slash_command, protondb_message_context_menu class DiscordBot(discord.Client): def __init__(self, *, intents: discord.Intents): @@ -49,9 +49,11 @@ class DiscordBot(discord.Client): moderation_ctx_ban_author, moderation_ctx_kick_author, moderation_ctx_timeout_author, + protondb_slash_command, + protondb_message_context_menu, ): self.tree.add_command(cmd) - logging.info("Commandes d'application (transfert, ban, kick, timeout) ajoutées au CommandTree") + logging.info("Commandes d'application (transfert, modération, ProtonDB) ajoutées au CommandTree") async def on_ready(self): logging.info(f'Connecté en tant que {self.user} (ID: {self.user.id})') @@ -230,103 +232,6 @@ async def on_message(message: Message): except Exception as e: logging.error(f'Échec de l\'exécution de la commande Discord : {e}') - if (ConfigurationHelper().getValue('proton_db_enable_enable') and (message.content.startswith('!protondb') or message.content.startswith('!pdb'))): - if (message.content.find('<@')>0) : - mention = message.content[message.content.find('<@'):] - else : - mention = message.author.mention - name = message.content - if name.startswith('!protondb'): - name = name.replace('!protondb', '', 1) - elif name.startswith('!pdb'): - name = name.replace('!pdb', '', 1) - name = name.replace(f'{mention}', '').strip(); - - if not name or len(name) == 0: - try: - await message.delete() - delete_time = ConfigurationHelper().getIntValue('proton_db_delete_time') or 10 - help_msg = await message.channel.send( - f"{mention} ⚠️ Utilisation: `!pdb nom du jeu` ou `!protondb nom du jeu`\n" - f"Exemple: `!pdb Elden Ring`", - suppress_embeds=True - ) - await asyncio.sleep(delete_time) - await help_msg.delete() - except Exception as e: - logging.error(f"Échec de la gestion du message d'aide ProtonDB : {e}") - return - - try: - searching_msg = await message.channel.send(f"🔍 Recherche en cours pour **{name}**...") - games = searhProtonDb(name) - await searching_msg.delete() - except: - games = searhProtonDb(name) - - if (len(games)==0) : - msg = f'{mention} Je n\'ai pas trouvé de jeux correspondant à **{name}**. Es-tu sûr que le jeu est disponible sur Steam ?' - try: - await message.channel.send(msg, suppress_embeds=True) - except Exception as e: - logging.error(f"Échec de l'envoi du message ProtonDB : {e}") - return - total_games = len(games) - tier_colors = {'platinum': '🟣', 'gold': '🟡', 'silver': '⚪', 'bronze': '🟤', 'borked': '🔴'} - content = "" - max_games = 15 - - for count, game in enumerate(games[:max_games]): - g_name = str(game.get('name')) - g_id = str(game.get('id')) - tier = str(game.get('tier') or 'N/A').lower() - tier_icon = tier_colors.get(tier, '⚫') - - new_entry = f"**[{g_name}]()**\n{tier_icon} Classé **{tier.capitalize()}**" - - ac_status = game.get('anticheat_status') - if ac_status: - status_lower = str(ac_status).lower() - ac_map = { - 'supported': ('✅', 'Supporté'), - 'running': ('⚠️', 'Fonctionne'), - 'broken': ('❌', 'Cassé'), - 'denied': ('🚫', 'Refusé'), - 'planned': ('📅', 'Planifié') - } - ac_emoji, ac_label = ac_map.get(status_lower, ('❔', str(ac_status))) - acs = game.get('anticheats') or [] - ac_list = ', '.join([str(ac) for ac in acs if ac]) - new_entry += f" • [Anti-cheat {ac_emoji} {ac_label}" - if ac_list: - new_entry += f" ({ac_list})" - new_entry += f"]()" - - new_entry += "\n\n" - - # Vérifier la limite avant d'ajouter - if len(content) + len(new_entry) > 3900: - rest = len(games) - count - content += f"*... et {rest} autre{'s' if rest > 1 else ''} jeu{'x' if rest > 1 else ''}*" - break - - content += new_entry - else: - rest = max(0, len(games) - max_games) - if rest > 0: - content += f"*... et {rest} autre{'s' if rest > 1 else ''} jeu{'x' if rest > 1 else ''}*" - - embed = discord.Embed( - title=f"🎮 Résultats ProtonDB - **{total_games} jeu{'x' if total_games > 1 else ''} trouvé{'s' if total_games > 1 else ''}**", - description=content, - color=0x5865F2 - ) - - try : - await message.channel.send(embed=embed) - except Exception as e: - logging.error(f"Échec de l'envoi de l'embed ProtonDB : {e}") - @bot.event async def on_voice_state_update(member: Member, before, after): await on_voice_state_update_auto_rooms(bot, member, before, after) diff --git a/discordbot/moderation.py b/discordbot/moderation.py index fc3d9e6..1dcbe01 100644 --- a/discordbot/moderation.py +++ b/discordbot/moderation.py @@ -910,9 +910,9 @@ async def handle_staff_help_command(message: Message, bot): if ConfigurationHelper().getValue('proton_db_enable_enable'): public_commands.append( "**🎮 ProtonDB**\n" - "• `!protondb nom du jeu` ou `!pdb nom du jeu`\n" - "Recherche un jeu sur ProtonDB pour vérifier sa compatibilité Linux\n" - "Ex: `!pdb Elden Ring`" + "• `/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" ) from database.models import Commande diff --git a/discordbot/protondb_discord.py b/discordbot/protondb_discord.py new file mode 100644 index 0000000..6ecf511 --- /dev/null +++ b/discordbot/protondb_discord.py @@ -0,0 +1,131 @@ +import logging +from typing import Any, List + +import discord +from discord import app_commands + +from database.helpers import ConfigurationHelper +from protondb import searhProtonDb + + +def _build_protondb_embed(games: List[Any]) -> discord.Embed: + total_games = len(games) + tier_colors = {'platinum': '🟣', 'gold': '🟡', 'silver': '⚪', 'bronze': '🟤', 'borked': '🔴'} + content = "" + max_games = 15 + + for count, game in enumerate(games[:max_games]): + g_name = str(game.get('name')) + g_id = str(game.get('id')) + tier = str(game.get('tier') or 'N/A').lower() + tier_icon = tier_colors.get(tier, '⚫') + + new_entry = f"**[{g_name}]()**\n{tier_icon} Classé **{tier.capitalize()}**" + + ac_status = game.get('anticheat_status') + if ac_status: + status_lower = str(ac_status).lower() + ac_map = { + 'supported': ('✅', 'Supporté'), + 'running': ('⚠️', 'Fonctionne'), + 'broken': ('❌', 'Cassé'), + 'denied': ('🚫', 'Refusé'), + 'planned': ('📅', 'Planifié') + } + ac_emoji, ac_label = ac_map.get(status_lower, ('❔', str(ac_status))) + acs = game.get('anticheats') or [] + ac_list = ', '.join([str(ac) for ac in acs if ac]) + new_entry += f" • [Anti-cheat {ac_emoji} {ac_label}" + if ac_list: + new_entry += f" ({ac_list})" + new_entry += f"]()" + + new_entry += "\n\n" + + if len(content) + len(new_entry) > 3900: + rest = len(games) - count + content += f"*... et {rest} autre{'s' if rest > 1 else ''} jeu{'x' if rest > 1 else ''}*" + break + + content += new_entry + else: + rest = max(0, len(games) - max_games) + if rest > 0: + content += f"*... et {rest} autre{'s' if rest > 1 else ''} jeu{'x' if rest > 1 else ''}*" + + return discord.Embed( + title=f"🎮 Résultats ProtonDB - **{total_games} jeu{'x' if total_games > 1 else ''} trouvé{'s' if total_games > 1 else ''}**", + description=content, + color=0x5865F2 + ) + + +async def _protondb_search_followup(interaction: discord.Interaction, query: str) -> None: + 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, + ) + return + + embed = _build_protondb_embed(games) + try: + await interaction.followup.send(embed=embed) + except Exception as e: + logging.error(f"ProtonDB : envoi embed : {e}") + + +@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): + 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 = jeu.strip() + if not query: + await interaction.response.send_message( + "⚠️ Indique le nom d'un jeu.\nExemple : `/protondb jeu:Elden Ring`", + 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)