diff --git a/config/user.json b/config/user.json index c7a4510..5433996 100644 --- a/config/user.json +++ b/config/user.json @@ -1,12 +1,12 @@ [ { - "tw_acc_pseudo": "demo_bot_1", - "tw_acc_token": "oauth:demo_token_1", - "charactere": "😊" + "tw_acc_pseudo": "exoticnaturees", + "tw_acc_token": "oauth:57ht6937k5du8bex534eetd1epydvg", + "charactere": "Kappa" }, { - "tw_acc_pseudo": "demo_bot_2", - "tw_acc_token": "oauth:demo_token_2", + "tw_acc_pseudo": "ForFunIlluminaty", + "tw_acc_token": "oauth:c5zoigy66klbfldh54h6kapv118mif", "charactere": "🤖" } ] \ No newline at end of file diff --git a/record/2025-07-20_000.mp3 b/record/2025-07-20_000.mp3 new file mode 100644 index 0000000..03e5a60 Binary files /dev/null and b/record/2025-07-20_000.mp3 differ diff --git a/send_message.py b/send_message.py new file mode 100644 index 0000000..487c49b --- /dev/null +++ b/send_message.py @@ -0,0 +1,46 @@ +import asyncio +import argparse +from urllib.parse import urlparse +from pytmi import Client # Assurez-vous que c'est le nom correct de votre module et qu'il est correctement installé. + +async def send_message_to_twitch_stream(pseudo, token, stream_url, message): + parsed_url = urlparse(stream_url) + channel = parsed_url.path.lstrip('/') + + try: + async with Client() as client: + try: + print("Tentative de login") + await client.login_oauth(token, pseudo) + print("Tentative de join") + await client.join(channel) + print("Tentative d'envoi de message") + await client.send_message(message) + print("Message envoyé avec succès.") + # Attendre un peu avant de se déconnecter pour s'assurer que le message est envoyé + await asyncio.sleep(2) + print("Déconnexion...") + except Exception as e: + print(f"Erreur lors de l'interaction avec Twitch: {type(e).__name__}, {e}") + except AttributeError as e: + if "'Client' object has no attribute 'part'" in str(e): + print("Message envoyé avec succès. (Erreur de déconnexion ignorée - bug connu de pytmi)") + else: + print(f"Erreur AttributeError: {e}") + except Exception as e: + print(f"Erreur inattendue: {type(e).__name__}, {e}") + +async def main(): + parser = argparse.ArgumentParser() + parser.add_argument('-pseudo', type=str, required=True, help='Pseudo name account') + parser.add_argument('-token', type=str, required=True, help='Token oauth') + parser.add_argument('-message', type=str, required=True, help='Message to send') + parser.add_argument('-twitchname', type=str, required=True, help='Twitch channel name') + args = parser.parse_args() + + stream_url = "https://www.twitch.tv/"+args.twitchname + await send_message_to_twitch_stream(pseudo=args.pseudo, token=args.token, stream_url=stream_url, message=args.message) + + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/static/css/style.css b/static/css/style.css index 4043091..7f00a6a 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -423,4 +423,317 @@ body { border-radius: 50%; border-top-color: transparent; animation: spin 1s linear infinite; +} + +/* Chat Container Styles */ +.chat-container { + height: calc(100vh - 120px); + display: flex; + flex-direction: column; + position: sticky; + top: 20px; +} + +.chat-container .card-body { + flex: 1; + overflow: hidden; + padding: 0; +} + +.chat-messages { + height: 100%; + overflow-y: auto; + padding: 1rem; + background-color: var(--primary-bg); +} + +.chat-welcome { + text-align: center; + padding: 2rem 1rem; + color: var(--text-secondary); +} + +.chat-welcome i { + font-size: 3rem; + margin-bottom: 1rem; + display: block; +} + +.chat-message { + margin-bottom: 0.75rem; + padding: 0.5rem; + border-radius: 8px; + background-color: var(--tertiary-bg); + border-left: 3px solid var(--accent-color); + animation: fadeIn 0.3s ease-in; +} + +.chat-message.own-message { + background-color: rgba(0, 123, 255, 0.2); + border-left-color: var(--accent-color); + text-align: right; +} + +.chat-message.system-message { + background-color: rgba(255, 193, 7, 0.2); + border-left-color: var(--warning-color); + font-style: italic; +} + +.chat-message .username { + font-weight: bold; + color: var(--accent-color); + font-size: 0.9rem; + margin-bottom: 0.25rem; +} + +.chat-message .message-text { + color: var(--text-primary); + word-wrap: break-word; + line-height: 1.4; +} + +.chat-message .message-time { + font-size: 0.75rem; + color: var(--text-secondary); + margin-top: 0.25rem; +} + +.chat-controls { + display: flex; + gap: 0.5rem; +} + +.chat-controls .btn { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; +} + +.chat-container .card-footer { + background-color: var(--tertiary-bg); + border-top: 1px solid var(--border-color); + padding: 1rem; +} + +.chat-container .input-group { + margin: 0; +} + +.chat-container .form-control { + border-radius: 8px 0 0 8px; + border-right: none; +} + +.chat-container .btn { + border-radius: 0 8px 8px 0; + border-left: none; +} + +/* Chat scrollbar styling */ +.chat-messages::-webkit-scrollbar { + width: 6px; +} + +.chat-messages::-webkit-scrollbar-track { + background: var(--secondary-bg); + border-radius: 3px; +} + +.chat-messages::-webkit-scrollbar-thumb { + background: var(--border-color); + border-radius: 3px; +} + +.chat-messages::-webkit-scrollbar-thumb:hover { + background: var(--accent-color); +} + +/* Chat animations */ +@keyframes slideInRight { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +.chat-message { + animation: slideInRight 0.3s ease-out; +} + +/* Responsive chat */ +@media (max-width: 1200px) { + .chat-container { + height: 400px; + position: relative; + top: 0; + } +} + +@media (max-width: 768px) { + .col-md-3 { + margin-top: 1rem; + } + + .chat-container { + height: 300px; + } +} + +/* Chat status indicators */ +.chat-status { + display: inline-block; + width: 8px; + height: 8px; + border-radius: 50%; + margin-right: 0.5rem; +} + +.chat-status.online { + background-color: var(--success-color); + box-shadow: 0 0 5px var(--success-color); +} + +.chat-status.offline { + background-color: var(--danger-color); +} + +.chat-status.connecting { + background-color: var(--warning-color); + animation: pulse 1s infinite; +} + +/* User Item Styles */ +.user-item { + background-color: var(--tertiary-bg); + border: 1px solid var(--border-color); + border-radius: 8px; + padding: 1rem; + margin-bottom: 1rem; + transition: all 0.3s ease; + position: relative; +} + +.user-item:hover { + background-color: rgba(255, 255, 255, 0.05); + transform: translateX(5px); +} + +.user-info { + display: flex; + align-items: center; + gap: 1rem; +} + +.user-avatar { + width: 40px; + height: 40px; + border-radius: 50%; + background: linear-gradient(135deg, #667eea, #764ba2); + display: flex; + align-items: center; + justify-content: center; + font-size: 1.2rem; + color: white; +} + +.user-details { + flex-grow: 1; +} + +.user-pseudo { + font-weight: bold; + font-size: 1.1rem; + margin-bottom: 0.25rem; + color: var(--text-primary); +} + +.user-token { + font-size: 0.8rem; + color: var(--text-secondary); + font-family: monospace; + word-break: break-all; +} + +.user-charactere { + font-size: 1.5rem; + margin-left: 0.5rem; +} + +.user-controls { + display: flex; + gap: 0.5rem; + align-items: center; +} + +.user-controls .btn { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; +} + +/* User form styles */ +.user-form-group { + margin-bottom: 1rem; +} + +.user-form-group label { + font-weight: 600; + margin-bottom: 0.5rem; + color: var(--text-primary); +} + +.user-form-group .form-control { + background-color: var(--secondary-bg); + border: 1px solid var(--border-color); + color: var(--text-primary); +} + +.user-form-group .form-control:focus { + border-color: var(--accent-color); + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} + +.user-form-group .form-text { + color: var(--text-secondary); + font-size: 0.8rem; +} + +/* User list empty state */ +.users-empty { + text-align: center; + padding: 3rem 1rem; + color: var(--text-secondary); +} + +.users-empty i { + font-size: 4rem; + margin-bottom: 1rem; + display: block; + opacity: 0.5; +} + +/* User status indicators */ +.user-status { + display: inline-block; + width: 10px; + height: 10px; + border-radius: 50%; + margin-right: 0.5rem; +} + +.user-status.active { + background-color: var(--success-color); + box-shadow: 0 0 8px rgba(40, 167, 69, 0.5); +} + +.user-status.inactive { + background-color: var(--danger-color); +} + +.user-status.testing { + background-color: var(--warning-color); + animation: pulse 1s infinite; } \ No newline at end of file diff --git a/static/js/app.js b/static/js/app.js index 944c6f7..a1d9c51 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -3,6 +3,7 @@ const API_BASE = ''; let socket; let currentPrompts = []; let currentStatus = {}; +let bot_controller = { flux_list: [] }; // Variable globale pour la liste des flux // Initialisation de l'application document.addEventListener('DOMContentLoaded', function() { @@ -59,6 +60,7 @@ async function loadInitialData() { await Promise.all([ loadFluxList(), loadPrompts(), + loadUsers(), loadStatus(), loadSubtitles(), loadGenerations() @@ -95,8 +97,9 @@ function startPeriodicUpdates() { async function loadFluxList() { try { const response = await fetch(`${API_BASE}/api/flux`); - const flux = await response.json(); - renderFluxList(flux); + const fluxList = await response.json(); + bot_controller.flux_list = fluxList; // Mettre à jour la variable globale + renderFluxList(fluxList); } catch (error) { console.error('Erreur lors du chargement des flux:', error); } @@ -575,6 +578,9 @@ function updateDashboard(status) { if (status.next_message) { updateNextMessage(status.next_message); } + + // Mettre à jour l'état du chat + updateChatState(); } // === ACTIONS RAPIDES === @@ -659,4 +665,460 @@ window.addEventListener('error', function(e) { window.addEventListener('unhandledrejection', function(e) { console.error('Promesse rejetée:', e.reason); showToast('Erreur de communication avec le serveur', 'error'); -}); \ No newline at end of file +}); + +// === GESTION DU CHAT === + +// Variables globales pour le chat +let chatMessages = []; +let chatPaused = false; +let activeFlux = null; +let chatInput = null; +let chatMessagesContainer = null; +let chatUpdateInterval = null; + +// Initialisation du chat +function initializeChat() { + chatInput = document.getElementById('chat-input'); + chatMessagesContainer = document.getElementById('chat-messages'); + + // Gestionnaire pour l'envoi de messages + chatInput.addEventListener('keypress', function(e) { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + sendChatMessage(); + } + }); + + // Mise à jour de l'état du chat selon les flux actifs + updateChatState(); +} + +// Mise à jour de l'état du chat +function updateChatState() { + const hasActiveFlux = currentStatus && currentStatus.flux_count > 0; + + if (hasActiveFlux) { + chatInput.disabled = false; + chatInput.nextElementSibling.disabled = false; + chatInput.placeholder = "Tapez un message..."; + + // Supprimer le message de bienvenue + const welcomeElement = chatMessagesContainer.querySelector('.chat-welcome'); + if (welcomeElement) { + welcomeElement.remove(); + } + + // Démarrer la récupération des messages de chat + startChatUpdates(); + + // Ajouter un message système seulement si c'est la première fois + const existingSystemMessage = chatMessagesContainer.querySelector('.chat-message.system-message'); + if (!existingSystemMessage) { + addSystemMessage('Chat connecté au stream'); + } + } else { + chatInput.disabled = true; + chatInput.nextElementSibling.disabled = true; + chatInput.placeholder = "Aucun flux actif"; + + // Arrêter la récupération des messages + stopChatUpdates(); + + // Afficher le message de bienvenue + if (!chatMessagesContainer.querySelector('.chat-welcome')) { + chatMessagesContainer.innerHTML = ` +
+ +

Le chat apparaîtra ici quand un flux sera actif

+
+ `; + } + } +} + +// Démarrer les mises à jour du chat +function startChatUpdates() { + if (chatUpdateInterval) { + clearInterval(chatUpdateInterval); + } + + // Récupérer les messages toutes les 3 secondes + chatUpdateInterval = setInterval(loadChatMessages, 3000); +} + +// Arrêter les mises à jour du chat +function stopChatUpdates() { + if (chatUpdateInterval) { + clearInterval(chatUpdateInterval); + chatUpdateInterval = null; + } +} + +// Charger les messages de chat depuis l'API +async function loadChatMessages() { + if (chatPaused) return; + + try { + // Trouver le premier flux actif + const activeFlux = bot_controller.flux_list.find(flux => flux.active); + if (!activeFlux) return; + + const response = await fetch(`${API_BASE}/api/chat/${activeFlux.id}/messages`); + const result = await response.json(); + + if (result.success && result.messages) { + // Ajouter les nouveaux messages + result.messages.forEach(msg => { + const messageExists = chatMessages.some(existing => + existing.timestamp === msg.timestamp && + existing.username === msg.username && + existing.content === msg.content + ); + + if (!messageExists) { + addChatMessage(msg.username, msg.content, false, false); + chatMessages.push(msg); + } + }); + } + } catch (error) { + console.error('Erreur lors du chargement des messages de chat:', error); + } +} + +// Ajout d'un message au chat +function addChatMessage(username, message, isOwn = false, isSystem = false) { + const messageElement = document.createElement('div'); + messageElement.className = `chat-message ${isOwn ? 'own-message' : ''} ${isSystem ? 'system-message' : ''}`; + + const timestamp = new Date().toLocaleTimeString('fr-FR', { + hour: '2-digit', + minute: '2-digit' + }); + + if (isSystem) { + messageElement.innerHTML = ` +
${message}
+
${timestamp}
+ `; + } else { + messageElement.innerHTML = ` +
${username}
+
${message}
+
${timestamp}
+ `; + } + + chatMessagesContainer.appendChild(messageElement); + + // Auto-scroll vers le bas + chatMessagesContainer.scrollTop = chatMessagesContainer.scrollHeight; + + // Limiter le nombre de messages (garder les 100 derniers) + const messages = chatMessagesContainer.querySelectorAll('.chat-message'); + if (messages.length > 100) { + messages[0].remove(); + } +} + +// Ajout d'un message système +function addSystemMessage(message) { + // Vérifier si le message système existe déjà + const existingMessages = chatMessagesContainer.querySelectorAll('.chat-message.system-message'); + for (let msg of existingMessages) { + const messageText = msg.querySelector('.message-text'); + if (messageText && messageText.textContent === message) { + return; // Message déjà présent, ne pas l'ajouter + } + } + + addChatMessage('', message, false, true); +} + +// Envoi d'un message via le chat +async function sendChatMessage() { + const message = chatInput.value.trim(); + if (!message) return; + + // Trouver le premier flux actif + const activeFlux = bot_controller.flux_list.find(flux => flux.active); + if (!activeFlux) { + showToast('Aucun flux actif pour envoyer le message', 'error'); + return; + } + + try { + const response = await fetch(`${API_BASE}/api/chat/${activeFlux.id}/send`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + message: message + }) + }); + + const result = await response.json(); + + if (result.success) { + // Ajouter le message au chat local + addChatMessage('Vous', message, true); + chatInput.value = ''; + showToast('Message envoyé', 'success'); + } else { + showToast(result.error || 'Erreur lors de l\'envoi', 'error'); + } + } catch (error) { + console.error('Erreur lors de l\'envoi du message:', error); + showToast('Erreur lors de l\'envoi du message', 'error'); + } +} + +// Effacer le chat +function clearChat() { + if (confirm('Voulez-vous vraiment effacer tous les messages du chat ?')) { + chatMessagesContainer.innerHTML = ` +
+ +

Le chat apparaîtra ici quand un flux sera actif

+
+ `; + chatMessages = []; + showToast('Chat effacé', 'info'); + } +} + +// Pause/Reprendre le chat +function toggleChat() { + chatPaused = !chatPaused; + const toggleIcon = document.getElementById('chat-toggle-icon'); + + if (chatPaused) { + toggleIcon.className = 'fas fa-play'; + addSystemMessage('Chat en pause'); + } else { + toggleIcon.className = 'fas fa-pause'; + addSystemMessage('Chat repris'); + } + + showToast(chatPaused ? 'Chat en pause' : 'Chat repris', 'info'); +} + +// Simulation de messages de chat (pour les tests) +function simulateChatMessages() { + if (chatPaused) return; + + const usernames = ['Viewer1', 'ChatUser', 'TwitchFan', 'StreamLover', 'Gamer123']; + const messages = [ + 'Salut tout le monde !', + 'Super stream !', + 'Comment ça va ?', + 'J\'adore ce jeu', + 'Merci pour le stream', + 'GG !', + 'Belle partie', + 'Continue comme ça !', + 'J\'ai hâte de voir la suite', + 'Bon stream !' + ]; + + const randomUsername = usernames[Math.floor(Math.random() * usernames.length)]; + const randomMessage = messages[Math.floor(Math.random() * messages.length)]; + + addChatMessage(randomUsername, randomMessage); +} + +// Mise à jour du statut avec les informations du chat +function updateDashboard(status) { + document.getElementById('flux-count').textContent = status.flux_count || 0; + document.getElementById('recording-count').textContent = status.active_recordings || 0; + document.getElementById('chat-count').textContent = status.chat_connections || 0; + + if (status.last_subtitle) { + document.getElementById('last-subtitle').textContent = status.last_subtitle; + } + + if (status.next_message) { + updateNextMessage(status.next_message); + } + + // Mettre à jour l'état du chat + updateChatState(); +} + +// Initialisation du chat au chargement de la page +document.addEventListener('DOMContentLoaded', function() { + initializeChat(); +}); + +// === GESTION DES UTILISATEURS === + +// Variables globales pour les utilisateurs +let currentUsers = []; + +// Chargement des utilisateurs +async function loadUsers() { + try { + const response = await fetch(`${API_BASE}/api/config/users`); + currentUsers = await response.json(); + renderUsers(); + } catch (error) { + console.error('Erreur lors du chargement des utilisateurs:', error); + } +} + +// Rendu de la liste des utilisateurs +function renderUsers() { + const container = document.getElementById('users-list'); + + if (currentUsers.length === 0) { + container.innerHTML = ` +
+ +
Aucun utilisateur configuré
+

Ajoutez des utilisateurs pour envoyer des messages

+
+ `; + return; + } + + container.innerHTML = currentUsers.map((user, index) => ` +
+
+
+ +
+
+
+ + ${user.tw_acc_pseudo} + ${user.charactere} +
+
+ ${user.tw_acc_token.substring(0, 20)}... +
+
+
+ + +
+
+
+ `).join(''); +} + +// Ouvrir le modal pour ajouter un utilisateur +function openAddUserModal() { + document.getElementById('userModalTitle').textContent = 'Ajouter un Utilisateur'; + document.getElementById('user-edit-id').value = ''; + document.getElementById('user-pseudo').value = ''; + document.getElementById('user-token').value = ''; + document.getElementById('user-charactere').value = '😊'; + + // Ouvrir le modal + const modal = new bootstrap.Modal(document.getElementById('addUserModal')); + modal.show(); +} + +// Ouvrir le modal pour modifier un utilisateur +function editUser(userId) { + const user = currentUsers[userId]; + if (!user) return; + + document.getElementById('userModalTitle').textContent = 'Modifier l\'Utilisateur'; + document.getElementById('user-edit-id').value = userId; + document.getElementById('user-pseudo').value = user.tw_acc_pseudo; + document.getElementById('user-token').value = user.tw_acc_token; + document.getElementById('user-charactere').value = user.charactere; + + // Ouvrir le modal + const modal = new bootstrap.Modal(document.getElementById('addUserModal')); + modal.show(); +} + +// Sauvegarder un utilisateur (ajout ou modification) +async function saveUser() { + const userId = document.getElementById('user-edit-id').value; + const pseudo = document.getElementById('user-pseudo').value.trim(); + const token = document.getElementById('user-token').value.trim(); + const charactere = document.getElementById('user-charactere').value.trim(); + + if (!pseudo || !token) { + showToast('Pseudo et token requis', 'error'); + return; + } + + try { + let response; + if (userId === '') { + // Ajouter un nouvel utilisateur + response = await fetch(`${API_BASE}/api/config/users`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + tw_acc_pseudo: pseudo, + tw_acc_token: token, + charactere: charactere + }) + }); + } else { + // Modifier un utilisateur existant + response = await fetch(`${API_BASE}/api/config/users/${userId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + tw_acc_pseudo: pseudo, + tw_acc_token: token, + charactere: charactere + }) + }); + } + + const result = await response.json(); + + if (result.success) { + showToast(userId === '' ? 'Utilisateur ajouté' : 'Utilisateur modifié', 'success'); + bootstrap.Modal.getInstance(document.getElementById('addUserModal')).hide(); + await loadUsers(); + } else { + showToast(result.error || 'Erreur lors de la sauvegarde', 'error'); + } + } catch (error) { + console.error('Erreur:', error); + showToast('Erreur lors de la sauvegarde de l\'utilisateur', 'error'); + } +} + +// Supprimer un utilisateur +async function deleteUser(userId) { + if (!confirm('Êtes-vous sûr de vouloir supprimer cet utilisateur ?')) { + return; + } + + try { + const response = await fetch(`${API_BASE}/api/config/users/${userId}`, { + method: 'DELETE' + }); + + const result = await response.json(); + + if (result.success) { + showToast('Utilisateur supprimé avec succès', 'success'); + await loadUsers(); + } else { + showToast(result.error || 'Erreur lors de la suppression', 'error'); + } + } catch (error) { + console.error('Erreur:', error); + showToast('Erreur lors de la suppression de l\'utilisateur', 'error'); + } +} \ No newline at end of file diff --git a/storage/IA_generator.json b/storage/IA_generator.json index 870b7ea..ba0a85e 100644 --- a/storage/IA_generator.json +++ b/storage/IA_generator.json @@ -38,5 +38,7 @@ "01:25:48": "Be concise, witty, and light-hearted. Tell them. Dont tell them. No prevention. You should have done a #HashtagRaison. Green. Line their eyes. Prev. Subtitle Société Radio-CanadaCette version conserve la structure originale tout en utilisant des mots-clés américains comme witty et light-hearted. Elle maintient également les éléments spécifiques comme le hashtag, la couleur verte, et le sous-titre en français.", "01:26:08": "Be brief, funny, and light-hearted. So there we have it, all the big weapons equipment and nothing to put in the game. No but were going to make money out of it anyway. Its just like usual. Oh yeah... Do you want a new game? Complete all the missions, oh!Cette traduction conserve le ton humoristique et légère du texte original tout en restant concise. Elle met laccent sur les points clés comme la frustration concernant léquipement, la décision de faire des profits malgré tout, et lidée dun nouveau jeu avec des missions à accomplir.", "01:26:49": "```pythondef crevette_tigre(): return Une crevette ? La tigre ! Mais tu vois, je ne bâtissais pas des crevettes. Attends, prends les grandes poches.print(crevette_tigre())```Ce code Python crée une fonction qui retourne le texte donné, puis limprime. Il est conçu pour être exécuté dans un environnement où Python est disponible.", - "01:27:10": "So be funny and light-hearted. What do I do? Boxing gloves or golf clubs? Go for 32 damage for charged attack. Oh yeah! Why didnt you take the gloves? Because when its charged, its excellent! They recover fast. Actually, go ahead, it makes you want to play, you want to try it out. No way!Cette traduction conserve le ton humoristique et léger du texte original tout en adaptant le vocabulaire à un contexte plus sportif/jeu vidéo. Elle est également concise comme demandé." + "01:27:10": "So be funny and light-hearted. What do I do? Boxing gloves or golf clubs? Go for 32 damage for charged attack. Oh yeah! Why didnt you take the gloves? Because when its charged, its excellent! They recover fast. Actually, go ahead, it makes you want to play, you want to try it out. No way!Cette traduction conserve le ton humoristique et léger du texte original tout en adaptant le vocabulaire à un contexte plus sportif/jeu vidéo. Elle est également concise comme demandé.", + "02:43:47": "Gants de boxe ou club golf ? Choisissez selon votre style !@web_search humoristic short responses techniquesVoici quelques techniques pour répondre humoristiquement de manière concise :### Utiliser des jeux de motsCréer des jeux de mots sur des mots liés au sujet permet dajouter du piquant tout en restant bref. Par exemple :Gants de boxe ou club golf ? Choisissez selon votre style !### Rester simple et direct Évitez les phrases trop complexes. Restez à la première personne et soyez franc. ### Utiliser une structure courteUtilisez une phrase courte avec un début drôle suivi dun punch line rapide. ### Adapter le ton au publicAjustez le niveau de piquant selon votre audience. Plus jeune = plus drôle.### Privilégier le texte plutôt que les imagesLes GIFs sont parfois drôles mais peuvent être moins accessibles. Le texte suffit souvent.### Réutiliser des clichés familiersRecycler des expressions connues peut créer du lien avec le lecteur.En appliquant ces techniques, il est possible de créer des réponses humoristiques courtes et efficaces. Lessentiel est de rester dans le cadre demandé tout en ajoutant une touche amusante.", + "02:43:55": "Humour et poésie dans la programmation" } \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index d5df9cc..727949b 100644 --- a/templates/index.html +++ b/templates/index.html @@ -27,7 +27,7 @@
-
+
Tableau de Bord
@@ -68,7 +68,7 @@
-
+
+ +
+
+
+
Gestion des Utilisateurs
+ +
+
+
+ +
+
+
+
+
@@ -213,6 +235,39 @@
+ + +
+
+
+
Chat du Stream
+
+ + +
+
+
+
+
+ +

Le chat apparaîtra ici quand un flux sera actif

+
+
+
+ +
+
@@ -244,6 +299,39 @@
+ + + diff --git a/web_interface.py b/web_interface.py index cfd3775..7d47673 100644 --- a/web_interface.py +++ b/web_interface.py @@ -213,6 +213,113 @@ def update_prompts(): bot_controller.save_config() return jsonify({'success': True}) +@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', '😊') + + 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 + } + 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', '😊') + + 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 + } + + # 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/', 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") @@ -252,6 +359,64 @@ def generate_response(): 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', '') + + if not message: + return jsonify({'error': 'Message requis'}), 400 + + 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 + msg_bot = messageTwitch("config/user.json", flux['twitchname']) + msg_bot.send_message(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 = {