import json import os import subprocess import sys import threading import time from datetime import datetime _ROOT = os.path.dirname(os.path.abspath(__file__)) if _ROOT not in sys.path: sys.path.insert(0, _ROOT) from flask import Flask, render_template, request, jsonify from flask_socketio import SocketIO, emit from fonction.first_class import IA_generator, messageTwitch, storage from twitch_bot import chat_state from twitch_bot.controller import bot_controller from twitch_bot.interaction_chat import InteractionChatConfig def _first_enabled_user_index(config_path: str = "config/user.json") -> int: try: with open(config_path, "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 app = Flask(__name__) app.config['SECRET_KEY'] = 'your-secret-key-here' socketio = SocketIO(app, cors_allowed_origins="*") @app.route('/') def index(): return render_template('index.html') @app.route('/api/flux', methods=['GET']) def get_flux(): return jsonify(bot_controller.get_flux_list()) @app.route('/api/flux', methods=['POST']) def add_flux(): data = request.json channel_name = data.get('channel_name') record_audio = data.get('record_audio', True) if not channel_name: return jsonify({'error': 'Nom du canal requis'}), 400 try: flux_id = bot_controller.add_flux(channel_name, record_audio) return jsonify({'success': True, 'flux_id': flux_id}) except Exception as e: error_msg = f"Erreur lors de l'ajout du flux: {str(e)}" print(f"Erreur API add_flux: {error_msg}") return jsonify({'error': error_msg}), 500 @app.route('/api/flux/', methods=['DELETE']) def remove_flux(flux_id): if bot_controller.remove_flux(flux_id): return jsonify({'success': True}) return jsonify({'error': 'Flux non trouvé'}), 404 @app.route('/api/flux//status', methods=['GET']) def get_flux_status(flux_id): """Obtenir le statut détaillé d'un flux spécifique""" for flux in bot_controller.flux_list: if flux['id'] == flux_id: status = { 'id': flux_id, 'name': flux['name'], 'active': flux['active'], 'status': flux.get('status', 'unknown'), 'created_at': flux['created_at'], 'bots': {} } # Ajouter les informations des bots si disponibles if flux_id in bot_controller.bots: bots = bot_controller.bots[flux_id] if bots['chat_bot']: status['bots']['chat'] = { 'running': bots['chat_bot'].is_running if hasattr(bots['chat_bot'], 'is_running') else True } if bots['record_bot']: status['bots']['record'] = { 'running': bots['record_bot'].running if hasattr(bots['record_bot'], 'running') else True } return jsonify(status) return jsonify({'error': 'Flux non trouvé'}), 404 @app.route('/api/flux//toggle', methods=['POST']) def toggle_flux(flux_id): """Activer/désactiver un flux""" for flux in bot_controller.flux_list: if flux['id'] == flux_id: flux['active'] = not flux['active'] # Arrêter/démarrer les bots selon le nouveau statut if flux_id in bot_controller.bots: bots = bot_controller.bots[flux_id] if not flux['active']: # Arrêter les bots try: if bots['chat_bot']: bots['chat_bot'].stop() if bots['record_bot']: bots['record_bot'].stop() if bots['subtitle_bot']: bots['subtitle_bot'].stop() if bots['ia_bot']: bots['ia_bot'].stop() if bots['message_bot']: bots['message_bot'].stop() except Exception as e: print(f"Erreur lors de l'arrêt des bots: {e}") else: # Redémarrer les bots try: if bots['chat_bot']: bots['chat_bot'].start_background() if bots['record_bot']: threading.Thread(target=bots['record_bot'].main, daemon=True).start() if bots['subtitle_bot']: bots['subtitle_bot'].start_main_loop() if bots['ia_bot']: bots['ia_bot'].start_main_loop() if bots['message_bot']: bots['message_bot'].start_loop_respond() except Exception as e: print(f"Erreur lors du redémarrage des bots: {e}") return jsonify({'success': True, 'active': flux['active']}) return jsonify({'error': 'Flux non trouvé'}), 404 @app.route('/api/config/prompts', methods=['GET']) def get_prompts(): return jsonify(bot_controller.config.get('list_prompt', [])) @app.route('/api/config/prompts', methods=['POST']) def update_prompts(): data = request.json prompts = data.get('prompts', []) bot_controller.config['list_prompt'] = prompts bot_controller.save_config() return jsonify({'success': True}) @app.route('/api/config/settings', methods=['GET']) def get_settings(): """Récupérer les paramètres généraux (config/config.json).""" try: cfg = bot_controller.load_config() return jsonify({ 'success': True, 'settings': { 'twitch_message_max_chars': int(cfg.get('twitch_message_max_chars', 100)), } }) except Exception as e: return jsonify({'success': False, 'error': str(e)}), 500 @app.route('/api/config/settings', methods=['POST']) def save_settings(): """Sauvegarder les paramètres généraux (config/config.json).""" try: data = request.json or {} settings = data.get('settings') or {} cfg = bot_controller.load_config() try: cfg['twitch_message_max_chars'] = int(settings.get('twitch_message_max_chars', cfg.get('twitch_message_max_chars', 100))) except Exception: cfg['twitch_message_max_chars'] = 100 bot_controller.config = cfg bot_controller.save_config() return jsonify({'success': True, 'settings': {'twitch_message_max_chars': cfg['twitch_message_max_chars']}}) except Exception as e: return jsonify({'success': False, 'error': str(e)}), 500 @app.route('/api/config/users', methods=['GET']) def get_users(): """Récupérer la liste des utilisateurs""" try: with open('config/user.json', 'r') as file: users = json.load(file) return jsonify(users) except FileNotFoundError: return jsonify([]) except Exception as e: return jsonify({'error': str(e)}), 500 @app.route('/api/config/users', methods=['POST']) def add_user(): """Ajouter un nouvel utilisateur""" data = request.json pseudo = data.get('tw_acc_pseudo') token = data.get('tw_acc_token') charactere = data.get('charactere', '😊') enabled = bool(data.get('enabled', True)) interaction_bypass_antiloop = bool(data.get('interaction_bypass_antiloop', False)) if not pseudo or not token: return jsonify({'error': 'Pseudo et token requis'}), 400 try: with open('config/user.json', 'r') as file: users = json.load(file) # Vérifier si l'utilisateur existe déjà for user in users: if user['tw_acc_pseudo'] == pseudo: return jsonify({'error': 'Cet utilisateur existe déjà'}), 400 # Ajouter le nouvel utilisateur new_user = { 'tw_acc_pseudo': pseudo, 'tw_acc_token': token, 'charactere': charactere, 'enabled': enabled, 'interaction_bypass_antiloop': interaction_bypass_antiloop, } users.append(new_user) # Sauvegarder with open('config/user.json', 'w') as file: json.dump(users, file, indent=4, ensure_ascii=False) return jsonify({'success': True, 'user': new_user}) except Exception as e: return jsonify({'error': str(e)}), 500 @app.route('/api/config/users/', methods=['PUT']) def update_user(user_id): """Modifier un utilisateur existant""" data = request.json pseudo = data.get('tw_acc_pseudo') token = data.get('tw_acc_token') charactere = data.get('charactere', '😊') enabled = bool(data.get('enabled', True)) interaction_bypass_antiloop = bool(data.get('interaction_bypass_antiloop', False)) if not pseudo or not token: return jsonify({'error': 'Pseudo et token requis'}), 400 try: with open('config/user.json', 'r') as file: users = json.load(file) if user_id >= len(users): return jsonify({'error': 'Utilisateur non trouvé'}), 404 # Vérifier si le pseudo existe déjà (sauf pour l'utilisateur actuel) for i, user in enumerate(users): if i != user_id and user['tw_acc_pseudo'] == pseudo: return jsonify({'error': 'Ce pseudo est déjà utilisé'}), 400 # Mettre à jour l'utilisateur users[user_id] = { 'tw_acc_pseudo': pseudo, 'tw_acc_token': token, 'charactere': charactere, 'enabled': enabled, 'interaction_bypass_antiloop': interaction_bypass_antiloop, } # Sauvegarder with open('config/user.json', 'w') as file: json.dump(users, file, indent=4, ensure_ascii=False) return jsonify({'success': True, 'user': users[user_id]}) except Exception as e: return jsonify({'error': str(e)}), 500 @app.route('/api/config/users//enabled', methods=['POST']) def set_user_enabled(user_id): """Activer/désactiver un utilisateur sans toucher pseudo/token.""" try: data = request.json or {} enabled = bool(data.get('enabled', True)) with open('config/user.json', 'r', encoding='utf-8') as file: users = json.load(file) if user_id >= len(users): return jsonify({'error': 'Utilisateur non trouvé'}), 404 if not isinstance(users[user_id], dict): return jsonify({'error': 'Utilisateur invalide'}), 400 users[user_id]['enabled'] = enabled with open('config/user.json', 'w', encoding='utf-8') as file: json.dump(users, file, indent=4, ensure_ascii=False) return jsonify({'success': True, 'user': users[user_id]}) except Exception as e: return jsonify({'error': str(e)}), 500 @app.route('/api/config/users//interaction-bypass', methods=['POST']) def set_user_interaction_bypass(user_id): """Autoriser un utilisateur à bypass l’anti-boucle Interaction chat.""" try: data = request.json or {} bypass = bool(data.get('interaction_bypass_antiloop', False)) with open('config/user.json', 'r', encoding='utf-8') as file: users = json.load(file) if user_id >= len(users): return jsonify({'error': 'Utilisateur non trouvé'}), 404 if not isinstance(users[user_id], dict): return jsonify({'error': 'Utilisateur invalide'}), 400 users[user_id]['interaction_bypass_antiloop'] = bypass with open('config/user.json', 'w', encoding='utf-8') as file: json.dump(users, file, indent=4, ensure_ascii=False) return jsonify({'success': True, 'user': users[user_id]}) except Exception as e: return jsonify({'error': str(e)}), 500 @app.route('/api/config/users/', methods=['DELETE']) def delete_user(user_id): """Supprimer un utilisateur""" try: with open('config/user.json', 'r') as file: users = json.load(file) if user_id >= len(users): return jsonify({'error': 'Utilisateur non trouvé'}), 404 # Supprimer l'utilisateur deleted_user = users.pop(user_id) # Sauvegarder with open('config/user.json', 'w') as file: json.dump(users, file, indent=4, ensure_ascii=False) return jsonify({'success': True, 'deleted_user': deleted_user}) except Exception as e: return jsonify({'error': str(e)}), 500 @app.route('/api/subtitles', methods=['GET']) def get_subtitles(): data = storage.read("subtitle_data") return jsonify(data) @app.route('/api/subtitles/clear', methods=['POST']) def clear_subtitles(): """Nettoyer l'historique des sous-titres""" try: # Récupérer toutes les clés de sous-titres subtitle_data = storage.read("subtitle_data") # Supprimer chaque clé une par une for key in list(subtitle_data.keys()): storage.delete("subtitle_data", key) # Supprimer également le fichier JSON s'il existe storage_dir = "storage" subtitle_file = os.path.join(storage_dir, "subtitle_data.json") if os.path.exists(subtitle_file): os.remove(subtitle_file) return jsonify({ 'success': True, 'message': 'Historique des sous-titres nettoyé avec succès' }) except Exception as e: return jsonify({ 'success': False, 'error': f'Erreur lors du nettoyage: {str(e)}' }), 500 @app.route('/api/subtitles/process', methods=['POST']) def process_subtitles(): """Lancer manuellement le traitement des sous-titres""" try: import subprocess import os import json from datetime import datetime # Vérifier que le dossier record existe record_dir = "record" if not os.path.exists(record_dir): return jsonify({ 'success': False, 'error': 'Dossier record non trouvé' }), 404 # Trouver les fichiers audio audio_files = [f for f in os.listdir(record_dir) if f.endswith('.mp3')] if not audio_files: return jsonify({ 'success': False, 'error': 'Aucun fichier audio trouvé' }), 404 subtitles_created = 0 subtitles_data = {} # Traiter chaque fichier audio for audio_file in audio_files[:3]: # Limiter à 3 fichiers pour éviter de surcharger audio_path = os.path.join(record_dir, audio_file) try: # Lancer Whisper command = [ 'whisper', '--language', 'fr', audio_path, '--device', 'cuda', '--model', 'large-v3' ] result = subprocess.run(command, capture_output=True, text=True, timeout=60) if result.returncode == 0: # Lire le fichier .txt généré txt_file = audio_file.replace('.mp3', '.txt') if os.path.exists(txt_file): with open(txt_file, 'r', encoding='utf-8') as f: content = f.read().strip() # Nettoyer le contenu content = content.replace("'", "").replace('"', "").replace("\n", " ").replace(",", "") # Supprimer les répétitions de mots words = content.split() seen = set() result_words = [] for word in words: if word not in seen: result_words.append(word) seen.add(word) cleaned_content = " ".join(result_words) # Sauvegarder dans le stockage current_time = datetime.now().strftime('%H:%M:%S') subtitles_data[current_time] = cleaned_content # Sauvegarder dans le fichier de stockage storage_dir = "storage" if not os.path.exists(storage_dir): os.makedirs(storage_dir) subtitle_file = os.path.join(storage_dir, "subtitle_data.json") try: with open(subtitle_file, 'r', encoding='utf-8') as f: existing_data = json.load(f) except (FileNotFoundError, json.JSONDecodeError): existing_data = {} existing_data[current_time] = cleaned_content with open(subtitle_file, 'w', encoding='utf-8') as f: json.dump(existing_data, f, indent=4, ensure_ascii=False) subtitles_created += 1 # Nettoyer les fichiers temporaires os.remove(txt_file) os.remove(audio_path) # Supprimer le fichier audio traité except subprocess.TimeoutExpired: continue # Passer au fichier suivant except Exception as e: continue # Passer au fichier suivant return jsonify({ 'success': True, 'message': f'Traitement terminé. {subtitles_created} sous-titre(s) créé(s).', 'subtitles': subtitles_data }) except Exception as e: return jsonify({ 'success': False, 'error': f'Erreur lors du traitement: {str(e)}' }), 500 @app.route('/api/generations', methods=['GET']) def get_generations(): data = storage.read("IA_generator") return jsonify(data) @app.route('/api/send-message', methods=['POST']) def send_message(): data = request.json message = data.get('message') channel = data.get('channel', 'default') user_id = data.get('user_id', 0) # Nouveau paramètre pour choisir l'utilisateur if not message: return jsonify({'error': 'Message requis'}), 400 # Vérifier si l'envoi de messages est activé if not chat_state.chat_messages_enabled: return jsonify({'error': 'Envoi de messages désactivé'}), 403 # Trouver le bot de message pour ce canal try: msg_bot = messageTwitch("config/user.json", channel) # Utiliser send_message_user au lieu de send_message pour spécifier l'utilisateur msg_bot.send_message_user(user_id, message) return jsonify({'success': True}) except Exception as e: return jsonify({'error': str(e)}), 500 @app.route('/api/generate-response', methods=['POST']) def generate_response(): data = request.json text = data.get('text', '') try: ia_gen = IA_generator("config/config.json") ia_gen.main_ask(text) return jsonify({'success': True}) except Exception as e: return jsonify({'error': str(e)}), 500 @app.route('/api/chat//messages', methods=['GET']) def get_chat_messages(flux_id): """Récupérer les messages de chat d'un flux spécifique""" try: if flux_id not in bot_controller.bots: return jsonify({'error': 'Flux non trouvé'}), 404 chat_bot = bot_controller.bots[flux_id]['chat_bot'] if not chat_bot: return jsonify({'error': 'Bot de chat non disponible'}), 404 # Récupérer les messages du bot de chat messages = [] for msg in chat_bot.messages[-50:]: # Derniers 50 messages messages.append({ 'timestamp': msg.timestamp.isoformat(), 'username': msg.username, 'content': msg.content }) return jsonify({ 'success': True, 'flux_id': flux_id, 'messages': messages }) except Exception as e: return jsonify({'error': str(e)}), 500 @app.route('/api/chat//send', methods=['POST']) def send_chat_message(flux_id): """Envoyer un message dans le chat d'un flux spécifique""" data = request.json message = data.get('message', '') user_id = data.get('user_id', 0) # Nouveau paramètre pour choisir l'utilisateur if not message: return jsonify({'error': 'Message requis'}), 400 # Vérifier si l'envoi de messages est activé if not chat_state.chat_messages_enabled: return jsonify({'error': 'Envoi de messages désactivé'}), 403 try: # Trouver le flux flux = None for f in bot_controller.flux_list: if f['id'] == flux_id: flux = f break if not flux: return jsonify({'error': 'Flux non trouvé'}), 404 # Envoyer le message avec l'utilisateur spécifié msg_bot = messageTwitch("config/user.json", flux['twitchname']) msg_bot.send_message_user(user_id, message) return jsonify({'success': True}) except Exception as e: return jsonify({'error': str(e)}), 500 @app.route('/api/status', methods=['GET']) def get_status(): status = { 'flux_count': len(bot_controller.flux_list), 'active_recordings': sum(1 for f in bot_controller.flux_list if f['record_audio'] and f['active']), 'chat_connections': sum(1 for f in bot_controller.flux_list if f['active']), 'last_subtitle': '', 'next_message': '', 'recent_messages': [] } # Récupérer le dernier sous-titre subtitle_data = storage.read("subtitle_data") if subtitle_data: sorted_keys = sorted(subtitle_data.keys()) if sorted_keys: status['last_subtitle'] = subtitle_data[sorted_keys[-1]] # Récupérer la dernière génération generation_data = storage.read("IA_generator") if generation_data: sorted_keys = sorted(generation_data.keys()) if sorted_keys: status['next_message'] = generation_data[sorted_keys[-1]] return jsonify(status) @socketio.on('connect') def handle_connect(): print('Client connecté') emit('status', {'message': 'Connecté au serveur'}) @socketio.on('disconnect') def handle_disconnect(): print('Client déconnecté') # Thread pour envoyer les mises à jour en temps réel def background_updates(): while True: try: status = { 'timestamp': datetime.now().isoformat(), 'flux_count': len(bot_controller.flux_list), 'active_recordings': sum(1 for f in bot_controller.flux_list if f['record_audio'] and f['active']), } socketio.emit('status_update', status) time.sleep(5) # Mise à jour toutes les 5 secondes except Exception as e: print(f"Erreur dans background_updates: {e}") time.sleep(10) # Variables globales pour la génération automatique auto_subtitle_running = False current_processing_file = None # Variables globales pour l'envoi automatique de messages auto_message_running = False current_message_bot = None @app.route('/api/subtitles/auto/start', methods=['POST']) def start_auto_subtitle(): """Démarrer la génération automatique de sous-titres""" global auto_subtitle_running try: # Vérifier si déjà en cours if auto_subtitle_running: return jsonify({ 'success': False, 'error': 'La génération automatique est déjà en cours' }), 400 auto_subtitle_running = True print(f"[{datetime.now().strftime('%H:%M:%S')}] Démarrage de la génération automatique de sous-titres") # Démarrer le thread de génération automatique threading.Thread(target=auto_subtitle_loop, daemon=True).start() return jsonify({ 'success': True, 'message': 'Génération automatique démarrée' }) except Exception as e: auto_subtitle_running = False return jsonify({ 'success': False, 'error': f'Erreur lors du démarrage: {str(e)}' }), 500 @app.route('/api/subtitles/auto/stop', methods=['POST']) def stop_auto_subtitle(): """Arrêter la génération automatique de sous-titres""" global auto_subtitle_running try: if not auto_subtitle_running: return jsonify({ 'success': False, 'error': 'La génération automatique n\'est pas en cours' }), 400 print(f"[{datetime.now().strftime('%H:%M:%S')}] Arrêt de la génération automatique de sous-titres") auto_subtitle_running = False return jsonify({ 'success': True, 'message': 'Génération automatique arrêtée' }) except Exception as e: return jsonify({ 'success': False, 'error': f'Erreur lors de l\'arrêt: {str(e)}' }), 500 @app.route('/api/subtitles/auto/force-stop', methods=['POST']) def force_stop_auto_subtitle(): """Forcer l'arrêt de la génération automatique de sous-titres""" global auto_subtitle_running, current_processing_file try: print(f"[{datetime.now().strftime('%H:%M:%S')}] Arrêt forcé de la génération automatique de sous-titres") auto_subtitle_running = False current_processing_file = None return jsonify({ 'success': True, 'message': 'Arrêt forcé de la génération automatique de sous-titres' }) except Exception as e: return jsonify({ 'success': False, 'error': f'Erreur lors de l\'arrêt forcé: {str(e)}' }), 500 @app.route('/api/subtitles/auto/status', methods=['GET']) def get_auto_subtitle_status(): """Obtenir le statut de la génération automatique""" global auto_subtitle_running, current_processing_file return jsonify({ 'running': auto_subtitle_running, 'current_file': current_processing_file }) def auto_subtitle_loop(): """Boucle de génération automatique de sous-titres""" global auto_subtitle_running, current_processing_file print(f"[{datetime.now().strftime('%H:%M:%S')}] Démarrage de la boucle de génération automatique de sous-titres") while auto_subtitle_running: try: # Vérifier s'il y a des fichiers audio à traiter record_dir = "record" if not os.path.exists(record_dir): time.sleep(5) continue audio_files = [f for f in os.listdir(record_dir) if f.endswith('.mp3')] if not audio_files: time.sleep(5) continue # Traiter le premier fichier audio_file = audio_files[0] current_processing_file = audio_file print(f"[{datetime.now().strftime('%H:%M:%S')}] Traitement de: {audio_file}") # Émettre l'événement de début de traitement socketio.emit('subtitle_processing_start', { 'file': audio_file, 'timestamp': datetime.now().isoformat() }) # Traiter le fichier audio_path = os.path.join(record_dir, audio_file) try: # Lancer Whisper avec vérification périodique de l'arrêt command = [ 'whisper', '--language', 'fr', audio_path, '--device', 'cuda', '--model', 'large-v3' ] # Utiliser Popen pour pouvoir interrompre le processus process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) # Attendre la fin du processus avec vérification périodique while process.poll() is None: if not auto_subtitle_running: print(f"[{datetime.now().strftime('%H:%M:%S')}] Arrêt demandé, interruption du processus Whisper") process.terminate() try: process.wait(timeout=5) except subprocess.TimeoutExpired: process.kill() break time.sleep(1) # Si la boucle a été arrêtée, sortir if not auto_subtitle_running: print(f"[{datetime.now().strftime('%H:%M:%S')}] Arrêt de la boucle de génération automatique") break result = process.returncode stdout, stderr = process.communicate() if result == 0: # Lire le fichier .txt généré txt_file = audio_file.replace('.mp3', '.txt') if os.path.exists(txt_file): with open(txt_file, 'r', encoding='utf-8') as f: content = f.read().strip() # Nettoyer le contenu content = content.replace("'", "").replace('"', "").replace("\n", " ").replace(",", "") # Supprimer les répétitions de mots words = content.split() seen = set() result_words = [] for word in words: if word not in seen: result_words.append(word) seen.add(word) cleaned_content = " ".join(result_words) # Sauvegarder dans le stockage current_time = datetime.now().strftime('%H:%M:%S') # Sauvegarder dans le fichier de stockage storage_dir = "storage" if not os.path.exists(storage_dir): os.makedirs(storage_dir) subtitle_file = os.path.join(storage_dir, "subtitle_data.json") try: with open(subtitle_file, 'r', encoding='utf-8') as f: existing_data = json.load(f) except (FileNotFoundError, json.JSONDecodeError): existing_data = {} existing_data[current_time] = cleaned_content with open(subtitle_file, 'w', encoding='utf-8') as f: json.dump(existing_data, f, indent=4, ensure_ascii=False) # Émettre l'événement de fin de traitement socketio.emit('subtitle_processing_complete', { 'file': audio_file, 'subtitle': cleaned_content, 'timestamp': current_time }) print(f"[{datetime.now().strftime('%H:%M:%S')}] Sous-titre généré: {cleaned_content}") # Nettoyer les fichiers temporaires os.remove(txt_file) os.remove(audio_path) else: # Émettre l'événement d'erreur socketio.emit('subtitle_processing_error', { 'file': audio_file, 'error': 'Aucun fichier .txt généré' }) print(f"[{datetime.now().strftime('%H:%M:%S')}] Erreur: Aucun fichier .txt généré") else: # Émettre l'événement d'erreur socketio.emit('subtitle_processing_error', { 'file': audio_file, 'error': f'Erreur Whisper: {stderr}' }) print(f"[{datetime.now().strftime('%H:%M:%S')}] Erreur Whisper: {stderr}") except subprocess.TimeoutExpired: socketio.emit('subtitle_processing_error', { 'file': audio_file, 'error': 'Timeout - Whisper a pris trop de temps' }) print(f"[{datetime.now().strftime('%H:%M:%S')}] Timeout - Whisper a pris trop de temps") except Exception as e: socketio.emit('subtitle_processing_error', { 'file': audio_file, 'error': f'Erreur: {str(e)}' }) print(f"[{datetime.now().strftime('%H:%M:%S')}] Erreur lors du traitement: {str(e)}") current_processing_file = None # Vérifier à nouveau si la boucle doit continuer if not auto_subtitle_running: print(f"[{datetime.now().strftime('%H:%M:%S')}] Arrêt de la boucle de génération automatique") break # Attendre avant de traiter le prochain fichier time.sleep(2) except Exception as e: print(f"[{datetime.now().strftime('%H:%M:%S')}] Erreur dans la boucle de génération automatique: {e}") time.sleep(5) print(f"[{datetime.now().strftime('%H:%M:%S')}] Fin de la boucle de génération automatique de sous-titres") @app.route('/api/messages/auto/start', methods=['POST']) def start_auto_message(): """Démarrer l'envoi automatique de messages""" global auto_message_running try: # Vérifier si déjà en cours if auto_message_running: return jsonify({ 'success': False, 'error': 'L\'envoi automatique est déjà en cours' }), 400 auto_message_running = True # Créer une instance du bot de messages current_message_bot = messageTwitch("config/user.json", "default") # Démarrer le thread d'envoi automatique threading.Thread(target=auto_message_loop, daemon=True).start() print(f"[{datetime.now().strftime('%H:%M:%S')}] Envoi automatique de messages démarré") return jsonify({ 'success': True, 'message': 'Envoi automatique de messages démarré' }) except Exception as e: auto_message_running = False current_message_bot = None return jsonify({ 'success': False, 'error': f'Erreur lors du démarrage: {str(e)}' }), 500 @app.route('/api/messages/auto/stop', methods=['POST']) def stop_auto_message(): """Arrêter l'envoi automatique de messages""" global auto_message_running, current_message_bot try: if not auto_message_running: return jsonify({ 'success': False, 'error': 'L\'envoi automatique n\'est pas en cours' }), 400 auto_message_running = False current_message_bot = None print(f"[{datetime.now().strftime('%H:%M:%S')}] Envoi automatique de messages arrêté") return jsonify({ 'success': True, 'message': 'Envoi automatique de messages arrêté' }) except Exception as e: return jsonify({ 'success': False, 'error': f'Erreur lors de l\'arrêt: {str(e)}' }), 500 @app.route('/api/messages/auto/force-stop', methods=['POST']) def force_stop_auto_message(): """Forcer l'arrêt de l'envoi automatique de messages""" global auto_message_running, current_message_bot try: auto_message_running = False current_message_bot = None print(f"[{datetime.now().strftime('%H:%M:%S')}] Arrêt forcé de l'envoi automatique de messages") return jsonify({ 'success': True, 'message': 'Arrêt forcé de l\'envoi automatique de messages' }) except Exception as e: return jsonify({ 'success': False, 'error': f'Erreur lors de l\'arrêt forcé: {str(e)}' }), 500 @app.route('/api/messages/auto/status', methods=['GET']) def get_auto_message_status(): """Obtenir le statut de l'envoi automatique de messages""" global auto_message_running return jsonify({ 'running': auto_message_running }) def auto_message_loop(): """Boucle d'envoi automatique de messages""" global auto_message_running, current_message_bot print(f"[{datetime.now().strftime('%H:%M:%S')}] Démarrage de la boucle d'envoi automatique") while auto_message_running: try: # Vérifier si l'envoi de messages est activé if not chat_state.chat_messages_enabled: print(f"[{datetime.now().strftime('%H:%M:%S')}] Envoi de messages désactivé, attente...") time.sleep(5) continue # Vérifier s'il y a des générations disponibles generation_data = storage.read("IA_generator") if not generation_data: time.sleep(5) continue # Récupérer la dernière génération sorted_keys = sorted(generation_data.keys()) if not sorted_keys: time.sleep(5) continue last_generation = generation_data[sorted_keys[-1]] print(f"[{datetime.now().strftime('%H:%M:%S')}] Envoi automatique: {last_generation}") # Émettre l'événement de début d'envoi socketio.emit('message_sending_start', { 'message': last_generation, 'timestamp': datetime.now().isoformat() }) # Envoyer le message try: if current_message_bot: # Utiliser le premier utilisateur par défaut current_message_bot.send_message_user(_first_enabled_user_index(), last_generation) # Émettre l'événement de fin d'envoi socketio.emit('message_sending_complete', { 'message': last_generation, 'timestamp': datetime.now().isoformat() }) # Supprimer la génération envoyée storage.delete("IA_generator", sorted_keys[-1]) print(f"[{datetime.now().strftime('%H:%M:%S')}] Message envoyé avec succès") except Exception as e: # Émettre l'événement d'erreur socketio.emit('message_sending_error', { 'message': last_generation, 'error': str(e) }) print(f"[{datetime.now().strftime('%H:%M:%S')}] Erreur d'envoi: {e}") # Attendre avant d'envoyer le prochain message time.sleep(10) except Exception as e: print(f"[{datetime.now().strftime('%H:%M:%S')}] Erreur dans la boucle d'envoi automatique: {e}") time.sleep(5) print(f"[{datetime.now().strftime('%H:%M:%S')}] Arrêt de la boucle d'envoi automatique") @app.route('/api/chat/messages/enable', methods=['POST']) def enable_chat_messages(): """Activer l'envoi de messages dans le chat""" try: chat_state.chat_messages_enabled = True return jsonify({ 'success': True, 'message': 'Envoi de messages dans le chat activé' }) except Exception as e: return jsonify({ 'success': False, 'error': f'Erreur lors de l\'activation: {str(e)}' }), 500 @app.route('/api/chat/messages/disable', methods=['POST']) def disable_chat_messages(): """Désactiver l'envoi de messages dans le chat""" try: chat_state.chat_messages_enabled = False return jsonify({ 'success': True, 'message': 'Envoi de messages dans le chat désactivé' }) except Exception as e: return jsonify({ 'success': False, 'error': f'Erreur lors de la désactivation: {str(e)}' }), 500 @app.route('/api/chat/messages/status', methods=['GET']) def get_chat_messages_status(): """Obtenir le statut de l'envoi de messages dans le chat""" return jsonify({ 'enabled': chat_state.chat_messages_enabled }) @app.route('/api/system-status', methods=['GET']) def get_system_status(): """Obtenir le statut de tous les composants""" return jsonify(bot_controller.get_system_status()) @app.route('/api/interaction/config', methods=['GET']) def get_interaction_config(): """ Récupérer la config Interaction Chat (réponses préenregistrées + règles). Stockée en JSON sous storage/interaction_chat_config.json. """ try: # Utiliser le processor du premier flux actif si possible, sinon un "fallback" via une instance ad-hoc processor = None for flux_id, bots in bot_controller.bots.items(): processor = bots.get('interaction') if processor: break if not processor: # fallback: config globale, même fichier storage from twitch_bot.interaction_chat import InteractionChatStorage st = InteractionChatStorage() cfg = InteractionChatConfig(st.read_json("interaction_chat_config", default={})) data = cfg.to_dict() else: data = processor.load_config().to_dict() # Comptes enregistrés (config/user.json) try: with open('config/user.json', 'r', encoding='utf-8') as f: users = json.load(f) registered = [u.get('tw_acc_pseudo') for u in (users or []) if isinstance(u, dict) and u.get('tw_acc_pseudo')] except Exception: registered = [] return jsonify({'success': True, 'config': data, 'registered_accounts': registered}) except Exception as e: return jsonify({'success': False, 'error': str(e)}), 500 @app.route('/api/interaction/config', methods=['POST']) def save_interaction_config(): """Sauvegarder la config Interaction Chat.""" try: payload = request.json or {} config_dict = payload.get('config') or {} # Sauvegarder via un processor si présent, sinon via stockage direct processor = None for flux_id, bots in bot_controller.bots.items(): processor = bots.get('interaction') if processor: break if processor: cfg = processor.save_config(config_dict) else: from twitch_bot.interaction_chat import InteractionChatStorage st = InteractionChatStorage() cfg = InteractionChatConfig(config_dict) st.write_json("interaction_chat_config", cfg.to_dict()) return jsonify({'success': True, 'config': cfg.to_dict()}) except Exception as e: return jsonify({'success': False, 'error': str(e)}), 500 @app.route('/api/interaction/log', methods=['GET']) def get_interaction_log(): """Lire les logs Interaction Chat.""" try: limit = int(request.args.get('limit', '100')) limit = max(1, min(500, limit)) processor = None for flux_id, bots in bot_controller.bots.items(): processor = bots.get('interaction') if processor: break if processor: logs = processor.read_log(limit=limit) else: from twitch_bot.interaction_chat import InteractionChatStorage st = InteractionChatStorage() logs = st.read_json("interaction_chat_log", default=[]) if not isinstance(logs, list): logs = [] logs = logs[-limit:] return jsonify({'success': True, 'logs': logs}) except Exception as e: return jsonify({'success': False, 'error': str(e)}), 500 @app.route('/api/ia-generator/start', methods=['POST']) def start_ia_generator(): """Démarrer le générateur IA""" success, message = bot_controller.start_ia_generator() if success: # Synchroniser l'état de l'interface web socketio.emit('ia_generator_status_changed', { 'running': True, 'message': message }) return jsonify({'success': success, 'message': message}) @app.route('/api/ia-generator/stop', methods=['POST']) def stop_ia_generator(): """Arrêter le générateur IA""" success, message = bot_controller.stop_ia_generator() if success: # Synchroniser l'état de l'interface web socketio.emit('ia_generator_status_changed', { 'running': False, 'message': message }) return jsonify({'success': success, 'message': message}) @app.route('/api/control-twitch/start', methods=['POST']) def start_control_twitch(): """Démarrer le contrôleur Twitch""" success, message = bot_controller.start_control_twitch() return jsonify({'success': success, 'message': message}) @app.route('/api/control-twitch/stop', methods=['POST']) def stop_control_twitch(): """Arrêter le contrôleur Twitch""" success, message = bot_controller.stop_control_twitch() return jsonify({'success': success, 'message': message}) if __name__ == '__main__': # Démarrer les mises à jour en arrière-plan update_thread = threading.Thread(target=background_updates, daemon=True) update_thread.start() # Démarrer l'application Flask socketio.run(app, host='0.0.0.0', port=5000, debug=True)