"""Contrôle centralisé des flux Twitch, bots et boucles IA / envoi chat.""" import json import threading import time from datetime import datetime from fonction.first_class import ( IA_generator, RecordTwitch, Subtitle_translation, TwitchChatBot, messageTwitch, storage, ) from twitch_bot import chat_state from twitch_bot.interaction_chat import InteractionChatProcessor def _resolve_user_index(pseudo: str) -> int: """ Retourne l'index d'un compte dans config/user.json (case-insensitive). Fallback: 0. """ try: with open("config/user.json", "r", encoding="utf-8") as f: users = json.load(f) or [] pseudo_l = (pseudo or "").strip().lstrip("@").lower() for i, u in enumerate(users): p = (u or {}).get("tw_acc_pseudo") if isinstance(p, str) and p.strip().lower() == pseudo_l: return i except Exception: pass return 0 def _resolve_first_enabled_user_index() -> int: try: with open("config/user.json", "r", encoding="utf-8") as f: users = json.load(f) or [] for i, u in enumerate(users): if isinstance(u, dict) and u.get("enabled", True): return i except Exception: pass return 0 class BotController: def __init__(self): self.bots = {} # Stockage des instances de bots (pour l'utilisation interne) self.flux_list = [] # Liste des flux surveillés (pour l'API JSON) self.config = self.load_config() self.ia_generator = None self.control_twitch = None self.ia_generator_running = False self.control_twitch_running = False def load_config(self): try: with open('config/config.json', 'r') as file: return json.load(file) except FileNotFoundError: return {} def save_config(self): with open('config/config.json', 'w') as file: json.dump(self.config, file, indent=4, ensure_ascii=False) def get_system_status(self): """Obtenir le statut de tous les composants""" return { 'ia_generator': { 'running': self.ia_generator_running, 'status': 'En cours' if self.ia_generator_running else 'Arrêté' }, 'control_twitch': { 'running': self.control_twitch_running, 'status': 'En cours' if self.control_twitch_running else 'Arrêté' }, 'flux_count': len(self.flux_list), 'active_flux': len([f for f in self.flux_list if f['active']]) } def add_flux(self, channel_name, record_audio=True): flux_id = len(self.flux_list) + 1 # Créer l'objet flux pour l'API (sans les instances de bots) flux_data = { 'id': flux_id, 'name': channel_name, 'twitchname': channel_name, 'record_audio': record_audio, 'active': True, 'created_at': datetime.now().isoformat(), 'status': 'starting' } try: # Créer le bot de chat pour ce flux chat_bot = TwitchChatBot(channel_name) # Interaction chat (mentions -> réponses préenregistrées) def get_registered_accounts(): try: with open("config/user.json", "r", encoding="utf-8") as f: users = json.load(f) pseudos = [] for u in users or []: p = (u or {}).get("tw_acc_pseudo") enabled = (u or {}).get("enabled", True) if enabled and isinstance(p, str) and p.strip(): pseudos.append(p.strip()) return pseudos except Exception: return [] def get_account_policies(): try: with open("config/user.json", "r", encoding="utf-8") as f: users = json.load(f) or [] out = {} for u in users: if not isinstance(u, dict): continue p = (u.get("tw_acc_pseudo") or "").strip().lstrip("@").lower() if not p: continue out[p] = { "enabled": bool(u.get("enabled", True)), "interaction_bypass_antiloop": bool(u.get("interaction_bypass_antiloop", False)), } return out except Exception: return {} msg_bot_for_interaction = messageTwitch("config/user.json", channel_name) interaction = InteractionChatProcessor( channel_name=channel_name, get_registered_accounts=get_registered_accounts, get_account_policies=get_account_policies, send_message_as=lambda pseudo, text: msg_bot_for_interaction.send_message_user( _resolve_user_index(pseudo), text ), ) self.bots[flux_id] = { 'chat_bot': chat_bot, 'record_bot': None, 'subtitle_bot': None, 'ia_bot': None, 'message_bot': None, 'interaction': interaction, } chat_bot.start_background() interaction.start_background(get_latest_messages=lambda: chat_bot.messages) # Si enregistrement audio activé if record_audio: record_bot = RecordTwitch(channel_name, 60) self.bots[flux_id]['record_bot'] = record_bot threading.Thread(target=record_bot.main, daemon=True).start() # Démarrer le bot de sous-titres subtitle_bot = Subtitle_translation("config/config.json") self.bots[flux_id]['subtitle_bot'] = subtitle_bot subtitle_bot.start_main_loop() # Démarrer le générateur IA ia_bot = IA_generator("config/config.json") self.bots[flux_id]['ia_bot'] = ia_bot ia_bot.start_main_loop() # Démarrer le contrôleur de messages message_bot = messageTwitch("config/user.json", channel_name) self.bots[flux_id]['message_bot'] = message_bot message_bot.start_loop_respond() # Mettre à jour le statut flux_data['status'] = 'active' self.flux_list.append(flux_data) return flux_id except Exception as e: print(f"Erreur lors de l'ajout du flux {channel_name}: {str(e)}") # Nettoyer en cas d'erreur if flux_id in self.bots: try: if self.bots[flux_id]['chat_bot']: self.bots[flux_id]['chat_bot'].stop() if self.bots[flux_id]['record_bot']: self.bots[flux_id]['record_bot'].stop() if self.bots[flux_id]['subtitle_bot']: self.bots[flux_id]['subtitle_bot'].stop() if self.bots[flux_id]['ia_bot']: self.bots[flux_id]['ia_bot'].stop() if self.bots[flux_id]['message_bot']: self.bots[flux_id]['message_bot'].stop() except Exception: pass del self.bots[flux_id] flux_data['status'] = 'error' flux_data['error'] = str(e) self.flux_list.append(flux_data) raise e def remove_flux(self, flux_id): for i, flux in enumerate(self.flux_list): if flux['id'] == flux_id: # Arrêter les bots si ils existent if flux_id in self.bots: try: if self.bots[flux_id].get('interaction'): self.bots[flux_id]['interaction'].stop() if self.bots[flux_id]['chat_bot']: self.bots[flux_id]['chat_bot'].stop() if self.bots[flux_id]['record_bot']: self.bots[flux_id]['record_bot'].stop() if self.bots[flux_id]['subtitle_bot']: self.bots[flux_id]['subtitle_bot'].stop() if self.bots[flux_id]['ia_bot']: self.bots[flux_id]['ia_bot'].stop() if self.bots[flux_id]['message_bot']: self.bots[flux_id]['message_bot'].stop() except Exception as e: print(f"Erreur lors de l'arrêt des bots: {e}") del self.bots[flux_id] del self.flux_list[i] return True return False def get_flux_list(self): # Retourner seulement les données JSON (pas les instances de bots) return self.flux_list def start_ia_generator(self): """Démarrer le générateur IA de manière contrôlée""" if self.ia_generator_running: return False, "IA Generator déjà en cours d'exécution" try: self.ia_generator = IA_generator("config/config.json") self.ia_generator_running = True # Activer l'envoi de messages quand l'IA Generator est démarré chat_state.chat_messages_enabled = True # Démarrer dans un thread séparé threading.Thread(target=self._ia_generator_loop, daemon=True).start() print(f"[{datetime.now().strftime('%H:%M:%S')}] IA Generator démarré") return True, "IA Generator démarré avec succès" except Exception as e: self.ia_generator_running = False return False, f"Erreur lors du démarrage de l'IA Generator: {str(e)}" def stop_ia_generator(self): """Arrêter le générateur IA""" if not self.ia_generator_running: return False, "IA Generator n'est pas en cours d'exécution" try: self.ia_generator_running = False if self.ia_generator: self.ia_generator.stop() # Désactiver l'envoi de messages quand l'IA Generator est arrêté chat_state.chat_messages_enabled = False print(f"[{datetime.now().strftime('%H:%M:%S')}] IA Generator arrêté") return True, "IA Generator arrêté avec succès" except Exception as e: return False, f"Erreur lors de l'arrêt de l'IA Generator: {str(e)}" def _ia_generator_loop(self): """Boucle contrôlée pour l'IA Generator""" while self.ia_generator_running: try: if self.ia_generator: self.ia_generator.main_ask("") # Génération automatique time.sleep(20) # Attendre 20 secondes entre les générations except Exception as e: print(f"[{datetime.now().strftime('%H:%M:%S')}] Erreur dans IA Generator: {e}") time.sleep(10) def start_control_twitch(self): """Démarrer le contrôleur Twitch de manière contrôlée""" if self.control_twitch_running: return False, "Control Twitch déjà en cours d'exécution" try: # Utiliser le premier utilisateur par défaut self.control_twitch = messageTwitch("config/user.json", "default") self.control_twitch_running = True # Démarrer dans un thread séparé threading.Thread(target=self._control_twitch_loop, daemon=True).start() print(f"[{datetime.now().strftime('%H:%M:%S')}] Control Twitch démarré") return True, "Control Twitch démarré avec succès" except Exception as e: self.control_twitch_running = False return False, f"Erreur lors du démarrage de Control Twitch: {str(e)}" def stop_control_twitch(self): """Arrêter le contrôleur Twitch""" if not self.control_twitch_running: return False, "Control Twitch n'est pas en cours d'exécution" try: self.control_twitch_running = False if self.control_twitch: self.control_twitch.stop() print(f"[{datetime.now().strftime('%H:%M:%S')}] Control Twitch arrêté") return True, "Control Twitch arrêté avec succès" except Exception as e: return False, f"Erreur lors de l'arrêt de Control Twitch: {str(e)}" def _control_twitch_loop(self): """Boucle contrôlée pour Control Twitch""" while self.control_twitch_running: try: if self.control_twitch: # Vérifier s'il y a des générations à envoyer generation_data = storage.read("IA_generator") if generation_data: sorted_keys = sorted(generation_data.keys()) if sorted_keys: last_generation = generation_data[sorted_keys[-1]] # Envoyer le message avec le premier utilisateur activé self.control_twitch.send_message_user(_resolve_first_enabled_user_index(), last_generation) # Supprimer la génération envoyée storage.delete("IA_generator", sorted_keys[-1]) print(f"[{datetime.now().strftime('%H:%M:%S')}] Message envoyé: {last_generation[:50]}...") time.sleep(10) # Attendre 10 secondes entre les vérifications except Exception as e: print(f"[{datetime.now().strftime('%H:%M:%S')}] Erreur dans Control Twitch: {e}") time.sleep(10) bot_controller = BotController()