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:
+155
-12
@@ -1,4 +1,5 @@
|
||||
import logging
|
||||
import discord
|
||||
|
||||
from twitchAPI.twitch import Twitch
|
||||
from twitchAPI.object.api import Stream
|
||||
@@ -11,11 +12,76 @@ 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 :
|
||||
@@ -24,26 +90,103 @@ async def checkOnlineStreamer(twitch: Twitch) :
|
||||
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 _notifyAlert(alert : LiveAlert, stream : Stream):
|
||||
message : str = alert.message.format(stream)
|
||||
logger.info(f'Message de notification : {message}')
|
||||
bot.loop.create_task(_sendMessage(alert.notify_channel, message))
|
||||
|
||||
async def _sendMessage(channel : int, message : str) :
|
||||
logger.info(f'Envoi de notification : {message}')
|
||||
await bot.get_channel(channel).send(message)
|
||||
logger.info(f'Notification envoyé')
|
||||
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 _retreiveStreams(twitch: Twitch, alerts : list[LiveAlert]) -> list[Stream] :
|
||||
streams : list[Stream] = []
|
||||
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]):
|
||||
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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user