diff --git a/discordbot/__init__.py b/discordbot/__init__.py index 65ee04c..64ac489 100644 --- a/discordbot/__init__.py +++ b/discordbot/__init__.py @@ -84,6 +84,10 @@ class DiscordBot(discord.Client): async def updateStatus(self): while not self.is_closed(): + bot_status = webapp.config.get("BOT_STATUS", {}) + if bot_status.get("twitch_is_live") or bot_status.get("discord_streaming_activity"): + await asyncio.sleep(60) + continue humeurs = Humeur.query.all() if len(humeurs)>0 : humeur = random.choice(humeurs) diff --git a/twitchbot/__init__.py b/twitchbot/__init__.py index c486b1a..001cb66 100644 --- a/twitchbot/__init__.py +++ b/twitchbot/__init__.py @@ -1,5 +1,6 @@ import asyncio import logging +import time from twitchAPI.twitch import Twitch from twitchAPI.type import AuthScope, ChatEvent @@ -71,6 +72,13 @@ async def _onMessage(msg: ChatMessage): # Stocker le message dans BOT_STATUS pour l'affichage web with webapp.app_context(): from datetime import datetime + now_ts = time.time() + msg_timestamps = webapp.config["BOT_STATUS"].setdefault("twitch_msg_timestamps", []) + msg_timestamps.append(now_ts) + cutoff = now_ts - 60 + webapp.config["BOT_STATUS"]["twitch_msg_timestamps"] = [ts for ts in msg_timestamps if ts >= cutoff] + webapp.config["BOT_STATUS"]["twitch_msg_per_minute"] = len(webapp.config["BOT_STATUS"]["twitch_msg_timestamps"]) + message_data = { 'username': msg.user.name, 'text': msg.text, @@ -176,6 +184,19 @@ class TwitchBot(): self.chat.start() except Exception as e: logging.error(f'Échec de l\'authentification Twitch : {e}') + finally: + webapp.config["BOT_STATUS"]["twitch_connected"] = False + self._loop = None + try: + if hasattr(self, 'chat') and self.chat: + self.chat.stop() + except Exception: + pass + try: + if hasattr(self, 'twitch') and self.twitch: + await self.twitch.close() + except Exception: + pass else: logging.info("Twitch n'est pas configuré") @@ -287,7 +308,22 @@ class TwitchBot(): await asyncio.sleep(120) def begin(self): - asyncio.run(self._connect()) + retry_delay = 15 + while True: + try: + if not _isConfigured(): + logging.info("Twitch non configuré, nouvelle tentative dans 60s") + with webapp.app_context(): + webapp.config["BOT_STATUS"]["twitch_connected"] = False + time.sleep(60) + continue + asyncio.run(self._connect()) + logging.warning("Session Twitch terminée, reconnexion dans %ss", retry_delay) + except Exception as e: + logging.error("Déconnexion/erreur Twitch: %s", e) + with webapp.app_context(): + webapp.config["BOT_STATUS"]["twitch_connected"] = False + time.sleep(retry_delay) async def _close(self): self.chat.stop() diff --git a/twitchbot/live_alert.py b/twitchbot/live_alert.py index 830da64..b54946e 100644 --- a/twitchbot/live_alert.py +++ b/twitchbot/live_alert.py @@ -1,4 +1,6 @@ import logging +import asyncio +from datetime import datetime import discord from twitchAPI.twitch import Twitch @@ -47,6 +49,8 @@ async def checkOnlineStreamer(twitch: Twitch) : global _live_alert_first_check with webapp.app_context() : alerts : list[LiveAlert] = LiveAlert.query.all() + bot_status = webapp.config["BOT_STATUS"] + was_live = bot_status.get("twitch_is_live", False) try: streams = await _retreiveStreams(twitch, alerts) @@ -65,17 +69,37 @@ async def checkOnlineStreamer(twitch: Twitch) : # 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) - webapp.config["BOT_STATUS"]["twitch_stream_title"] = getattr(main_stream, 'title', '') or '' - webapp.config["BOT_STATUS"]["twitch_game_name"] = getattr(main_stream, 'game_name', '') or '' - webapp.config["BOT_STATUS"]["twitch_started_at"] = main_stream.started_at.isoformat() if getattr(main_stream, 'started_at', None) else None + bot_status["twitch_is_live"] = True + bot_status["twitch_viewer_count"] = getattr(main_stream, 'viewer_count', 0) + bot_status["twitch_stream_title"] = getattr(main_stream, 'title', '') or '' + bot_status["twitch_game_name"] = getattr(main_stream, 'game_name', '') or '' + bot_status["twitch_started_at"] = main_stream.started_at.isoformat() if getattr(main_stream, 'started_at', None) else None + bot_status["twitch_ended_at"] = None + bot_status["twitch_chat_clear_notice_sent"] = False else: - webapp.config["BOT_STATUS"]["twitch_is_live"] = False - webapp.config["BOT_STATUS"]["twitch_viewer_count"] = 0 - webapp.config["BOT_STATUS"]["twitch_stream_title"] = "" - webapp.config["BOT_STATUS"]["twitch_game_name"] = "" - webapp.config["BOT_STATUS"]["twitch_started_at"] = None + bot_status["twitch_is_live"] = False + bot_status["twitch_viewer_count"] = 0 + bot_status["twitch_stream_title"] = "" + bot_status["twitch_game_name"] = "" + bot_status["twitch_started_at"] = None + if was_live and not bot_status.get("twitch_ended_at"): + bot_status["twitch_ended_at"] = datetime.now().isoformat() + if was_live and not bot_status.get("twitch_chat_clear_notice_sent"): + messages = bot_status.setdefault("twitch_chat_messages", []) + now_iso = datetime.now().isoformat() + messages.append({ + 'username': 'System', + 'text': 'Live terminé, ce chat sera vidé dans 1h.', + 'timestamp': now_iso, + 'is_mod': False, + 'is_subscriber': False, + 'is_vip': False, + 'color': '#22c55e', + 'panel_only': True, + }) + if len(messages) > 100: + messages.pop(0) + bot_status["twitch_chat_clear_notice_sent"] = True # Premier check : synchronisation sans notification if _live_alert_first_check: @@ -113,15 +137,27 @@ async def checkOnlineStreamer(twitch: Twitch) : async def _updateBotActivity(stream: Stream | None): + if not bot.loop or bot.loop.is_closed(): + logger.warning("Loop Discord non disponible pour mise à jour de présence") + return + 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) + with webapp.app_context(): + webapp.config["BOT_STATUS"]["discord_streaming_activity"] = True + future = asyncio.run_coroutine_threadsafe( + bot.change_presence(status=discord.Status.online, activity=activity), + bot.loop + ) + future.result(timeout=10) else: logger.info('Aucun stream à regarder, retour à l\'activité normale') + with webapp.app_context(): + webapp.config["BOT_STATUS"]["discord_streaming_activity"] = False # Remettre une humeur aléatoire from database.models import Humeur import random @@ -129,10 +165,18 @@ async def _updateBotActivity(stream: Stream | None): 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)) + future = asyncio.run_coroutine_threadsafe( + bot.change_presence(status=discord.Status.online, activity=discord.CustomActivity(humeur.text)), + bot.loop + ) + future.result(timeout=10) else: # Si pas de humeur, remettre un statut par défaut - await bot.change_presence(status=discord.Status.online, activity=None) + future = asyncio.run_coroutine_threadsafe( + bot.change_presence(status=discord.Status.online, activity=None), + bot.loop + ) + future.result(timeout=10) async def _notifyAlert(alert: LiveAlert, stream: Stream): stream_url = f'https://www.twitch.tv/{stream.user_login}' diff --git a/webapp/__init__.py b/webapp/__init__.py index 2220d08..67e43c2 100644 --- a/webapp/__init__.py +++ b/webapp/__init__.py @@ -11,6 +11,7 @@ webapp.config["SECRET_KEY"] = os.environ.get("SECRET_KEY", "dev-secret-change-in webapp.config["BOT_STATUS"] = { "discord_connected": False, "discord_guild_count": 0, + "discord_streaming_activity": False, "twitch_connected": False, "twitch_channel_name": None, "twitch_is_live": False, @@ -18,6 +19,10 @@ webapp.config["BOT_STATUS"] = { "twitch_stream_title": "", "twitch_game_name": "", "twitch_started_at": None, + "twitch_ended_at": None, + "twitch_chat_clear_notice_sent": False, + "twitch_msg_per_minute": 0, + "twitch_msg_timestamps": [], # Unix timestamps des 60 dernières secondes "twitch_chat_messages": [], # Derniers messages du chat (max 100) "shoutbox_heartbeats": {}, # {"username": datetime} — présence des modos } diff --git a/webapp/templates/twitch-moderation.html b/webapp/templates/twitch-moderation.html index de7be9c..955d54f 100644 --- a/webapp/templates/twitch-moderation.html +++ b/webapp/templates/twitch-moderation.html @@ -135,116 +135,69 @@ - {% if is_live %} - - -
Envoyé via le bot • Ouvrir le chat