389 lines
16 KiB
Python
389 lines
16 KiB
Python
"""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
|
|
from twitch_bot import twitch_helix
|
|
from twitch_bot.subtitle_rules import SubtitleRulesProcessor
|
|
|
|
|
|
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, send_messages=True, enable_ia=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,
|
|
'send_messages': bool(send_messages),
|
|
'enable_ia': bool(enable_ia),
|
|
'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)
|
|
def create_clip_as(pseudo: str, broadcaster_login: str, has_delay: bool = False):
|
|
client_id = twitch_helix.twitch_client_id("config/config.json")
|
|
bearer = twitch_helix.bearer_token_for_pseudo(pseudo, "config/user.json")
|
|
broadcaster_id = twitch_helix.get_user_id_by_login(
|
|
client_id=client_id,
|
|
bearer=bearer,
|
|
login=broadcaster_login,
|
|
)
|
|
return twitch_helix.create_clip(
|
|
client_id=client_id,
|
|
bearer=bearer,
|
|
broadcaster_id=broadcaster_id,
|
|
has_delay=bool(has_delay),
|
|
)
|
|
|
|
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)
|
|
if bool(flux_data.get("send_messages", True))
|
|
else None
|
|
),
|
|
create_clip_as=create_clip_as,
|
|
)
|
|
|
|
subtitle_rules = SubtitleRulesProcessor(
|
|
channel_name=channel_name,
|
|
send_message_as=lambda pseudo, text: (
|
|
msg_bot_for_interaction.send_message_user(_resolve_user_index(pseudo), text)
|
|
if bool(flux_data.get("send_messages", True))
|
|
else None
|
|
),
|
|
create_clip_as=create_clip_as,
|
|
resolve_default_account=lambda: (get_registered_accounts() or [""])[0],
|
|
)
|
|
|
|
self.bots[flux_id] = {
|
|
'chat_bot': chat_bot,
|
|
'record_bot': None,
|
|
'subtitle_bot': None,
|
|
'ia_bot': None,
|
|
'message_bot': None,
|
|
'interaction': interaction,
|
|
'subtitle_rules': subtitle_rules,
|
|
}
|
|
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_storage_key = f"subtitle_data__{channel_name.lower()}"
|
|
subtitle_bot = Subtitle_translation(
|
|
"config/config.json",
|
|
storage_key=subtitle_storage_key,
|
|
on_new_subtitle=lambda ts, text: subtitle_rules.on_new_subtitle(ts_key=ts, subtitle_text=text),
|
|
segment_seconds=30,
|
|
max_backlog_files=3,
|
|
)
|
|
self.bots[flux_id]['subtitle_bot'] = subtitle_bot
|
|
subtitle_bot.start_main_loop()
|
|
|
|
# Démarrer le générateur IA
|
|
if bool(flux_data.get("enable_ia", True)):
|
|
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 (uniquement si autorisé sur ce flux)
|
|
if bool(flux_data.get("send_messages", True)) and bool(flux_data.get("enable_ia", True)):
|
|
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()
|