Initialisation du repository de Beta
This commit is contained in:
commit
14985f6dbb
9469 changed files with 1903273 additions and 0 deletions
BIN
commandes/security/__pycache__/auto_moderation.cpython-311.pyc
Normal file
BIN
commandes/security/__pycache__/auto_moderation.cpython-311.pyc
Normal file
Binary file not shown.
BIN
commandes/security/__pycache__/auto_moderation.cpython-312.pyc
Normal file
BIN
commandes/security/__pycache__/auto_moderation.cpython-312.pyc
Normal file
Binary file not shown.
BIN
commandes/security/__pycache__/ban.cpython-311.pyc
Normal file
BIN
commandes/security/__pycache__/ban.cpython-311.pyc
Normal file
Binary file not shown.
BIN
commandes/security/__pycache__/ban.cpython-312.pyc
Normal file
BIN
commandes/security/__pycache__/ban.cpython-312.pyc
Normal file
Binary file not shown.
BIN
commandes/security/__pycache__/kick.cpython-311.pyc
Normal file
BIN
commandes/security/__pycache__/kick.cpython-311.pyc
Normal file
Binary file not shown.
BIN
commandes/security/__pycache__/kick.cpython-312.pyc
Normal file
BIN
commandes/security/__pycache__/kick.cpython-312.pyc
Normal file
Binary file not shown.
BIN
commandes/security/__pycache__/mute.cpython-311.pyc
Normal file
BIN
commandes/security/__pycache__/mute.cpython-311.pyc
Normal file
Binary file not shown.
BIN
commandes/security/__pycache__/mute.cpython-312.pyc
Normal file
BIN
commandes/security/__pycache__/mute.cpython-312.pyc
Normal file
Binary file not shown.
BIN
commandes/security/__pycache__/purge.cpython-311.pyc
Normal file
BIN
commandes/security/__pycache__/purge.cpython-311.pyc
Normal file
Binary file not shown.
BIN
commandes/security/__pycache__/purge.cpython-312.pyc
Normal file
BIN
commandes/security/__pycache__/purge.cpython-312.pyc
Normal file
Binary file not shown.
BIN
commandes/security/__pycache__/timeout.cpython-311.pyc
Normal file
BIN
commandes/security/__pycache__/timeout.cpython-311.pyc
Normal file
Binary file not shown.
BIN
commandes/security/__pycache__/unban.cpython-311.pyc
Normal file
BIN
commandes/security/__pycache__/unban.cpython-311.pyc
Normal file
Binary file not shown.
BIN
commandes/security/__pycache__/unban.cpython-312.pyc
Normal file
BIN
commandes/security/__pycache__/unban.cpython-312.pyc
Normal file
Binary file not shown.
BIN
commandes/security/__pycache__/unmute.cpython-312.pyc
Normal file
BIN
commandes/security/__pycache__/unmute.cpython-312.pyc
Normal file
Binary file not shown.
BIN
commandes/security/__pycache__/warn.cpython-311.pyc
Normal file
BIN
commandes/security/__pycache__/warn.cpython-311.pyc
Normal file
Binary file not shown.
BIN
commandes/security/__pycache__/warn.cpython-312.pyc
Normal file
BIN
commandes/security/__pycache__/warn.cpython-312.pyc
Normal file
Binary file not shown.
198
commandes/security/auto_moderation.py
Normal file
198
commandes/security/auto_moderation.py
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
"""
|
||||
Module de modération automatique pour Superviseur Bot
|
||||
Gère la détection et sanction des messages inappropriés automatiquement sur tous les messages
|
||||
"""
|
||||
|
||||
import discord
|
||||
import aiohttp
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
WARNINGS_FILE = 'warnings.json'
|
||||
|
||||
def load_warnings():
|
||||
"""Charge les avertissements depuis le fichier JSON"""
|
||||
if os.path.exists(WARNINGS_FILE):
|
||||
with open(WARNINGS_FILE, 'r', encoding='utf-8') as f:
|
||||
content = f.read().strip()
|
||||
if content:
|
||||
return json.loads(content)
|
||||
return {}
|
||||
|
||||
def save_warnings(warnings):
|
||||
"""Sauvegarde les avertissements dans le fichier JSON"""
|
||||
with open(WARNINGS_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(warnings, f, indent=4, ensure_ascii=False)
|
||||
|
||||
|
||||
async def check_message_content_global(bot, content):
|
||||
"""
|
||||
Vérifie le contenu d'un message pour tout contenu inapproprié
|
||||
Utilise une liste locale de mots inappropriés pour performance optimale
|
||||
"""
|
||||
# Liste optimisée de mots inappropriés (mots-clés critiques seulement pour performance)
|
||||
bad_words = {
|
||||
# Mots critiques (haute sévérité)
|
||||
'nigger', 'nigga', 'pédé', 'bougnoul', 'viol', 'pédophile', 'inceste', 'naziste', 'hitler',
|
||||
'suicide', 'se tuer', 'se suicider', 'tuer', 'assassiner', 'meurtre',
|
||||
# Gros mots courants
|
||||
'fuck', 'shit', 'asshole', 'bitch', 'dick', 'pussy', 'cunt', 'motherfucker',
|
||||
'merde', 'putain', 'salope', 'connard', 'enculé', 'bordel', 'emmerde',
|
||||
'ta gueule', 'va te faire foutre', 'va te faire enculer', 'va chier', 'crève',
|
||||
'branleur', 'sucer', 'pipe', 'pétasse', 'garce', 'traînée', 'salopard', 'ordure',
|
||||
'fumier', 'pourri', 'dégueulasse', 'gueule', 'gueuler',
|
||||
# Insultes
|
||||
'con', 'abruti', 'débile', 'crétin', 'imbécile', 'loser', 'nullos', 'naze',
|
||||
'ridicule', 'pathétique', 'minable', 'nul', 'inutile', 'incapable', 'bon à rien',
|
||||
# NSFW/sexuel
|
||||
'sexe', 'porn', 'porno', 'bite', 'seins', 'sodomie', 'agression sexuelle',
|
||||
'harceleur', 'stalker',
|
||||
# Discrimination
|
||||
'raciste', 'antisémite', 'fasciste', 'gay', 'homo', 'féminin',
|
||||
# Drogues
|
||||
'drogue', 'cocaine', 'héroine', 'cannabis', 'ecstasy', 'lsd', 'crack', 'speed',
|
||||
'amphétamine', 'steroide', 'anabolisant',
|
||||
# Autres
|
||||
'cancer', 'maladie grave', 'crever', 'mourir'
|
||||
}
|
||||
|
||||
content_lower = content.lower()
|
||||
detected_bad_words = []
|
||||
|
||||
# Recherche rapide avec set pour performance
|
||||
for bad_word in bad_words:
|
||||
bad_word_lower = bad_word.lower()
|
||||
# Recherche insensible à la casse avec délimiteurs pour éviter faux positifs
|
||||
if f" {bad_word_lower} " in f" {content_lower} " or content_lower == bad_word_lower:
|
||||
detected_bad_words.append(bad_word)
|
||||
# Continue pour lister tous les mots détectés, mais limite à 3 pour performance
|
||||
if len(detected_bad_words) >= 3:
|
||||
break
|
||||
|
||||
if detected_bad_words:
|
||||
severity = "high" if any(word in ['nigger', 'nigga', 'pédé', 'bougnoul', 'viol', 'pédophile', 'inceste', 'naziste', 'hitler', 'suicide', 'se tuer', 'tuer', 'assassiner', 'meurtre'] for word in detected_bad_words) else "medium"
|
||||
return {
|
||||
"inappropriate": True,
|
||||
"severity": severity,
|
||||
"reason": f"Mots inappropriés détectés: {', '.join(detected_bad_words[:3])}",
|
||||
"bad_words": detected_bad_words[:3]
|
||||
}
|
||||
|
||||
return {"inappropriate": False}
|
||||
|
||||
|
||||
async def warn_and_check_ban_global(message, reason, message_deleted=False):
|
||||
"""
|
||||
Émet un avertissement et vérifie si l'utilisateur doit être banni (3+ avertissements)
|
||||
"""
|
||||
guild_id = str(message.guild.id)
|
||||
all_warnings = load_warnings()
|
||||
|
||||
if guild_id not in all_warnings:
|
||||
all_warnings[guild_id] = {}
|
||||
|
||||
user_id_str = str(message.author.id)
|
||||
if user_id_str not in all_warnings[guild_id]:
|
||||
all_warnings[guild_id][user_id_str] = []
|
||||
|
||||
# Ajouter l'avertissement
|
||||
all_warnings[guild_id][user_id_str].append({
|
||||
'reason': reason,
|
||||
'timestamp': datetime.now().isoformat(),
|
||||
'issued_by': 'AUTOMATIC_MODERATION'
|
||||
})
|
||||
|
||||
warning_count = len(all_warnings[guild_id][user_id_str])
|
||||
save_warnings(all_warnings)
|
||||
|
||||
from commandes.autres.logger import action_logger
|
||||
action_logger.log_action('AUTOMATIC_MODERATION', str(message.author), str(message.guild), {
|
||||
'reason': reason,
|
||||
'warning_count': warning_count,
|
||||
'original_message': message.content
|
||||
}, True)
|
||||
|
||||
# Vérifier si bannissement automatique nécessaire
|
||||
if warning_count >= 3:
|
||||
try:
|
||||
await message.author.ban(reason=f"Bannissement automatique: 3 avertissements accumulés. Dernière raison: {reason}")
|
||||
embed = discord.Embed(
|
||||
title="🚫 Bannissement automatique",
|
||||
description=f"{message.author.mention}, vous avez été banni pour accumulation d'avertissements (3+ avertissements).\n**Dernière raison:** {reason}",
|
||||
color=discord.Color.red()
|
||||
)
|
||||
embed.set_footer(text=f"Total d'avertissements: {warning_count}")
|
||||
await message.channel.send(embed=embed)
|
||||
return True # Utilisateur banni
|
||||
except discord.Forbidden:
|
||||
embed = discord.Embed(
|
||||
title="Ban requis",
|
||||
description=f"❌ {message.author.mention} a 3+ avertissements, mais le bot n'a pas les permissions de bannir.\n**Agissez manuellement!**",
|
||||
color=discord.Color.red()
|
||||
)
|
||||
embed.set_footer(text=f"Total d'avertissements: {warning_count}")
|
||||
await message.channel.send(embed=embed)
|
||||
return False
|
||||
else:
|
||||
# Envoyer message d'avertissement
|
||||
deletion_text = "Votre message a été supprimé pour contenu inapproprié." if message_deleted else "Mots inappropriés détectés dans votre message."
|
||||
warning_message = f"⚠️ **Avertissement automatique** {message.author.mention}\n{deletion_text}\n**Raison:** {reason}\n**Avertissements totaux:** {warning_count}\n**Avant bannissement:** {3 - warning_count} avertissement(s)\n*Modération automatique par IA*"
|
||||
await message.channel.send(warning_message)
|
||||
return False
|
||||
|
||||
|
||||
async def moderate_message(bot, message):
|
||||
"""
|
||||
Fonction principale de modération automatique d'un message
|
||||
Retourne True si le message a été modéré (et donc supprimé), False sinon
|
||||
"""
|
||||
# Préparer le contenu à analyser
|
||||
content_to_check = message.content
|
||||
if message.attachments:
|
||||
for attachment in message.attachments:
|
||||
if attachment.content_type and attachment.content_type.startswith('image/'):
|
||||
if not content_to_check.strip():
|
||||
content_to_check = f"[Image: {attachment.filename}]"
|
||||
else:
|
||||
content_to_check += f" [Image: {attachment.filename}]"
|
||||
|
||||
# Analyser le contenu
|
||||
if content_to_check.strip():
|
||||
print(f"[MODERATION] Analysing message from {message.author}: '{content_to_check}'")
|
||||
moderation_result = await check_message_content_global(bot, content_to_check)
|
||||
print(f"[MODERATION] Result: {moderation_result}")
|
||||
|
||||
bad_words = moderation_result.get('bad_words', [])
|
||||
inappropriate = moderation_result.get('inappropriate', False)
|
||||
print(f"[MODERATION] bad_words: {bad_words}, inappropriate: {inappropriate}")
|
||||
|
||||
# Toujours donner un avertissement si des mots inappropriés sont détectés
|
||||
if bad_words:
|
||||
reason = f"Mots inappropriés détectés: {', '.join(bad_words[:5])}"
|
||||
print(f"[MODERATION] Processing bad words, reason: {reason}")
|
||||
if inappropriate:
|
||||
print(f"[MODERATION] Message inappropriate, deleting...")
|
||||
try:
|
||||
await message.delete()
|
||||
print(f"[MODERATION] Message deleted successfully")
|
||||
except discord.Forbidden:
|
||||
print(f"[MODERATION] Forbidden to delete message")
|
||||
pass # Impossible de supprimer
|
||||
except Exception as e:
|
||||
print(f"[MODERATION] Error deleting message: {e}")
|
||||
await warn_and_check_ban_global(message, reason, message_deleted=True)
|
||||
print(f"[MODERATION] Warning sent and ban checked, returning True")
|
||||
return True # Message modéré
|
||||
else:
|
||||
print(f"[MODERATION] Not inappropriate, sending warning without deletion")
|
||||
await warn_and_check_ban_global(message, reason, message_deleted=False)
|
||||
|
||||
return False # Message autorisé
|
||||
|
||||
|
||||
# Test function pour vérifier la fonctionnalité
|
||||
async def test_auto_moderation():
|
||||
"""Fonction de test pour vérifier que les avertissements sont toujours donnés"""
|
||||
print("Test auto-moderation: vérification que les avertissements sont donnés pour les mots inappropriés")
|
||||
# Cette fonction peut être appelée pour tester manuellement si nécessaire
|
||||
23
commandes/security/ban.py
Normal file
23
commandes/security/ban.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import discord
|
||||
from commandes.autres.utils import parse_user_mention
|
||||
|
||||
async def execute(bot, params, message):
|
||||
users = params.get('users', [])
|
||||
if not users:
|
||||
users = [params]
|
||||
banned_count = 0
|
||||
for user_params in users:
|
||||
target_mention = user_params.get('target_user_mention')
|
||||
user_id, member = await parse_user_mention(bot, target_mention, message)
|
||||
if user_id is None:
|
||||
await message.channel.send(f"Utilisateur {target_mention} introuvable.")
|
||||
continue
|
||||
try:
|
||||
await message.guild.ban(discord.Object(id=user_id), reason="Bannissement par le Superviseur")
|
||||
banned_count += 1
|
||||
except discord.Forbidden:
|
||||
await message.channel.send(f"Je n'ai pas les permissions pour bannir l'utilisateur avec ID {user_id}.")
|
||||
except discord.HTTPException as e:
|
||||
await message.channel.send(f"Erreur lors du bannissement de l'utilisateur avec ID {user_id}: {e}")
|
||||
if banned_count > 0:
|
||||
await message.channel.send(f"{banned_count} utilisateur(s) banni(s).")
|
||||
24
commandes/security/kick.py
Normal file
24
commandes/security/kick.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import discord
|
||||
from commandes.autres.utils import parse_user_mention
|
||||
|
||||
async def execute(bot, params, message):
|
||||
users = params.get('users', [])
|
||||
if not users:
|
||||
users = [params]
|
||||
kicked_count = 0
|
||||
for user_params in users:
|
||||
target_mention = user_params.get('target_user_mention')
|
||||
user_id, member = await parse_user_mention(bot, target_mention, message)
|
||||
if user_id is None:
|
||||
await message.channel.send(f"Utilisateur {target_mention} introuvable.")
|
||||
continue
|
||||
if not member:
|
||||
await message.channel.send(f"Utilisateur {target_mention} n'est pas dans ce serveur.")
|
||||
continue
|
||||
try:
|
||||
await member.kick(reason="Expulsion par le Superviseur")
|
||||
kicked_count += 1
|
||||
except discord.Forbidden:
|
||||
await message.channel.send(f"Je n'ai pas les permissions pour expulser {member.mention}.")
|
||||
if kicked_count > 0:
|
||||
await message.channel.send(f"{kicked_count} utilisateur(s) expulsé(s).")
|
||||
35
commandes/security/list_warnings.py
Normal file
35
commandes/security/list_warnings.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import discord
|
||||
import json
|
||||
import os
|
||||
|
||||
WARNINGS_FILE = 'warnings.json'
|
||||
|
||||
def load_warnings():
|
||||
if os.path.exists(WARNINGS_FILE):
|
||||
with open(WARNINGS_FILE, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
return {}
|
||||
|
||||
async def execute(bot, params, message):
|
||||
all_warnings = load_warnings()
|
||||
guild_id = str(message.guild.id)
|
||||
if guild_id not in all_warnings:
|
||||
await message.channel.send("Aucune avertissement enregistré pour ce serveur.")
|
||||
return
|
||||
|
||||
embed = discord.Embed(
|
||||
title="Avertissements du serveur",
|
||||
description="Liste des utilisateurs ayant des avertissements :",
|
||||
color=discord.Color.orange()
|
||||
)
|
||||
|
||||
for user_id, warnings in all_warnings[guild_id].items():
|
||||
member = message.guild.get_member(int(user_id))
|
||||
name = member.display_name if member else f"Utilisateur {user_id}"
|
||||
embed.add_field(
|
||||
name=f"{name} ({len(warnings)} avertissements)",
|
||||
value="Dernier : " + warnings[-1]['reason'][:100] + "..." if len(warnings[-1]['reason']) > 100 else warnings[-1]['reason'],
|
||||
inline=False
|
||||
)
|
||||
|
||||
await message.channel.send(embed=embed)
|
||||
78
commandes/security/mute.py
Normal file
78
commandes/security/mute.py
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import discord
|
||||
from discord.ext import commands
|
||||
import asyncio
|
||||
import re
|
||||
from ..autres.config import config
|
||||
|
||||
async def execute(bot, params, message):
|
||||
users = params.get('users', [])
|
||||
if not users:
|
||||
# Fallback for single user
|
||||
users = [params]
|
||||
# Get mute role from config or default to 'muted'
|
||||
mute_role_name = config.get_mute_role(message.guild.id) or 'muted'
|
||||
mute_role = discord.utils.get(message.guild.roles, name=mute_role_name)
|
||||
if not mute_role:
|
||||
await message.channel.send(f"Rôle '{mute_role_name}' introuvable. Veuillez le créer ou le configurer.")
|
||||
return
|
||||
muted_count = 0
|
||||
failed_count = 0
|
||||
for user_params in users:
|
||||
target_mention = user_params.get('target_user_mention')
|
||||
duration_str = user_params.get('duration', 10) # default 10 minutes
|
||||
# Parse duration to extract numeric value
|
||||
if isinstance(duration_str, str):
|
||||
match = re.search(r'(\d+)', duration_str)
|
||||
if match:
|
||||
duration = int(match.group(1))
|
||||
else:
|
||||
duration = 10 # fallback to default
|
||||
else:
|
||||
duration = int(duration_str) if duration_str else 10
|
||||
# Parse mention or ID
|
||||
if target_mention.startswith('<@') and target_mention.endswith('>'):
|
||||
user_id = int(target_mention[2:-1])
|
||||
elif target_mention.startswith('<@!') and target_mention.endswith('>'):
|
||||
user_id = int(target_mention[3:-1])
|
||||
elif target_mention.startswith('@'):
|
||||
# Handle @username
|
||||
username = target_mention[1:] # Remove @
|
||||
member = discord.utils.find(lambda m: m.name == username or m.display_name == username, message.guild.members)
|
||||
if not member:
|
||||
await message.channel.send(f"Utilisateur {target_mention} introuvable.")
|
||||
failed_count += 1
|
||||
continue
|
||||
user_id = member.id
|
||||
else:
|
||||
try:
|
||||
user_id = int(target_mention)
|
||||
except ValueError:
|
||||
await message.channel.send(f"Mention ou ID invalide: {target_mention}")
|
||||
failed_count += 1
|
||||
continue
|
||||
member = message.guild.get_member(user_id)
|
||||
if not member:
|
||||
await message.channel.send(f"Utilisateur introuvable dans le serveur: {target_mention}")
|
||||
failed_count += 1
|
||||
continue
|
||||
try:
|
||||
await member.add_roles(mute_role, reason=f"Muté par le Superviseur pour {duration} minutes")
|
||||
muted_count += 1
|
||||
# Schedule unmute
|
||||
asyncio.create_task(unmute_after_delay(member, mute_role, duration * 60)) # duration in minutes to seconds
|
||||
except discord.Forbidden:
|
||||
await message.channel.send(f"Je n'ai pas les permissions pour muter {member.mention}.")
|
||||
failed_count += 1
|
||||
if muted_count > 0:
|
||||
await message.channel.send(f"{muted_count} utilisateur(s) muté(s) pour {duration} {'minute' if duration == 1 else 'minutes'}.")
|
||||
|
||||
async def unmute_after_delay(member, mute_role, delay_seconds):
|
||||
await asyncio.sleep(delay_seconds)
|
||||
try:
|
||||
await member.remove_roles(mute_role, reason="Fin du mute automatique")
|
||||
except discord.Forbidden:
|
||||
# Log or handle permission error, but don't send message as it's async
|
||||
pass
|
||||
except discord.HTTPException:
|
||||
# Member might have left or role removed
|
||||
pass
|
||||
32
commandes/security/purge.py
Normal file
32
commandes/security/purge.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import discord
|
||||
|
||||
async def execute(bot, params, message):
|
||||
purges = params.get('purges', [])
|
||||
if not purges:
|
||||
# Fallback for single purge
|
||||
purges = [params]
|
||||
total_deleted = 0
|
||||
for purge_params in purges:
|
||||
channel_name = purge_params.get('channel_name')
|
||||
channel_id = purge_params.get('channel_id')
|
||||
amount = purge_params.get('amount', 10)
|
||||
guild = message.guild
|
||||
channel = None
|
||||
if channel_id:
|
||||
try:
|
||||
channel = guild.get_channel(int(channel_id))
|
||||
except ValueError:
|
||||
pass
|
||||
elif channel_name:
|
||||
channel = discord.utils.find(lambda c: c.name.casefold() == channel_name.casefold(), guild.channels)
|
||||
if not channel:
|
||||
identifier = channel_id or channel_name or 'inconnu'
|
||||
await message.channel.send(f"Salon '{identifier}' introuvable.")
|
||||
continue
|
||||
try:
|
||||
deleted = await channel.purge(limit=amount)
|
||||
total_deleted += len(deleted)
|
||||
except discord.Forbidden:
|
||||
await message.channel.send(f"Je n'ai pas les permissions pour purger {channel.mention}.")
|
||||
if total_deleted > 0:
|
||||
await message.channel.send(f"{total_deleted} messages supprimés au total.")
|
||||
42
commandes/security/timeout.py
Normal file
42
commandes/security/timeout.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import discord
|
||||
|
||||
async def execute(bot, params, message):
|
||||
users = params.get('users', [])
|
||||
if not users:
|
||||
# Fallback for single user
|
||||
users = [params]
|
||||
timed_out_count = 0
|
||||
for user_params in users:
|
||||
target_mention = user_params.get('target_user_mention')
|
||||
duration = user_params.get('duration', 10) # default 10 minutes
|
||||
# Parse mention or ID
|
||||
if target_mention.startswith('<@') and target_mention.endswith('>'):
|
||||
user_id = int(target_mention[2:-1])
|
||||
elif target_mention.startswith('<@!') and target_mention.endswith('>'):
|
||||
user_id = int(target_mention[3:-1])
|
||||
elif target_mention.startswith('@'):
|
||||
# Handle @username
|
||||
username = target_mention[1:] # Remove @
|
||||
member = discord.utils.find(lambda m: m.name == username or m.display_name == username, message.guild.members)
|
||||
if not member:
|
||||
await message.channel.send(f"Utilisateur {target_mention} introuvable.")
|
||||
continue
|
||||
user_id = member.id
|
||||
else:
|
||||
try:
|
||||
user_id = int(target_mention)
|
||||
except ValueError:
|
||||
await message.channel.send(f"Mention ou ID invalide: {target_mention}")
|
||||
continue
|
||||
member = message.guild.get_member(user_id)
|
||||
if not member:
|
||||
await message.channel.send(f"Utilisateur {target_mention} introuvable.")
|
||||
continue
|
||||
try:
|
||||
# Timeout for duration minutes
|
||||
await member.timeout(discord.utils.utcnow() + discord.timedelta(minutes=duration), reason=f"Timeout par le Superviseur pour {duration} minutes")
|
||||
timed_out_count += 1
|
||||
except discord.Forbidden:
|
||||
await message.channel.send(f"Je n'ai pas les permissions pour mettre en timeout {member.mention}.")
|
||||
if timed_out_count > 0:
|
||||
await message.channel.send(f"{timed_out_count} utilisateur(s) mis en timeout pour {duration} {'minute' if duration == 1 else 'minutes'}.")
|
||||
78
commandes/security/unban.py
Normal file
78
commandes/security/unban.py
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import discord
|
||||
|
||||
async def execute(bot, params, message):
|
||||
users = params.get('users', [])
|
||||
if not users:
|
||||
# Fallback for single user
|
||||
users = [params]
|
||||
unbanned_count = 0
|
||||
for user_params in users:
|
||||
target_mention = user_params.get('target_user_mention')
|
||||
user_id = None
|
||||
|
||||
# 1. Try strict ID parsing first
|
||||
try:
|
||||
user_id = int(target_mention)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# 2. Try Mention parsing (specific to generic)
|
||||
if user_id is None:
|
||||
if target_mention.startswith('<@!') and target_mention.endswith('>'):
|
||||
try:
|
||||
user_id = int(target_mention[3:-1])
|
||||
except ValueError:
|
||||
pass
|
||||
elif target_mention.startswith('<@') and target_mention.endswith('>'):
|
||||
try:
|
||||
user_id = int(target_mention[2:-1])
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
user = None
|
||||
if user_id:
|
||||
try:
|
||||
user = bot.get_user(user_id) or await bot.fetch_user(user_id)
|
||||
except discord.NotFound:
|
||||
pass
|
||||
except discord.HTTPException:
|
||||
pass
|
||||
|
||||
# 3. If no ID match, search by name in ban list
|
||||
if not user:
|
||||
try:
|
||||
# Fetch all bans to search by name
|
||||
bans = [entry async for entry in message.guild.bans()]
|
||||
target_lower = target_mention.lower()
|
||||
target_clean = target_lower[1:] if target_lower.startswith('@') else target_lower
|
||||
|
||||
for ban_entry in bans:
|
||||
u = ban_entry.user
|
||||
# Check: name, global_name, name#discrim
|
||||
u_global = (u.global_name or "").lower()
|
||||
u_name = u.name.lower()
|
||||
|
||||
if (u_name == target_lower or
|
||||
u_name == target_clean or
|
||||
u_global == target_lower or
|
||||
u_global == target_clean or
|
||||
f"{u_name}#{u.discriminator}" == target_lower):
|
||||
user = u
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"Error searching bans: {e}")
|
||||
|
||||
if not user:
|
||||
await message.channel.send(f"Utilisateur {target_mention} introuvable (ni ID, ni Mention, ni Nom dans les bannis).")
|
||||
continue
|
||||
|
||||
try:
|
||||
await message.guild.unban(user, reason="Débannissement par le Superviseur")
|
||||
unbanned_count += 1
|
||||
except discord.Forbidden:
|
||||
await message.channel.send(f"Je n'ai pas les permissions pour débannir {user.mention}.")
|
||||
except discord.NotFound:
|
||||
await message.channel.send(f"{user.mention} n'est pas banni.")
|
||||
|
||||
if unbanned_count > 0:
|
||||
await message.channel.send(f"{unbanned_count} utilisateur(s) débanni(s).")
|
||||
79
commandes/security/unmute.py
Normal file
79
commandes/security/unmute.py
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import discord
|
||||
from discord.ext import commands
|
||||
import asyncio
|
||||
from ..autres.config import config
|
||||
|
||||
async def execute(bot, params, message):
|
||||
users = params.get('users', [])
|
||||
if not users:
|
||||
# Fallback for single user
|
||||
users = [params]
|
||||
|
||||
# Get mute role from config or default to 'muted'
|
||||
mute_role_name = config.get_mute_role(message.guild.id) or 'muted'
|
||||
mute_role = discord.utils.get(message.guild.roles, name=mute_role_name)
|
||||
|
||||
if not mute_role:
|
||||
await message.channel.send(f"Rôle '{mute_role_name}' introuvable. Impossible de démuter.")
|
||||
return
|
||||
|
||||
unmuted_count = 0
|
||||
failed_count = 0
|
||||
|
||||
for user_params in users:
|
||||
target_mention = user_params.get('target_user_mention')
|
||||
user_id = None
|
||||
|
||||
# Parse mention or ID
|
||||
if target_mention.startswith('<@') and target_mention.endswith('>'):
|
||||
try:
|
||||
# Handle <@123> and <@!123>
|
||||
clean_id = target_mention[2:-1]
|
||||
if clean_id.startswith('!'):
|
||||
clean_id = clean_id[1:]
|
||||
user_id = int(clean_id)
|
||||
except ValueError:
|
||||
pass
|
||||
elif target_mention.startswith('@'):
|
||||
# Handle @username
|
||||
username = target_mention[1:]
|
||||
member = discord.utils.find(lambda m: m.name == username or m.display_name == username, message.guild.members)
|
||||
if member:
|
||||
user_id = member.id
|
||||
else:
|
||||
try:
|
||||
user_id = int(target_mention)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if not user_id:
|
||||
# Try finding member by name if ID parsing failed
|
||||
username = target_mention.lstrip('@')
|
||||
member = discord.utils.find(lambda m: m.name == username or m.display_name == username, message.guild.members)
|
||||
if member:
|
||||
user_id = member.id
|
||||
|
||||
if not user_id:
|
||||
await message.channel.send(f"Mention ou ID invalide: {target_mention}")
|
||||
failed_count += 1
|
||||
continue
|
||||
|
||||
member = message.guild.get_member(user_id)
|
||||
if not member:
|
||||
await message.channel.send(f"Utilisateur introuvable dans le serveur: {target_mention}")
|
||||
failed_count += 1
|
||||
continue
|
||||
|
||||
if mute_role not in member.roles:
|
||||
await message.channel.send(f"{member.mention} n'est pas muté.")
|
||||
continue
|
||||
|
||||
try:
|
||||
await member.remove_roles(mute_role, reason="Démuté par le Superviseur")
|
||||
unmuted_count += 1
|
||||
except discord.Forbidden:
|
||||
await message.channel.send(f"Je n'ai pas les permissions pour démuter {member.mention}.")
|
||||
failed_count += 1
|
||||
|
||||
if unmuted_count > 0:
|
||||
await message.channel.send(f"{unmuted_count} utilisateur(s) démuté(s).")
|
||||
196
commandes/security/warn.py
Normal file
196
commandes/security/warn.py
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
import discord
|
||||
import json
|
||||
import os
|
||||
import aiohttp
|
||||
from datetime import datetime
|
||||
|
||||
WARNINGS_FILE = 'warnings.json'
|
||||
|
||||
def load_warnings():
|
||||
if os.path.exists(WARNINGS_FILE):
|
||||
with open(WARNINGS_FILE, 'r', encoding='utf-8') as f:
|
||||
content = f.read().strip()
|
||||
if content:
|
||||
return json.loads(content)
|
||||
return {}
|
||||
|
||||
def save_warnings(warnings):
|
||||
with open(WARNINGS_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(warnings, f, indent=4, ensure_ascii=False)
|
||||
|
||||
async def rephrase_reason(original_reason, api_url, api_key, model, timeout, temperature):
|
||||
try:
|
||||
payload = {
|
||||
"model": model,
|
||||
"prompt": f"Reformule la raison suivante d'avertissement en français de manière polie et claire :\n{original_reason}\nRéformulation :",
|
||||
"stream": False,
|
||||
"options": {"temperature": temperature}
|
||||
}
|
||||
headers = {}
|
||||
if api_key:
|
||||
headers["Authorization"] = f"Bearer {api_key}"
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(api_url, json=payload, headers=headers, timeout=aiohttp.ClientTimeout(total=30)) as response:
|
||||
response.raise_for_status()
|
||||
try:
|
||||
data = await response.json()
|
||||
raw_response = data.get('response', '').strip()
|
||||
# If the response is JSON (some models do that), try to parse it
|
||||
try:
|
||||
json_resp = json.loads(raw_response)
|
||||
if isinstance(json_resp, dict) and 'response' in json_resp:
|
||||
rephrased = json_resp['response'].strip()
|
||||
else:
|
||||
rephrased = raw_response
|
||||
except json.JSONDecodeError:
|
||||
rephrased = raw_response
|
||||
except Exception:
|
||||
# If not JSON, try to get text
|
||||
content = await response.text()
|
||||
try:
|
||||
data = json.loads(content)
|
||||
raw_response = data.get('response', '').strip()
|
||||
try:
|
||||
json_resp = json.loads(raw_response)
|
||||
rephrased = json_resp.get('response', raw_response) if isinstance(json_resp, dict) else raw_response
|
||||
except json.JSONDecodeError:
|
||||
rephrased = raw_response
|
||||
except json.JSONDecodeError:
|
||||
rephrased = content.strip()
|
||||
return rephrased if rephrased else None
|
||||
except Exception as e:
|
||||
print(f"Erreur lors de la reformulation : {e}")
|
||||
return None
|
||||
|
||||
async def generate_dm_message(original_reason, warning_count, api_url, api_key, model, timeout, temperature):
|
||||
try:
|
||||
payload = {
|
||||
"model": model,
|
||||
"prompt": f"Écris seulement le message d'avertissement poli et concis pour un user en DM, sans ajouter de métadonnées ou de JSON. Le message doit commencer par 'Bonjour,' et finir par 'Cordialement, le Superviseur'. Raison originale : {original_reason}, nombre d'avertissements : {warning_count}.",
|
||||
"stream": False,
|
||||
"options": {"temperature": temperature}
|
||||
}
|
||||
headers = {}
|
||||
if api_key:
|
||||
headers["Authorization"] = f"Bearer {api_key}"
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(api_url, json=payload, headers=headers, timeout=aiohttp.ClientTimeout(total=30)) as response:
|
||||
response.raise_for_status()
|
||||
try:
|
||||
data = await response.json()
|
||||
raw_message = data.get('response', '').strip()
|
||||
# If the response is JSON (some models do that), try to parse it
|
||||
try:
|
||||
json_resp = json.loads(raw_message)
|
||||
if isinstance(json_resp, dict):
|
||||
final_message = json_resp.get('generated_message') or json_resp.get('response', raw_message)
|
||||
else:
|
||||
final_message = raw_message
|
||||
except json.JSONDecodeError:
|
||||
final_message = raw_message
|
||||
except Exception:
|
||||
# If not JSON, try to get text
|
||||
content = await response.text()
|
||||
try:
|
||||
data = json.loads(content)
|
||||
raw_message = data.get('response', '').strip()
|
||||
try:
|
||||
json_resp = json.loads(raw_message)
|
||||
final_message = json_resp.get('generated_message') or json_resp.get('response', raw_message) if isinstance(json_resp, dict) else raw_message
|
||||
except json.JSONDecodeError:
|
||||
final_message = raw_message
|
||||
except json.JSONDecodeError:
|
||||
final_message = content.strip()
|
||||
# Ensure message is not too long (Discord DM limit is 2000 chars)
|
||||
if len(final_message) > 1950:
|
||||
final_message = final_message[:1950] + "..."
|
||||
return final_message if final_message else None
|
||||
except Exception as e:
|
||||
print(f"Erreur lors de la génération du message DM : {e}")
|
||||
return None
|
||||
|
||||
async def execute(bot, params, message):
|
||||
warnings_list = params.get('warnings', []) # warnings is param name
|
||||
if not warnings_list:
|
||||
# Fallback for single warning
|
||||
warnings_list = [params]
|
||||
warned_count = 0
|
||||
all_warnings = load_warnings()
|
||||
guild_id = str(message.guild.id)
|
||||
if guild_id not in all_warnings:
|
||||
all_warnings[guild_id] = {}
|
||||
for warn_params in warnings_list:
|
||||
target_mention = warn_params.get('target_user_mention')
|
||||
original_reason = warn_params.get('reason', 'Aucune raison spécifiée')
|
||||
# Rephrase the reason using Ollama
|
||||
rephrased_reason = await rephrase_reason(original_reason, bot.ollama_api_url, bot.ollama_api_key, bot.ollama_model, bot.ollama_timeout, bot.ollama_temperature)
|
||||
reason = rephrased_reason if rephrased_reason else original_reason
|
||||
# Parse mention or ID
|
||||
if target_mention.startswith('<@') and target_mention.endswith('>'):
|
||||
user_id = int(target_mention[2:-1])
|
||||
elif target_mention.startswith('<@!') and target_mention.endswith('>'):
|
||||
user_id = int(target_mention[3:-1])
|
||||
elif target_mention.startswith('@'):
|
||||
# Handle @username
|
||||
username = target_mention[1:] # Remove @
|
||||
member = discord.utils.find(lambda m: m.name == username or m.display_name == username, message.guild.members)
|
||||
if not member:
|
||||
await message.channel.send(f"Utilisateur {target_mention} introuvable.")
|
||||
continue
|
||||
user_id = member.id
|
||||
else:
|
||||
try:
|
||||
user_id = int(target_mention)
|
||||
except ValueError:
|
||||
await message.channel.send(f"Mention ou ID invalide: {target_mention}")
|
||||
continue
|
||||
member = message.guild.get_member(user_id)
|
||||
if not member:
|
||||
await message.channel.send(f"Utilisateur {target_mention} introuvable.")
|
||||
continue
|
||||
# Add warning to record
|
||||
user_id_str = str(user_id)
|
||||
if user_id_str not in all_warnings[guild_id]:
|
||||
all_warnings[guild_id][user_id_str] = []
|
||||
all_warnings[guild_id][user_id_str].append({
|
||||
'reason': reason,
|
||||
'timestamp': datetime.now().isoformat(),
|
||||
'issued_by': str(message.author.id)
|
||||
})
|
||||
save_warnings(all_warnings)
|
||||
warning_count = len(all_warnings[guild_id][user_id_str])
|
||||
# Send warning message in channel with count
|
||||
embed = discord.Embed(
|
||||
title=f"Avertissement #{warning_count}",
|
||||
description=f"{member.mention}, {reason}\n"
|
||||
f"Nombre total d'avertissements : {warning_count}",
|
||||
color=discord.Color.orange()
|
||||
)
|
||||
await message.channel.send(embed=embed)
|
||||
# Generate full DM message using AI with original reason for polite reformulation
|
||||
dm_message = await generate_dm_message(original_reason, warning_count, bot.ollama_api_url, bot.ollama_api_key, bot.ollama_model, bot.ollama_timeout, bot.ollama_temperature)
|
||||
dm_content = dm_message if dm_message else f"Avertissement : {reason}. Nombre total : {warning_count}."
|
||||
# Send DM to user
|
||||
try:
|
||||
await member.send(dm_content)
|
||||
warned_count += 1
|
||||
except discord.Forbidden:
|
||||
await message.channel.send(f"Impossible d'envoyer un DM à {member.mention}, avertissement public.")
|
||||
warned_count += 1 # Still count as warned
|
||||
# If 3+ warnings, auto-ban
|
||||
if warning_count >= 3:
|
||||
try:
|
||||
await member.ban(reason=f"3 avertissements accumulés. Dernière raison: {reason}")
|
||||
embed.add_field(name="Action automatique", value="Utilisateur banni pour accumulation d'avertissements.", inline=False)
|
||||
await message.channel.send(embed=embed)
|
||||
await message.channel.send(f"{member.mention} a été banni pour accumulation d'avertissements.")
|
||||
continue # Skip further processing for this user
|
||||
except discord.Forbidden:
|
||||
embed.add_field(name="Action suggérée", value="Utilisateur a 3+ avertissements, mais bot n'a pas les permissions pour bannir.", inline=False)
|
||||
await message.channel.send(embed=embed)
|
||||
except discord.HTTPException as e:
|
||||
embed.add_field(name="Erreur de bannissement", value=f"Impossible de bannir: {e}", inline=False)
|
||||
await message.channel.send(embed=embed)
|
||||
save_warnings(all_warnings) # Save at end
|
||||
if warned_count > 0:
|
||||
await message.channel.send(f"{warned_count} utilisateur(s) averti(s).")
|
||||
Loading…
Add table
Add a link
Reference in a new issue