Ajout de fonctionnalités majeures Twitch, Discord et interface web
Nouvelles fonctionnalités : - Système de modération Twitch complet (bans, timeouts, avertissements) - Filtre de liens intelligent pour Twitch avec whitelist/blacklist - Notifications d'événements Twitch (follows, subs, raids, etc.) - Système Freeloot Discord avec flux RSS dédié - Salons automatiques Discord (création/suppression dynamique) - Authentification utilisateur pour l'interface web (login/register) - Gestion des utilisateurs et permissions - Interface de modération Twitch dans la webapp - Interface de gestion des événements Twitch - Configuration des paramètres utilisateur - Migration BDD pour les mots bannis Améliorations de l'interface : - Refonte complète des templates (configurations, commandes, humeurs, etc.) - Nouvelle page de settings utilisateur - Page de gestion des utilisateurs (admin) - Page d'erreur 403 personnalisée - Amélioration de la navigation et du design global - Intégration de nouvelles sections dans le menu principal Modifications techniques : - Ajout de nouveaux modèles en base de données - Extension des helpers database - Mise à jour des dépendances (requirements.txt) - Amélioration de la gestion des annonces Twitch - Refactorisation du code pour meilleure maintenabilité Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+99
-13
@@ -2,6 +2,7 @@ import logging
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from twitchAPI.chat import Chat
|
||||
from twitchAPI.twitch import Twitch
|
||||
|
||||
from database import db
|
||||
from database.models import TwitchAnnouncement
|
||||
@@ -10,25 +11,110 @@ from webapp import webapp
|
||||
logger = logging.getLogger('twitch-announcements')
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
# Délai minimum entre deux annonces (quelle qu'elles soient) pour éviter le spam
|
||||
MIN_DELAY_BETWEEN_ANNOUNCEMENTS_MINUTES = 5
|
||||
|
||||
async def checkAndSendAnnouncements(chat: Chat, channel: str):
|
||||
_message_count: int = 0
|
||||
_last_announcement_index: int = -1 # Pour la rotation round-robin
|
||||
|
||||
|
||||
def incrementMessageCount():
|
||||
global _message_count
|
||||
_message_count += 1
|
||||
|
||||
|
||||
def _getAndResetMessageCount() -> int:
|
||||
global _message_count
|
||||
count = _message_count
|
||||
_message_count = 0
|
||||
return count
|
||||
|
||||
|
||||
async def _is_channel_live(twitch: Twitch, channel: str) -> bool:
|
||||
"""Vérifie si la chaîne Twitch est actuellement en live."""
|
||||
try:
|
||||
async for _ in twitch.get_streams(user_login=[channel]):
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning(f'Impossible de vérifier le statut live de {channel}: {e}')
|
||||
return False
|
||||
|
||||
|
||||
async def checkAndSendAnnouncements(chat: Chat, channel: str, twitch: Twitch):
|
||||
"""
|
||||
Vérifie et envoie les annonces dont la périodicité est écoulée.
|
||||
Envoie une annonce en rotation parmi celles dont la périodicité est écoulée,
|
||||
uniquement si la chaîne est en live et qu'il y a assez d'activité dans le chat.
|
||||
Appelé périodiquement par le bot Twitch.
|
||||
"""
|
||||
global _last_announcement_index
|
||||
|
||||
with webapp.app_context():
|
||||
announcements: list[TwitchAnnouncement] = TwitchAnnouncement.query.filter_by(enable=True).all()
|
||||
if not await _is_channel_live(twitch, channel):
|
||||
return
|
||||
|
||||
announcements: list[TwitchAnnouncement] = TwitchAnnouncement.query.filter_by(enable=True).order_by(TwitchAnnouncement.id).all()
|
||||
if not announcements:
|
||||
return
|
||||
|
||||
now = datetime.now()
|
||||
|
||||
for announcement in announcements:
|
||||
if _shouldSend(announcement, now):
|
||||
try:
|
||||
await _sendAnnouncement(chat, channel, announcement)
|
||||
announcement.last_sent = now
|
||||
db.session.commit()
|
||||
logger.info(f'Annonce envoyée : {announcement.name}')
|
||||
except Exception as e:
|
||||
logger.error(f'Erreur lors de l\'envoi de l\'annonce "{announcement.name}": {e}')
|
||||
|
||||
# Ne pas envoyer si une annonce (quelle qu'elle soit) a été envoyée récemment
|
||||
last_any = max((a.last_sent for a in announcements if a.last_sent), default=None)
|
||||
if last_any and (now - last_any) < timedelta(minutes=MIN_DELAY_BETWEEN_ANNOUNCEMENTS_MINUTES):
|
||||
return
|
||||
|
||||
# Filtrer les annonces dont la périodicité est écoulée
|
||||
due = [a for a in announcements if _shouldSend(a, now)]
|
||||
if not due:
|
||||
return
|
||||
|
||||
# Rotation round-robin : chercher la prochaine annonce après la dernière envoyée
|
||||
announcement = _selectNextAnnouncement(due, announcements)
|
||||
if not announcement:
|
||||
return
|
||||
|
||||
# Vérifier le nombre minimum de messages dans le chat
|
||||
message_count = _getAndResetMessageCount()
|
||||
if message_count < announcement.min_chat_messages:
|
||||
logger.debug(f'Annonce "{announcement.name}" ignorée : seulement {message_count} messages (minimum requis : {announcement.min_chat_messages})')
|
||||
return
|
||||
|
||||
try:
|
||||
await _sendAnnouncement(chat, channel, announcement)
|
||||
announcement.last_sent = now
|
||||
_last_announcement_index = announcements.index(announcement)
|
||||
db.session.commit()
|
||||
logger.info(f'Annonce envoyée : {announcement.name} (après {message_count} messages)')
|
||||
except Exception as e:
|
||||
logger.error(f'Erreur lors de l\'envoi de l\'annonce "{announcement.name}": {e}')
|
||||
|
||||
|
||||
def _selectNextAnnouncement(due: list[TwitchAnnouncement], all_announcements: list[TwitchAnnouncement]) -> TwitchAnnouncement | None:
|
||||
"""
|
||||
Sélectionne la prochaine annonce selon un système de rotation round-robin.
|
||||
Cherche la première annonce éligible après la dernière envoyée.
|
||||
"""
|
||||
global _last_announcement_index
|
||||
|
||||
if not due:
|
||||
return None
|
||||
|
||||
# Si c'est la première annonce ou si l'index est invalide, prendre la première de la liste
|
||||
if _last_announcement_index == -1 or _last_announcement_index >= len(all_announcements):
|
||||
return due[0]
|
||||
|
||||
# Chercher la prochaine annonce éligible après la dernière envoyée
|
||||
start_index = _last_announcement_index + 1
|
||||
|
||||
# Parcourir depuis l'index suivant jusqu'à la fin, puis revenir au début
|
||||
for i in range(len(all_announcements)):
|
||||
idx = (start_index + i) % len(all_announcements)
|
||||
announcement = all_announcements[idx]
|
||||
if announcement in due:
|
||||
return announcement
|
||||
|
||||
# Fallback : retourner la première annonce éligible
|
||||
return due[0]
|
||||
|
||||
|
||||
def _shouldSend(announcement: TwitchAnnouncement, now: datetime) -> bool:
|
||||
|
||||
Reference in New Issue
Block a user