big update

This commit is contained in:
gpatruno
2026-04-22 22:42:36 +02:00
parent 68cf59ae75
commit 7b2135bfed
25 changed files with 2661 additions and 564 deletions
+216 -272
View File
@@ -1,272 +1,39 @@
from flask import Flask, render_template, request, jsonify, redirect, url_for
from flask_socketio import SocketIO, emit
import json
import os
import subprocess
import sys
import threading
import time
from datetime import datetime
import sys
# Ajouter le chemin de l'environnement virtuel au PYTHONPATH
venv_path = os.path.join(os.path.dirname(__file__), 'env', 'lib', 'python3.10', 'site-packages')
if venv_path not in sys.path:
sys.path.insert(0, venv_path)
_ROOT = os.path.dirname(os.path.abspath(__file__))
if _ROOT not in sys.path:
sys.path.insert(0, _ROOT)
# Import des classes du bot
sys.path.append('.')
from fonction.first_class import RecordTwitch, Subtitle_translation, IA_generator, messageTwitch, TwitchChatBot, storage
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="*")
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)
self.bots[flux_id] = {
'chat_bot': chat_bot,
'record_bot': None,
'subtitle_bot': None,
'ia_bot': None,
'message_bot': None
}
chat_bot.start_background()
# 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:
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]['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é
global chat_messages_enabled
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é
global chat_messages_enabled
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
self.control_twitch.send_message_user(0, 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()
@app.route('/')
def index():
return render_template('index.html')
@@ -385,6 +152,41 @@ def update_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"""
@@ -404,6 +206,8 @@ def add_user():
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
@@ -421,7 +225,9 @@ def add_user():
new_user = {
'tw_acc_pseudo': pseudo,
'tw_acc_token': token,
'charactere': charactere
'charactere': charactere,
'enabled': enabled,
'interaction_bypass_antiloop': interaction_bypass_antiloop,
}
users.append(new_user)
@@ -440,6 +246,8 @@ def update_user(user_id):
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
@@ -460,7 +268,9 @@ def update_user(user_id):
users[user_id] = {
'tw_acc_pseudo': pseudo,
'tw_acc_token': token,
'charactere': charactere
'charactere': charactere,
'enabled': enabled,
'interaction_bypass_antiloop': interaction_bypass_antiloop,
}
# Sauvegarder
@@ -471,6 +281,58 @@ def update_user(user_id):
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/config/users/<int:user_id>/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/<int:user_id>/interaction-bypass', methods=['POST'])
def set_user_interaction_bypass(user_id):
"""Autoriser un utilisateur à bypass lanti-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/<int:user_id>', methods=['DELETE'])
def delete_user(user_id):
"""Supprimer un utilisateur"""
@@ -648,7 +510,7 @@ def send_message():
return jsonify({'error': 'Message requis'}), 400
# Vérifier si l'envoi de messages est activé
if not chat_messages_enabled:
if not chat_state.chat_messages_enabled:
return jsonify({'error': 'Envoi de messages désactivé'}), 403
# Trouver le bot de message pour ce canal
@@ -712,7 +574,7 @@ def send_chat_message(flux_id):
return jsonify({'error': 'Message requis'}), 400
# Vérifier si l'envoi de messages est activé
if not chat_messages_enabled:
if not chat_state.chat_messages_enabled:
return jsonify({'error': 'Envoi de messages désactivé'}), 403
try:
@@ -794,9 +656,6 @@ current_processing_file = None
auto_message_running = False
current_message_bot = None
# Variable globale pour contrôler l'envoi de messages dans le chat
chat_messages_enabled = False
@app.route('/api/subtitles/auto/start', methods=['POST'])
def start_auto_subtitle():
"""Démarrer la génération automatique de sous-titres"""
@@ -1140,7 +999,7 @@ def auto_message_loop():
while auto_message_running:
try:
# Vérifier si l'envoi de messages est activé
if not chat_messages_enabled:
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
@@ -1171,7 +1030,7 @@ def auto_message_loop():
try:
if current_message_bot:
# Utiliser le premier utilisateur par défaut
current_message_bot.send_message_user(0, last_generation)
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', {
@@ -1204,9 +1063,8 @@ def auto_message_loop():
@app.route('/api/chat/messages/enable', methods=['POST'])
def enable_chat_messages():
"""Activer l'envoi de messages dans le chat"""
global chat_messages_enabled
try:
chat_messages_enabled = True
chat_state.chat_messages_enabled = True
return jsonify({
'success': True,
'message': 'Envoi de messages dans le chat activé'
@@ -1220,9 +1078,8 @@ def enable_chat_messages():
@app.route('/api/chat/messages/disable', methods=['POST'])
def disable_chat_messages():
"""Désactiver l'envoi de messages dans le chat"""
global chat_messages_enabled
try:
chat_messages_enabled = False
chat_state.chat_messages_enabled = False
return jsonify({
'success': True,
'message': 'Envoi de messages dans le chat désactivé'
@@ -1236,9 +1093,8 @@ def disable_chat_messages():
@app.route('/api/chat/messages/status', methods=['GET'])
def get_chat_messages_status():
"""Obtenir le statut de l'envoi de messages dans le chat"""
global chat_messages_enabled
return jsonify({
'enabled': chat_messages_enabled
'enabled': chat_state.chat_messages_enabled
})
@app.route('/api/system-status', methods=['GET'])
@@ -1246,6 +1102,94 @@ 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"""