diff --git a/discordbot/__init__.py b/discordbot/__init__.py index ce06bef..45f8da3 100644 --- a/discordbot/__init__.py +++ b/discordbot/__init__.py @@ -21,7 +21,8 @@ from discordbot.moderation import ( handle_ban_list_command, handle_staff_help_command, handle_timeout_command, - handle_say_command + handle_say_command, + handle_transfer_command ) from discordbot.welcome import sendWelcomeMessage, sendLeaveMessage, updateInviteCache from discordbot.youtube import checkYouTubeVideos @@ -167,6 +168,10 @@ async def on_message(message: Message): await handle_say_command(message, bot) return + if command_name in ['!transfert', '!transfer', '!move']: + await handle_transfer_command(message, bot) + return + if command_name in ['!aide', '!help']: await handle_staff_help_command(message, bot) return diff --git a/discordbot/moderation.py b/discordbot/moderation.py index 366aeb9..4935080 100644 --- a/discordbot/moderation.py +++ b/discordbot/moderation.py @@ -4,12 +4,13 @@ import time import os import re import discord +import io from datetime import datetime, timezone, timedelta from zoneinfo import ZoneInfo from database import db from database.helpers import ConfigurationHelper from database.models import ModerationEvent -from discord import Message +from discord import Message, TextChannel, ForumChannel, Thread def _get_local_tz(): tz_name = os.environ.get('APP_TZ') or os.environ.get('TZ') or 'Europe/Paris' @@ -1055,7 +1056,15 @@ async def handle_staff_help_command(message: Message, bot): value=( "• `!say #channel message`\n" " Envoie un message en tant que bot\n" - " Ex: `!say #annonces Nouvelle fonctionnalité !`" + " Ex: `!say #annonces Nouvelle fonctionnalité !`\n\n" + "• `!transfert #canal message_id [raison]`\n" + " Transfère un message vers un autre canal\n" + " *Alias: !transfer, !move*\n" + " Ex: `!transfert #entraide 123456789012345678`\n" + " Ex: `!transfert #général https://discord.com/channels/.../...`\n" + " Le message sera envoyé comme si c'était l'auteur original\n" + " Supporte les canaux textuels, threads et forums (crée un post)\n" + " Pour les forums, la raison devient le titre du post" ), inline=False ) @@ -1405,3 +1414,298 @@ async def handle_say_command(message: Message, bot): except Exception as e: logging.error(f"Erreur lors de l'envoi du message: {e}") +async def handle_transfer_command(message: Message, bot): + if not has_staff_role(message.author.roles): + await send_access_denied(message.channel) + return + + parts = message.content.split(maxsplit=3) + + if len(parts) < 3: + embed = discord.Embed( + title="📋 Utilisation de la commande", + description="**Syntaxe :** `!transfert #canal message_id [raison]` ou `!transfert #canal lien_message [raison]`", + color=discord.Color.blue() + ) + embed.add_field( + name="Exemples", + value=( + "• `!transfert #entraide 123456789012345678`\n" + "• `!transfert #general https://discord.com/channels/.../...`\n" + "• `!transfert #entraide 123456789012345678 Message posté dans le mauvais canal`" + ), + inline=False + ) + embed.add_field( + name="Aliases", + value="`!transfert`, `!transfer`, `!move`", + inline=False + ) + embed.add_field( + name="💡 Astuce", + value="Faites un clic droit sur un message → Copier l'identifiant du message, ou copiez le lien du message", + inline=False + ) + msg = await message.channel.send(embed=embed) + asyncio.create_task(delete_after_delay(msg)) + return + + target_channel = None + if message.channel_mentions: + target_channel = message.channel_mentions[0] + else: + try: + channel_id = int(parts[1].strip('<#>')) + target_channel = bot.get_channel(channel_id) + except ValueError: + pass + + if not target_channel: + embed = discord.Embed( + title="❌ Erreur", + description="Canal de destination invalide. Mentionnez un canal avec #canal.", + color=discord.Color.red() + ) + msg = await message.channel.send(embed=embed) + asyncio.create_task(delete_after_delay(msg)) + return + + if not isinstance(target_channel, (TextChannel, ForumChannel, Thread)): + embed = discord.Embed( + title="❌ Erreur", + description=f"Le transfert n'est pas supporté vers ce type de canal. Utilisez un canal textuel, un forum ou un thread.", + color=discord.Color.red() + ) + msg = await message.channel.send(embed=embed) + asyncio.create_task(delete_after_delay(msg)) + return + + message_id = None + source_channel = message.channel + + if 'discord.com/channels/' in parts[2]: + try: + link_parts = parts[2].split('/') + source_channel_id = int(link_parts[-2]) + message_id = int(link_parts[-1]) + source_channel = bot.get_channel(source_channel_id) + + if not source_channel: + embed = discord.Embed( + title="❌ Erreur", + description="Canal source introuvable.", + color=discord.Color.red() + ) + msg = await message.channel.send(embed=embed) + asyncio.create_task(delete_after_delay(msg)) + return + except (ValueError, IndexError): + embed = discord.Embed( + title="❌ Erreur", + description="Lien de message invalide.", + color=discord.Color.red() + ) + msg = await message.channel.send(embed=embed) + asyncio.create_task(delete_after_delay(msg)) + return + else: + try: + message_id = int(parts[2]) + except ValueError: + embed = discord.Embed( + title="❌ Erreur", + description="ID de message invalide. Utilisez un ID numérique ou un lien Discord.", + color=discord.Color.red() + ) + msg = await message.channel.send(embed=embed) + asyncio.create_task(delete_after_delay(msg)) + return + + try: + original_message = await source_channel.fetch_message(message_id) + except discord.NotFound: + embed = discord.Embed( + title="❌ Erreur", + description="Message introuvable. Vérifiez l'ID ou le lien.", + color=discord.Color.red() + ) + msg = await message.channel.send(embed=embed) + asyncio.create_task(delete_after_delay(msg)) + return + except discord.Forbidden: + embed = discord.Embed( + title="❌ Erreur", + description="Je n'ai pas la permission d'accéder à ce message.", + color=discord.Color.red() + ) + msg = await message.channel.send(embed=embed) + asyncio.create_task(delete_after_delay(msg)) + return + + reason = parts[3] if len(parts) > 3 else "Message posté dans le mauvais canal" + + content = original_message.content + embeds = original_message.embeds + files_to_send = [] + + for attachment in original_message.attachments: + try: + file_data = await attachment.read() + files_to_send.append(discord.File( + fp=io.BytesIO(file_data), + filename=attachment.filename + )) + except Exception as e: + logging.error(f"Erreur lors du téléchargement de la pièce jointe: {e}") + + transferred_message = None + + if isinstance(target_channel, ForumChannel): + try: + post_title = f"{original_message.author.display_name} - " + if reason and reason != "Message posté dans le mauvais canal": + remaining_length = 100 - len(post_title) + post_title += reason[:remaining_length] + elif content and len(content) > 0: + remaining_length = 100 - len(post_title) + post_title += content[:remaining_length] + else: + post_title += "Message transféré" + + if len(post_title) > 100: + post_title = post_title[:97] + "..." + + transfer_notice = f"**Message original de {original_message.author.mention}**\n" + transfer_notice += f"*Ce message a été transféré par un membre du staff depuis {source_channel.mention}*\n" + transfer_notice += "─" * 50 + "\n\n" + + full_content = transfer_notice + (content or "") + + thread = await target_channel.create_thread( + name=post_title, + content=full_content, + embeds=embeds[:10] if embeds else [], + files=files_to_send, + reason=f"Transfert depuis {source_channel.name} par {message.author.name}" + ) + transferred_message = thread.message + + except discord.HTTPException as e: + logging.error(f"Erreur lors de la création du post dans le forum: {e}") + embed = discord.Embed( + title="❌ Erreur", + description=f"Une erreur est survenue lors de la création du post dans le forum: {str(e)}", + color=discord.Color.red() + ) + msg = await message.channel.send(embed=embed) + asyncio.create_task(delete_after_delay(msg)) + return + + else: + webhooks = await target_channel.webhooks() + webhook = None + + for wh in webhooks: + if wh.user == bot.user: + webhook = wh + break + + if not webhook: + try: + webhook = await target_channel.create_webhook(name="Mamie Henriette - Transfert") + except discord.Forbidden: + embed = discord.Embed( + title="❌ Erreur", + description="Je n'ai pas la permission de créer un webhook dans le canal de destination.", + color=discord.Color.red() + ) + msg = await message.channel.send(embed=embed) + asyncio.create_task(delete_after_delay(msg)) + return + + try: + transferred_message = await webhook.send( + content=content, + username=original_message.author.display_name, + avatar_url=original_message.author.display_avatar.url, + embeds=embeds[:10] if embeds else [], + files=files_to_send, + allowed_mentions=discord.AllowedMentions.none(), + wait=True + ) + except discord.HTTPException as e: + logging.error(f"Erreur lors du transfert du message: {e}") + embed = discord.Embed( + title="❌ Erreur", + description=f"Une erreur est survenue lors du transfert du message: {str(e)}", + color=discord.Color.red() + ) + msg = await message.channel.send(embed=embed) + asyncio.create_task(delete_after_delay(msg)) + return + + try: + await original_message.delete() + except discord.Forbidden: + logging.warning(f"Impossible de supprimer le message original (ID: {message_id})") + + transfer_details = f"De {source_channel.name} vers {target_channel.name}" + if isinstance(target_channel, ForumChannel): + transfer_details += " (forum)" + + transfer_event = ModerationEvent( + type='transfer', + username=original_message.author.name, + discord_id=str(original_message.author.id), + created_at=datetime.now(timezone.utc), + reason=f"{reason} | {transfer_details}", + staff_id=str(message.author.id), + staff_name=message.author.name + ) + db.session.add(transfer_event) + _commit_with_retry() + + local_now = _to_local(datetime.now(timezone.utc)) + destination_info = target_channel.mention if isinstance(target_channel, (TextChannel, Thread)) else f"le forum {target_channel.name}" + + embed = discord.Embed( + title="✅ Message transféré", + description=f"Le message de **{original_message.author.name}** a été transféré vers {destination_info}", + color=discord.Color.green(), + timestamp=datetime.now(timezone.utc) + ) + embed.add_field(name="👤 Auteur original", value=f"{original_message.author.mention}", inline=True) + embed.add_field(name="📤 Canal source", value=source_channel.mention, inline=True) + embed.add_field(name="📥 Canal destination", value=destination_info, inline=True) + embed.add_field(name="🛡️ Modérateur", value=message.author.mention, inline=True) + embed.add_field(name="📝 Raison", value=reason, inline=False) + + if isinstance(target_channel, ForumChannel): + embed.add_field(name="ℹ️ Type", value="Nouveau post créé dans le forum", inline=False) + + confirmation_msg = await source_channel.send(embed=embed) + asyncio.create_task(delete_after_delay(confirmation_msg)) + + log_embed = discord.Embed( + title="📨 Transfert de message", + description=f"Un message de **{original_message.author.name}** a été transféré", + color=discord.Color.blue(), + timestamp=datetime.now(timezone.utc) + ) + log_embed.add_field(name="👤 Auteur original", value=f"{original_message.author.name}\n`{original_message.author.id}`", inline=True) + log_embed.add_field(name="🛡️ Modérateur", value=f"**{message.author.name}**", inline=True) + log_embed.add_field(name="📅 Date et heure", value=local_now.strftime('%d/%m/%Y à %H:%M'), inline=True) + log_embed.add_field(name="📤 De", value=source_channel.mention, inline=True) + log_embed.add_field(name="📥 Vers", value=f"{target_channel.name} ({type(target_channel).__name__})", inline=True) + log_embed.add_field(name="📝 Raison", value=reason, inline=False) + + preview = content[:100] + "..." if content and len(content) > 100 else content + if preview: + log_embed.add_field(name="💬 Aperçu du message", value=preview, inline=False) + + log_embed.set_footer(text=f"ID Auteur: {original_message.author.id} • Serveur: {message.guild.name}") + + await send_to_moderation_log_channel(bot, log_embed) + + await safe_delete_message(message) + diff --git a/webapp/templates/moderation.html b/webapp/templates/moderation.html index b35f643..13bbad8 100644 --- a/webapp/templates/moderation.html +++ b/webapp/templates/moderation.html @@ -95,6 +95,10 @@ !banlist Liste des utilisateurs bannis +
+ !transfert #canal message_id + Transfère un message vers un autre canal (textuel, thread ou forum) +
@@ -129,6 +133,10 @@ Warn {% elif mod_event.type == 'unban' %} Unban + {% elif mod_event.type == 'transfer' %} + Transfert + {% elif mod_event.type == 'timeout' %} + Timeout {% else %} {{ mod_event.type }} {% endif %}