Merge pull request #22 from skylanix/twitch-live-notification

Twitch live notification
This commit is contained in:
skylanix
2025-09-04 00:10:00 +02:00
committed by GitHub
17 changed files with 222 additions and 42 deletions

View File

@@ -2,7 +2,9 @@ name: Create and publish a Docker image
on:
push:
branches: ['*']
branches: ['main']
pull_request:
branches: ['main']
env:
REGISTRY: ghcr.io
@@ -34,9 +36,8 @@ jobs:
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha,prefix={{branch}}-
type=sha,prefix={{branch}}-,enable={{is_default_branch}}
type=raw,value=latest,enable={{is_default_branch}}
type=raw,value={{branch}},enable=${{ github.ref_name != 'main' }}
- name: Build and push Docker image
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4

View File

@@ -19,6 +19,14 @@ class GameBundle(db.Model):
name = db.Column(db.String(256))
json = db.Column(db.String(1024))
class LiveAlert(db.Model):
id = db.Column(db.Integer, primary_key=True)
enable = db.Column(db.Boolean, default=True)
online = db.Column(db.Boolean, default=False)
login = db.Column(db.String(128))
notify_channel = db.Column(db.Integer)
message = db.Column(db.String(2000))
class Message(db.Model):
id = db.Column(db.Integer, primary_key=True)
enable = db.Column(db.Boolean, default=False)

View File

@@ -22,6 +22,15 @@ CREATE TABLE IF NOT EXISTS `humeur` (
`text` VARCHAR(256) NULL
);
CREATE TABLE IF NOT EXISTS live_alert (
id INTEGER PRIMARY KEY AUTOINCREMENT,
`enable` BOOLEAN NOT NULL DEFAULT TRUE,
`online` BOOLEAN NOT NULL DEFAULT FALSE,
`login` VARCHAR(128) UNIQUE NOT NULL,
`notify_channel` INTEGER NOT NULL,
`message` VARCHAR(2000) NOT NULL
);
CREATE TABLE IF NOT EXISTS `message` (
id INTEGER PRIMARY KEY AUTOINCREMENT,
`enable` BOOLEAN NOT NULL DEFAULT FALSE,

View File

@@ -6,7 +6,7 @@ import random
from database import db
from database.helpers import ConfigurationHelper
from database.models import Configuration, Humeur, Commande
from discord import Message
from discord import Message, TextChannel
from discordbot.humblebundle import checkHumbleBundleAndNotify
from protondb import searhProtonDb
@@ -36,6 +36,14 @@ class DiscordBot(discord.Client):
# toutes les 30 minutes
await asyncio.sleep(30*60)
def getAllTextChannel(self) -> list[TextChannel]:
channels = []
for channel in self.get_all_channels():
if isinstance(channel, TextChannel):
channels.append(channel)
return channels
def begin(self) :
token = Configuration.query.filter_by(key='discord_token').first()
if token :

View File

@@ -7,7 +7,7 @@ from twitchAPI.type import AuthScope, ChatEvent
from twitchAPI.chat import Chat, ChatEvent, ChatMessage, EventData
from database.helpers import ConfigurationHelper
from twitchbot.live_alert import checkOnlineStreamer
from webapp import webapp
USER_SCOPE = [AuthScope.CHAT_READ, AuthScope.CHAT_EDIT]
@@ -16,6 +16,8 @@ async def _onReady(ready_event: EventData):
logging.info('Bot Twitch prêt')
with webapp.app_context():
await ready_event.chat.join_room(ConfigurationHelper().getValue('twitch_channel'))
asyncio.get_event_loop().create_task(twitchBot._checkOnlineStreamers())
async def _onMessage(msg: ChatMessage):
logging.info(f'Dans {msg.room.name}, {msg.user.name} a dit : {msg.text}')
@@ -48,6 +50,16 @@ class TwitchBot() :
else:
logging.info("Twitch n'est pas configuré")
async def _checkOnlineStreamers(self):
# pas bon faudrait faire un truc mieux
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)
def begin(self):
asyncio.run(self._connect())

36
twitchbot/live_alert.py Normal file
View File

@@ -0,0 +1,36 @@
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
async def checkOnlineStreamer(twitch: Twitch) :
with webapp.app_context() :
alerts : list[LiveAlert] = LiveAlert.query.all()
streams = await _retreiveStreams(twitch, alerts)
for alert in alerts :
stream = next((s for s in streams if s.user_login == alert.login), None)
if stream :
if not alert.online and alert.enable :
await _notifyAlert(alert, stream)
alert.online = True
else :
alert.online = False
db.session.commit()
async def _notifyAlert(alert : LiveAlert, stream : Stream):
message : str = alert.message.format(stream)
bot.loop.create_task(_sendMessage(alert.notify_channel, message))
async def _sendMessage(channel : int, message : str) :
await bot.get_channel(channel).send(message)
async def _retreiveStreams(twitch: Twitch, alerts : list[LiveAlert]) -> list[Stream] :
streams : list[Stream] = []
async for stream in twitch.get_streams(user_login = [alert.login for alert in alerts]):
streams.append(stream)
return streams

View File

@@ -2,4 +2,4 @@ from flask import Flask
webapp = Flask(__name__)
from webapp import commandes, configurations, index, humeurs, messages, moderation, protondb, twitch_auth
from webapp import commandes, configurations, index, humeurs, protondb, live_alert, twitch_auth

View File

@@ -3,15 +3,10 @@ from webapp import webapp
from database import db
from database.helpers import ConfigurationHelper
from discordbot import bot
from discord import TextChannel
@webapp.route("/configurations")
def openConfigurations():
channels = []
for channel in bot.get_all_channels():
if isinstance(channel, TextChannel):
channels.append(channel)
return render_template("configurations.html", configuration = ConfigurationHelper(), channels = channels)
return render_template("configurations.html", configuration = ConfigurationHelper(), channels = bot.getAllTextChannel())
@webapp.route("/configurations/update", methods=['POST'])
def updateConfiguration():

54
webapp/live_alert.py Normal file
View File

@@ -0,0 +1,54 @@
from flask import render_template, request, redirect, url_for
from webapp import webapp
from database import db
from database.models import LiveAlert
from discordbot import bot
@webapp.route("/live-alert")
def openLiveAlert():
alerts : list[LiveAlert] = LiveAlert.query.all()
channels = bot.getAllTextChannel()
for alert in alerts :
for channel in channels:
if alert.notify_channel == channel.id :
alert.notify_channel_name = channel.name
return render_template("live-alert.html", alerts = alerts, channels = channels)
@webapp.route("/live-alert/add", methods=['POST'])
def addLiveAlert():
alert = LiveAlert(enable = True, login = request.form.get('login'), notify_channel = request.form.get('notify_channel'), message = request.form.get('message'))
db.session.add(alert)
db.session.commit()
return redirect(url_for("openLiveAlert"))
@webapp.route("/live-alert/toggle/<int:id>")
def toggleLiveAlert(id):
alert : LiveAlert = LiveAlert.query.get_or_404(id)
alert.enable = not alert.enable
db.session.commit()
return redirect(url_for("openLiveAlert"))
@webapp.route("/live-alert/edit/<int:id>")
def openEditLiveAlert(id):
alert = LiveAlert.query.get_or_404(id)
channels = bot.getAllTextChannel()
return render_template("live-alert.html", alert = alert, channels = channels)
@webapp.route("/live-alert/edit/<int:id>", methods=['POST'])
def submitEditLiveAlert(id):
alert : LiveAlert = LiveAlert.query.get_or_404(id)
alert.login = request.form.get('login')
alert.notify_channel = request.form.get('notify_channel')
alert.message = request.form.get('message')
db.session.commit()
return redirect(url_for("openLiveAlert"))
@webapp.route("/live-alert/del/<int:id>")
def delLiveAlert(id):
alert = LiveAlert.query.get_or_404(id)
db.session.delete(alert)
db.session.commit()
return redirect(url_for("openLiveAlert"))

View File

@@ -1,6 +0,0 @@
from flask import render_template
from webapp import webapp
@webapp.route("/messages")
def messages():
return render_template("messages.html")

View File

@@ -1,6 +0,0 @@
from flask import render_template
from webapp import webapp
@webapp.route("/moderation")
def moderation():
return render_template("moderation.html")

View File

@@ -7,7 +7,13 @@ table td {
text-align: left;
vertical-align: top;
overflow: hidden;
text-overflow: ellipsis;
white-space: normal;
max-width: 250px;
}
table.live-alert tr td:last-child {
white-space: nowrap;
}
a.icon {
text-decoration: none;
}

View File

@@ -19,12 +19,12 @@
<td>{{ commande.trigger }}</td>
<td>{{ commande.response }}</td>
<td>
<a href="{{ url_for('toggle_discord_commande', commande_id = commande.id) }}" style="text-decoration: none; font-size: 1.2em;">
<a href="{{ url_for('toggle_discord_commande', commande_id = commande.id) }}" class="icon">
{{ '✅' if commande.discord_enable else '❌' }}
</a>
</td>
<td>
<a href="{{ url_for('toggle_twitch_commande', commande_id = commande.id) }}" style="text-decoration: none; font-size: 1.2em;">
<a href="{{ url_for('toggle_twitch_commande', commande_id = commande.id) }}" class="icon">
{{ '✅' if commande.twitch_enable else '❌' }}
</a>
</td>

View File

@@ -0,0 +1,76 @@
{% extends "template.html" %}
{% block content %}
<h1>Alerte Live</h1>
<p>
Liste des chaines surveillées pour les alertes de live twitch.
Le bot vérifie toutes les 5 minutes qui est en live dans la liste en dessous.
Le bot enregistre le status de stream toutes les 5 minutes, quand le status pass de "hors-ligne" à "en ligne" alors
le bot le notifiera sur discord.
Ne peu surveiller qu'au maximum 100 chaines.
</p>
{% if not alert %}
<h2>Alertes</h2>
<table class="live-alert">
<thead>
<tr>
<th>Chaine</th>
<th>Canal</th>
<th>Message</th>
<th>#</th>
</tr>
</thead>
<tbody>
{% for alert in alerts %}
<tr>
<td>{{alert.login}}</td>
<td>{{alert.notify_channel_name}}</td>
<td>{{alert.message}}</td>
<td>
<a href="{{ url_for('toggleLiveAlert', id = alert.id) }}" class="icon">{{ '✅' if alert.enable else '❌' }}</a>
<a href="{{ url_for('openEditLiveAlert', id = alert.id) }}" class="icon"></a>
<a href="{{ url_for('delLiveAlert', id = alert.id) }}"
onclick="return confirm('Êtes-vous sûr de vouloir supprimer cette alerte ?')" class="icon">🗑</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
<h2>{{ 'Editer une alerte' if alert else 'Ajouter une alerte de Live' }}</h2>
<form action="{{ url_for('submitEditLiveAlert', id = alert.id) if alert else url_for('addLiveAlert') }}" method="POST">
<label for="login">Chaine</label>
<input name="login" type="text" maxlength="32" required="required" value="{{alert.login if alert}}"/>
<label for="notify_channel">Canal de Notification</label>
<select name="notify_channel">
{% for channel in channels %}
<option value="{{channel.id}}"{% if alert and alert.notify_channel == channel.id %}
selected="selected" {% endif %}>{{channel.name}}</option>
{% endfor %}
</select>
<label for="message">Message</label>
<textarea name="message" rows="5" cols="50" required="required">{{alert.message if alert}}</textarea>
<input type="Submit" value="Ajouter">
<p>
La chaine est le login de la chaine, par exemple <strong>chainesteve</strong> pour <strong>https://www.twitch.tv/chainesteve</strong>.
</p>
<p>
Pour le message vous avez acces à ces variables :
<ul>
<li>{0.user_login} : pour le lien vers la chaine</li>
<li>{0.user_name} : à priviligier pour le text</li>
<li>{0.game_name}</li>
<li>{0.title}</li>
<li>{0.language}</li>
</ul>
Le message est au format <a href="https://commonmark.org/" target="_blank">common-mark</a> dans la limite de ce que
support discord.
Pour mettre un lien vers la chaine : [description](https://www.twitch.fr/{0.user_login})
</p>
</form>
{% endblock %}

View File

@@ -1,6 +0,0 @@
{% extends "template.html" %}
{% block content %}
<h1>Messages de Mamie</h1>
<p>TODO</p>
{% endblock %}

View File

@@ -1,6 +0,0 @@
{% extends "template.html" %}
{% block content %}
<h1>Modération</h1>
<p>Outils de modération communautaire pour gérer votre serveur Discord (fonctionnalité en développement).</p>
{% endblock %}

View File

@@ -18,10 +18,9 @@
<nav>
<a href="/"><img src="/static/ico/favicon.ico"></a>
<ul>
<li><a href="/live-alert">Alerte live</a></li>
<li><a href="/commandes">Commandes</a></li>
<li><a href="/humeurs">Humeurs</a></li>
<li><a href="/messages">Messages</a></li>
<li><a href="/moderation">Modération</a></li>
<li><a href="/protondb">ProtonDB</a></li>
<li><a href="/configurations">Configurations</a></li>
</ul>