The begining

This commit is contained in:
Mathis 2026-01-01 18:48:25 +01:00
commit dee311e709
71 changed files with 8349 additions and 0 deletions

View file

@ -0,0 +1,153 @@
import discord
from discord import app_commands
from discord.ext import commands
import json
import os
# --------- Config ---------
BLACKLIST_FILE = "data/blacklist.json"
AGENTS_FILE = "data/agents.json"
TICKETS_FILE = "data/tickets_blacklist.json"
MAIN_GUILD_ID = 1369669999345537145 # ID du serveur principal
# --------- Utils JSON ---------
def load_json(path, default):
if not os.path.exists(path):
with open(path, "w") as f:
json.dump(default, f, indent=4)
with open(path, "r") as f:
return json.load(f)
def save_json(path, data):
with open(path, "w") as f:
json.dump(data, f, indent=4)
# Initialisation fichiers
blacklist = load_json(BLACKLIST_FILE, {})
tickets = load_json(TICKETS_FILE, {})
# --------- Cog ---------
class Blacklist(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
def is_agent(self, user_id: int):
agents = load_json(AGENTS_FILE, {"agents": []})
return str(user_id) in agents["agents"]
# ---------- Slash Commands ----------
@app_commands.command(name="blacklist", description="Blacklist un utilisateur")
@app_commands.describe(user="Utilisateur à blacklister", reason="Raison")
async def blacklist_slash(self, interaction: discord.Interaction, user: discord.User, reason: str = "Aucune raison fournie"):
if not self.is_agent(interaction.user.id):
return await interaction.response.send_message("⛔ Tu nas pas la permission.", ephemeral=True)
blacklist[str(user.id)] = {"reason": reason}
save_json(BLACKLIST_FILE, blacklist)
try:
view = TicketButton(user.id)
await user.send(f"🚫 Tu as été blacklisté.\n**Raison :** {reason}", view=view)
except:
return await interaction.response.send_message("Impossible denvoyer un MP à lutilisateur.", ephemeral=True)
await interaction.response.send_message(f"{user.mention} a été ajouté à la blacklist.", ephemeral=True)
@app_commands.command(name="unblacklist", description="Retire un utilisateur de la blacklist")
@app_commands.describe(user="Utilisateur à retirer")
async def unblacklist_slash(self, interaction: discord.Interaction, user: discord.User):
if not self.is_agent(interaction.user.id):
return await interaction.response.send_message("⛔ Tu nas pas la permission.", ephemeral=True)
if str(user.id) in blacklist:
del blacklist[str(user.id)]
save_json(BLACKLIST_FILE, blacklist)
await interaction.response.send_message(f"{user.mention} a été retiré de la blacklist.", ephemeral=True)
else:
await interaction.response.send_message("❌ Cet utilisateur nest pas blacklisté.", ephemeral=True)
# ---------- Listener pour nouveau MP ou notification ----------
@commands.Cog.listener()
async def on_member_join(self, member):
if str(member.id) in blacklist:
try:
view = TicketButton(member.id)
await member.send(
"🚫 Tu es blacklisté de ce serveur affilié. "
"Tu peux demander plus dinformations via le serveur principal.",
view=view
)
except:
pass
# --------- Boutons ----------
class TicketButton(discord.ui.View):
def __init__(self, user_id: int):
super().__init__(timeout=None)
self.user_id = user_id
@discord.ui.button(label="📩 Ouvrir une demande de déblacklist", style=discord.ButtonStyle.danger)
async def open_ticket(self, interaction: discord.Interaction, button: discord.ui.Button):
if interaction.user.id != self.user_id:
return await interaction.response.send_message("❌ Ce bouton ne tappartient pas.", ephemeral=True)
if str(self.user_id) in tickets:
return await interaction.response.send_message("❌ Tu as déjà ouvert une demande.", ephemeral=True)
# Création du ticket (en MP avec le bot)
channel = await interaction.user.create_dm()
# Sauvegarde
tickets[str(self.user_id)] = {"dm_channel_id": channel.id}
save_json(TICKETS_FILE, tickets)
view = CloseTicketView(self.user_id)
await channel.send(
f"📩 Ticket ouvert par {interaction.user.mention}\n"
"Un agent Omega Kube va bientôt te répondre.",
view=view
)
await interaction.response.send_message("✅ Ton ticket a été créé en MP avec le bot.", ephemeral=True)
class CloseTicketView(discord.ui.View):
def __init__(self, user_id: int):
super().__init__(timeout=None)
self.user_id = user_id
@discord.ui.button(label="❌ Fermer le ticket", style=discord.ButtonStyle.danger)
async def close_ticket(self, interaction: discord.Interaction, button: discord.ui.Button):
agents = load_json(AGENTS_FILE, {"agents": []})
if str(interaction.user.id) not in agents["agents"]:
return await interaction.response.send_message("⛔ Seuls les agents Omega Kube peuvent fermer un ticket.", ephemeral=True)
view = ConfirmCloseView(self.user_id)
await interaction.response.send_message("⚠️ Veux-tu vraiment fermer ce ticket ?", view=view, ephemeral=True)
class ConfirmCloseView(discord.ui.View):
def __init__(self, user_id: int):
super().__init__(timeout=30)
self.user_id = user_id
@discord.ui.button(label="✅ Oui", style=discord.ButtonStyle.success)
async def confirm(self, interaction: discord.Interaction, button: discord.ui.Button):
agents = load_json(AGENTS_FILE, {"agents": []})
if str(interaction.user.id) not in agents["agents"]:
return await interaction.response.send_message("⛔ Tu nas pas la permission.", ephemeral=True)
if str(self.user_id) in tickets:
del tickets[str(self.user_id)]
save_json(TICKETS_FILE, tickets)
await interaction.channel.send("✅ Ticket fermé.")
await interaction.channel.delete()
@discord.ui.button(label="❌ Non", style=discord.ButtonStyle.secondary)
async def cancel(self, interaction: discord.Interaction, button: discord.ui.Button):
await interaction.response.send_message("❌ Fermeture annulée.", ephemeral=True)
# --------- Setup Cog ----------
async def setup(bot: commands.Bot):
cog = Blacklist(bot)
await bot.add_cog(cog)
await bot.tree.sync(guild=discord.Object(id=MAIN_GUILD_ID))

2
commandes/__init__.py Executable file
View file

@ -0,0 +1,2 @@
# Dossier des commandes pour Kuby
# Ce dossier contient toutes les commandes et cogs du bot

276
commandes/backup.py Executable file
View file

@ -0,0 +1,276 @@
# commandes/backup.py
import os
import io
import json
import zipfile
import asyncio
from datetime import datetime
from typing import Dict, Any, List
import discord
from discord.ext import commands
from discord import app_commands
BACKUPS_ROOT = "data/security_backups" # dossier de sortie des sauvegardes
def ensure_dir(path: str) -> None:
os.makedirs(path, exist_ok=True)
def human_dt() -> str:
return datetime.now().strftime("%Y%m%d_%H%M%S")
def progress_bar(pct: float, width: int = 20) -> str:
pct = max(0.0, min(1.0, pct))
filled = int(round(width * pct))
return f"[{''*filled}{''*(width-filled)}] {int(pct*100)}%"
def estimate_duration_seconds(
nb_roles: int,
nb_categories: int,
nb_text_channels: int,
nb_members: int,
save_messages: bool,
message_limit_per_channel: int,
) -> int:
meta_cost = nb_roles * 0.02 + nb_categories * 0.03 + nb_text_channels * 0.05 + nb_members * 0.01
if save_messages:
pages_per_chan = max(1, (message_limit_per_channel + 99) // 100)
msg_cost = nb_text_channels * pages_per_chan * 0.12
else:
msg_cost = 0.0
base_overhead = 3.0
return int(meta_cost + msg_cost + base_overhead)
def serialize_overwrites(channel: discord.abc.GuildChannel) -> List[Dict[str, Any]]:
serialized: List[Dict[str, Any]] = []
for target, perms in channel.overwrites.items():
target_type = "role" if isinstance(target, discord.Role) else "member"
serialized.append({
"target_type": target_type,
"target_id": target.id,
"permissions": perms._values
})
return serialized
class Backup(commands.Cog):
"""Sauvegarde de serveur avec estimation & progression."""
def __init__(self, bot: commands.Bot):
self.bot = bot
backup_group = app_commands.Group(name="backup", description="Outils de sauvegarde du serveur")
@backup_group.command(name="create", description="Créer une sauvegarde du serveur")
@app_commands.describe(
save_messages="Inclure l'export des messages",
privacy_notice="Afficher un avertissement public si messages sauvegardés",
message_limit_per_channel="Nombre max de messages à exporter par salon (0 = tous)"
)
async def backup_create(
self,
interaction: discord.Interaction,
save_messages: bool = True,
privacy_notice: bool = True,
message_limit_per_channel: int = 10000
):
guild = interaction.guild
assert guild is not None, "Cette commande doit être utilisée dans un serveur."
# Vérification de la whitelist
whitelist_cog = self.bot.get_cog("whitelist")
if not whitelist_cog:
await interaction.response.send_message("❌ Cog Whitelist introuvable.", ephemeral=True)
return
if not whitelist_cog.is_whitelisted(guild.id, interaction.user.id):
await interaction.response.send_message(
"❌ Vous n'êtes pas dans la Whitelist.", ephemeral=True
)
return
# Déferre l'interaction
await interaction.response.defer(ephemeral=True, thinking=True)
# Avertissement de confidentialité
if privacy_notice and save_messages:
try:
await interaction.channel.send(
embed=discord.Embed(
title="📢 Information de confidentialité (transparence)",
description=(
"Une **sauvegarde du serveur** est en cours et **inclus les messages**.\n"
"Vous êtes dans l'obligation de prévenir les membres."
),
color=discord.Color.orange()
)
)
except Exception:
pass
# Préparation chemins
ts = human_dt()
base_dir = os.path.join(BACKUPS_ROOT, f"{guild.id}_{ts}")
ensure_dir(base_dir)
ensure_dir(os.path.join(base_dir, "messages"))
# Estimation
nb_roles = len(guild.roles)
nb_categories = len(guild.categories)
nb_text_channels = len(guild.text_channels)
nb_members = guild.member_count or len(guild.members)
if message_limit_per_channel < 0:
message_limit_per_channel = 0
estimation_s = estimate_duration_seconds(
nb_roles, nb_categories, nb_text_channels, nb_members, save_messages,
message_limit_per_channel if message_limit_per_channel else 100000
)
prog_embed = discord.Embed(
title="🗄️ Sauvegarde en cours",
description=(
f"**Serveur :** {guild.name} (`{guild.id}`)\n"
f"**Estimation :** ~{estimation_s} secondes\n"
f"**Options** : messages={'oui' if save_messages else 'non'} • limite/chan={message_limit_per_channel or 'tous'}"
),
color=discord.Color.blurple()
)
prog_embed.add_field(name="Progression", value=progress_bar(0.0), inline=False)
progress_msg = await interaction.followup.send(embed=prog_embed, ephemeral=False)
# ================= EXPORT =================
weights = {"roles":0.08,"categories":0.12,"channels":0.20,"members":0.10,"messages":0.10 if save_messages else 0.0,"zip":0.10}
done = 0.0
async def bump(step_key:str, extra_pct:float=0.0,note:str=""):
nonlocal done
done += weights.get(step_key,0.0)+extra_pct
done = min(done,0.999)
prog_embed.set_field_at(0,name="Progression",value=progress_bar(done),inline=False)
if note:
prog_embed.set_footer(text=note)
await progress_msg.edit(embed=prog_embed)
# --- RÔLES ---
roles_dump = [{"id": r.id,"name": r.name,"color": r.color.value,"hoist": r.hoist,"mentionable": r.mentionable,"permissions": r.permissions.value,"position": r.position} for r in guild.roles]
with open(os.path.join(base_dir,"roles.json"),"w",encoding="utf-8") as f:
json.dump(roles_dump,f,indent=2,ensure_ascii=False)
await bump("roles", note="Rôles exportés")
# --- CATÉGORIES ---
cats_dump = [{"id": c.id,"name": c.name,"position": c.position,"nsfw": getattr(c,"nsfw",False),"overwrites": serialize_overwrites(c)} for c in guild.categories]
with open(os.path.join(base_dir,"categories.json"),"w",encoding="utf-8") as f:
json.dump(cats_dump,f,indent=2,ensure_ascii=False)
await bump("categories", note="Catégories exportées")
# --- SALONS ---
chans_dump=[]
for ch in guild.channels:
base={"id":ch.id,"name":ch.name,"type":str(ch.type),"position":ch.position,"category_id":ch.category.id if getattr(ch,"category",None) else None,"overwrites":serialize_overwrites(ch)}
if isinstance(ch,discord.TextChannel):
base.update({"topic":ch.topic,"nsfw":ch.nsfw,"slowmode_delay":ch.slowmode_delay,"default_auto_archive_duration":ch.default_auto_archive_duration})
elif isinstance(ch,discord.VoiceChannel):
base.update({"bitrate":ch.bitrate,"user_limit":ch.user_limit,"rtc_region":str(ch.rtc_region) if ch.rtc_region else None})
elif isinstance(ch,discord.ForumChannel):
base.update({"nsfw":ch.nsfw,"default_auto_archive_duration":ch.default_auto_archive_duration})
chans_dump.append(base)
with open(os.path.join(base_dir,"channels.json"),"w",encoding="utf-8") as f:
json.dump(chans_dump,f,indent=2,ensure_ascii=False)
await bump("channels", note="Salons exportés")
# --- MEMBRES ---
members_dump=[]
for m in guild.members:
members_dump.append({"id":m.id,"name":str(m),"nick":m.nick,"roles":[r.id for r in m.roles if r!=guild.default_role],"bot":m.bot,"joined_at":m.joined_at.isoformat() if m.joined_at else None})
with open(os.path.join(base_dir,"members.json"),"w",encoding="utf-8") as f:
json.dump(members_dump,f,indent=2,ensure_ascii=False)
await bump("members", note="Membres exportés")
# --- MESSAGES ---
if save_messages:
text_chans=list(guild.text_channels)
total_tc=len(text_chans)
per_chan_weight=weights["messages"]/max(1,total_tc)
exported_total=0
for idx,tc in enumerate(text_chans,start=1):
file_path=os.path.join(base_dir,"messages",f"{tc.id}.jsonl")
count=0
with open(file_path,"w",encoding="utf-8") as f:
try:
async for msg in tc.history(limit=message_limit_per_channel if message_limit_per_channel>0 else None, oldest_first=True):
data={"id":msg.id,"channel_id":msg.channel.id,"author_id":msg.author.id,"author_bot":msg.author.bot,"created_at":msg.created_at.isoformat(),"content":msg.content,"attachments":[a.url for a in msg.attachments],"embeds":[e.to_dict() for e in msg.embeds],"reference":{"message_id":getattr(msg.reference,"message_id",None),"channel_id":getattr(msg.reference,"channel_id",None)} if msg.reference else None}
f.write(json.dumps(data,ensure_ascii=False)+"\n")
count+=1
except discord.Forbidden: pass
except Exception: pass
exported_total+=count
done+=per_chan_weight
prog_embed.set_field_at(0,name="Progression",value=progress_bar(min(done,0.999)),inline=False)
prog_embed.set_footer(text=f"Messages exportés : {exported_total} | Salon {idx}/{total_tc}")
await progress_msg.edit(embed=prog_embed)
# --- ZIP FINAL ---
zip_path=os.path.join(BACKUPS_ROOT,f"{guild.id}_{ts}.zip")
try:
with zipfile.ZipFile(zip_path,"w",compression=zipfile.ZIP_DEFLATED,compresslevel=6) as zf:
for root,_,files in os.walk(base_dir):
for name in files:
full=os.path.join(root,name)
rel=os.path.relpath(full,base_dir)
zf.write(full,rel)
except Exception as exc:
prog_embed.color=discord.Color.red()
prog_embed.set_field_at(0,name="Progression",value=progress_bar(1.0),inline=False)
prog_embed.set_footer(text=f"Erreur lors de la création du ZIP : {exc}")
await progress_msg.edit(embed=prog_embed)
await interaction.followup.send("❌ Erreur lors de la compression ZIP. Si l'erreur persiste veuillez ouvrir un ticket sur Omega Kube", ephemeral=True)
return
# Finalisation
prog_embed.color=discord.Color.green()
prog_embed.set_field_at(0,name="Progression",value=progress_bar(1.0),inline=False)
prog_embed.add_field(name="Archive",value=f"`{zip_path}`",inline=False)
prog_embed.set_footer(text="Sauvegarde terminée ✅")
await progress_msg.edit(embed=prog_embed)
await interaction.followup.send("🎉 Sauvegarde terminée. Un message de progression avec le chemin de larchive a été publié.", ephemeral=True)
@backup_group.command(name="list", description="Lister les archives de sauvegarde disponibles")
async def backup_list(self, interaction: discord.Interaction):
guild = interaction.guild
assert guild is not None, "Cette commande doit être utilisée dans un serveur."
whitelist_cog = self.bot.get_cog("whitelist")
if not whitelist_cog:
await interaction.response.send_message("❌ Cog Whitelist introuvable.", ephemeral=True)
return
if not whitelist_cog.is_whitelisted(guild.id, interaction.user.id):
await interaction.response.send_message(
"❌ Vous n'êtes pas autorisé à utiliser cette commande.", ephemeral=True
)
return
await interaction.response.defer(ephemeral=True, thinking=True)
ensure_dir(BACKUPS_ROOT)
files = [f for f in os.listdir(BACKUPS_ROOT) if f.endswith(".zip")]
files.sort(reverse=True)
if not files:
await interaction.followup.send("Aucune archive trouvée dans `backups/`.", ephemeral=True)
return
desc="\n".join(f"• `{name}`" for name in files[:20])
embed=discord.Embed(title="📦 Archives disponibles",description=desc,color=discord.Color.blurple())
if len(files)>20:
embed.set_footer(text=f"... et {len(files)-20} de plus")
await interaction.followup.send(embed=embed,ephemeral=True)
async def setup(bot: commands.Bot):
await bot.add_cog(Backup(bot))

204
commandes/backup_restore.py Executable file
View file

@ -0,0 +1,204 @@
# commandes/backup_restore.py
import os
import json
import zipfile
import tempfile
import shutil
from typing import List
import discord
from discord import app_commands
from discord.ext import commands
BACKUPS_ROOT = "data/security_backups"
def ensure_dir(path: str):
os.makedirs(path, exist_ok=True)
class BackupRestore(commands.Cog):
"""Restauration granulaire d'une sauvegarde."""
def __init__(self, bot: commands.Bot):
self.bot = bot
async def check_whitelisted(self, guild_id: int, user_id: int) -> bool:
"""Vérifie si un utilisateur est whitelisté."""
# Recherche dynamique dun cog ayant une méthode is_whitelisted
for cog in self.bot.cogs.values():
if hasattr(cog, "is_whitelisted"):
return cog.is_whitelisted(guild_id, user_id)
raise RuntimeError("Cog Whitelist non chargé ou mal nommé !")
async def list_backup_elements(self, backup_path: str) -> List[str]:
"""Liste les éléments exportables dans une archive ou dossier de backup."""
elements = []
if os.path.isdir(backup_path):
for f in os.listdir(backup_path):
if f.endswith(".json") or os.path.isdir(os.path.join(backup_path, f)):
elements.append(f.replace(".json", ""))
elif os.path.isfile(backup_path) and backup_path.endswith(".zip"):
with zipfile.ZipFile(backup_path, "r") as zf:
for f in zf.namelist():
base = f.split("/")[0]
if base and base not in elements:
elements.append(base)
return elements
async def element_autocomplete(self, interaction: discord.Interaction, current: str):
"""Complétion automatique pour l'option 'element'."""
guild = interaction.guild
if guild is None:
return []
ensure_dir(BACKUPS_ROOT)
files = [f for f in os.listdir(BACKUPS_ROOT) if f.startswith(str(guild.id))]
files.sort(reverse=True)
if not files:
return []
last_backup = files[0]
backup_path = os.path.join(BACKUPS_ROOT, last_backup)
elements = await self.list_backup_elements(backup_path)
return [app_commands.Choice(name=e, value=e) for e in elements if current.lower() in e.lower()][:25]
async def restore_roles(self, guild: discord.Guild, backup_path: str):
"""Restaure les rôles à partir du backup."""
roles_file = os.path.join(backup_path, "roles.json")
if os.path.isfile(roles_file):
with open(roles_file, "r", encoding="utf-8") as f:
roles_data = json.load(f)
for r in roles_data:
try:
await guild.create_role(
name=r["name"],
permissions=discord.Permissions(r["permissions"]),
color=discord.Color(r.get("color", 0)),
hoist=r.get("hoist", False),
mentionable=r.get("mentionable", False),
reason="Restauration backup"
)
except discord.Forbidden:
continue
async def restore_channels(self, guild: discord.Guild, backup_path: str):
"""Restaure les channels à partir du backup."""
channels_file = os.path.join(backup_path, "channels.json")
if os.path.isfile(channels_file):
with open(channels_file, "r", encoding="utf-8") as f:
channels_data = json.load(f)
for c in channels_data:
try:
if c["type"] == 0: # text
await guild.create_text_channel(
name=c["name"],
category=guild.get_channel(c.get("category_id")),
topic=c.get("topic", ""),
nsfw=c.get("nsfw", False),
reason="Restauration backup"
)
elif c["type"] == 2: # voice
await guild.create_voice_channel(
name=c["name"],
category=guild.get_channel(c.get("category_id")),
bitrate=c.get("bitrate", 64000),
user_limit=c.get("user_limit", 0),
reason="Restauration backup"
)
except discord.Forbidden:
continue
async def restore_messages(self, channel: discord.TextChannel, backup_path: str):
"""Restaure les messages à partir du backup (solution ZIP incluse)."""
if backup_path.endswith(".zip"):
temp_dir = tempfile.mkdtemp()
try:
with zipfile.ZipFile(backup_path, "r") as zip_ref:
zip_ref.extractall(temp_dir)
messages_file = os.path.join(temp_dir, "messages.json")
if not os.path.exists(messages_file):
return
with open(messages_file, "r", encoding="utf-8") as f:
messages = json.load(f)
for msg in messages:
await channel.send(msg["content"])
finally:
shutil.rmtree(temp_dir)
else:
messages_file = os.path.join(backup_path, "messages.json")
if not os.path.exists(messages_file):
return
with open(messages_file, "r", encoding="utf-8") as f:
messages = json.load(f)
for msg in messages:
await channel.send(msg["content"])
@app_commands.command(name="granulaire", description="Restaure un élément précis d'une sauvegarde")
@app_commands.describe(
backup="Nom ou ID de la sauvegarde",
element="Élément à restaurer (roles, channels, messages...)"
)
@app_commands.autocomplete(element=element_autocomplete)
async def restore_granulaire(
self,
interaction: discord.Interaction,
backup: str,
element: str
):
guild = interaction.guild
if guild is None:
await interaction.response.send_message("Cette commande doit être utilisée dans un serveur.", ephemeral=True)
return
if not await self.check_whitelisted(guild.id, interaction.user.id):
await interaction.response.send_message(
"❌ Vous n'êtes pas autorisé à utiliser cette commande.",
ephemeral=True
)
return
backup_path = os.path.join(BACKUPS_ROOT, backup)
if not os.path.exists(backup_path):
await interaction.response.send_message(f"❌ La sauvegarde `{backup}` est introuvable.", ephemeral=True)
return
await interaction.response.defer(ephemeral=True, thinking=True)
available_elements = await self.list_backup_elements(backup_path)
if element not in available_elements:
await interaction.followup.send(
f"❌ Élément `{element}` introuvable dans la sauvegarde.",
ephemeral=True
)
return
# Détection si cest un zip pour extraire temporairement
temp_path = backup_path
if backup_path.endswith(".zip"):
temp_path = tempfile.mkdtemp()
with zipfile.ZipFile(backup_path, "r") as zip_ref:
zip_ref.extractall(temp_path)
try:
if element == "roles":
await self.restore_roles(guild, temp_path)
elif element == "channels":
await self.restore_channels(guild, temp_path)
elif element == "messages":
# Restaure messages dans tous les channels textuels ?
for ch in guild.text_channels:
await self.restore_messages(ch, temp_path)
else:
await interaction.followup.send(f"⚠️ Élément `{element}` non géré par la restauration automatique.", ephemeral=True)
return
finally:
if backup_path.endswith(".zip"):
shutil.rmtree(temp_path)
await interaction.followup.send(
f"✅ Restauration granulaire de `{element}` depuis la sauvegarde `{backup}` terminée.",
ephemeral=True
)
async def setup(bot: commands.Bot):
await bot.add_cog(BackupRestore(bot))

279
commandes/blacklist.py Executable file
View file

@ -0,0 +1,279 @@
import discord
from discord.ext import commands, tasks
from discord import app_commands
import json
import os
import asyncio
from datetime import datetime, timedelta
BLACKLIST_FILE = "data/blacklist.json"
AGENTS_FILE = "data/agents.json"
TICKETS_FILE = "data/tickets_blacklist.json"
MAIN_GUILD_ID = 1369669999345537145 # ID serveur principal
TICKET_CATEGORY_ID = 1369669999677145104 # Catégorie tickets
# --------- Utils JSON ---------
def load_json(path, default):
if not os.path.exists(path):
with open(path, "w") as f:
json.dump(default, f, indent=4)
with open(path, "r") as f:
return json.load(f)
def save_json(path, data):
with open(path, "w") as f:
json.dump(data, f, indent=4)
# --------- Cog ---------
class Blacklist(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
self.check_ban_expiry.start() # boucle auto pour bannir après 7j
def cog_unload(self):
self.check_ban_expiry.cancel()
def is_agent(self, user_id: int):
agents = load_json(AGENTS_FILE, {"agents": []})
return str(user_id) in agents["agents"]
@app_commands.command(name="blacklist", description="Blacklist un utilisateur")
async def blacklist(self, interaction: discord.Interaction, user: discord.User, reason: str = "Aucune raison fournie"):
if not self.is_agent(interaction.user.id):
return await interaction.response.send_message("⛔ Tu nas pas la permission.", ephemeral=True)
blacklist = load_json(BLACKLIST_FILE, {})
blacklist[str(user.id)] = {
"reason": reason,
"timestamp": datetime.utcnow().isoformat()
}
save_json(BLACKLIST_FILE, blacklist)
# Préparer l'embed pro
embed = discord.Embed(
title="🚫 Mise en blacklist - Réseau Omega Kube",
description=(
f"Bonjour {user.mention},\n\n"
f"Vous avez été **blacklisté** du **Réseau Omega Kube**.\n\n"
f"**Raison :** {reason}\n\n"
"⚠️ Vous êtes banni de tous nos serveurs partenaires. "
"Cependant, vous restez présent sur le **serveur principal**, "
"mais tu ne peux pas interagir avec la communauté.\n\n"
"📩 Vous disposez de **7 jours** pour contester cette décision en ouvrant un ticket via le bouton ci-dessous.\n\n"
"⏳ Passé ce délai, vous serez **définitivement banni de tout le réseau**, sans autre recours possible."
),
color=discord.Color.red()
)
embed.set_footer(text="Omega Kube - Nixfix06 & Mgstudios")
view = TicketButton(user.id)
# Envoyer MP avant action
try:
await user.send(embed=embed, view=view)
await asyncio.sleep(2)
except:
pass
# Bannir de tous les serveurs sauf principal
for guild in self.bot.guilds:
if guild.id == MAIN_GUILD_ID:
member = guild.get_member(user.id)
if member:
try:
# Supprime la permission d'écrire partout
for channel in guild.channels:
overwrite = channel.overwrites_for(member)
overwrite.send_messages = False
overwrite.add_reactions = False
await channel.set_permissions(member, overwrite=overwrite)
except:
pass
else:
member = guild.get_member(user.id)
if member:
try:
await member.ban(reason=f"Blacklisté : {reason}")
except:
pass
await interaction.response.send_message(
f"{user.mention} blacklisté, banni des partenaires et restreint sur le serveur principal.",
ephemeral=True
)
@app_commands.command(name="unblacklist", description="Retirer un utilisateur de la blacklist")
async def unblacklist(self, interaction: discord.Interaction, user: discord.User):
if not self.is_agent(interaction.user.id):
return await interaction.response.send_message("⛔ Tu nas pas la permission.", ephemeral=True)
blacklist = load_json(BLACKLIST_FILE, {})
if str(user.id) in blacklist:
del blacklist[str(user.id)]
save_json(BLACKLIST_FILE, blacklist)
await interaction.response.send_message(f"{user.mention} retiré de la blacklist.", ephemeral=True)
else:
await interaction.response.send_message("❌ Cet utilisateur nest pas blacklisté.", ephemeral=True)
@tasks.loop(hours=1)
async def check_ban_expiry(self):
"""Boucle qui vérifie si 7 jours sont écoulés → ban total."""
blacklist = load_json(BLACKLIST_FILE, {})
to_remove = []
for user_id, data in blacklist.items():
try:
ts = datetime.fromisoformat(data["timestamp"])
if datetime.utcnow() > ts + timedelta(days=7):
# Ban définitif partout
user = await self.bot.fetch_user(int(user_id))
for guild in self.bot.guilds:
member = guild.get_member(int(user_id))
if member:
try:
await member.ban(reason="Blacklist définitive (7j écoulés)")
except:
pass
to_remove.append(user_id)
except:
continue
for uid in to_remove:
del blacklist[uid]
save_json(BLACKLIST_FILE, blacklist)
@commands.Cog.listener()
async def on_message(self, message: discord.Message):
if message.author.bot:
return
tickets = load_json(TICKETS_FILE, {})
if isinstance(message.channel, discord.DMChannel):
user_id = str(message.author.id)
if user_id in tickets:
guild = self.bot.get_guild(MAIN_GUILD_ID)
ticket_channel = guild.get_channel(tickets[user_id]["channel_id"])
if ticket_channel:
embed = discord.Embed(
description=message.content,
color=discord.Color.blue()
).set_author(name=message.author, icon_url=message.author.display_avatar.url)
await ticket_channel.send(embed=embed)
else:
for user_id, data in tickets.items():
if message.channel.id == data["channel_id"] and self.is_agent(message.author.id):
user = await self.bot.fetch_user(int(user_id))
try:
embed = discord.Embed(
description=message.content,
color=discord.Color.green()
).set_author(name=f"Agent {message.author}", icon_url=message.author.display_avatar.url)
await user.send(embed=embed)
except:
pass
# --------- Boutons ---------
class TicketButton(discord.ui.View):
def __init__(self, user_id: int):
super().__init__(timeout=None)
self.user_id = user_id
@discord.ui.button(label="📩 Ouvrir une demande de révision", style=discord.ButtonStyle.primary)
async def open_ticket(self, interaction: discord.Interaction, button: discord.ui.Button):
if interaction.user.id != self.user_id:
return await interaction.response.send_message("❌ Ce bouton ne tappartient pas.", ephemeral=True)
tickets = load_json(TICKETS_FILE, {})
if str(self.user_id) in tickets:
return await interaction.response.send_message("❌ Tu as déjà un ticket ouvert.", ephemeral=True)
guild = interaction.client.get_guild(MAIN_GUILD_ID)
category = guild.get_channel(TICKET_CATEGORY_ID)
ticket_channel = await guild.create_text_channel(
name=f"ticket-{self.user_id}",
category=category,
topic=f"Ticket de l'utilisateur {self.user_id}"
)
tickets[str(self.user_id)] = {"channel_id": ticket_channel.id}
save_json(TICKETS_FILE, tickets)
view_close = CloseTicketView(self.user_id)
embed = discord.Embed(
title="📩 Ticket ouvert",
description=f"Un utilisateur blacklisté (<@{self.user_id}>) a ouvert une demande de révision.\n\nUn agent Omega Kube prendra en charge ce dossier.",
color=discord.Color.blurple()
)
await ticket_channel.send(embed=embed, view=view_close)
await interaction.response.send_message("✅ Ton ticket a bien été créé.", ephemeral=True)
button.disabled = True
self.stop()
class CloseTicketView(discord.ui.View):
def __init__(self, user_id: int):
super().__init__(timeout=None)
self.user_id = user_id
@discord.ui.button(label="❌ Fermer le ticket", style=discord.ButtonStyle.danger)
async def close_ticket(self, interaction: discord.Interaction, button: discord.ui.Button):
if not interaction.client.get_cog("Blacklist").is_agent(interaction.user.id):
return await interaction.response.send_message("⛔ Seuls les agents peuvent fermer ce ticket.", ephemeral=True)
view = ConfirmCloseView(self.user_id)
await interaction.response.send_message("⚠️ Veux-tu vraiment fermer ce ticket ?", view=view, ephemeral=True)
class ConfirmCloseView(discord.ui.View):
def __init__(self, user_id: int):
super().__init__(timeout=30)
self.user_id = user_id
@discord.ui.button(label="✅ Oui", style=discord.ButtonStyle.success)
async def confirm(self, interaction: discord.Interaction, button: discord.ui.Button):
if not interaction.client.get_cog("Blacklist").is_agent(interaction.user.id):
return await interaction.response.send_message("⛔ Tu nas pas la permission.", ephemeral=True)
tickets = load_json(TICKETS_FILE, {})
if str(self.user_id) in tickets:
channel_id = tickets[str(self.user_id)]["channel_id"]
guild = interaction.guild
channel = guild.get_channel(channel_id)
if channel:
await channel.delete()
del tickets[str(self.user_id)]
save_json(TICKETS_FILE, tickets)
user = await interaction.client.fetch_user(int(self.user_id))
try:
embed = discord.Embed(
title="🔒 Ticket fermé",
description="Ton ticket a été examiné et fermé.\n\n⚠️ Après analyse, la sanction est confirmée et tu restes blacklisté de lensemble du réseau.",
color=discord.Color.orange()
)
await user.send(embed=embed)
await asyncio.sleep(2)
except:
pass
for guild in interaction.client.guilds:
member = guild.get_member(self.user_id)
if member:
try:
await member.ban(reason="Ticket blacklist fermé")
except:
pass
await interaction.response.send_message("✅ Ticket fermé avec succès.", ephemeral=True)
@discord.ui.button(label="❌ Non", style=discord.ButtonStyle.secondary)
async def cancel(self, interaction: discord.Interaction, button: discord.ui.Button):
await interaction.response.send_message("❌ Fermeture annulée.", ephemeral=True)
# --------- Setup ---------
async def setup(bot: commands.Bot):
await bot.add_cog(Blacklist(bot))

17
commandes/fun.py Executable file
View file

@ -0,0 +1,17 @@
import discord
from discord import app_commands
from discord.ext import commands
import random
class FunCommands(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.emojis = ['😀', '😂', '😎', '😍', '😡', '🤖', '👻', '🎉', '🔥', '🌈', '🍕', '🍔', '🍟', '🚀', '🌟']
@app_commands.command(name="fun", description="Envoie 10 emojis fun aléatoires")
async def fun(self, interaction: discord.Interaction):
chosen_emojis = random.sample(self.emojis, 10)
await interaction.response.send_message(" ".join(chosen_emojis))
async def setup(bot):
await bot.add_cog(FunCommands(bot))

109
commandes/goodbye.py Normal file
View file

@ -0,0 +1,109 @@
import discord
from discord.ext import commands
from discord import app_commands
import sqlite3
import aiohttp
import io
import os
from PIL import Image, ImageDraw, ImageFont
from datetime import datetime, timezone
class Goodbye(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.db_path = os.path.join(os.path.dirname(__file__), "..", "config.db")
def humanize_time_delta(self, date):
if date is None: return "date inconnue"
if date.tzinfo is None: date = date.replace(tzinfo=timezone.utc)
now = datetime.now(timezone.utc)
delta = now - date
total_days = delta.total_seconds() / (3600 * 24)
rounded_days = round(total_days)
years = rounded_days // 365
months = rounded_days // 30
weeks = rounded_days // 7
days = rounded_days
if years > 0: return f"il y a {years} {'an' if years == 1 else 'ans'}"
elif months > 0: return f"il y a {months} mois"
elif weeks > 0: return f"il y a {weeks} {'semaine' if weeks == 1 else 'semaines'}"
elif days > 0: return f"il y a {days} {'jour' if days == 1 else 'jours'}"
elif delta.seconds // 3600 > 0:
hours = delta.seconds // 3600
return f"il y a {hours} {'heure' if hours == 1 else 'heures'}"
elif (delta.seconds % 3600) // 60 > 0:
minutes = (delta.seconds % 3600) // 60
return f"il y a {minutes} {'minute' if minutes == 1 else 'minutes'}"
else:
seconds = delta.seconds % 60
return f"il y a {seconds} {'seconde' if seconds <= 1 else 'secondes'}"
async def create_goodbye_image(self, username, avatar_url):
current_dir = os.path.dirname(__file__)
base_dir = os.path.abspath(os.path.join(current_dir, ".."))
async with aiohttp.ClientSession() as session:
async with session.get(avatar_url) as resp:
avatar_bytes = await resp.read()
background = Image.open(os.path.join(base_dir, 'background_template_goodbye.png'))
draw = ImageDraw.Draw(background)
font_large = ImageFont.truetype(os.path.join(base_dir, 'custom_font_large.otf'), 70)
font_small = ImageFont.truetype(os.path.join(base_dir, 'custom_font_small.otf'), 50)
font_large_omega = ImageFont.truetype(os.path.join(base_dir, 'custom_font_large.otf'), 55)
draw.text((325, 70), "A Bientot", font=font_large, fill=(255,255,255))
draw.text((325, 135), f"{username} chez", font=font_small, fill=(255,255,255))
draw.text((325, 195), "OMEGA KUBE", font=font_large_omega, fill=(255,255,255))
avatar = Image.open(io.BytesIO(avatar_bytes)).convert("RGBA").resize((224, 224), Image.LANCZOS)
mask = Image.new('L', avatar.size, 0)
mask_draw = ImageDraw.Draw(mask)
mask_draw.ellipse((12, 12, 212, 212), fill=255)
background.paste(avatar, (40, 40), mask)
img_byte_arr = io.BytesIO()
background.save(img_byte_arr, format='PNG')
img_byte_arr.seek(0)
return img_byte_arr
@commands.Cog.listener()
async def on_member_remove(self, member):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute('SELECT goodbye_channel FROM server_configs WHERE server_id = ?', (member.guild.id,))
result = cursor.fetchone()
conn.close()
if result and result[0]:
channel = member.guild.get_channel(result[0])
if channel:
img = await self.create_goodbye_image(member.name, member.avatar.url if member.avatar else member.default_avatar.url)
joined_message = self.humanize_time_delta(member.joined_at)
embed = discord.Embed(title=f"{member} viens de nous quitter.",
description="Nous espérons vous revoir bientôt <a:rain:1317973615714504806>",
color=discord.Color.dark_magenta())
embed.set_footer(text=f"Avait rejoint {joined_message}")
embed.set_image(url="attachment://goodbye.png")
await channel.send(embed=embed, file=discord.File(img, filename="goodbye.png"))
@app_commands.command(name="setgoodbye", description="Définit le salon d'aurevoir")
@app_commands.checks.has_permissions(administrator=True)
async def set_goodbye(self, interaction: discord.Interaction, channel: discord.TextChannel):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute('''INSERT INTO server_configs (server_id, goodbye_channel)
VALUES (?, ?) ON CONFLICT(server_id)
DO UPDATE SET goodbye_channel=excluded.goodbye_channel''',
(interaction.guild_id, channel.id))
conn.commit()
conn.close()
await interaction.response.send_message(f"Salon d'aurevoir défini sur {channel.mention}", ephemeral=True)
async def setup(bot):
await bot.add_cog(Goodbye(bot))

View file

@ -0,0 +1,60 @@
import discord
from discord.ext import commands
import json
import os
from datetime import datetime
SETTINGS_FILE = "data/security_settings.json"
WHITELIST_FILE = "data/whitelist.json"
def is_whitelisted(guild_id, user_id):
if not os.path.exists(WHITELIST_FILE): return False
try:
with open(WHITELIST_FILE, "r", encoding='utf-8') as f:
data = json.load(f)
return user_id in data.get(str(guild_id), [])
except: return False
class AntiRaid(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.join_cache = {}
@commands.Cog.listener()
async def on_member_join(self, member):
if member.bot: return
# 1. WHITELIST
if is_whitelisted(member.guild.id, member.id): return
# 2. LECTURE DYNAMIQUE
if not os.path.exists(SETTINGS_FILE): return
with open(SETTINGS_FILE, "r", encoding='utf-8') as f:
settings = json.load(f).get(str(member.guild.id), {})
if not settings.get("anti_raid"): return
guild_id = member.guild.id
now = datetime.now()
if guild_id not in self.join_cache: self.join_cache[guild_id] = []
self.join_cache[guild_id].append(now)
self.join_cache[guild_id] = [t for t in self.join_cache[guild_id] if (now - t).seconds < 10]
if len(self.join_cache[guild_id]) > settings.get("raid_threshold", 3):
await self.handle_raid(member.guild, settings)
async def handle_raid(self, guild, settings):
if settings.get("auto_ban"):
# Logique d'auto-ban des derniers membres ici
pass
log_channel_id = settings.get("log_channel_id")
if log_channel_id:
channel = guild.get_channel(int(log_channel_id))
if channel:
embed = discord.Embed(title="🚨 ALERTE RAID", description=f"Vague de {len(self.join_cache[guild.id])} membres !", color=0xFF0000)
await channel.send(embed=embed)
async def setup(bot):
await bot.add_cog(AntiRaid(bot))

View file

@ -0,0 +1,155 @@
import discord
from discord.ext import commands
import json
import os
import asyncio
from datetime import datetime
SETTINGS_FILE = "data/security_settings.json"
WHITELIST_FILE = "data/whitelist.json"
class AntiSpam(commands.Cog):
def __init__(self, bot):
self.bot = bot
# Compteurs pour le Spam
self.message_counts = {}
self.spam_warnings = {}
# Compteurs pour les Liens
self.link_warnings = {}
def is_whitelisted(self, guild_id, user_id):
"""Vérifie si l'utilisateur est protégé (Whitelist ou Owner)"""
# 1. Le propriétaire est toujours whitelist
guild = self.bot.get_guild(guild_id)
if guild and user_id == guild.owner_id:
return True
# 2. Vérification fichier Whitelist
if not os.path.exists(WHITELIST_FILE): return False
try:
with open(WHITELIST_FILE, "r", encoding='utf-8') as f:
data = json.load(f)
return user_id in data.get(str(guild_id), [])
except: return False
@commands.Cog.listener()
async def on_message(self, message):
if message.author.bot or not message.guild: return
# --- 1. IMMUNITÉ TOTALE ---
# Si Whitelisté, on ignore tout (Spam et Liens)
if self.is_whitelisted(message.guild.id, message.author.id):
return
# --- 2. CHARGEMENT CONFIG ---
if not os.path.exists(SETTINGS_FILE): return
with open(SETTINGS_FILE, "r", encoding='utf-8') as f:
settings = json.load(f).get(str(message.guild.id), {})
# =================================================================
# MODULE ANTI-LINK (3 Avertissements -> Mute)
# =================================================================
if settings.get("anti_link"):
# Liste des déclencheurs interdits
forbidden = ["discord.gg/", "http://", "https://", "www."]
# On vérifie si le message contient un lien interdit
if any(trigger in message.content.lower() for trigger in forbidden):
# Si l'auteur a la permission de gérer les messages, on laisse passer (optionnel, sinon retirer cette ligne)
if not message.author.guild_permissions.manage_messages:
await self.handle_link_violation(message)
return # On arrête là, pas besoin de vérifier le spam si le message est supprimé
# =================================================================
# MODULE ANTI-SPAM (3 Avertissements -> Mute)
# =================================================================
if settings.get("anti_spam"):
user_id = message.author.id
now = datetime.now().timestamp()
if user_id not in self.message_counts:
self.message_counts[user_id] = []
self.message_counts[user_id].append(now)
# Fenêtre de temps : 5 secondes
self.message_counts[user_id] = [t for t in self.message_counts[user_id] if now - t < 5]
# Seuil dynamique (défaut: 5)
limit = settings.get("spam_threshold", 5)
if len(self.message_counts[user_id]) > limit:
await self.handle_spam_violation(message)
# --- GESTION DES VIOLATIONS LIENS ---
async def handle_link_violation(self, message):
try:
await message.delete()
except:
pass # Message déjà supprimé ou manque de perms
user_id = message.author.id
warnings = self.link_warnings.get(user_id, 0) + 1
self.link_warnings[user_id] = warnings
if warnings <= 3:
embed = discord.Embed(
title="🔗 Liens Interdits",
description=f"{message.author.mention}, les liens ne sont pas autorisés.\n**Avertissement : {warnings}/3**",
color=0xFFA500
)
await message.channel.send(embed=embed, delete_after=5)
else:
await self.sanction_user(message, "Publication de liens interdits")
self.link_warnings[user_id] = 0 # Reset après sanction
# --- GESTION DES VIOLATIONS SPAM ---
async def handle_spam_violation(self, message):
user_id = message.author.id
# Nettoyage visuel du spam
try:
await message.channel.purge(limit=5, check=lambda m: m.author == message.author)
except: pass
warnings = self.spam_warnings.get(user_id, 0) + 1
self.spam_warnings[user_id] = warnings
# Reset le compteur de messages pour éviter le spam d'avertissements
self.message_counts[user_id] = []
if warnings <= 3:
embed = discord.Embed(
title="🛡️ Anti-Spam",
description=f"{message.author.mention}, veuillez cesser de spammer.\n**Avertissement : {warnings}/3**",
color=0xFFA500
)
await message.channel.send(embed=embed, delete_after=5)
else:
await self.sanction_user(message, "Spam excessif")
self.spam_warnings[user_id] = 0 # Reset après sanction
# --- SANCTION COMMUNE (TIMEOUT 5 MIN) ---
async def sanction_user(self, message, reason):
try:
# Durée : 5 minutes
duration = discord.utils.utcnow() + asyncio.timedelta(minutes=5)
# Application du Timeout (Exclusion temporaire)
await message.author.timeout(duration, reason=f"Kuby Security: {reason} (3 Warns)")
embed = discord.Embed(
title="⛔ Exclusion Temporaire",
description=f"**Utilisateur :** {message.author.mention}\n**Durée :** 5 minutes\n**Raison :** {reason} (Limite atteinte).",
color=0xFF0000
)
await message.channel.send(embed=embed)
except discord.Forbidden:
await message.channel.send("⚠️ Je n'ai pas la permission d'exclure ce membre (Vérifiez ma hiérarchie).")
except Exception as e:
print(f"Erreur Sanction: {e}")
async def setup(bot):
await bot.add_cog(AntiSpam(bot))

View file

@ -0,0 +1,62 @@
import discord
from discord.ext import commands
import json
import os
from datetime import datetime
SETTINGS_FILE = "data/security_settings.json"
def get_log_channel(guild, settings):
channel_id = settings.get("log_channel_id")
if channel_id:
return guild.get_channel(int(channel_id))
return None
class LogsManager(commands.Cog):
def __init__(self, bot):
self.bot = bot
@commands.Cog.listener()
async def on_message_delete(self, message):
if message.author.bot: return
settings = dict(json.load(open(SETTINGS_FILE, "r"))).get(str(message.guild.id), {})
log_chan = get_log_channel(message.guild, settings)
if log_chan:
embed = discord.Embed(
title="🗑️ Message Supprimé",
description=f"**Auteur :** {message.author.mention}\n**Salon :** {message.channel.mention}",
color=0xFFA500,
timestamp=datetime.utcnow()
)
embed.add_field(name="Contenu", value=message.content or "*(Image ou Embed)*")
await log_chan.send(embed=embed)
@commands.Cog.listener()
async def on_member_remove(self, member):
settings = dict(json.load(open(SETTINGS_FILE, "r"))).get(str(member.guild.id), {})
log_chan = get_log_channel(member.guild, settings)
if log_chan:
embed = discord.Embed(
title="📤 Départ d'un membre",
description=f"{member.mention} ({member.id}) a quitté le serveur.",
color=0xFF0000,
timestamp=datetime.utcnow()
)
await log_chan.send(embed=embed)
@commands.Cog.listener()
async def on_member_ban(self, guild, user):
settings = dict(json.load(open(SETTINGS_FILE, "r"))).get(str(guild.id), {})
log_chan = get_log_channel(guild, settings)
if log_chan:
embed = discord.Embed(
title="🔨 Bannissement",
description=f"**Utilisateur :** {user.mention} ({user.id}) a été banni.",
color=0x000000,
timestamp=datetime.utcnow()
)
await log_chan.send(embed=embed)
async def setup(bot):
await bot.add_cog(LogsManager(bot))

51
commandes/no_link.py Executable file
View file

@ -0,0 +1,51 @@
# utils/no_link.py
import re
import discord
from discord.ext import commands
import json
import os
# Fichier JSON de configuration security
SECURITY_FILE = "security.json"
def load_guild_security(guild_id: int) -> dict:
"""Retourne la configuration d'un serveur."""
if not os.path.isfile(SECURITY_FILE):
return {}
with open(SECURITY_FILE, "r") as f:
data = json.load(f)
return data.get(str(guild_id), {})
# Regex pour détecter les liens
URL_REGEX = re.compile(r"(?i)\b((?:https?://|www\.)[^\s<>]+)\b")
class NoLink(commands.Cog):
"""Supprime les messages contenant un lien si l'option anti_link est activée pour le serveur."""
def __init__(self, bot: commands.Bot) -> None:
self.bot = bot
@commands.Cog.listener()
async def on_message(self, message: discord.Message) -> None:
if message.author.bot or not message.guild:
return
guild_settings = load_guild_security(message.guild.id)
if not guild_settings.get("anti_link", False):
return # Option désactivée → ne rien faire
if URL_REGEX.search(message.content):
try:
await message.delete()
await message.channel.send(
f"{message.author.mention} ⚠️ Les liens sont interdits sur ce serveur.",
delete_after=5
)
except discord.Forbidden:
print(f"Impossible de supprimer le message de {message.author} (permissions manquantes).")
except Exception as exc:
print(f"Erreur lors de la suppression du message : {exc}")
async def setup(bot: commands.Bot) -> None:
await bot.add_cog(NoLink(bot))

82
commandes/scan.py Executable file
View file

@ -0,0 +1,82 @@
# scan.py
import discord
from discord import app_commands
from discord.ext import commands
import hashlib
import os
import json
import re
MALWARE_DB = "malware.json"
SUSPICIOUS_EXTENSIONS = [".exe", ".scr", ".bat", ".js", ".vbs", ".cmd"]
MALWARE_PATTERNS = [
rb"powershell\s+-nop",
rb"cmd\s+/c",
rb"eval\(",
rb"base64_decode\(",
rb"CreateObject\(\"WScript\.Shell\"\)"
]
def load_malware_db():
if not os.path.isfile(MALWARE_DB):
with open(MALWARE_DB, "w") as f:
json.dump([], f)
return []
with open(MALWARE_DB, "r") as f:
return json.load(f)
def get_file_hash(file_path):
sha256_hash = hashlib.sha256()
with open(file_path, "rb") as f:
for byte_block in iter(lambda: f.read(4096), b""):
sha256_hash.update(byte_block)
return sha256_hash.hexdigest()
def scan_file(file_path):
if not os.path.isfile(file_path):
return {"error": "Fichier introuvable."}
file_hash = get_file_hash(file_path)
malware_db = load_malware_db()
if file_hash in malware_db:
return {"hash": file_hash, "malicious": True, "reason": "Hash connu dans la base malveillante."}
_, ext = os.path.splitext(file_path)
if ext.lower() in SUSPICIOUS_EXTENSIONS:
return {"hash": file_hash, "malicious": True, "reason": f"Extension suspecte : {ext}"}
with open(file_path, "rb") as f:
content = f.read()
for pattern in MALWARE_PATTERNS:
if re.search(pattern, content):
return {"hash": file_hash, "malicious": True, "reason": f"Pattern suspect détecté : {pattern.decode(errors='ignore')}"}
return {"hash": file_hash, "malicious": False, "reason": "Fichier sain."}
class Scan(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
@app_commands.command(name="scan", description="Scanner un fichier pour détecter un malware.")
async def scan(self, interaction: discord.Interaction, file: discord.Attachment):
await interaction.response.defer() # Permet de prendre un peu de temps pour le scan
# Téléchargement temporaire du fichier
tmp_path = f"tmp_{file.filename}"
await file.save(tmp_path)
result = scan_file(tmp_path)
os.remove(tmp_path) # Supprime le fichier temporaire
if result.get("error"):
await interaction.followup.send(f"❌ Erreur : {result['error']}")
elif result.get("malicious"):
await interaction.followup.send(f"⚠️ Fichier malveillant détecté !\nRaison : {result['reason']}\nHash : `{result['hash']}`")
else:
await interaction.followup.send(f"✅ Fichier sain.\nHash : `{result['hash']}`")
async def setup(bot: commands.Bot):
await bot.add_cog(Scan(bot))

161
commandes/security.py Executable file
View file

@ -0,0 +1,161 @@
import discord
from discord import app_commands
from discord.ext import commands
import json
import os
# Chemins des fichiers
SETTINGS_FILE = "data/security_settings.json"
WHITELIST_FILE = "data/whitelist.json"
# --- UTILITAIRES ---
def load_settings(guild_id):
if not os.path.exists(SETTINGS_FILE): return {}
try:
with open(SETTINGS_FILE, "r", encoding='utf-8') as f:
return json.load(f).get(str(guild_id), {})
except: return {}
def save_settings(guild_id, settings):
data = {}
if os.path.exists(SETTINGS_FILE):
try:
with open(SETTINGS_FILE, "r", encoding='utf-8') as f: data = json.load(f)
except: pass
data[str(guild_id)] = settings
os.makedirs(os.path.dirname(SETTINGS_FILE), exist_ok=True)
with open(SETTINGS_FILE, "w", encoding='utf-8') as f:
json.dump(data, f, indent=4, ensure_ascii=False)
def is_whitelisted(guild_id, user_id):
if not os.path.exists(WHITELIST_FILE): return False
try:
with open(WHITELIST_FILE, "r", encoding='utf-8') as f:
data = json.load(f)
return user_id in data.get(str(guild_id), [])
except: return False
# --- MODAL DE RÉGLAGE DU SEUIL ---
class ThresholdModal(discord.ui.Modal, title="Réglage du Seuil Anti-Spam"):
threshold_input = discord.ui.TextInput(
label="Messages max autorisés (en 5 secondes)",
placeholder="Par défaut : 5",
min_length=1,
max_length=2,
required=True
)
def __init__(self, guild_id, settings, view):
super().__init__()
self.guild_id = guild_id
self.settings = settings
self.parent_view = view
async def on_submit(self, interaction: discord.Interaction):
if not self.threshold_input.value.isdigit():
return await interaction.response.send_message("❌ Erreur : Veuillez entrer un nombre entier.", ephemeral=True)
new_val = int(self.threshold_input.value)
self.settings["spam_threshold"] = new_val
save_settings(self.guild_id, self.settings)
await interaction.response.send_message(f"✅ Le seuil a été réglé sur **{new_val}** messages.", ephemeral=True)
# --- VUE DE GESTION ---
class SecurityView(discord.ui.View):
def __init__(self, interaction, settings, category=None):
super().__init__(timeout=None)
self.interaction = interaction
self.guild = interaction.guild
self.settings = settings
self.category = category
self.build_view()
def build_view(self):
self.clear_items()
if self.category is None:
self.add_item(CategorySelect())
else:
config_map = {
"raid": [("anti_raid", "🛡️ Anti-Raid"), ("auto_ban", "🔨 Auto-Ban")],
"spam": [("anti_spam", "🛡️ Anti-Spam"), ("anti_link", "🔗 Anti-Link"), ("anti_mention", "👥 Anti-Mention")],
"nuke": [("anti_channel_delete", "📁 Anti-Salon"), ("anti_role_create", "🎭 Anti-Rôle")],
"logs": [("server_logs", "📋 Activer Logs")]
}
for key, label in config_map.get(self.category, []):
is_on = self.settings.get(key, False)
style = discord.ButtonStyle.green if is_on else discord.ButtonStyle.red
status = "" if is_on else ""
btn = discord.ui.Button(label=f"{label} [{status}]", style=style, custom_id=key)
btn.callback = self.toggle_setting
self.add_item(btn)
# BOUTON SPÉCIAL : RÉGLAGE SEUIL (uniquement dans spam)
if self.category == "spam":
btn_threshold = discord.ui.Button(label=f"🔢 Seuil: {self.settings.get('spam_threshold', 5)}", style=discord.ButtonStyle.blurple)
btn_threshold.callback = self.open_threshold_modal
self.add_item(btn_threshold)
back_btn = discord.ui.Button(label="⬅️ Retour", style=discord.ButtonStyle.gray)
back_btn.callback = self.back_to_main
self.add_item(back_btn)
async def toggle_setting(self, interaction: discord.Interaction):
key = interaction.data['custom_id']
self.settings[key] = not self.settings.get(key, False)
save_settings(self.guild.id, self.settings)
self.build_view()
await interaction.response.edit_message(view=self)
async def open_threshold_modal(self, interaction: discord.Interaction):
await interaction.response.send_modal(ThresholdModal(self.guild.id, self.settings, self))
async def back_to_main(self, interaction: discord.Interaction):
self.category = None
self.build_view()
embed = self.create_main_embed()
await interaction.response.edit_message(embed=embed, view=self)
def create_main_embed(self):
embed = discord.Embed(title="🛡️ Centre de Sécurité Kuby", description="Interface modulaire de protection.", color=0x2b2d31)
if self.guild.icon: embed.set_thumbnail(url=self.guild.icon.url)
embed.add_field(name="Propriétaire", value=self.guild.owner.mention)
return embed
class CategorySelect(discord.ui.Select):
def __init__(self):
options = [
discord.SelectOption(label="Anti-Raid", value="raid", emoji="⚔️"),
discord.SelectOption(label="Anti-Spam", value="spam", emoji="🛡️"),
discord.SelectOption(label="Anti-Nuke", value="nuke", emoji="☢️"),
discord.SelectOption(label="Logs", value="logs", emoji="📋")
]
super().__init__(placeholder="Sélectionnez un module...", options=options)
async def callback(self, interaction: discord.Interaction):
view: SecurityView = self.view
view.category = self.values[0]
view.build_view()
embed = discord.Embed(title=f"⚙️ Configuration : {view.category.upper()}", color=0x2b2d31)
await interaction.response.edit_message(embed=embed, view=view)
# --- COG ---
class Security(commands.Cog):
def __init__(self, bot):
self.bot = bot
@app_commands.command(name="security", description="Ouvrir le panneau Kuby")
async def security(self, interaction: discord.Interaction):
if not (interaction.user.guild_permissions.administrator or
interaction.user.id == interaction.guild.owner_id or
is_whitelisted(interaction.guild.id, interaction.user.id)):
return await interaction.response.send_message("❌ Accès refusé.", ephemeral=True)
settings = load_settings(interaction.guild.id)
view = SecurityView(interaction, settings)
await interaction.response.send_message(embed=view.create_main_embed(), view=view)
async def setup(bot):
await bot.add_cog(Security(bot))

28
commandes/sendbotmessages.py Executable file
View file

@ -0,0 +1,28 @@
import discord
from discord import app_commands
from discord.ext import commands
class SentBotMessages(commands.Cog):
def __init__(self, bot):
self.bot = bot
@app_commands.command(name="sentbotmessages", description="Le bot envoie un message dans un salon choisi.")
@app_commands.describe(
message="Le message à envoyer",
salon="Le salon où envoyer le message"
)
async def sentbotmessages(self, interaction: discord.Interaction, message: str, salon: discord.TextChannel):
# Vérifie si l'utilisateur est admin
if not interaction.user.guild_permissions.administrator:
await interaction.response.send_message("⛔ Tu n'as pas la permission d'utiliser cette commande.", ephemeral=True)
return
try:
await salon.send(message)
await interaction.response.send_message(f"✅ Message envoyé dans {salon.mention}.", ephemeral=True)
except Exception as e:
await interaction.response.send_message(f"❌ Une erreur est survenue : {e}", ephemeral=True)
async def setup(bot):
await bot.add_cog(SentBotMessages(bot))

114
commandes/snipe.py Executable file
View file

@ -0,0 +1,114 @@
import discord
from discord import app_commands
from discord.ext import commands
from datetime import datetime
from collections import deque
class SnipeCommands(commands.Cog):
def __init__(self, bot):
self.bot = bot
# Dictionary to store deleted messages per channel
# Key: channel_id, Value: deque of deleted messages (max 10 per channel)
self.deleted_messages = {}
@commands.Cog.listener()
async def on_message_delete(self, message):
"""Track deleted messages for the snipe command"""
if message.author.bot:
return
channel_id = message.channel.id
# Initialize deque for this channel if it doesn't exist
if channel_id not in self.deleted_messages:
self.deleted_messages[channel_id] = deque(maxlen=10)
# Store message details
message_data = {
'content': message.content,
'author': message.author,
'timestamp': datetime.now(),
'attachments': [attachment.url for attachment in message.attachments],
'embeds': len(message.embeds)
}
self.deleted_messages[channel_id].appendleft(message_data)
@app_commands.command(name="snipe", description="Voir les derniers messages supprimés d'un salon")
@app_commands.describe(nombre="Nombre de messages à afficher (1-10, par défaut: 1)")
async def snipe(self, interaction: discord.Interaction, nombre: int = 1):
"""Retrieve deleted messages from the current channel"""
channel_id = interaction.channel.id
# Validate the number parameter
if nombre < 1 or nombre > 10:
await interaction.response.send_message(
"❌ Le nombre doit être entre 1 et 10.",
ephemeral=True
)
return
# Check if we have any deleted messages for this channel
if channel_id not in self.deleted_messages or not self.deleted_messages[channel_id]:
await interaction.response.send_message(
"❌ Aucun message supprimé trouvé dans ce salon.",
ephemeral=True
)
return
# Get the requested number of messages
messages = list(self.deleted_messages[channel_id])[:nombre]
# Create embed for displaying deleted messages
embed = discord.Embed(
title=f"🗑️ Messages supprimés ({len(messages)} sur {len(self.deleted_messages[channel_id])} disponibles)",
color=discord.Color.red()
)
for i, msg_data in enumerate(messages, 1):
author = msg_data['author']
content = msg_data['content'] if msg_data['content'] else "*Message sans texte*"
timestamp = msg_data['timestamp'].strftime("%H:%M:%S")
# Truncate long messages
if len(content) > 1000:
content = content[:997] + "..."
field_name = f"#{i} - {author.display_name} ({timestamp})"
field_value = content
# Add attachment info if present
if msg_data['attachments']:
field_value += f"\n📎 {len(msg_data['attachments'])} pièce(s) jointe(s)"
if msg_data['embeds'] > 0:
field_value += f"\n📊 {msg_data['embeds']} embed(s)"
embed.add_field(
name=field_name,
value=field_value,
inline=False
)
embed.set_footer(text=f"Canal: #{interaction.channel.name}")
await interaction.response.send_message(embed=embed)
@app_commands.command(name="snipeclear", description="Effacer l'historique des messages supprimés pour ce salon")
async def snipeclear(self, interaction: discord.Interaction):
"""Clear the deleted messages history for the current channel"""
channel_id = interaction.channel.id
if channel_id in self.deleted_messages:
self.deleted_messages[channel_id].clear()
await interaction.response.send_message(
"✅ Historique des messages supprimés effacé pour ce salon.",
ephemeral=True
)
else:
await interaction.response.send_message(
"❌ Aucun historique à effacer pour ce salon.",
ephemeral=True
)
async def setup(bot):
await bot.add_cog(SnipeCommands(bot))

207
commandes/ticket.py Normal file
View file

@ -0,0 +1,207 @@
import discord
from discord.ext import commands, tasks
from discord.ui import View, Button, Modal, TextInput
import json
import os
from datetime import datetime
import asyncio
# --- Fonctions de gestion de données ---
def load_config():
"""Charge la configuration des tickets"""
path = "data/tickets/config.json"
if not os.path.exists("data/tickets"):
os.makedirs("data/tickets")
if not os.path.exists(path):
with open(path, "w") as f:
json.dump({}, f, indent=4)
with open(path, "r") as f:
return json.load(f)
def save_config(config):
"""Sauvegarde la configuration des tickets"""
path = "data/tickets/config.json"
with open(path, "w", encoding="utf-8") as f:
json.dump(config, f, indent=4, ensure_ascii=False)
# --- Vues (Interactions) ---
class TicketView(View):
def __init__(self, bot, config):
super().__init__(timeout=None)
self.bot = bot
self.config = config
@discord.ui.button(label="Créer un ticket", style=discord.ButtonStyle.blurple, custom_id="create_ticket")
async def create_ticket(self, interaction: discord.Interaction, button: Button):
await interaction.response.defer(ephemeral=True)
guild_id = str(interaction.guild_id)
if guild_id not in self.config:
return await interaction.followup.send("Le système n'est pas configuré.", ephemeral=True)
guild_config = self.config[guild_id]
# Vérifier si l'utilisateur a déjà un ticket
for channel in interaction.guild.text_channels:
if channel.name.startswith(f"ticket-{interaction.user.id}"):
return await interaction.followup.send(f"Vous avez déjà un ticket : {channel.mention}", ephemeral=True)
# Configuration des permissions
category = interaction.guild.get_channel(int(guild_config["category_id"]))
overwrites = {
interaction.guild.default_role: discord.PermissionOverwrite(read_messages=False),
interaction.user: discord.PermissionOverwrite(read_messages=True, send_messages=True, attach_files=True),
interaction.guild.me: discord.PermissionOverwrite(read_messages=True, send_messages=True)
}
for role_id in guild_config["staff_roles"]:
role = interaction.guild.get_role(int(role_id))
if role:
overwrites[role] = discord.PermissionOverwrite(read_messages=True, send_messages=True)
# Création du salon
channel_name = f"ticket-{interaction.user.id}-{datetime.now().strftime('%m%d%H%M')}"
ticket_channel = await interaction.guild.create_text_channel(
name=channel_name,
category=category,
overwrites=overwrites,
topic=f"Ticket de {interaction.user} ({interaction.user.id})"
)
embed = discord.Embed(
title="Nouveau Ticket",
description=guild_config["ticket_message"],
color=discord.Color.blue(),
timestamp=datetime.now()
)
embed.set_author(name=interaction.user.display_name, icon_url=interaction.user.display_avatar.url)
await ticket_channel.send(embed=embed, view=TicketCloseView(self.bot, self.config))
# Logs
if guild_config.get("log_channel"):
log_ch = self.bot.get_channel(int(guild_config["log_channel"]))
if log_ch:
log_embed = discord.Embed(title="Ticket Créé", color=discord.Color.green())
log_embed.add_field(name="Auteur", value=interaction.user.mention)
log_embed.add_field(name="Salon", value=ticket_channel.mention)
await log_ch.send(embed=log_embed)
await interaction.followup.send(f"Ticket créé : {ticket_channel.mention}", ephemeral=True)
class TicketCloseView(View):
def __init__(self, bot, config):
super().__init__(timeout=None)
self.bot = bot
self.config = config
@discord.ui.button(label="Fermer le ticket", style=discord.ButtonStyle.red, custom_id="close_ticket")
async def close_ticket(self, interaction: discord.Interaction, button: Button):
await interaction.response.defer(ephemeral=True)
guild_config = self.config.get(str(interaction.guild_id))
# Modifier permissions (bloquer l'écriture)
overwrites = interaction.channel.overwrites
for target in overwrites:
if isinstance(target, discord.Member) and target != interaction.guild.me:
overwrites[target] = discord.PermissionOverwrite(read_messages=True, send_messages=False)
await interaction.channel.edit(name=f"closed-{interaction.channel.name}", overwrites=overwrites)
embed = discord.Embed(title="Ticket Fermé", description=f"Fermé par {interaction.user.mention}", color=discord.Color.red())
await interaction.channel.send(embed=embed, view=TicketReopenView(self.bot, self.config))
await interaction.followup.send("Ticket fermé.", ephemeral=True)
class TicketReopenView(View):
def __init__(self, bot, config):
super().__init__(timeout=None)
self.bot = bot
self.config = config
@discord.ui.button(label="Rouvrir", style=discord.ButtonStyle.green, custom_id="reopen_ticket")
async def reopen(self, interaction: discord.Interaction, button: Button):
await interaction.response.defer(ephemeral=True)
new_name = interaction.channel.name.replace("closed-", "")
await interaction.channel.edit(name=new_name)
await interaction.channel.send(f"Ticket rouvert par {interaction.user.mention}", view=TicketCloseView(self.bot, self.config))
@discord.ui.button(label="Supprimer", style=discord.ButtonStyle.grey, custom_id="delete_ticket")
async def delete(self, interaction: discord.Interaction, button: Button):
await interaction.response.send_message("Suppression dans 3s...")
await asyncio.sleep(3)
await interaction.channel.delete()
# --- Modale de Configuration ---
class TicketConfigModal(Modal):
def __init__(self, bot, config, guild_id):
super().__init__(title="Configuration Tickets")
self.bot = bot
self.config = config
self.guild_id = str(guild_id)
self.category_id = TextInput(label="ID Catégorie", placeholder="Ex: 123456789", required=True)
self.staff_roles = TextInput(label="IDs Rôles Staff (virgule)", placeholder="ID1, ID2", required=True)
self.log_channel = TextInput(label="ID Salon Logs", required=False)
self.ticket_msg = TextInput(label="Message Ticket", style=discord.TextStyle.paragraph, required=True)
self.welcome_msg = TextInput(label="Message Panel", required=True)
for item in [self.category_id, self.staff_roles, self.log_channel, self.ticket_msg, self.welcome_msg]:
self.add_item(item)
async def on_submit(self, interaction: discord.Interaction):
role_ids = [r.strip() for r in self.staff_roles.value.split(",")]
self.config[self.guild_id] = {
"category_id": self.category_id.value,
"staff_roles": role_ids,
"log_channel": self.log_channel.value,
"ticket_message": self.ticket_msg.value,
"welcome_message": self.welcome_msg.value
}
save_config(self.config)
await interaction.response.send_message("Configuration sauvegardée !", ephemeral=True)
# --- Le Cog Principal ---
class Ticket(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.config = load_config()
@commands.Cog.listener()
async def on_ready(self):
# Enregistrer les vues persistantes (pour qu'elles marchent après reboot)
self.bot.add_view(TicketView(self.bot, self.config))
self.bot.add_view(TicketCloseView(self.bot, self.config))
self.bot.add_view(TicketReopenView(self.bot, self.config))
print("Module Ticket prêt")
@commands.hybrid_group(name="ticket")
@commands.has_permissions(administrator=True)
async def ticket(self, ctx):
if ctx.invoked_subcommand is None:
await ctx.send("Utilisez `/ticket setup` ou `/ticket panel`", ephemeral=True)
@ticket.command(name="setup")
async def setup_cmd(self, ctx):
await ctx.interaction.response.send_modal(TicketConfigModal(self.bot, self.config, ctx.guild.id))
@ticket.command(name="panel")
async def panel_cmd(self, ctx, channel: discord.TextChannel = None):
guild_id = str(ctx.guild.id)
if guild_id not in self.config:
return await ctx.send("Configurez d'abord le système via `/ticket setup`.")
target = channel or ctx.channel
embed = discord.Embed(
title="Support",
description=self.config[guild_id]["welcome_message"],
color=discord.Color.blue()
)
await target.send(embed=embed, view=TicketView(self.bot, self.config))
await ctx.send("Panel envoyé !", ephemeral=True)
async def setup(bot):
await bot.add_cog(Ticket(bot))

116
commandes/welcome.py Normal file
View file

@ -0,0 +1,116 @@
import discord
from discord.ext import commands
from discord import app_commands
import sqlite3
import aiohttp
import io
import os
import logging
from PIL import Image, ImageDraw, ImageFont
class Welcome(commands.Cog):
def __init__(self, bot):
self.bot = bot
# Base de données à la racine
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 server_configs (
server_id INTEGER PRIMARY KEY,
welcome_channel INTEGER,
goodbye_channel INTEGER,
welcome_dm_message TEXT
)
''')
conn.commit()
conn.close()
async def create_welcome_image(self, username, avatar_url):
current_dir = os.path.dirname(__file__)
base_dir = os.path.abspath(os.path.join(current_dir, ".."))
try:
async with aiohttp.ClientSession() as session:
async with session.get(avatar_url) as resp:
avatar_bytes = await resp.read()
background = Image.open(os.path.join(base_dir, 'background_template.png'))
draw = ImageDraw.Draw(background)
font_large = ImageFont.truetype(os.path.join(base_dir, 'custom_font_large.otf'), 70)
font_small = ImageFont.truetype(os.path.join(base_dir, 'custom_font_small.otf'), 50)
font_large_omega = ImageFont.truetype(os.path.join(base_dir, 'custom_font_large.otf'), 55)
draw.text((325, 70), "Bienvenue", font=font_large, fill=(255,255,255))
draw.text((325, 135), f"{username} chez", font=font_small, fill=(255,255,255))
draw.text((325, 195), "OMEGA KUBE", font=font_large_omega, fill=(255,255,255))
avatar = Image.open(io.BytesIO(avatar_bytes)).convert("RGBA").resize((224, 224), Image.LANCZOS)
mask = Image.new('L', avatar.size, 0)
mask_draw = ImageDraw.Draw(mask)
mask_draw.ellipse((12, 12, 212, 212), fill=255)
background.paste(avatar, (40, 40), mask)
img_byte_arr = io.BytesIO()
background.save(img_byte_arr, format='PNG')
img_byte_arr.seek(0)
return img_byte_arr
except Exception as e:
logging.error(f"Erreur image: {e}")
return None
@commands.Cog.listener()
async def on_member_join(self, member):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# On récupère 2 colonnes : index 0 et 1
cursor.execute('SELECT welcome_channel, welcome_dm_message FROM server_configs WHERE server_id = ?', (member.guild.id,))
result = cursor.fetchone()
conn.close()
# 1. Gestion du DM (Index 1)
welcome_dm_text = result[1] if result and result[1] else "Bienvenue {username} sur le serveur {servername} ! 🎉\n\nNous sommes ravis de t'accueillir parmi nous."
try:
formatted_message = welcome_dm_text.format(
username=member.name,
servername=member.guild.name,
member_count=member.guild.member_count
)
await member.send(formatted_message)
await member.send("https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExNXV1NW02NTk0bGF2ZHcyMGFna2F6amRrbGpobGpsMHowOW5xa2E5eCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/ZtFuW4rsUbfrRzPauJ/giphy.gif")
except Exception as e:
logging.warning(f"Impossible d'envoyer le DM à {member.name}: {e}")
# 2. Gestion du salon de bienvenue (Index 0)
if result and result[0]:
channel = member.guild.get_channel(result[0])
if channel:
img = await self.create_welcome_image(member.name, member.avatar.url if member.avatar else member.default_avatar.url)
if img:
embed = discord.Embed(title=f"Merci d'accueillir {member} 🤝",
description="Bienvenue dans le serveur Omega Kube !\nSi vous cherchez des RolePlay de qualité,\nVous êtes au bon endroit. <a:sip:1316891821858619452>",
color=discord.Color.dark_purple())
embed.set_image(url="attachment://welcome.png")
await channel.send(content=member.mention, embed=embed, file=discord.File(img, filename="welcome.png"))
@app_commands.command(name="setwelcome", description="Définit le salon et le message DM de bienvenue")
@app_commands.checks.has_permissions(administrator=True)
async def set_welcome(self, interaction: discord.Interaction, channel: discord.TextChannel, message_dm: str = None):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute('''INSERT INTO server_configs (server_id, welcome_channel, welcome_dm_message)
VALUES (?, ?, ?) ON CONFLICT(server_id)
DO UPDATE SET welcome_channel=excluded.welcome_channel,
welcome_dm_message=COALESCE(excluded.welcome_dm_message, welcome_dm_message)''',
(interaction.guild_id, channel.id, message_dm))
conn.commit()
conn.close()
await interaction.response.send_message("Configuration de bienvenue enregistrée !", ephemeral=True)
async def setup(bot):
await bot.add_cog(Welcome(bot))

98
commandes/whitelist.py Executable file
View file

@ -0,0 +1,98 @@
import discord
from discord import app_commands
from discord.ext import commands
import json
import os
from typing import List
WHITELIST_FILE = "data/whitelist.json"
class Whitelist(commands.GroupCog, name="whitelist"):
def __init__(self, bot):
self.bot = bot
self.whitelist = self.load_whitelist()
def load_whitelist(self):
if not os.path.exists(WHITELIST_FILE):
return {}
try:
with open(WHITELIST_FILE, "r", encoding="utf-8") as f:
return json.load(f)
except json.JSONDecodeError:
return {}
def save_whitelist(self):
with open(WHITELIST_FILE, "w", encoding="utf-8") as f:
json.dump(self.whitelist, f, ensure_ascii=False, indent=4)
def _get_guild_whitelist(self, guild_id: int) -> List[str]:
return self.whitelist.get(str(guild_id), [])
def _set_guild_whitelist(self, guild_id: int, users: List[str]):
self.whitelist[str(guild_id)] = users
self.save_whitelist()
# ✅ Ajouter un utilisateur
@app_commands.command(name="ajouter", description="Ajoute un utilisateur à la whitelist")
@app_commands.describe(utilisateur="L'utilisateur à ajouter")
async def ajouter(self, interaction: discord.Interaction, utilisateur: discord.User):
guild_id = interaction.guild.id
users = self._get_guild_whitelist(guild_id)
user_id = str(utilisateur.id)
if user_id in users:
await interaction.response.send_message(f"🔒 {utilisateur.mention} est déjà dans la whitelist.", ephemeral=True)
return
users.append(user_id)
self._set_guild_whitelist(guild_id, users)
await interaction.response.send_message(f"{utilisateur.mention} a été ajouté à la whitelist.", ephemeral=True)
# ❌ Retirer un utilisateur
@app_commands.command(name="retirer", description="Retire un utilisateur de la whitelist")
@app_commands.describe(utilisateur="L'utilisateur à retirer")
async def retirer(self, interaction: discord.Interaction, utilisateur: discord.User):
guild_id = interaction.guild.id
users = self._get_guild_whitelist(guild_id)
user_id = str(utilisateur.id)
if user_id not in users:
await interaction.response.send_message(f"⚠️ {utilisateur.mention} n'est pas dans la whitelist.", ephemeral=True)
return
users.remove(user_id)
self._set_guild_whitelist(guild_id, users)
await interaction.response.send_message(f"{utilisateur.mention} a été retiré de la whitelist.", ephemeral=True)
# 📋 Voir la whitelist
@app_commands.command(name="liste", description="Affiche les utilisateurs de la whitelist")
async def liste(self, interaction: discord.Interaction):
guild_id = interaction.guild.id
users = self._get_guild_whitelist(guild_id)
if not users:
await interaction.response.send_message("📭 La whitelist est vide pour ce serveur.", ephemeral=True)
return
membres = []
for user_id in users:
try:
utilisateur = await self.bot.fetch_user(int(user_id))
membres.append(f"- {utilisateur.mention} ({utilisateur.name}#{utilisateur.discriminator})")
except discord.NotFound:
membres.append(f"- <@{user_id}> (Utilisateur introuvable)")
texte = "\n".join(membres)
await interaction.response.send_message(f"📃 **Utilisateurs whitelistés pour ce serveur :**\n{texte}", ephemeral=True)
def is_whitelisted(self, guild_id: int, user_id: int) -> bool:
"""Check if a user is whitelisted for a specific guild."""
return str(user_id) in self._get_guild_whitelist(guild_id)
def get_whitelist_users(self, guild_id: int) -> List[str]:
"""Get all whitelisted user IDs for a specific guild."""
return self._get_guild_whitelist(guild_id).copy()
async def setup(bot):
await bot.add_cog(Whitelist(bot))

256
commandes/whitelist_monitor.py Executable file
View file

@ -0,0 +1,256 @@
import discord
from discord.ext import commands
from discord.ui import View, Button
import json
import os
from datetime import datetime
from typing import List, Dict, Optional
class WhitelistMonitor(commands.Cog):
"""Monitors whitelist members for role changes and server leave events."""
def __init__(self, bot):
self.bot = bot
self.whitelist_file = "data/whitelist.json"
self.big_permissions = [
discord.Permissions.administrator,
discord.Permissions.manage_guild,
discord.Permissions.manage_roles,
discord.Permissions.manage_channels,
discord.Permissions.kick_members,
discord.Permissions.ban_members,
discord.Permissions.manage_messages,
discord.Permissions.moderate_members,
discord.Permissions.manage_nicknames,
discord.Permissions.manage_emojis,
discord.Permissions.manage_webhooks,
discord.Permissions.view_audit_log
]
def load_whitelist(self) -> List[str]:
"""Load whitelist from JSON file."""
if not os.path.exists(self.whitelist_file):
return []
try:
with open(self.whitelist_file, "r", encoding="utf-8") as f:
return json.load(f)
except (json.JSONDecodeError, FileNotFoundError):
return []
def save_whitelist(self, whitelist: List[str]):
"""Save whitelist to JSON file."""
with open(self.whitelist_file, "w", encoding="utf-8") as f:
json.dump(whitelist, f, ensure_ascii=False, indent=4)
def is_whitelisted(self, user_id: int) -> bool:
"""Check if user is whitelisted."""
whitelist = self.load_whitelist()
return str(user_id) in whitelist
def remove_from_whitelist(self, user_id: int) -> bool:
"""Remove user from whitelist."""
whitelist = self.load_whitelist()
user_str = str(user_id)
if user_str in whitelist:
whitelist.remove(user_str)
self.save_whitelist(whitelist)
return True
return False
def has_big_permissions(self, role: discord.Role) -> bool:
"""Check if role has significant permissions."""
permissions = role.permissions
for perm in self.big_permissions:
if permissions.administrator or getattr(permissions, perm.name, False):
return True
return False
async def send_whitelist_notification(self, member: discord.Member, removed_roles: List[discord.Role]):
"""Send DM notification to user about whitelist member losing roles."""
try:
# Find guild administrators to notify
admins = [m for m in member.guild.members if m.guild_permissions.administrator and not m.bot]
if not admins:
return
# Create embed
embed = discord.Embed(
title="⚠️ Whitelist Alert",
description=f"Un membre whitelisté a perdu des rôles importants dans **{member.guild.name}**.",
color=discord.Color.orange(),
timestamp=datetime.now()
)
embed.add_field(
name="👤 Membre concerné",
value=f"{member.mention} ({member.display_name})",
inline=False
)
if removed_roles:
roles_text = "\n".join([f"{role.name}" for role in removed_roles])
embed.add_field(
name="🗑️ Rôles retirés",
value=roles_text,
inline=False
)
embed.add_field(
name="📋 Statut whitelist",
value="✅ Toujours whitelisté",
inline=True
)
# Create view with removal button
view = WhitelistRemovalView(member.id, member.guild.id)
# Send to all admins
for admin in admins:
try:
await admin.send(embed=embed, view=view)
except discord.Forbidden:
continue # User has DMs disabled
except Exception as e:
print(f"Error sending whitelist notification: {e}")
@commands.Cog.listener()
async def on_member_update(self, before: discord.Member, after: discord.Member):
"""Monitor role changes for whitelist members."""
if before.roles == after.roles:
return
if not self.is_whitelisted(after.id):
return
# Find removed roles
removed_roles = [role for role in before.roles if role not in after.roles]
# Check if any removed roles had big permissions
important_removed = [role for role in removed_roles if self.has_big_permissions(role)]
if important_removed:
await self.send_whitelist_notification(after, important_removed)
@commands.Cog.listener()
async def on_member_remove(self, member: discord.Member):
"""Auto-remove from whitelist when member leaves server."""
if self.is_whitelisted(member.id):
removed = self.remove_from_whitelist(member.id)
if removed:
# Log the removal
try:
log_channel_id = self.get_log_channel(member.guild.id)
if log_channel_id:
log_channel = member.guild.get_channel(log_channel_id)
if log_channel:
embed = discord.Embed(
title="🗑️ Auto-Whitelist Removal",
description=f"{member.mention} a été retiré automatiquement de la whitelist après avoir quitté le serveur.",
color=discord.Color.red(),
timestamp=datetime.now()
)
await log_channel.send(embed=embed)
except Exception as e:
print(f"Error logging whitelist removal: {e}")
def get_log_channel(self, guild_id: int) -> Optional[int]:
"""Get log channel ID from security settings."""
try:
security_file = "data/security_settings.json"
if os.path.exists(security_file):
with open(security_file, "r") as f:
settings = json.load(f)
return settings.get(str(guild_id), {}).get("log_channel_id")
except:
pass
return None
class WhitelistRemovalView(View):
"""View with button to remove user from whitelist."""
def __init__(self, user_id: int, guild_id: int):
super().__init__(timeout=3600) # 1 hour timeout
self.user_id = user_id
self.guild_id = guild_id
@discord.ui.button(
label="Retirer de la whitelist",
style=discord.ButtonStyle.red,
emoji="🗑️"
)
async def remove_whitelist(
self,
interaction: discord.Interaction,
button: Button
):
"""Remove user from whitelist when button is clicked."""
try:
# Get the cog instance
cog = self.bot.get_cog("WhitelistMonitor")
if not cog:
await interaction.response.send_message(
"❌ Erreur: Système de whitelist non disponible.",
ephemeral=True
)
return
# Remove from whitelist
removed = cog.remove_from_whitelist(self.user_id)
if removed:
# Get user info
try:
user = await self.bot.fetch_user(self.user_id)
user_mention = user.mention
except:
user_mention = f"<@{self.user_id}>"
# Update button
button.disabled = True
button.label = "✅ Retiré"
button.style = discord.ButtonStyle.green
await interaction.response.edit_message(view=self)
# Send confirmation
embed = discord.Embed(
title="✅ Retrait effectué",
description=f"{user_mention} a été retiré de la whitelist.",
color=discord.Color.green()
)
await interaction.followup.send(embed=embed, ephemeral=True)
# Log the removal
try:
guild = self.bot.get_guild(self.guild_id)
if guild:
log_channel_id = cog.get_log_channel(self.guild_id)
if log_channel_id:
log_channel = guild.get_channel(log_channel_id)
if log_channel:
embed = discord.Embed(
title="🗑️ Whitelist Manual Removal",
description=f"{user_mention} a été retiré manuellement de la whitelist.",
color=discord.Color.green(),
timestamp=datetime.now()
)
embed.set_footer(text=f"Retiré par {interaction.user.display_name}")
await log_channel.send(embed=embed)
except Exception as e:
print(f"Error logging manual removal: {e}")
else:
await interaction.response.send_message(
" L'utilisateur n'était pas dans la whitelist.",
ephemeral=True
)
except Exception as e:
await interaction.response.send_message(
f"❌ Erreur lors du retrait: {str(e)}",
ephemeral=True
)
async def setup(bot):
await bot.add_cog(WhitelistMonitor(bot))