Files
MamieHenriette/twitchbot/announcements.py
T
Mow910andCursor b274dcaa51 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>
2026-02-12 22:37:22 +01:00

138 lines
4.4 KiB
Python

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
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
_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):
"""
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():
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()
# 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:
"""
Vérifie si une annonce doit être envoyée basée sur sa périodicité.
"""
if announcement.last_sent is None:
return True
time_since_last = now - announcement.last_sent
periodicity_delta = timedelta(minutes=announcement.periodicity)
return time_since_last >= periodicity_delta
async def _sendAnnouncement(chat: Chat, channel: str, announcement: TwitchAnnouncement):
"""
Envoie une annonce dans le chat Twitch.
"""
await chat.send_message(channel, announcement.text)