Beta/commandes/security/warn.py

199 lines
9.9 KiB
Python
Raw Permalink Normal View History

2026-02-06 22:23:20 +01:00
import discord
import json
import os
import aiohttp
from datetime import datetime
WARNINGS_FILE = 'data/warnings.json'
2026-02-06 22:23:20 +01:00
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
ollama_url = os.environ.get("OLLAMA_BASE_URL", "http://localhost:11434") + "/api/generate"
ollama_model = os.environ.get("OLLAMA_MODEL", "gpt-oss:20b")
rephrased_reason = await rephrase_reason(original_reason, ollama_url, "", ollama_model, 30, 0.7)
2026-02-06 22:23:20 +01:00
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, ollama_url, "", ollama_model, 30, 0.7)
2026-02-06 22:23:20 +01:00
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).")