Mise à jour du système de whitelist, sécurité et logging

This commit is contained in:
Mathis 2026-03-29 19:29:17 +02:00
parent d82f704c75
commit 1e384032be
7 changed files with 199 additions and 145 deletions

View file

@ -34,7 +34,7 @@ class AdvancedLogger:
"details": details or {}
}
self._write_log("messages", log_data)
asyncio.create_task(self._write_log_async("messages", log_data))
def log_role_event(self, member: discord.Member, role: discord.Role, event_type: str):
"""Log role changes (added/removed)"""
@ -49,7 +49,7 @@ class AdvancedLogger:
"role_permissions": role.permissions.value
}
self._write_log("roles", log_data)
asyncio.create_task(self._write_log_async("roles", log_data))
def log_voice_event(self, member: discord.Member, channel: discord.VoiceChannel, event_type: str):
"""Log voice channel events (join/leave)"""
@ -63,7 +63,7 @@ class AdvancedLogger:
"channel_type": str(channel.type)
}
self._write_log("voice", log_data)
asyncio.create_task(self._write_log_async("voice", log_data))
def log_member_leave(self, member: discord.Member):
"""Log when a member leaves the server"""
@ -85,10 +85,10 @@ class AdvancedLogger:
}
# Save to member-specific log
self._write_member_log(member.id, log_data)
asyncio.create_task(self._write_member_log_async(member.id, log_data))
# Also save to general logs
self._write_log("member_events", log_data)
asyncio.create_task(self._write_log_async("member_events", log_data))
def log_member_join(self, member: discord.Member):
"""Log when a member joins the server"""
@ -101,10 +101,13 @@ class AdvancedLogger:
"member_avatar": str(member.avatar.url) if member.avatar else None
}
self._write_log("member_events", log_data)
asyncio.create_task(self._write_log_async("member_events", log_data))
def check_previous_roles(self, member_id: int) -> Optional[Dict]:
"""Check if member has previous role data when rejoining"""
# This one stays synchronous for now as it's called in on_member_join and needs immediate result
# but since it only reads ONE file, it's less problematic than writing to cumulative logs.
# Format update: it now needs to read NDJSON.
member_log_file = f"{self.member_logs_dir}/{member_id}.json"
if not os.path.exists(member_log_file):
@ -112,7 +115,8 @@ class AdvancedLogger:
try:
with open(member_log_file, 'r', encoding='utf-8') as f:
logs = json.load(f)
# Read line by line for NDJSON
logs = [json.loads(line) for line in f if line.strip()]
# Find the most recent leave event
leave_events = [log for log in logs if log.get("event_type") == "member_leave"]
@ -123,71 +127,39 @@ class AdvancedLogger:
kuby_logger.error(f"Error checking previous roles for {member_id}: {e}")
return None
def _write_log(self, log_type: str, data: Dict):
"""Write log data to appropriate file safely with atomic write"""
date_str = datetime.now().strftime("%Y-%m-%d")
log_file = f"{self.logs_dir}/{log_type}_{date_str}.json"
temp_file = f"{log_file}.tmp"
existing_logs = []
async def _write_log_async(self, log_type: str, data: Dict):
"""Asynchronous wrapper for log writing"""
return await asyncio.to_thread(self._write_log_sync, log_type, data)
async def _write_member_log_async(self, member_id: int, data: Dict):
"""Asynchronous wrapper for member log writing"""
return await asyncio.to_thread(self._write_member_log_sync, member_id, data)
def _write_log_sync(self, log_type: str, data: Dict):
"""Write log data to appropriate file using NDJSON (Append-only)"""
try:
if os.path.exists(log_file):
with open(log_file, 'r', encoding='utf-8') as f:
try:
existing_logs = json.load(f)
except json.JSONDecodeError as e:
# Handle corrupted file by backing it up and starting fresh
corrupted_backup = f"{log_file}.corrupted"
os.rename(log_file, corrupted_backup)
kuby_logger.warning(f"Corrupted log file detected and backed up to {corrupted_backup}: {e}. Starting new log.")
existing_logs = []
date_str = datetime.now().strftime("%Y-%m-%d")
log_file = f"{self.logs_dir}/{log_type}_{date_str}.json"
existing_logs.append(data)
# Atomic write using a temporary file
with open(temp_file, 'w', encoding='utf-8') as f:
json.dump(existing_logs, f, indent=2, ensure_ascii=False)
os.replace(temp_file, log_file)
# Simple append in NDJSON format
with open(log_file, 'a', encoding='utf-8') as f:
f.write(json.dumps(data, ensure_ascii=False) + "\n")
except Exception as e:
kuby_logger.error(f"Error writing log for {log_type}: {e}")
if os.path.exists(temp_file):
try: os.remove(temp_file)
except: pass
# We don't use kuby_logger here to avoid infinite recursion if nested
print(f"CRITICAL ERROR in AdvancedLogger._write_log_sync: {e}")
def _write_member_log(self, member_id: int, data: Dict):
"""Write member-specific log safely with atomic write"""
member_log_file = f"{self.member_logs_dir}/{member_id}.json"
temp_file = f"{member_log_file}.tmp"
existing_logs = []
def _write_member_log_sync(self, member_id: int, data: Dict):
"""Write member-specific log using NDJSON (Append-only)"""
try:
if os.path.exists(member_log_file):
with open(member_log_file, 'r', encoding='utf-8') as f:
try:
existing_logs = json.load(f)
except json.JSONDecodeError as e:
# Handle corrupted file by backing it up and starting fresh
corrupted_backup = f"{member_log_file}.corrupted"
os.rename(member_log_file, corrupted_backup)
kuby_logger.warning(f"Corrupted member log detected and backed up to {corrupted_backup}: {e}. Starting new log.")
existing_logs = []
member_log_file = f"{self.member_logs_dir}/{member_id}.json"
existing_logs.append(data)
# Atomic write using a temporary file
with open(temp_file, 'w', encoding='utf-8') as f:
json.dump(existing_logs, f, indent=2, ensure_ascii=False)
os.replace(temp_file, member_log_file)
with open(member_log_file, 'a', encoding='utf-8') as f:
f.write(json.dumps(data, ensure_ascii=False) + "\n")
except Exception as e:
kuby_logger.error(f"Error writing member log for {member_id}: {e}")
if os.path.exists(temp_file):
try: os.remove(temp_file)
except: pass
print(f"CRITICAL ERROR in AdvancedLogger._write_member_log_sync: {e}")
async def send_leave_notification(self, member: discord.Member, channel: discord.TextChannel):
"""Send notification when member leaves"""

View file

@ -188,10 +188,10 @@ async def generate_servers_json(bot: discord.Client) -> dict:
entry["verificationLevel"] = str(guild.verification_level)
if fields.get("memberCount"):
try:
members = [m async for m in guild.fetch_members(limit=None)]
entry["memberCount"] = sum(1 for m in members if not m.bot)
except Exception:
# Use cache if available (intents.members is True), otherwise fallback to count
if guild.members:
entry["memberCount"] = sum(1 for m in guild.members if not m.bot)
else:
entry["memberCount"] = guild.member_count
if fields.get("owner"):
@ -209,22 +209,27 @@ async def generate_servers_json(bot: discord.Client) -> dict:
pass
# Membres avec un rôle dont le nom correspond à founderRoles
try:
async for member in guild.fetch_members(limit=None):
if member.bot or member.id == guild.owner_id:
continue
for role in member.roles:
if any(key in role.name.lower() for key in founder_keys):
founders.append({
"id": str(member.id),
"username": member.name,
"displayName": member.display_name,
"avatar": str(member.display_avatar.url),
"isOwner": False,
})
break
except Exception:
pass
# Use cache if possible
members_to_check = guild.members if guild.members else []
if not members_to_check:
try:
members_to_check = [m async for m in guild.fetch_members(limit=None)]
except Exception:
pass
for member in members_to_check:
if member.bot or member.id == guild.owner_id:
continue
for role in member.roles:
if any(key in role.name.lower() for key in founder_keys):
founders.append({
"id": str(member.id),
"username": member.name,
"displayName": member.display_name,
"avatar": str(member.display_avatar.url),
"isOwner": False,
})
break
entry["founders"] = founders
@ -259,7 +264,14 @@ async def generate_members_json(bot: discord.Client, guild_id: Optional[int] = N
raise ValueError(f"Serveur introuvable : {target_id}")
members_list = []
async for member in guild.fetch_members(limit=None):
# Use cached members if available, or fetch if not
source = guild.members if guild.members else []
if not source:
async for m in guild.fetch_members(limit=None):
if not m.bot:
source.append(m)
for member in source:
if member.bot:
continue