Ajout de boutons de modération dans la shoutbox pour les modérateurs Twitch, permettant de supprimer des messages, de mettre en timeout ou de bannir des utilisateurs directement depuis l'interface. Mise à jour de la logique de journalisation pour inclure le nom de l'administrateur au lieu de "WebApp". Amélioration de l'expérience utilisateur avec des prompts pour la durée et la raison des actions de modération.

This commit is contained in:
2026-03-10 18:25:55 +01:00
parent 589cb1f428
commit 6d364f28f1
2 changed files with 45 additions and 10 deletions
+35 -2
View File
@@ -676,10 +676,23 @@ function addMessageToDisplay(username, message, timestamp, badges, userColor) {
if (badges && badges.is_vip) badgeIcons += '<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-pink-100 text-pink-800 dark:bg-pink-900/30 dark:text-pink-400" title="VIP">VIP</span>';
if (badges && badges.is_subscriber) badgeIcons += '<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400" title="Abonné">SUB</span>';
var safeUser = escapeHtml(username);
var modButtons = '<div class="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity ml-auto flex-shrink-0">'
+ '<button onclick="executeModerationAction(\'clean\', { username: \'' + safeUser + '\' })" class="p-1 rounded hover:bg-red-100 dark:hover:bg-red-900/30 text-gray-400 hover:text-red-500 transition-colors" title="Supprimer les messages">'
+ '<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>'
+ '</button>'
+ '<button onclick="promptTimeout(\'' + safeUser + '\')" class="p-1 rounded hover:bg-yellow-100 dark:hover:bg-yellow-900/30 text-gray-400 hover:text-yellow-500 transition-colors" title="Timeout">'
+ '<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>'
+ '</button>'
+ '<button onclick="promptBan(\'' + safeUser + '\')" class="p-1 rounded hover:bg-red-100 dark:hover:bg-red-900/30 text-gray-400 hover:text-red-600 transition-colors" title="Ban">'
+ '<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"/></svg>'
+ '</button>'
+ '</div>';
var messageDiv = document.createElement('div');
messageDiv.className = 'bg-white dark:bg-gray-800 rounded-lg p-3 text-sm animate-fade-in border-l-2';
messageDiv.className = 'group bg-white dark:bg-gray-800 rounded-lg p-3 text-sm animate-fade-in border-l-2';
messageDiv.style.borderLeftColor = userColor || '#9146FF';
messageDiv.innerHTML = '<div class="flex items-start gap-2"><div class="flex-1 min-w-0"><div class="flex items-center gap-2 mb-1 flex-wrap"><span class="font-bold" style="color: ' + (userColor || '#9146FF') + '">' + escapeHtml(username) + '</span>' + badgeIcons + '<span class="text-xs text-gray-500 dark:text-gray-400">' + timeStr + '</span></div><div class="text-gray-900 dark:text-gray-100 break-words">' + escapeHtml(message) + '</div></div></div>';
messageDiv.innerHTML = '<div class="flex items-start gap-2"><div class="flex-1 min-w-0"><div class="flex items-center gap-2 mb-1 flex-wrap"><span class="font-bold" style="color: ' + (userColor || '#9146FF') + '">' + safeUser + '</span>' + badgeIcons + '<span class="text-xs text-gray-500 dark:text-gray-400">' + timeStr + '</span>' + modButtons + '</div><div class="text-gray-900 dark:text-gray-100 break-words">' + escapeHtml(message) + '</div></div></div>';
messagesContainer.appendChild(messageDiv);
while (messagesContainer.children.length > 100) messagesContainer.removeChild(messagesContainer.firstChild);
@@ -806,6 +819,26 @@ function executeQuickAction(action) {
}
}
function promptTimeout(username) {
var duration = prompt('Durée du timeout en secondes pour ' + username + ' :', '600');
if (duration !== null) {
duration = parseInt(duration);
if (duration > 0) {
var reason = prompt('Raison (optionnel) :', 'Timeout via chat') || 'Timeout via chat';
executeModerationAction('timeout', { username: username, duration: duration, reason: reason });
} else {
showNotification('Durée invalide', 'error');
}
}
}
function promptBan(username) {
if (confirm('Êtes-vous sûr de vouloir ban ' + username + ' ?')) {
var reason = prompt('Raison (optionnel) :', 'Ban via chat') || 'Ban via chat';
executeModerationAction('ban', { username: username, reason: reason });
}
}
function executeModerationAction(action, params) {
fetch('{{ url_for("execute_moderation_action") }}', {
method: 'POST',
+10 -8
View File
@@ -393,6 +393,8 @@ def execute_moderation_action():
from twitchAPI.chat import ChatMessage
from types import SimpleNamespace
admin_name = f"WebApp ({current_user.username})"
# Exécuter l'action de manière asynchrone
try:
async def execute_action():
@@ -409,7 +411,7 @@ def execute_moderation_action():
if user_id:
await twitchBot.twitch.ban_user(broadcaster_id, moderator_id, user_id, reason=reason, duration=duration)
_log_action("timeout", "WebApp", username, f"{duration}s - {reason}")
_log_action("timeout", admin_name, username, f"{duration}s - {reason}")
return {"success": True, "message": f"Timeout de {username} pour {duration}s"}
return {"success": False, "error": f"Utilisateur {username} introuvable"}
@@ -424,7 +426,7 @@ def execute_moderation_action():
if user_id:
await twitchBot.twitch.ban_user(broadcaster_id, moderator_id, user_id, reason=reason)
_log_action("ban", "WebApp", username, reason)
_log_action("ban", admin_name, username, reason)
return {"success": True, "message": f"Ban de {username}"}
return {"success": False, "error": f"Utilisateur {username} introuvable"}
@@ -439,12 +441,12 @@ def execute_moderation_action():
user_id = await _get_user_id(twitchBot.twitch, username)
if user_id:
await twitchBot.twitch.delete_chat_messages(broadcaster_id, moderator_id, user_id=user_id)
_log_action("clean", "WebApp", username)
_log_action("clean", admin_name, username)
return {"success": True, "message": f"Messages de {username} supprimés"}
return {"success": False, "error": f"Utilisateur {username} introuvable"}
else:
await twitchBot.twitch.delete_chat_messages(broadcaster_id, moderator_id)
_log_action("clean", "WebApp", None, "Chat complet")
_log_action("clean", admin_name, None, "Chat complet")
return {"success": True, "message": "Chat nettoyé"}
elif action == 'permit':
@@ -473,19 +475,19 @@ def execute_moderation_action():
if action == 'subon':
await twitchBot.twitch.update_chat_settings(broadcaster_id, moderator_id, subscriber_mode=True)
_log_action("subon", "WebApp")
_log_action("subon", admin_name)
return {"success": True, "message": "Mode abonnés activé"}
elif action == 'suboff':
await twitchBot.twitch.update_chat_settings(broadcaster_id, moderator_id, subscriber_mode=False)
_log_action("suboff", "WebApp")
_log_action("suboff", admin_name)
return {"success": True, "message": "Mode abonnés désactivé"}
elif action == 'emoteon':
await twitchBot.twitch.update_chat_settings(broadcaster_id, moderator_id, emote_mode=True)
_log_action("emoteon", "WebApp")
_log_action("emoteon", admin_name)
return {"success": True, "message": "Mode emote activé"}
elif action == 'emoteoff':
await twitchBot.twitch.update_chat_settings(broadcaster_id, moderator_id, emote_mode=False)
_log_action("emoteoff", "WebApp")
_log_action("emoteoff", admin_name)
return {"success": True, "message": "Mode emote désactivé"}
return {"success": False, "error": f"Action '{action}' non reconnue"}