Files
MamieHenriette/twitchbot/live_alert.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

193 lines
6.8 KiB
Python

import logging
import discord
from twitchAPI.twitch import Twitch
from twitchAPI.object.api import Stream
from database import db
from database.models import LiveAlert
from discordbot import bot
from webapp import webapp
logger = logging.getLogger('live-alert')
logger.setLevel(logging.INFO)
_live_alert_first_check = True
def _stream_thumbnail_url(stream: Stream) -> str:
"""URL de la miniature du stream (preview Twitch)."""
url = getattr(stream, 'thumbnail_url', None) or ''
if '{width}' in url or '{height}' in url:
url = url.replace('{width}', '320').replace('{height}', '180')
if not url:
url = f"https://static-cdn.jtvnw.net/previews-ttv/live_user_{stream.user_login}-320x180.jpg"
return url
def _format_embed_text(text: str, stream: Stream, stream_url: str, thumbnail: str) -> str:
"""Formate un texte d'embed avec les variables stream."""
if not text:
return ''
try:
return text.format(
user_login=stream.user_login or '',
user_name=stream.user_name or '',
game_name=getattr(stream, 'game_name', None) or '',
title=stream.title or '',
language=getattr(stream, 'language', None) or '',
stream_url=stream_url,
thumbnail=thumbnail,
)
except KeyError:
return text
async def checkOnlineStreamer(twitch: Twitch) :
global _live_alert_first_check
with webapp.app_context() :
alerts : list[LiveAlert] = LiveAlert.query.all()
streams = await _retreiveStreams(twitch, alerts)
watch_stream = None
# Récupération du statut du live principal (channel configuré)
from database.helpers import ConfigurationHelper
main_channel = ConfigurationHelper().getValue('twitch_channel')
main_stream = None
if main_channel:
main_stream = next((s for s in streams if s.user_login.lower() == main_channel.lower()), None)
# Mise à jour du BOT_STATUS pour la webapp
if main_stream:
webapp.config["BOT_STATUS"]["twitch_is_live"] = True
webapp.config["BOT_STATUS"]["twitch_viewer_count"] = getattr(main_stream, 'viewer_count', 0)
else:
webapp.config["BOT_STATUS"]["twitch_is_live"] = False
webapp.config["BOT_STATUS"]["twitch_viewer_count"] = 0
# Premier check : synchronisation sans notification
if _live_alert_first_check:
logger.info('Live Alert: première vérification, synchronisation sans notification')
for alert in alerts:
stream = next((s for s in streams if s.user_login == alert.login), None)
if stream:
alert.online = True
if alert.watch_activity and alert.enable:
watch_stream = stream
else:
alert.online = False
await _updateBotActivity(watch_stream)
db.session.commit()
_live_alert_first_check = False
return
# Vérifications normales ensuite
for alert in alerts :
stream = next((s for s in streams if s.user_login == alert.login), None)
if stream :
logger.info(f'Streamer en ligne : {alert.login}')
if not alert.online and alert.enable :
logger.info(f'N\'etait pas en ligne auparavant : {alert.login}')
await _notifyAlert(alert, stream)
alert.online = True
if alert.watch_activity and alert.enable:
watch_stream = stream
else :
logger.info(f'Streamer hors ligne : {alert.login}')
alert.online = False
await _updateBotActivity(watch_stream)
db.session.commit()
async def _updateBotActivity(stream: Stream | None):
if stream:
logger.info(f'Mise à jour de l\'activité : Regarde le live de {stream.user_name}')
activity = discord.Streaming(
name=f'Regarde le live de {stream.user_name}',
url=f'https://www.twitch.tv/{stream.user_login}'
)
await bot.change_presence(status=discord.Status.online, activity=activity)
else:
logger.info('Aucun stream à regarder, retour à l\'activité normale')
# Remettre une humeur aléatoire
from database.models import Humeur
import random
humeurs = Humeur.query.all()
if humeurs:
humeur = random.choice(humeurs)
logger.info(f'Réinitialisation du statut : {humeur.text}')
await bot.change_presence(status=discord.Status.online, activity=discord.CustomActivity(humeur.text))
else:
# Si pas de humeur, remettre un statut par défaut
await bot.change_presence(status=discord.Status.online, activity=None)
async def _notifyAlert(alert: LiveAlert, stream: Stream):
stream_url = f'https://www.twitch.tv/{stream.user_login}'
thumbnail = _stream_thumbnail_url(stream)
# Message texte optionnel (avant l'embed)
message_text = None
if alert.message and alert.message.strip():
try:
message_text = alert.message.format(stream)
except KeyError:
message_text = alert.message
# Construction de l'embed Discord
try:
embed_color = int(alert.embed_color or '9146FF', 16)
except ValueError:
embed_color = 0x9146FF
embed_title = _format_embed_text(alert.embed_title, stream, stream_url, thumbnail) if alert.embed_title else (stream.title or f"{stream.user_name} est en live")
embed_description = _format_embed_text(alert.embed_description, stream, stream_url, thumbnail) if alert.embed_description else None
embed = discord.Embed(
title=embed_title,
url=stream_url,
color=embed_color
)
if embed_description:
embed.description = embed_description
author_name = _format_embed_text(alert.embed_author_name, stream, stream_url, thumbnail) if alert.embed_author_name else stream.user_name
user_id = getattr(stream, 'user_id', None)
author_icon = alert.embed_author_icon or (f"https://static-cdn.jtvnw.net/jtv_user_pictures/{user_id}-profile_image-70x70.png" if user_id else "https://static-cdn.jtvnw.net/ttv-favicon/favicon-32x32.png")
embed.set_author(name=author_name, icon_url=author_icon)
if alert.embed_thumbnail and thumbnail:
embed.set_thumbnail(url=thumbnail)
if alert.embed_image and thumbnail:
embed.set_image(url=thumbnail)
if alert.embed_footer:
footer_text = _format_embed_text(alert.embed_footer, stream, stream_url, thumbnail)
if footer_text:
embed.set_footer(text=footer_text)
logger.info(f'Envoi de notification live (embed) : {stream.user_login}')
bot.loop.create_task(_sendMessage(alert.notify_channel, message_text, embed))
async def _sendMessage(channel_id: int, message: str | None, embed: discord.Embed):
try:
discord_channel = bot.get_channel(channel_id)
if not discord_channel:
logger.error(f"Canal Discord {channel_id} introuvable")
return
if message and message.strip():
await discord_channel.send(content=message, embed=embed)
else:
await discord_channel.send(embed=embed)
logger.info('Notification live envoyée')
except Exception as e:
logger.error(f"Erreur lors de l'envoi de la notification live : {e}")
async def _retreiveStreams(twitch: Twitch, alerts: list[LiveAlert]) -> list[Stream]:
streams: list[Stream] = []
logger.info(f'Recherche de streams pour : {alerts}')
async for stream in twitch.get_streams(user_login=[alert.login for alert in alerts]):
streams.append(stream)
logger.info(f'Ces streams sont en ligne : {streams}')
return streams