Kuby/commandes/moderation.py

324 lines
19 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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}")
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.add_item(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.add_item(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: <t:{int(self.member.joined_at.timestamp())}:R>\nCréé: <t:{int(self.member.created_at.timestamp())}:R>", 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, interaction: disnake.Interaction, button: disnake.ui.Button):
await interaction.response.send_modal(ModReasonModal(self, "WARN"))
@disnake.ui.button(label="Timeout", style=disnake.ButtonStyle.secondary, emoji="")
async def timeout_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button):
await interaction.response.send_modal(ModReasonModal(self, "TIMEOUT"))
@disnake.ui.button(label="Expulser", style=disnake.ButtonStyle.secondary, emoji="👢")
async def kick_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button):
await interaction.response.send_modal(ModReasonModal(self, "KICK"))
@disnake.ui.button(label="Bannir", style=disnake.ButtonStyle.danger, emoji="🔨")
async def ban_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button):
await interaction.response.send_modal(ModReasonModal(self, "BAN"))
@disnake.ui.button(label="Historique", style=disnake.ButtonStyle.primary, emoji="📜")
async def history_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button):
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, interaction: disnake.Interaction, button: disnake.ui.Button):
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__(title=f"Modifier {field_name}")
self.field_name = field_name
self.input = disnake.ui.TextInput(
label=field_name,
default=str(current_value),
placeholder="Entrez une valeur numérique...",
required=True
)
self.add_item(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, interaction: disnake.Interaction, button: disnake.ui.Button):
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, interaction: disnake.Interaction, button: disnake.ui.Button):
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, interaction: disnake.Interaction, button: disnake.ui.Button):
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)
async def setup(bot):
await bot.add_cog(Moderation(bot))