2026-02-07 16:03:16 +01:00
|
|
|
import os
|
|
|
|
|
from dotenv import load_dotenv
|
|
|
|
|
|
|
|
|
|
# Charger les variables d'environnement AVANT tout le reste
|
|
|
|
|
load_dotenv()
|
|
|
|
|
|
2026-01-01 18:48:25 +01:00
|
|
|
import discord
|
2026-01-14 21:32:20 +01:00
|
|
|
from commandes.ticket.utils.permissions import can_access_config
|
|
|
|
|
from commandes.ticket.data.storage import get_storage
|
2026-01-01 18:48:25 +01:00
|
|
|
from discord.ext import commands
|
|
|
|
|
import asyncio
|
|
|
|
|
from src.logger import kuby_logger, log_performance
|
|
|
|
|
import logging
|
|
|
|
|
from src.advanced_logger import AdvancedLogger
|
2026-03-14 17:56:26 +01:00
|
|
|
import subprocess
|
|
|
|
|
import sys
|
2026-01-01 18:48:25 +01:00
|
|
|
|
|
|
|
|
# Récupération et validation de l'ID d'application
|
|
|
|
|
application_id_raw = os.getenv("APPLICATION_ID")
|
|
|
|
|
kuby_logger.debug(f"Raw APPLICATION_ID: {application_id_raw}")
|
|
|
|
|
try:
|
|
|
|
|
application_id = int(application_id_raw) if application_id_raw else None
|
|
|
|
|
kuby_logger.info(f"✅ APPLICATION_ID loaded successfully: {application_id}")
|
|
|
|
|
except ValueError:
|
|
|
|
|
kuby_logger.error(f"❌ APPLICATION_ID invalide : {application_id_raw}")
|
|
|
|
|
application_id = None
|
|
|
|
|
|
|
|
|
|
# Configuration des intents Discord
|
|
|
|
|
intents = discord.Intents.default()
|
|
|
|
|
intents.message_content = True
|
|
|
|
|
intents.members = True
|
|
|
|
|
intents.voice_states = True
|
|
|
|
|
kuby_logger.debug(f"Discord intents configured: {intents}")
|
|
|
|
|
|
|
|
|
|
class MyBot(commands.Bot):
|
|
|
|
|
def __init__(self):
|
|
|
|
|
kuby_logger.debug("Initializing MyBot class")
|
|
|
|
|
super().__init__(
|
2026-01-10 15:30:51 +01:00
|
|
|
command_prefix="!",
|
2026-01-01 18:48:25 +01:00
|
|
|
intents=intents,
|
|
|
|
|
application_id=application_id,
|
|
|
|
|
)
|
|
|
|
|
kuby_logger.info("MyBot instance created successfully")
|
|
|
|
|
|
|
|
|
|
async def setup_hook(self):
|
|
|
|
|
kuby_logger.info("🚀 Démarrage du bot Kuby...")
|
|
|
|
|
|
|
|
|
|
# Initialize advanced logger
|
|
|
|
|
self.advanced_logger = AdvancedLogger(self)
|
|
|
|
|
kuby_logger.info("✅ Advanced logger initialized")
|
|
|
|
|
|
2026-03-14 17:56:26 +01:00
|
|
|
# Start the Flask Webhook Server
|
|
|
|
|
self.flask_process = None
|
|
|
|
|
try:
|
|
|
|
|
flask_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "serveur_flask.py")
|
|
|
|
|
if os.path.exists(flask_path):
|
2026-03-14 18:35:18 +01:00
|
|
|
# On utilise DEVNULL car PM2 ou Nohup gèrent déjà les logs globaux
|
|
|
|
|
# et ça évite que le pipe se remplisse et bloque le serveur Flask
|
2026-03-14 17:56:26 +01:00
|
|
|
self.flask_process = subprocess.Popen(
|
|
|
|
|
[sys.executable, flask_path],
|
2026-03-14 18:35:18 +01:00
|
|
|
stdout=subprocess.DEVNULL,
|
|
|
|
|
stderr=subprocess.DEVNULL,
|
2026-03-14 17:56:26 +01:00
|
|
|
cwd=os.path.dirname(flask_path)
|
|
|
|
|
)
|
|
|
|
|
kuby_logger.info(f"✅ Serveur Flask démarré (PID: {self.flask_process.pid})")
|
|
|
|
|
else:
|
2026-03-14 18:35:18 +01:00
|
|
|
kuby_logger.warning(f"⚠️ Fichier serveur_flask.py introuvable")
|
2026-03-14 17:56:26 +01:00
|
|
|
except Exception as e:
|
|
|
|
|
kuby_logger.error(f"❌ Impossible de démarrer le serveur Flask: {e}")
|
|
|
|
|
|
2026-01-01 18:48:25 +01:00
|
|
|
# Charger les extensions
|
|
|
|
|
extensions = [
|
|
|
|
|
"commandes.whitelist",
|
|
|
|
|
"commandes.whitelist_monitor",
|
|
|
|
|
"commandes.sendbotmessages",
|
|
|
|
|
"commandes.goodbye",
|
|
|
|
|
"commandes.welcome",
|
|
|
|
|
"commandes.security",
|
|
|
|
|
"commandes.snipe",
|
|
|
|
|
"commandes.scan",
|
|
|
|
|
"commandes.no_link",
|
|
|
|
|
"commandes.blacklist",
|
|
|
|
|
"commandes.ticket",
|
|
|
|
|
"commandes.modules_security.antispam",
|
|
|
|
|
"commandes.modules_security.antiraid",
|
|
|
|
|
"commandes.modules_security.logs_manager",
|
2026-01-03 01:15:10 +01:00
|
|
|
"commandes.modules_security.rules",
|
2026-02-07 16:03:16 +01:00
|
|
|
"commandes.ticket_whitelist",
|
2026-02-27 14:18:01 +01:00
|
|
|
"commandes.bug_report",
|
2026-02-27 17:22:45 +01:00
|
|
|
"commandes.convocation",
|
2026-03-01 01:01:15 +01:00
|
|
|
"commandes.absence_staff",
|
|
|
|
|
"commandes.website_sync",
|
2026-05-01 16:44:50 +02:00
|
|
|
"commandes.moderation",
|
2026-01-01 18:48:25 +01:00
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for extension in extensions:
|
|
|
|
|
try:
|
|
|
|
|
await self.load_extension(extension)
|
|
|
|
|
kuby_logger.info(f"✅ Extension chargée : {extension}")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
kuby_logger.error(f"❌ Erreur lors du chargement de {extension} : {e}", exc_info=True)
|
|
|
|
|
|
|
|
|
|
# --- SYSTÈME DE WHITELIST (CORRIGÉ POUR ÉVITER LES ERREURS) ---
|
|
|
|
|
@self.check
|
|
|
|
|
async def whitelist_check(ctx):
|
|
|
|
|
try:
|
|
|
|
|
# 1. Bypass pour le propriétaire (Owner)
|
|
|
|
|
if await self.is_owner(ctx.author):
|
|
|
|
|
return True
|
|
|
|
|
|
2026-01-14 21:32:20 +01:00
|
|
|
# 1b. Bypass pour les commandes de config tickets
|
|
|
|
|
# Si la commande est une commande ticket et l'utilisateur a les perms ticket
|
|
|
|
|
if ctx.command and (ctx.command.name in ["ticketconfig", "ticketsetup", "configticket"]):
|
|
|
|
|
try:
|
|
|
|
|
storage = get_storage()
|
|
|
|
|
config = storage.load_config(ctx.guild.id)
|
|
|
|
|
if can_access_config(ctx, config):
|
|
|
|
|
return True
|
|
|
|
|
except Exception as e:
|
|
|
|
|
kuby_logger.error(f"Error checking ticket permissions in global whitelist: {e}")
|
|
|
|
|
# Fallthrough to normal whitelist check if error
|
|
|
|
|
|
|
|
|
|
|
2026-01-01 18:48:25 +01:00
|
|
|
# 2. Récupération du Cog
|
|
|
|
|
whitelist_cog = self.get_cog("WhitelistMonitor")
|
|
|
|
|
if not whitelist_cog:
|
|
|
|
|
kuby_logger.warning("WhitelistMonitor cog not found, allowing all commands")
|
|
|
|
|
return True
|
|
|
|
|
|
2026-01-10 15:30:51 +01:00
|
|
|
is_whitelisted = whitelist_cog.is_whitelisted(ctx.guild.id, ctx.author.id)
|
2026-01-01 18:48:25 +01:00
|
|
|
|
|
|
|
|
if not is_whitelisted:
|
|
|
|
|
kuby_logger.info(f"User {ctx.author} (ID: {ctx.author.id}) attempted to use command but is not whitelisted")
|
|
|
|
|
|
|
|
|
|
# Réponse adaptée (Slash vs Message)
|
|
|
|
|
msg = "❌ Vous n'êtes pas autorisé à utiliser cette commande. Veuillez demander à être ajouté à la whitelist."
|
|
|
|
|
if ctx.interaction:
|
|
|
|
|
await ctx.interaction.response.send_message(msg, ephemeral=True)
|
|
|
|
|
else:
|
|
|
|
|
await ctx.send(msg)
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
except Exception as e:
|
|
|
|
|
kuby_logger.error(f"Error in whitelist check: {e}")
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
# Sync slash commands
|
|
|
|
|
try:
|
|
|
|
|
await self.tree.sync()
|
|
|
|
|
kuby_logger.info("✅ Commandes slash synchronisées avec Discord")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
kuby_logger.error(f"❌ Erreur lors de la synchronisation des commandes slash : {e}", exc_info=True)
|
|
|
|
|
|
2026-03-14 17:56:26 +01:00
|
|
|
async def close(self):
|
|
|
|
|
if hasattr(self, 'flask_process') and self.flask_process:
|
|
|
|
|
kuby_logger.info("Arrêt du serveur Flask interne...")
|
|
|
|
|
self.flask_process.terminate()
|
|
|
|
|
try:
|
|
|
|
|
self.flask_process.wait(timeout=5)
|
|
|
|
|
except subprocess.TimeoutExpired:
|
|
|
|
|
self.flask_process.kill()
|
|
|
|
|
await super().close()
|
|
|
|
|
|
2026-01-01 18:48:25 +01:00
|
|
|
# --- ÉVÉNEMENTS RESTAURÉS ---
|
|
|
|
|
|
|
|
|
|
async def on_ready(self):
|
|
|
|
|
kuby_logger.info(f"🤖 Bot connecté : {self.user} (ID : {self.user.id})")
|
|
|
|
|
kuby_logger.info(f"📊 Serveurs : {len(self.guilds)}")
|
2026-02-08 14:42:40 +01:00
|
|
|
|
|
|
|
|
# Log intents status
|
|
|
|
|
kuby_logger.info(f"🔍 Intents Status - Members: {self.intents.members}, Presences: {self.intents.presences}, Core: {self.intents.message_content}")
|
|
|
|
|
|
|
|
|
|
if not self.intents.members:
|
|
|
|
|
kuby_logger.warning("⚠️ L'intent MEMBERS est désactivé ! Les événements de join/leave et les DMs pourraient ne pas fonctionner correctement.")
|
|
|
|
|
|
2026-01-01 18:48:25 +01:00
|
|
|
for guild in self.guilds:
|
|
|
|
|
kuby_logger.debug(f"Guild: {guild.name} (ID: {guild.id}) - Members: {guild.member_count}")
|
|
|
|
|
|
|
|
|
|
total_members = sum(guild.member_count for guild in self.guilds)
|
|
|
|
|
kuby_logger.info(f"📊 Total members across all guilds: {total_members}")
|
|
|
|
|
kuby_logger.info("✅ Bot prêt et opérationnel !")
|
|
|
|
|
|
|
|
|
|
async def on_guild_join(self, guild):
|
|
|
|
|
kuby_logger.info(f"🆕 Bot joined new guild: {guild.name} (ID: {guild.id})")
|
|
|
|
|
kuby_logger.audit(f"Bot added to guild: {guild.name}")
|
|
|
|
|
|
|
|
|
|
async def on_guild_remove(self, guild):
|
|
|
|
|
kuby_logger.info(f"🚪 Bot removed from guild: {guild.name}")
|
|
|
|
|
kuby_logger.audit(f"Bot removed from guild: {guild.name}")
|
|
|
|
|
|
|
|
|
|
async def on_member_join(self, member):
|
|
|
|
|
kuby_logger.info(f"👋 {member} joined {member.guild.name}")
|
|
|
|
|
if hasattr(self, 'advanced_logger'):
|
|
|
|
|
self.advanced_logger.log_member_join(member)
|
|
|
|
|
previous_roles = self.advanced_logger.check_previous_roles(member.id)
|
|
|
|
|
if previous_roles:
|
|
|
|
|
guild = member.guild
|
|
|
|
|
roles_to_add = [guild.get_role(rid) for rid in previous_roles.get("role_ids", [])]
|
|
|
|
|
roles_to_add = [r for r in roles_to_add if r and r < guild.me.top_role]
|
|
|
|
|
if roles_to_add:
|
|
|
|
|
try:
|
|
|
|
|
await member.add_roles(*roles_to_add)
|
|
|
|
|
kuby_logger.info(f"Restored {len(roles_to_add)} roles for {member}")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
kuby_logger.error(f"Failed to restore roles: {e}")
|
|
|
|
|
|
|
|
|
|
async def on_member_remove(self, member):
|
|
|
|
|
kuby_logger.info(f"👋 {member} left {member.guild.name}")
|
|
|
|
|
if hasattr(self, 'advanced_logger'):
|
|
|
|
|
self.advanced_logger.log_member_leave(member)
|
|
|
|
|
# Notification de départ
|
|
|
|
|
try:
|
|
|
|
|
from commandes.security import load_settings
|
|
|
|
|
settings = load_settings(member.guild.id)
|
|
|
|
|
log_channel_id = settings.get("log_channel_id")
|
|
|
|
|
if log_channel_id:
|
|
|
|
|
log_channel = member.guild.get_channel(log_channel_id)
|
|
|
|
|
if log_channel:
|
|
|
|
|
await self.advanced_logger.send_leave_notification(member, log_channel)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
kuby_logger.error(f"Error in leave notification: {e}")
|
|
|
|
|
|
|
|
|
|
async def on_message_delete(self, message):
|
|
|
|
|
if message.author.bot: return
|
|
|
|
|
if hasattr(self, 'advanced_logger'):
|
|
|
|
|
self.advanced_logger.log_message_event(message, "deleted")
|
|
|
|
|
|
|
|
|
|
async def on_message_edit(self, before, after):
|
|
|
|
|
if before.author.bot or before.content == after.content: return
|
|
|
|
|
if hasattr(self, 'advanced_logger'):
|
|
|
|
|
self.advanced_logger.log_message_event(before, "edited", {
|
|
|
|
|
"before_content": before.content, "after_content": after.content
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
async def on_voice_state_update(self, member, before, after):
|
|
|
|
|
if member.bot: return
|
|
|
|
|
if before.channel != after.channel and hasattr(self, 'advanced_logger'):
|
|
|
|
|
if before.channel is None:
|
|
|
|
|
self.advanced_logger.log_voice_event(member, after.channel, "joined")
|
|
|
|
|
elif after.channel is None:
|
|
|
|
|
self.advanced_logger.log_voice_event(member, before.channel, "left")
|
|
|
|
|
else:
|
|
|
|
|
self.advanced_logger.log_voice_event(member, before.channel, "moved_from")
|
|
|
|
|
self.advanced_logger.log_voice_event(member, after.channel, "moved_to")
|
|
|
|
|
|
|
|
|
|
async def on_command_error(self, ctx, error):
|
|
|
|
|
if isinstance(error, commands.CheckFailure):
|
|
|
|
|
return # Déjà géré dans le whitelist_check
|
|
|
|
|
kuby_logger.error(f"Command error in {ctx.command}: {str(error)}", exc_info=True)
|
|
|
|
|
|
2026-01-14 21:32:20 +01:00
|
|
|
bot = MyBot()
|