From 5e4af406ff5350604e76d4e8460182f07fcc1166 Mon Sep 17 00:00:00 2001 From: Mow910 Date: Tue, 17 Feb 2026 17:42:51 +0100 Subject: [PATCH] =?UTF-8?q?Ajout=20de=20la=20gestion=20des=20messages=20da?= =?UTF-8?q?ns=20les=20salons=20automatiques=20avec=20la=20fonction=20on=5F?= =?UTF-8?q?message=5Fauto=5Frooms.=20Mise=20=C3=A0=20jour=20des=20emojis?= =?UTF-8?q?=20pour=20les=20modes=20d'acc=C3=A8s=20et=20am=C3=A9lioration?= =?UTF-8?q?=20de=20l'embed=20de=20configuration=20des=20salons=20vocaux,?= =?UTF-8?q?=20incluant=20des=20sections=20pour=20les=20listes=20blanche=20?= =?UTF-8?q?et=20noire,=20ainsi=20que=20des=20informations=20sur=20le=20pro?= =?UTF-8?q?pri=C3=A9taire=20et=20les=20modes=20d'acc=C3=A8s.=20Correction?= =?UTF-8?q?=20de=20messages=20d'erreur=20et=20ajustements=20divers=20pour?= =?UTF-8?q?=20une=20meilleure=20exp=C3=A9rience=20utilisateur.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- discordbot/__init__.py | 6 +- discordbot/auto_rooms.py | 325 +++++++++++++++++++++++++++++++-------- 2 files changed, 270 insertions(+), 61 deletions(-) diff --git a/discordbot/__init__.py b/discordbot/__init__.py index 5b05c8f..ba74d87 100644 --- a/discordbot/__init__.py +++ b/discordbot/__init__.py @@ -27,7 +27,7 @@ from discordbot.moderation import ( ) from discordbot.welcome import sendWelcomeMessage, sendLeaveMessage, updateInviteCache from discordbot.youtube import checkYouTubeVideos -from discordbot.auto_rooms import on_voice_state_update_auto_rooms, on_raw_reaction_add_auto_rooms +from discordbot.auto_rooms import on_voice_state_update_auto_rooms, on_raw_reaction_add_auto_rooms, on_message_auto_rooms from protondb import searhProtonDb class DiscordBot(discord.Client): @@ -151,6 +151,10 @@ bot = DiscordBot(intents=intents) async def on_message(message: Message): if message.author == bot.user: return + + # Gestion des messages dans les auto rooms (avant le check des commandes !) + await on_message_auto_rooms(bot, message) + if not message.content.startswith('!'): return command_name = message.content.split()[0] diff --git a/discordbot/auto_rooms.py b/discordbot/auto_rooms.py index c74e7e1..82d11da 100644 --- a/discordbot/auto_rooms.py +++ b/discordbot/auto_rooms.py @@ -16,13 +16,14 @@ _control_message_ids: dict[int, tuple[int, int]] = {} REACTIONS = [ ("🔓", "open", "Ouvert"), ("🔒", "closed", "Fermé"), - ("🛡️", "private", "Privé"), + ("🔐", "private", "Privé"), ("✅", "whitelist", "Liste blanche"), ("🚫", "blacklist", "Liste noire"), ("🧹", "purge", "Purge"), ("👑", "transfer", "Propriété"), ("🎤", "speak", "Micro"), ("📹", "stream", "Vidéo"), + ("📊", "soundboards", "Soundboards"), ("📝", "status", "Statut"), ] @@ -34,40 +35,85 @@ def _status_display(access_mode: str) -> str: if access_mode == "closed": return "🔒 Fermé" if access_mode == "private": - return "🔒 Privé" + return "🔐 Privé" return "🔓 Ouvert" def _status_emoji(access_mode: str) -> str: """Emoji cadenas seul pour le nom du channel.""" + if access_mode == "private": + return "🔐" return "🔓" if access_mode == "open" else "🔒" -def _build_control_embed(owner: Member, voice_channel: discord.VoiceChannel, access_mode: str) -> discord.Embed: - """Construit l’embed de config avec infos du salon.""" +def _build_control_embed(owner: Member, voice_channel: discord.VoiceChannel, access_mode: str, room: dict = None) -> discord.Embed: + """Construit l'embed de config avec infos du salon.""" embed = discord.Embed( - title="Configuration du salon", + title="⚙️ Configuration du salon", description=( - "Voici l’espace de configuration de votre salon vocal. " - "Utilisez les réactions ci-dessous — seul le propriétaire peut réagir." + "Voici l'espace de configuration de votre salon vocal temporaire. " + "Les différentes options disponibles vous permettent de personnaliser les permissions de votre salon selon vos préférences." ), - color=discord.Color.blurple() + color=discord.Color.orange() ) - members_count = len(voice_channel.members) - user_limit = voice_channel.user_limit or 0 - limit_text = f"{user_limit} max" if user_limit else "Illimitée" - members_text = f"{members_count} / {user_limit}" if user_limit else str(members_count) - bitrate_kbps = (voice_channel.bitrate or 0) // 1000 - - embed.add_field(name="Propriétaire", value=owner.mention, inline=True) - embed.add_field(name="Statut du salon", value=_status_display(access_mode), inline=True) - embed.add_field(name="Nom du salon", value=voice_channel.name, inline=True) - embed.add_field(name="Membres", value=members_text, inline=True) - embed.add_field(name="Limite", value=limit_text, inline=True) - embed.add_field(name="Bitrate", value=f"{bitrate_kbps} kbps", inline=True) - embed.add_field(name="Accès", value="🔓 Ouvert · 🔒 Fermé · 🛡️ Privé", inline=False) - embed.add_field(name="Listes", value="✅ Liste blanche · 🚫 Liste noire", inline=False) - embed.add_field(name="Actions", value="🧹 Purge · 👑 Propriété · 🎤 Micro · 📹 Vidéo · 📝 Statut", inline=False) + + # Récupération des infos + whitelist = room.get("whitelist", set()) if room else set() + blacklist = room.get("blacklist", set()) if room else set() + whitelist_text = f"{len(whitelist)} membre(s)" if whitelist else "Aucun" + blacklist_text = f"{len(blacklist)} membre(s)" if blacklist else "Aucun" + + # Section Propriétaire + embed.add_field( + name=f"👤 Propriétaire du salon : {owner.display_name}", + value="", + inline=False + ) + + # Section Modes d'accès + mode_open = "🔓 **Ouvert**\nLe salon sera ouvert à tous les membres, sauf ceux figurant sur la liste noire." + mode_closed = "🔒 **Fermé**\nLe salon sera visible de tous, mais seulement accessible à la liste blanche." + mode_private = "🔐 **Privé**\nLe salon ne sera visible et accessible qu'aux membres de la liste blanche." + + embed.add_field(name=mode_open, value="", inline=True) + embed.add_field(name=mode_closed, value="", inline=True) + embed.add_field(name=mode_private, value="", inline=True) + + # Section Listes + embed.add_field( + name="📝 **Liste blanche**", + value=f"Les membres présents dans cette liste pourront toujours rejoindre le salon.\n\n{whitelist_text}", + inline=True + ) + embed.add_field( + name="🚫 **Liste noire**", + value=f"Les membres présents dans cette liste ne pourront jamais rejoindre le salon.\n\n{blacklist_text}", + inline=True + ) + embed.add_field(name="\u200b", value="", inline=True) # Spacer + + # Section Purge + embed.add_field( + name="🧹 **Purge**", + value="Déconnecter tous les membres du salon vocal à l'exception de ceux présents dans la liste blanche.", + inline=False + ) + + # Section Transfert + embed.add_field( + name="👑 **Transférer**", + value="Transférer la gestion du salon au membre de votre choix.", + inline=False + ) + + # Note importante + embed.add_field( + name="💡", + value="Les membres de la liste blanche ne sont pas impactés par les permissions refusées aux membres.", + inline=False + ) + + embed.set_footer(text="Réagissez avec les émojis ci-dessous pour configurer votre salon") return embed @@ -145,7 +191,7 @@ async def _handle_reaction_action(bot: discord.Client, guild_id: int, owner_id: """channel = salon vocal (partie texte / onglet Discussion).""" room = _get_room(guild_id, owner_id) if not room: - await channel.send("Ce salon n’existe plus.") + await channel.send("Ce salon n'existe plus.") return voice_channel = bot.get_channel(room["voice_channel_id"]) if not voice_channel or not isinstance(voice_channel, discord.VoiceChannel): @@ -157,34 +203,32 @@ async def _handle_reaction_action(bot: discord.Client, guild_id: int, owner_id: await _apply_access_mode(voice_channel, action, room.get("whitelist", set()), room.get("blacklist", set())) # Mettre à jour le cadenas dans le nom du channel try: - base_name = voice_channel.name.rstrip(" 🔓🔒") + base_name = voice_channel.name.rstrip(" 🔓🔒🔐") new_name = f"{base_name} {_status_emoji(action)}" await voice_channel.edit(name=new_name) except discord.HTTPException: pass - await channel.send(f"Accès du salon défini sur **{action}**.") - # Mettre à jour uniquement le statut (cadenas) dans le message de config - control_message_id = room.get("control_message_id") - if control_message_id: - try: - msg = await channel.fetch_message(control_message_id) - if msg.embeds: - embed = msg.embeds[0].copy() - for i, f in enumerate(embed.fields): - if f.name == "Statut du salon": - embed.set_field_at(i, name="Statut du salon", value=_status_display(action), inline=f.inline) - break - else: - embed.add_field(name="Statut du salon", value=_status_display(action), inline=False) - await msg.edit(embed=embed) - except discord.HTTPException: - pass + await channel.send(f"Accès du salon défini sur **{_status_display(action)}**.") + # Mettre à jour l'embed + await _update_control_panel(bot, guild_id, owner_id, channel) elif action == "whitelist": - await channel.send("Liste blanche : mentionnez un membre pour l’ajouter/retirer.") + whitelist = room.get("whitelist", set()) + whitelist_text = ", ".join([f"<@{uid}>" for uid in whitelist]) if whitelist else "Aucun membre" + await channel.send( + f"**📝 Liste blanche actuelle :** {whitelist_text}\n\n" + f"Mentionnez un membre pour l'ajouter ou le retirer de la liste blanche." + ) + room["awaiting_whitelist"] = True elif action == "blacklist": - await channel.send("Liste noire : mentionnez un membre pour l’ajouter/retirer.") + blacklist = room.get("blacklist", set()) + blacklist_text = ", ".join([f"<@{uid}>" for uid in blacklist]) if blacklist else "Aucun membre" + await channel.send( + f"**🚫 Liste noire actuelle :** {blacklist_text}\n\n" + f"Mentionnez un membre pour l'ajouter ou le retirer de la liste noire." + ) + room["awaiting_blacklist"] = True elif action == "purge": whitelist = room.get("whitelist", set()) @@ -197,30 +241,80 @@ async def _handle_reaction_action(bot: discord.Client, guild_id: int, owner_id: kicked += 1 except discord.HTTPException: pass - await channel.send(f"Purge effectuée : {kicked} membre(s) déconnecté(s).") + await channel.send(f"🧹 Purge effectuée : {kicked} membre(s) déconnecté(s).") elif action == "transfer": - await channel.send("Transférer le salon : mentionnez le membre à qui donner la propriété.") + await channel.send( + f"**👑 Transfert de propriété**\n\n" + f"Mentionnez le membre à qui vous souhaitez transférer la gestion du salon." + ) + room["awaiting_transfer"] = True - elif action in ("speak", "stream"): + elif action in ("speak", "stream", "soundboards"): everyone = voice_channel.guild.default_role overwrites = dict(voice_channel.overwrites) ow = overwrites.get(everyone) or discord.PermissionOverwrite() + + # BUG FIX : Par défaut, Discord autorise stream, speak et soundboards + # Si la permission n'est pas explicitement définie (None), on considère qu'elle est True current = getattr(ow, action) - setattr(ow, action, not current if current is not None else False) + if current is None: + # Permission non définie = autorisée par défaut dans Discord + # On veut la désactiver lors du premier clic + setattr(ow, action, False) + new_value = False + else: + # Permission définie, on l'inverse + setattr(ow, action, not current) + new_value = not current + overwrites[everyone] = ow await voice_channel.edit(overwrites=overwrites) - label = "Micro" if action == "speak" else "Vidéo" - await channel.send(f"{label} : {'autorisé' if getattr(ow, action) else 'désactivé'} pour tous.") + + labels = {"speak": "Micro", "stream": "Vidéo/Partage d'écran", "soundboards": "Soundboards"} + label = labels.get(action, action.capitalize()) + await channel.send(f"{label} : {'autorisé' if new_value else 'désactivé'} pour tous.") elif action == "status": - status_text = _status_display(room.get("access_mode", "open")) - await channel.send(f"Statut du salon : {status_text}\nRépondez avec le nouveau nom du salon pour le modifier.") + current_status = voice_channel.status or "Aucun statut défini" + await channel.send( + f"**Statut actuel du salon :** {current_status}\n\n" + f"Pour modifier le statut du salon (le texte affiché en haut du salon vocal), " + f"répondez avec le nouveau statut (max 500 caractères).\n" + f"💡 Pour supprimer le statut, répondez avec `clear` ou `effacer`." + ) + room["awaiting_status"] = True -async def send_control_panel(bot: discord.Client, guild_id: int, owner: Member, voice_channel: discord.VoiceChannel) -> Optional[int]: - """Envoie le message de config avec réactions dans la partie texte du salon vocal (onglet Discussion). Seul le proprio peut réagir. Retourne l’id du message.""" - embed = _build_control_embed(owner, voice_channel, "open") +async def _update_control_panel(bot: discord.Client, guild_id: int, owner_id: int, channel): + """Met à jour le panneau de contrôle avec les nouvelles informations.""" + room = _get_room(guild_id, owner_id) + if not room: + return + + control_message_id = room.get("control_message_id") + if not control_message_id: + return + + voice_channel = bot.get_channel(room["voice_channel_id"]) + if not voice_channel or not isinstance(voice_channel, discord.VoiceChannel): + return + + owner = voice_channel.guild.get_member(owner_id) + if not owner: + return + + try: + msg = await channel.fetch_message(control_message_id) + embed = _build_control_embed(owner, voice_channel, room.get("access_mode", "open"), room) + await msg.edit(embed=embed) + except discord.HTTPException: + pass + + +async def send_control_panel(bot: discord.Client, guild_id: int, owner: Member, voice_channel: discord.VoiceChannel, room: dict) -> Optional[int]: + """Envoie le message de config avec réactions dans la partie texte du salon vocal (onglet Discussion). Seul le proprio peut réagir. Retourne l'id du message.""" + embed = _build_control_embed(owner, voice_channel, "open", room) try: # Message dans la partie texte du vocal (onglet Discussion à droite) @@ -229,7 +323,7 @@ async def send_control_panel(bot: discord.Client, guild_id: int, owner: Member, await msg.add_reaction(emoji) return msg.id except discord.HTTPException as e: - logging.error(f"Impossible d’envoyer le panneau Auto Room dans le vocal : {e}") + logging.error(f"Impossible d'envoyer le panneau Auto Room dans le vocal : {e}") return None @@ -254,16 +348,22 @@ async def on_voice_state_update_auto_rooms(bot: discord.Client, member: Member, reason="Auto room" ) await member.move_to(new_channel) - control_message_id = await send_control_panel(bot, guild.id, member, new_channel) - _set_room(guild.id, member.id, { + + # Créer la room data d'abord + room_data = { "guild_id": guild.id, "voice_channel_id": new_channel.id, - "control_message_id": control_message_id, + "control_message_id": None, # Sera mis à jour après "owner_id": member.id, "whitelist": set(), "blacklist": set(), "access_mode": "open", - }) + } + + control_message_id = await send_control_panel(bot, guild.id, member, new_channel, room_data) + room_data["control_message_id"] = control_message_id + _set_room(guild.id, member.id, room_data) + logging.info(f"Auto room créé : {new_channel.name} pour {member.display_name}") except discord.HTTPException as e: logging.error(f"Erreur création auto room : {e}") @@ -287,6 +387,111 @@ async def on_voice_state_update_auto_rooms(bot: discord.Client, member: Member, pass +async def on_message_auto_rooms(bot: discord.Client, message: discord.Message): + """Gère les messages dans les salons vocaux pour les actions (statut, liste blanche/noire, etc.).""" + if message.author.bot: + return + if not ConfigurationHelper().getValue("auto_rooms_enable"): + return + + # Vérifier si c'est dans un salon vocal (partie texte) + if not isinstance(message.channel, discord.VoiceChannel): + return + + # Trouver si c'est une auto room + result = _find_room_by_channel(message.guild.id, message.channel.id) + if not result: + return + + owner_id, room = result + + # Seul le propriétaire peut interagir + if message.author.id != owner_id: + return + + voice_channel = message.channel + + # Gestion du statut de salon + if room.get("awaiting_status"): + room["awaiting_status"] = False + new_status = message.content.strip() + + try: + if new_status.lower() in ("clear", "effacer", "supprimer", "delete"): + await voice_channel.edit(status=None) + await message.channel.send("✅ Le statut du salon a été supprimé.") + elif len(new_status) > 500: + await message.channel.send("❌ Le statut ne peut pas dépasser 500 caractères.") + room["awaiting_status"] = True # Réessayer + else: + await voice_channel.edit(status=new_status) + await message.channel.send(f"✅ Le statut du salon a été mis à jour : **{new_status}**") + except discord.HTTPException as e: + await message.channel.send(f"❌ Erreur lors de la modification du statut : {e}") + return + + # Gestion de la liste blanche (si en attente) + if room.get("awaiting_whitelist"): + room["awaiting_whitelist"] = False + if message.mentions: + target = message.mentions[0] + whitelist = room.get("whitelist", set()) + if target.id in whitelist: + whitelist.remove(target.id) + await message.channel.send(f"✅ {target.mention} a été retiré de la liste blanche.") + else: + whitelist.add(target.id) + await message.channel.send(f"✅ {target.mention} a été ajouté à la liste blanche.") + room["whitelist"] = whitelist + await _apply_access_mode(voice_channel, room.get("access_mode", "open"), whitelist, room.get("blacklist", set())) + await _update_control_panel(bot, message.guild.id, owner_id, message.channel) + return + + # Gestion de la liste noire (si en attente) + if room.get("awaiting_blacklist"): + room["awaiting_blacklist"] = False + if message.mentions: + target = message.mentions[0] + blacklist = room.get("blacklist", set()) + if target.id in blacklist: + blacklist.remove(target.id) + await message.channel.send(f"✅ {target.mention} a été retiré de la liste noire.") + else: + blacklist.add(target.id) + await message.channel.send(f"✅ {target.mention} a été ajouté à la liste noire.") + room["blacklist"] = blacklist + await _apply_access_mode(voice_channel, room.get("access_mode", "open"), room.get("whitelist", set()), blacklist) + await _update_control_panel(bot, message.guild.id, owner_id, message.channel) + return + + # Gestion du transfert de propriété (si en attente) + if room.get("awaiting_transfer"): + room["awaiting_transfer"] = False + if message.mentions: + new_owner = message.mentions[0] + if new_owner.id == owner_id: + await message.channel.send("❌ Vous êtes déjà le propriétaire du salon.") + return + + # Transférer la propriété + old_owner_id = owner_id + _del_room(message.guild.id, old_owner_id) + room["owner_id"] = new_owner.id + _set_room(message.guild.id, new_owner.id, room) + + # Renommer le salon + try: + base_name = f"Salon de {new_owner.display_name}" + new_name = f"{base_name} {_status_emoji(room.get('access_mode', 'open'))}" + await voice_channel.edit(name=new_name) + except discord.HTTPException: + pass + + await message.channel.send(f"✅ La propriété du salon a été transférée à {new_owner.mention}.") + await _update_control_panel(bot, message.guild.id, new_owner.id, message.channel) + return + + async def on_raw_reaction_add_auto_rooms(bot: discord.Client, payload: discord.RawReactionActionEvent): """Seul le propriétaire peut réagir ; on retire la réaction des autres.""" if payload.user_id == bot.user.id: