From d941fdcf9cf89b7d68ff67f19a51c6251141cd1b Mon Sep 17 00:00:00 2001 From: Mow910 Date: Sun, 1 Feb 2026 13:38:48 +0100 Subject: [PATCH] Annonce twitch --- database/models.py | 12 +- database/schema.sql | 11 +- twitchbot/__init__.py | 60 +++++---- twitchbot/announcements.py | 51 ++++++++ webapp/__init__.py | 2 +- webapp/announcements.py | 66 ++++++++++ webapp/templates/announcements.html | 188 ++++++++++++++++++++++++++++ webapp/templates/template.html | 11 +- 8 files changed, 366 insertions(+), 35 deletions(-) create mode 100644 twitchbot/announcements.py create mode 100644 webapp/announcements.py create mode 100644 webapp/templates/announcements.html diff --git a/database/models.py b/database/models.py index d144c7e..16a74d8 100644 --- a/database/models.py +++ b/database/models.py @@ -27,11 +27,15 @@ class LiveAlert(db.Model): notify_channel = db.Column(db.Integer) message = db.Column(db.String(2000)) -class Message(db.Model): +class TwitchAnnouncement(db.Model): + __tablename__ = 'twitch_announcement' id = db.Column(db.Integer, primary_key=True) - enable = db.Column(db.Boolean, default=False) - text = db.Column(db.String(256)) - periodicity = db.Column(db.Integer) + enable = db.Column(db.Boolean, default=True) + name = db.Column(db.String(64)) + text = db.Column(db.String(500)) + periodicity = db.Column(db.Integer, default=10) + min_chat_messages = db.Column(db.Integer, default=0) + last_sent = db.Column(db.DateTime, nullable=True) class Commande(db.Model): id = db.Column(db.Integer, primary_key=True) diff --git a/database/schema.sql b/database/schema.sql index 0af5439..29a8d37 100644 --- a/database/schema.sql +++ b/database/schema.sql @@ -31,11 +31,14 @@ CREATE TABLE IF NOT EXISTS live_alert ( `message` VARCHAR(2000) NOT NULL ); -CREATE TABLE IF NOT EXISTS `message` ( +CREATE TABLE IF NOT EXISTS `twitch_announcement` ( id INTEGER PRIMARY KEY AUTOINCREMENT, - `enable` BOOLEAN NOT NULL DEFAULT FALSE, - `text` VARCHAR(256) NULL, - periodicity INTEGER NULL + `enable` BOOLEAN NOT NULL DEFAULT TRUE, + `name` VARCHAR(64) NOT NULL, + `text` VARCHAR(500) NOT NULL, + `periodicity` INTEGER NOT NULL DEFAULT 10, + `min_chat_messages` INTEGER NOT NULL DEFAULT 0, + `last_sent` DATETIME NULL ); CREATE TABLE IF NOT EXISTS `commande` ( diff --git a/twitchbot/__init__.py b/twitchbot/__init__.py index 45796f6..9eb411c 100644 --- a/twitchbot/__init__.py +++ b/twitchbot/__init__.py @@ -1,4 +1,3 @@ - import asyncio import logging @@ -8,10 +7,12 @@ from twitchAPI.chat import Chat, ChatEvent, ChatMessage, EventData from database.helpers import ConfigurationHelper from twitchbot.live_alert import checkOnlineStreamer +from twitchbot.announcements import checkAndSendAnnouncements, incrementMessageCount from webapp import webapp USER_SCOPE = [AuthScope.CHAT_READ, AuthScope.CHAT_EDIT] + async def _onReady(ready_event: EventData): logging.info('Bot Twitch prêt') channel = ConfigurationHelper().getValue('twitch_channel') @@ -20,56 +21,69 @@ async def _onReady(ready_event: EventData): with webapp.app_context(): await ready_event.chat.join_room(channel) asyncio.get_event_loop().create_task(twitchBot._checkOnlineStreamers()) - + asyncio.get_event_loop().create_task(twitchBot._runAnnouncements()) + async def _onMessage(msg: ChatMessage): logging.info(f'Dans {msg.room.name}, {msg.user.name} a dit : {msg.text}') + incrementMessageCount() + -# commande qui répond "bonjour" à "!hello" async def _helloCommand(msg: ChatMessage): await msg.reply(f'Bonjour {msg.user.name}') -def _isConfigured() -> bool: - helper = ConfigurationHelper() - return helper.getValue('twitch_client_id') != None and helper.getValue('twitch_client_secret') != None and helper.getValue('twitch_access_token') != None and helper.getValue('twitch_refresh_token') != None and helper.getValue('twitch_channel') != None -class TwitchBot() : +def _isConfigured(): + helper = ConfigurationHelper() + return (helper.getValue('twitch_client_id') is not None and + helper.getValue('twitch_client_secret') is not None and + helper.getValue('twitch_access_token') is not None and + helper.getValue('twitch_refresh_token') is not None and + helper.getValue('twitch_channel') is not None) + + +class TwitchBot(): async def _connect(self): with webapp.app_context(): - if _isConfigured() : - try : + if _isConfigured(): + try: helper = ConfigurationHelper() self.twitch = await Twitch(helper.getValue('twitch_client_id'), helper.getValue('twitch_client_secret')) await self.twitch.set_user_authentication(helper.getValue('twitch_access_token'), USER_SCOPE, helper.getValue('twitch_refresh_token')) self.chat = await Chat(self.twitch) self.chat.register_event(ChatEvent.READY, _onReady) self.chat.register_event(ChatEvent.MESSAGE, _onMessage) - # chat.register_event(ChatEvent.SUB, on_sub) self.chat.register_command('hello', _helloCommand) self.chat.start() - except Exception as e: - logging.error(f'Échec de l\'authentification Twitch. Vérifiez vos identifiants et redémarrez après correction : {e}') - else: + except Exception as e: + logging.error(f'Échec de l\'authentification Twitch : {e}') + else: logging.info("Twitch n'est pas configuré") - - async def _checkOnlineStreamers(self): - # pas bon faudrait faire un truc mieux - while True : + + async def _checkOnlineStreamers(self): + while True: try: await checkOnlineStreamer(self.twitch) except Exception as e: - logging.error(f'Erreur lors lors du check des streamers online : {e}') - # toutes les 5 minutes - await asyncio.sleep(5*60) + logging.error(f'Erreur check streamers online : {e}') + await asyncio.sleep(5 * 60) - def begin(self): + async def _runAnnouncements(self): + channel = ConfigurationHelper().getValue('twitch_channel') + while True: + try: + await checkAndSendAnnouncements(self.chat, channel) + except Exception as e: + logging.error(f'Erreur envoi annonces : {e}') + await asyncio.sleep(60) + + def begin(self): asyncio.run(self._connect()) - # je ne sais pas encore comment appeler ça async def _close(self): self.chat.stop() await self.twitch.close() -twitchBot = TwitchBot() +twitchBot = TwitchBot() diff --git a/twitchbot/announcements.py b/twitchbot/announcements.py new file mode 100644 index 0000000..d6c0bcf --- /dev/null +++ b/twitchbot/announcements.py @@ -0,0 +1,51 @@ +import logging +from datetime import datetime, timedelta + +from twitchAPI.chat import Chat + +from database import db +from database.models import TwitchAnnouncement +from webapp import webapp + +logger = logging.getLogger('twitch-announcements') +logger.setLevel(logging.INFO) + + +async def checkAndSendAnnouncements(chat: Chat, channel: str): + """ + Vérifie et envoie les annonces dont la périodicité est écoulée. + Appelé périodiquement par le bot Twitch. + """ + with webapp.app_context(): + announcements: list[TwitchAnnouncement] = TwitchAnnouncement.query.filter_by(enable=True).all() + 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}') + + +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) diff --git a/webapp/__init__.py b/webapp/__init__.py index 0ff86c7..b06c327 100644 --- a/webapp/__init__.py +++ b/webapp/__init__.py @@ -10,4 +10,4 @@ webapp.config["BOT_STATUS"] = { "twitch_channel_name": None, } -from webapp import commandes, configurations, index, humeurs, protondb, live_alert, twitch_auth, moderation, youtube +from webapp import commandes, configurations, index, humeurs, protondb, live_alert, twitch_auth, moderation, youtube, announcements diff --git a/webapp/announcements.py b/webapp/announcements.py new file mode 100644 index 0000000..32d97f0 --- /dev/null +++ b/webapp/announcements.py @@ -0,0 +1,66 @@ +from flask import render_template, request, redirect, url_for + +from webapp import webapp +from database import db +from database.models import TwitchAnnouncement + + +@webapp.route("/announcements") +def openAnnouncements(): + announcements = TwitchAnnouncement.query.all() + return render_template("announcements.html", announcements=announcements) + + +@webapp.route("/announcements/add", methods=['POST']) +def addAnnouncement(): + announcement = TwitchAnnouncement( + enable=True, + name=request.form.get('name'), + text=request.form.get('text'), + periodicity=int(request.form.get('periodicity', 10)), + min_chat_messages=int(request.form.get('min_chat_messages', 0)) + ) + db.session.add(announcement) + db.session.commit() + return redirect(url_for("openAnnouncements")) + + +@webapp.route("/announcements/toggle/") +def toggleAnnouncement(id): + announcement = TwitchAnnouncement.query.get_or_404(id) + announcement.enable = not announcement.enable + db.session.commit() + return redirect(url_for("openAnnouncements")) + + +@webapp.route("/announcements/edit/") +def openEditAnnouncement(id): + announcement = TwitchAnnouncement.query.get_or_404(id) + return render_template("announcements.html", announcement=announcement) + + +@webapp.route("/announcements/edit/", methods=['POST']) +def submitEditAnnouncement(id): + announcement = TwitchAnnouncement.query.get_or_404(id) + announcement.name = request.form.get('name') + announcement.text = request.form.get('text') + announcement.periodicity = int(request.form.get('periodicity', 10)) + announcement.min_chat_messages = int(request.form.get('min_chat_messages', 0)) + db.session.commit() + return redirect(url_for("openAnnouncements")) + + +@webapp.route("/announcements/del/") +def delAnnouncement(id): + announcement = TwitchAnnouncement.query.get_or_404(id) + db.session.delete(announcement) + db.session.commit() + return redirect(url_for("openAnnouncements")) + + +@webapp.route("/announcements/reset/") +def resetAnnouncement(id): + announcement = TwitchAnnouncement.query.get_or_404(id) + announcement.last_sent = None + db.session.commit() + return redirect(url_for("openAnnouncements")) diff --git a/webapp/templates/announcements.html b/webapp/templates/announcements.html new file mode 100644 index 0000000..f131867 --- /dev/null +++ b/webapp/templates/announcements.html @@ -0,0 +1,188 @@ +{% extends "template.html" %} + +{% block content %} +
+
+
+
+ + + +
+
+

Annonces Twitch

+

Messages automatiques périodiques dans le chat

+
+
+

+ Configurez des messages envoyés automatiquement dans le chat Twitch. + L'annonce est envoyée uniquement si le temps est écoulé ET si le nombre minimum de messages a été atteint. +

+
+ + {% if not announcement %} +
+
+

Annonces configurées

+
+ + {% if announcements %} +
+ + + + + + + + + + + + + {% for ann in announcements %} + + + + + + + + + {% endfor %} + +
NomMessageTempsMin. messagesDernier envoiActions
+ {{ ann.name }} + + {{ ann.text[:60] }}{% if ann.text|length > 60 %}...{% endif %} + + + {{ ann.periodicity }} min + + + + {{ ann.min_chat_messages }} + + + {% if ann.last_sent %} + {{ ann.last_sent.strftime('%d/%m %H:%M') }} + {% else %} + Jamais + {% endif %} + + +
+
+ {% else %} +
+ + + +

Aucune annonce

+

Commencez par créer votre première annonce automatique.

+
+ {% endif %} +
+ {% endif %} + +
+

+ {{ 'Modifier l\'annonce' if announcement else 'Ajouter une annonce' }} +

+ +
+
+
+ + +
+ +
+ + +

1 min à 1440 min (24h)

+
+ +
+ + +

0 = pas de minimum

+
+
+ +
+ + +

Maximum 500 caractères

+
+ +
+ + {% if announcement %} + + Annuler + + {% endif %} +
+
+
+ +
+{% endblock %} diff --git a/webapp/templates/template.html b/webapp/templates/template.html index be5a9c8..d33d0ec 100644 --- a/webapp/templates/template.html +++ b/webapp/templates/template.html @@ -128,9 +128,10 @@ Alerte live - - Bot Twitch — à venir - + + + Annonces + @@ -165,6 +166,10 @@ Alerte live + + + Annonces Twitch + YouTube