import disnake from disnake.ext import commands import sqlite3 import os from datetime import datetime, timedelta import re class ModReasonModal(disnake.ui.Modal): def __init__(self, parent_view, action_type): super().__init__(title=f"Raison pour {action_type}", components=[]) self.parent_view = parent_view self.action_type = action_type self.reason = disnake.ui.TextInput( label="Raison", placeholder="Entrez la raison ici...", required=True, max_length=500 ) self.append_component(self.reason) if action_type == "TIMEOUT": self.duration = disnake.ui.TextInput( label="Durée (ex: 1h, 30m, 1d)", placeholder="1h", required=True, max_length=10 ) self.append_component(self.duration) async def callback(self, interaction: disnake.Interaction): reason = self.reason.value member = self.parent_view.member cog = interaction.bot.get_cog("Moderation") if not cog: return await interaction.response.send_message("❌ Erreur interne.", ephemeral=True) if self.action_type == "WARN": await cog.warn(interaction, member, reason) elif self.action_type == "TIMEOUT": await cog.timeout(interaction, member, self.duration.value, reason) elif self.action_type == "KICK": await cog.kick(interaction, member, reason) elif self.action_type == "BAN": await cog.ban(interaction, member, reason) await self.parent_view.update_panel(interaction) class ModPanelView(disnake.ui.View): def __init__(self, member, moderator, db_path): super().__init__(timeout=120) self.member = member self.moderator = moderator self.db_path = db_path async def update_panel(self, interaction: disnake.Interaction): conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute('SELECT COUNT(*) FROM sanctions WHERE guild_id = ? AND user_id = ? AND type = "WARN" AND status = "ACTIVE"', (interaction.guild_id, self.member.id)) warn_count = cursor.fetchone()[0] conn.close() embed = disnake.Embed( title=f"🛡️ Panel de Modération - {self.member.name}", description=f"Gestion de l'utilisateur {self.member.mention}", color=disnake.Color.blue(), timestamp=datetime.now() ) embed.set_thumbnail(url=self.member.display_avatar.url) embed.add_field(name="👤 Utilisateur", value=f"ID: `{self.member.id}`\nTag: `{self.member}`", inline=True) embed.add_field(name="⚠️ Avertissements", value=f"Actifs: **{warn_count}**", inline=True) embed.add_field(name="📅 Dates", value=f"Rejoint: \nCréé: ", inline=False) if not interaction.response.is_done(): await interaction.response.edit_message(embed=embed, view=self) else: await interaction.edit_original_response(embed=embed, view=self) @disnake.ui.button(label="Avertir", style=disnake.ButtonStyle.secondary, emoji="⚠️") async def warn_btn(self, button: disnake.ui.Button, interaction: disnake.Interaction): await interaction.response.send_modal(ModReasonModal(self, "WARN")) @disnake.ui.button(label="Timeout", style=disnake.ButtonStyle.secondary, emoji="⏳") async def timeout_btn(self, button: disnake.ui.Button, interaction: disnake.Interaction): await interaction.response.send_modal(ModReasonModal(self, "TIMEOUT")) @disnake.ui.button(label="Expulser", style=disnake.ButtonStyle.secondary, emoji="👢") async def kick_btn(self, button: disnake.ui.Button, interaction: disnake.Interaction): await interaction.response.send_modal(ModReasonModal(self, "KICK")) @disnake.ui.button(label="Bannir", style=disnake.ButtonStyle.danger, emoji="🔨") async def ban_btn(self, button: disnake.ui.Button, interaction: disnake.Interaction): await interaction.response.send_modal(ModReasonModal(self, "BAN")) @disnake.ui.button(label="Historique", style=disnake.ButtonStyle.primary, emoji="📜") async def history_btn(self, button: disnake.ui.Button, interaction: disnake.Interaction): cog = interaction.bot.get_cog("Moderation") await cog.history(interaction, self.member) @disnake.ui.button(label="Clear Warns", style=disnake.ButtonStyle.danger, emoji="🧹") async def clear_btn(self, button: disnake.ui.Button, interaction: disnake.Interaction): cog = interaction.bot.get_cog("Moderation") await cog.clearwarns(interaction, self.member, reason="Nettoyage via Panel") await self.update_panel(interaction) class ModConfigModal(disnake.ui.Modal): def __init__(self, field_name, current_value): super().__init__(components=[], title=f"Modifier {field_name}") self.field_name = field_name self.input = disnake.ui.TextInput( label=field_name, value=str(current_value), placeholder="Entrez une valeur numérique...", required=True ) self.append_component(self.input) async def callback(self, interaction: disnake.Interaction): value = self.input.value cog = interaction.bot.get_cog("Moderation") mapping = { "Limite Timeout": "warn_limit_timeout", "Limite Kick": "warn_limit_kick", "Limite Ban": "warn_limit_ban", "Durée Timeout (s)": "timeout_duration" } if not value.isdigit(): return await interaction.response.send_message("❌ Veuillez entrer un nombre valide.", ephemeral=True) db_field = mapping.get(self.field_name) conn = sqlite3.connect(cog.db_path) cursor = conn.cursor() cursor.execute(f"UPDATE mod_config SET {db_field} = ? WHERE guild_id = ?", (int(value), interaction.guild_id)) conn.commit() conn.close() await interaction.response.send_message(f"✅ {self.field_name} mis à jour sur **{value}**.", ephemeral=True) class ModConfigView(disnake.ui.View): def __init__(self, guild_id, db_path): super().__init__(timeout=60) self.guild_id = guild_id self.db_path = db_path @disnake.ui.button(label="Log Channel", style=disnake.ButtonStyle.primary, emoji="📁") async def log_btn(self, button: disnake.ui.Button, interaction: disnake.Interaction): view = disnake.ui.View() select = disnake.ui.ChannelSelect( placeholder="Sélectionnez le salon de logs...", channel_types=[disnake.ChannelType.text] ) async def select_callback(interaction: disnake.Interaction): channel = select.values[0] conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute("UPDATE mod_config SET log_channel_id = ? WHERE guild_id = ?", (channel.id, interaction.guild_id)) conn.commit(); conn.close() await interaction.response.send_message(f"✅ Salon de logs défini sur {channel.mention}", ephemeral=True) select.callback = select_callback view.add_item(select) await interaction.response.send_message("Choisissez le salon de logs :", view=view, ephemeral=True) @disnake.ui.button(label="Paliers Warns", style=disnake.ButtonStyle.secondary, emoji="⚖️") async def limits_btn(self, button: disnake.ui.Button, interaction: disnake.Interaction): conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute("SELECT warn_limit_timeout, warn_limit_kick, warn_limit_ban FROM mod_config WHERE guild_id = ?", (self.guild_id,)) res = cursor.fetchone(); conn.close() view = disnake.ui.View() btn_t = disnake.ui.Button(label=f"Timeout ({res[0]})", style=disnake.ButtonStyle.secondary) btn_t.callback = lambda i: i.response.send_modal(ModConfigModal("Limite Timeout", res[0])) btn_k = disnake.ui.Button(label=f"Kick ({res[1]})", style=disnake.ButtonStyle.secondary) btn_k.callback = lambda i: i.response.send_modal(ModConfigModal("Limite Kick", res[1])) btn_b = disnake.ui.Button(label=f"Ban ({res[2]})", style=disnake.ButtonStyle.secondary) btn_b.callback = lambda i: i.response.send_modal(ModConfigModal("Limite Ban", res[2])) view.add_item(btn_t); view.add_item(btn_k); view.add_item(btn_b) await interaction.response.send_message("Quelle limite modifier ?", view=view, ephemeral=True) @disnake.ui.button(label="Durée Timeout", style=disnake.ButtonStyle.secondary, emoji="⏱️") async def duration_btn(self, button: disnake.ui.Button, interaction: disnake.Interaction): conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute("SELECT timeout_duration FROM mod_config WHERE guild_id = ?", (self.guild_id,)) res = cursor.fetchone(); conn.close() await interaction.response.send_modal(ModConfigModal("Durée Timeout (s)", res[0])) class Moderation(commands.Cog): def __init__(self, bot): self.bot = bot self.db_path = os.path.join(os.path.dirname(__file__), "..", "config.db") self._init_db() def _init_db(self): conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute('''CREATE TABLE IF NOT EXISTS sanctions (id INTEGER PRIMARY KEY AUTOINCREMENT, guild_id INTEGER, user_id INTEGER, moderator_id INTEGER, type TEXT, reason TEXT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, duration INTEGER, status TEXT DEFAULT 'ACTIVE')''') cursor.execute('''CREATE TABLE IF NOT EXISTS mod_config (guild_id INTEGER PRIMARY KEY, log_channel_id INTEGER, warn_limit_timeout INTEGER DEFAULT 3, warn_limit_kick INTEGER DEFAULT 5, warn_limit_ban INTEGER DEFAULT 10, timeout_duration INTEGER DEFAULT 3600)''') conn.commit(); conn.close() def parse_duration(self, duration_str: str) -> int: if not duration_str: return 0 units = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400, 'w': 604800} total_seconds = 0 matches = re.findall(r'(\d+)([smhdw])', duration_str.lower()) for value, unit in matches: total_seconds += int(value) * units[unit] return total_seconds async def send_mod_log(self, guild, embed): conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute('SELECT log_channel_id FROM mod_config WHERE guild_id = ?', (guild.id,)) result = cursor.fetchone(); conn.close() if result and result[0]: log_channel = guild.get_channel(result[0]) if log_channel: await log_channel.send(embed=embed) @commands.slash_command(name="modpanel", description="Ouvre le panel de modération pour un membre") @commands.has_permissions(moderate_members=True) async def modpanel(self, interaction: disnake.ApplicationCommandInteraction, member: disnake.Member): if member.top_role >= interaction.user.top_role and interaction.user.id != interaction.guild.owner_id: return await interaction.response.send_message("❌ Impossible de gérer ce membre.", ephemeral=True) conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute('SELECT COUNT(*) FROM sanctions WHERE guild_id = ? AND user_id = ? AND type = "WARN" AND status = "ACTIVE"', (interaction.guild_id, member.id)) warn_count = cursor.fetchone()[0]; conn.close() embed = disnake.Embed(title=f"🛡️ Panel de Modération - {member.name}", description=f"Gestion de {member.mention}", color=disnake.Color.blue(), timestamp=datetime.now()) embed.set_thumbnail(url=member.display_avatar.url) embed.add_field(name="👤 Utilisateur", value=f"ID: `{member.id}`", inline=True) embed.add_field(name="⚠️ Avertissements", value=f"Actifs: **{warn_count}**", inline=True) view = ModPanelView(member, interaction.user, self.db_path) await interaction.response.send_message(embed=embed, view=view, ephemeral=True) @commands.slash_command(name="modconfig", description="Configuration de la modération") @commands.has_permissions(administrator=True) async def modconfig(self, interaction: disnake.ApplicationCommandInteraction): conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute('INSERT OR IGNORE INTO mod_config (guild_id) VALUES (?)', (interaction.guild_id,)) cursor.execute('SELECT log_channel_id, warn_limit_timeout, warn_limit_kick, warn_limit_ban, timeout_duration FROM mod_config WHERE guild_id = ?', (interaction.guild_id,)) res = cursor.fetchone(); conn.commit(); conn.close() embed = disnake.Embed(title="⚙️ Configuration Modération", description="Modifiez les paramètres ci-dessous.", color=disnake.Color.blue()) embed.add_field(name="📁 Logs", value=f"<#{res[0]}>" if res[0] else "Non défini", inline=False) embed.add_field(name="⚖️ Paliers", value=f"Timeout: **{res[1]}**, Kick: **{res[2]}**, Ban: **{res[3]}**", inline=False) embed.add_field(name="⏱️ Durée Timeout Auto", value=f"**{res[4]}s**", inline=False) view = ModConfigView(interaction.guild_id, self.db_path) await interaction.response.send_message(embed=embed, view=view, ephemeral=True) async def warn(self, interaction: disnake.Interaction, member: disnake.Member, reason: str): conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute('INSERT INTO sanctions (guild_id, user_id, moderator_id, type, reason) VALUES (?, ?, ?, ?, ?)', (interaction.guild_id, member.id, interaction.user.id, 'WARN', reason)) cursor.execute('SELECT COUNT(*) FROM sanctions WHERE guild_id = ? AND user_id = ? AND type = "WARN" AND status = "ACTIVE"', (interaction.guild_id, member.id)) warn_count = cursor.fetchone()[0] cursor.execute('SELECT warn_limit_timeout, warn_limit_kick, warn_limit_ban, timeout_duration FROM mod_config WHERE guild_id = ?', (interaction.guild_id,)) config = cursor.fetchone(); conn.commit(); conn.close() if not interaction.response.is_done(): await interaction.response.send_message(f"✅ {member.mention} averti ({warn_count}).", ephemeral=True) else: await interaction.followup.send(f"✅ {member.mention} averti ({warn_count}).", ephemeral=True) await self.send_mod_log(interaction.guild, disnake.Embed(title="⚠️ Avertissement", description=f"Membre: {member.mention}\nRaison: {reason}\nTotal: {warn_count}", color=disnake.Color.orange())) try: await member.send(f"⚠️ Avertissement sur **{interaction.guild.name}**\nRaison: {reason}") except: pass if config: l_t, l_k, l_b, dur = config if warn_count >= l_b: await member.ban(reason=f"Auto: {warn_count} warns") elif warn_count >= l_k: await member.kick(reason=f"Auto: {warn_count} warns") elif warn_count >= l_t: await member.timeout(duration=timedelta(seconds=dur), reason=f"Auto: {warn_count} warns") async def timeout(self, interaction: disnake.Interaction, member: disnake.Member, duration_str: str, reason: str): secs = self.parse_duration(duration_str) if secs <= 0: return await interaction.response.send_message("❌ Durée invalide.", ephemeral=True) await member.timeout(duration=timedelta(seconds=secs), reason=reason) conn = sqlite3.connect(self.db_path); cursor = conn.cursor() cursor.execute('INSERT INTO sanctions (guild_id, user_id, moderator_id, type, reason, duration) VALUES (?, ?, ?, ?, ?, ?)', (interaction.guild_id, member.id, interaction.user.id, 'TIMEOUT', reason, secs)) conn.commit(); conn.close() if not interaction.response.is_done(): await interaction.response.send_message(f"✅ {member.mention} mis en sourdine.", ephemeral=True) else: await interaction.followup.send(f"✅ {member.mention} mis en sourdine.", ephemeral=True) async def kick(self, interaction: disnake.Interaction, member: disnake.Member, reason: str): await member.kick(reason=reason) conn = sqlite3.connect(self.db_path); cursor = conn.cursor() cursor.execute('INSERT INTO sanctions (guild_id, user_id, moderator_id, type, reason) VALUES (?, ?, ?, ?, ?)', (interaction.guild_id, member.id, interaction.user.id, 'KICK', reason)) conn.commit(); conn.close() if not interaction.response.is_done(): await interaction.response.send_message(f"✅ {member.name} expulsé.", ephemeral=True) else: await interaction.followup.send(f"✅ {member.name} expulsé.", ephemeral=True) async def ban(self, interaction: disnake.Interaction, user: disnake.User, reason: str): await interaction.guild.ban(user, reason=reason) conn = sqlite3.connect(self.db_path); cursor = conn.cursor() cursor.execute('INSERT INTO sanctions (guild_id, user_id, moderator_id, type, reason) VALUES (?, ?, ?, ?, ?)', (interaction.guild_id, user.id, interaction.user.id, 'BAN', reason)) conn.commit(); conn.close() if not interaction.response.is_done(): await interaction.response.send_message(f"✅ {user.name} banni.", ephemeral=True) else: await interaction.followup.send(f"✅ {user.name} banni.", ephemeral=True) async def history(self, interaction: disnake.Interaction, user: disnake.User): conn = sqlite3.connect(self.db_path); cursor = conn.cursor() cursor.execute('SELECT id, type, reason, timestamp FROM sanctions WHERE guild_id = ? AND user_id = ? ORDER BY timestamp DESC', (interaction.guild_id, user.id)) results = cursor.fetchall(); conn.close() if not results: if not interaction.response.is_done(): return await interaction.response.send_message("ℹ️ Aucun historique.", ephemeral=True) else: return await interaction.followup.send("ℹ️ Aucun historique.", ephemeral=True) embed = disnake.Embed(title=f"📜 Historique de {user.name}", color=disnake.Color.blue()) for s_id, s_type, reason, ts in results[:10]: embed.add_field(name=f"#{s_id} - {s_type}", value=f"**Date:** {ts}\n**Raison:** {reason}", inline=False) if not interaction.response.is_done(): await interaction.response.send_message(embed=embed, ephemeral=True) else: await interaction.followup.send(embed=embed, ephemeral=True) async def clearwarns(self, interaction: disnake.Interaction, member: disnake.Member, reason: str): conn = sqlite3.connect(self.db_path); cursor = conn.cursor() cursor.execute('UPDATE sanctions SET status = "REVOKED" WHERE guild_id = ? AND user_id = ? AND type = "WARN"', (interaction.guild_id, member.id)) conn.commit(); conn.close() if not interaction.response.is_done(): await interaction.response.send_message(f"✅ Warns de {member.name} effacés.", ephemeral=True) else: await interaction.followup.send(f"✅ Warns de {member.name} effacés.", ephemeral=True) def setup(bot): bot.add_cog(Moderation(bot))