import asyncio import datetime import logging import time import os import discord logger = logging.getLogger('Beta') class TaskManager: """Handles global background tasks, scheduled loops and reporting.""" def __init__(self, bot): self.bot = bot def start_all(self): """Starts all background loops attached to the bot's event loop.""" self.bot.loop.create_task(self._introspection_report_loop()) self.bot.loop.create_task(self._voice_report_loop()) self.bot.loop.create_task(self._daily_report_check_loop()) self.bot.loop.create_task(self._gdpr_purge_loop()) self.bot.loop.create_task(self._voice_evaluation_loop()) # Start interviews for missing assets for asset_id in self.bot.asset_ids: if str(asset_id) not in self.bot.dispatcher.assets: self.bot.loop.create_task(self.bot.dispatcher.start_interview(asset_id)) async def _voice_evaluation_loop(self): """Periodic check for tactical voice connections.""" await self.bot.wait_until_ready() auto_join = os.environ.get("AUTO_JOIN_VOICE", "true").lower() in ("true", "1", "yes") if not auto_join: logger.info("Auto-join vocal désactivé (AUTO_JOIN_VOICE=false)") return while not self.bot.is_closed(): for guild in self.bot.guilds: try: await self.bot.voice_manager.evaluate_and_connect(guild) except Exception as e: logger.debug(f"Voice evaluation skipped: {e}") await asyncio.sleep(30) async def _introspection_report_loop(self): """Periodic broadcast of system health metrics.""" await self.bot.wait_until_ready() while not self.bot.is_closed(): await asyncio.sleep(600) # Every 10 minutes await self._send_health_report() async def _send_health_report(self): """Build and send a clinical health report embed.""" total = self.bot.metrics["total_llm_requests"] success = self.bot.metrics["total_llm_success"] rate = (success / total * 100) if total > 0 else 100 avg_time = (self.bot.metrics["total_llm_time"] / success) if success > 0 else 0 status_report = ( f"**◈ INTELLIGENCE CORE**\n" f"- Requests Processed : `{total}`\n" f"- Reliability Rate : `{rate:.1f}%`\n" f"- Latency Heuristic : `{avg_time:.2f}s` (Target: < 5s)\n\n" f"**◈ TACTICAL OVERVIEW**\n" f"- Voice Protocol : `READY`\n" f"- Active Deployment : `{self.bot.voice_manager.current_channel.name if self.bot.voice_manager.current_channel else 'NONE'}`\n" ) await self.bot.introspection_log( "SYSTEM STATUS REPORT", status_report, discord.Color.dark_blue() ) async def _daily_report_check_loop(self): """Check for 21:30 and trigger the daily report using local timezone.""" await self.bot.wait_until_ready() report_sent_today = False while not self.bot.is_closed(): now = datetime.datetime.now(self.bot.timezone) if now.hour == 21 and now.minute == 30: if not report_sent_today: await self._send_daily_summary() report_sent_today = True else: report_sent_today = False await asyncio.sleep(60) async def _gdpr_purge_loop(self): """Daily purge of old memory files (30 days) for GDPR compliance.""" from brain.memoire import purge_old_memories while not self.bot.is_closed(): now = datetime.datetime.now(self.bot.timezone) if now.hour == 3 and now.minute == 0: logger.info("◈ RGPD: Démarrage de la purge quotidienne...") deleted = await purge_old_memories(days=30) logger.info(f"◈ RGPD: Purge terminée. {deleted} fichiers supprimés.") await asyncio.sleep(65) await asyncio.sleep(30) async def _send_daily_summary(self): """Generate and send a dense, highly detailed clinical report at 21:30.""" tasks = self.bot.dispatcher.tasks tense_sessions = self.bot.metrics.get("daily_tense_sessions", 0) asset_stats = {} for t_id, t_data in tasks.items(): a_id = t_data.get("asset_id") if a_id not in asset_stats: asset_name = "Inconnu" if str(a_id) in self.bot.dispatcher.assets: asset_name = self.bot.dispatcher.assets[str(a_id)].get("name", "Anonyme") asset_stats[a_id] = {"name": asset_name, "accepted": 0, "missed": 0, "total": 0} asset_stats[a_id]["total"] += 1 if t_data["status"] == "ACCEPTED": asset_stats[a_id]["accepted"] += 1 elif t_data["status"] == "PENDING" and t_data.get("deadline", 0) < time.time(): asset_stats[a_id]["missed"] += 1 stats_lines = [] for a_id, s in asset_stats.items(): stats_lines.append(f"- **{s['name']}** : `{s['accepted']}/{s['total']}` acceptées, `{s['missed']}` manquées.") task_details = [] for t_id, t_data in list(tasks.items())[-15:]: status_emoji = "✅" if t_data["status"] == "ACCEPTED" else "❌" if t_data.get("deadline", 0) < time.time() else "⏳" asset_name = self.bot.dispatcher.assets.get(str(t_data['asset_id']), {}).get('name', 'N/A') task_details.append(f"{status_emoji} `{t_id}` | Staff: {asset_name} | Urgence: {t_data['importance']} | Context: {t_data['content'][:50]}...") summary = ( f"**◈ RAPPORT ANALYTIQUE DE FIN DE CYCLE ◈**\n" f"Période : Dernières 24h | Heure : 21h30\n\n" f"**◈ BILAN GLOBAL DES OPÉRATIONS**\n" f"- Volume total d'actions de modération : `{len(tasks)}` \n" f"- Sessions vocales sous tension : `{tense_sessions}`\n\n" f"**◈ PERFORMANCE DES ASSETS**\n" + ("\n".join(stats_lines) if stats_lines else "Aucune activité de staff enregistrée.") + "\n\n" f"**◈ LOG DÉTAILLÉ DES DERNIERS SIGNAUX**\n" + ("\n".join(task_details) if task_details else "Aucun signal capturé.") + "\n\n" f"**◈ CONCLUSION HEURISTIQUE**\n" f"Le système est nominal. " + ("PRÉCONISATION : Vigilance accrue requise sur les membres suspects." if tense_sessions > 0 else "État de l'écosystème : Stable.") ) if summary and self.bot.admin_ids: for admin_id in self.bot.admin_ids: admin = self.bot.get_user(admin_id) or await self.bot.fetch_user(admin_id) if admin: await self.bot.messaging.send_embed(admin, "SYNTHÈSE CLINIQUE QUOTIDIENNE", summary, color=discord.Color.dark_purple()) self.bot.metrics["daily_tense_sessions"] = 0 self.bot.dispatcher.tasks = {k: v for k, v in tasks.items() if v['status'] == 'PENDING' and v.get('deadline', 0) > time.time()} # Le chemin de Active tasks doit être vers /data si on l'a déplacé. # Pour l'instant, on sauvegarde dans data/ data_dir = os.path.join(os.path.dirname(__file__), "..", "data") os.makedirs(data_dir, exist_ok=True) self.bot.dispatcher._save_data(self.bot.dispatcher.tasks, os.path.join(data_dir, "active_tasks.json")) async def _voice_report_loop(self): """Periodic broadcast of active voice session summaries.""" await self.bot.wait_until_ready() while not self.bot.is_closed(): await asyncio.sleep(900) # Every 15 minutes if self.bot.voice_manager.is_monitoring and self.bot.voice_manager.session_buffer: report = await self.bot.voice_manager.generate_session_report() await self.bot.introspection_log("VOICE SESSION UPDATE (15m)", report, discord.Color.blue())