mirror of
https://github.com/skylanix/MamieHenriette.git
synced 2026-02-16 13:00:39 +01:00
Merge pull request #22 from skylanix/twitch-live-notification
Twitch live notification
This commit is contained in:
7
.github/workflows/docker-build.yml
vendored
7
.github/workflows/docker-build.yml
vendored
@@ -2,7 +2,9 @@ name: Create and publish a Docker image
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ['*']
|
branches: ['main']
|
||||||
|
pull_request:
|
||||||
|
branches: ['main']
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: ghcr.io
|
||||||
@@ -34,9 +36,8 @@ jobs:
|
|||||||
tags: |
|
tags: |
|
||||||
type=ref,event=branch
|
type=ref,event=branch
|
||||||
type=ref,event=pr
|
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=latest,enable={{is_default_branch}}
|
||||||
type=raw,value={{branch}},enable=${{ github.ref_name != 'main' }}
|
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
|
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
|
||||||
|
|||||||
@@ -19,6 +19,14 @@ class GameBundle(db.Model):
|
|||||||
name = db.Column(db.String(256))
|
name = db.Column(db.String(256))
|
||||||
json = db.Column(db.String(1024))
|
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):
|
class Message(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
enable = db.Column(db.Boolean, default=False)
|
enable = db.Column(db.Boolean, default=False)
|
||||||
|
|||||||
@@ -22,6 +22,15 @@ CREATE TABLE IF NOT EXISTS `humeur` (
|
|||||||
`text` VARCHAR(256) NULL
|
`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` (
|
CREATE TABLE IF NOT EXISTS `message` (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
`enable` BOOLEAN NOT NULL DEFAULT FALSE,
|
`enable` BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import random
|
|||||||
from database import db
|
from database import db
|
||||||
from database.helpers import ConfigurationHelper
|
from database.helpers import ConfigurationHelper
|
||||||
from database.models import Configuration, Humeur, Commande
|
from database.models import Configuration, Humeur, Commande
|
||||||
from discord import Message
|
from discord import Message, TextChannel
|
||||||
from discordbot.humblebundle import checkHumbleBundleAndNotify
|
from discordbot.humblebundle import checkHumbleBundleAndNotify
|
||||||
from protondb import searhProtonDb
|
from protondb import searhProtonDb
|
||||||
|
|
||||||
@@ -36,6 +36,14 @@ class DiscordBot(discord.Client):
|
|||||||
# toutes les 30 minutes
|
# toutes les 30 minutes
|
||||||
await asyncio.sleep(30*60)
|
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) :
|
def begin(self) :
|
||||||
token = Configuration.query.filter_by(key='discord_token').first()
|
token = Configuration.query.filter_by(key='discord_token').first()
|
||||||
if token :
|
if token :
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from twitchAPI.type import AuthScope, ChatEvent
|
|||||||
from twitchAPI.chat import Chat, ChatEvent, ChatMessage, EventData
|
from twitchAPI.chat import Chat, ChatEvent, ChatMessage, EventData
|
||||||
|
|
||||||
from database.helpers import ConfigurationHelper
|
from database.helpers import ConfigurationHelper
|
||||||
|
from twitchbot.live_alert import checkOnlineStreamer
|
||||||
from webapp import webapp
|
from webapp import webapp
|
||||||
|
|
||||||
USER_SCOPE = [AuthScope.CHAT_READ, AuthScope.CHAT_EDIT]
|
USER_SCOPE = [AuthScope.CHAT_READ, AuthScope.CHAT_EDIT]
|
||||||
@@ -16,6 +16,8 @@ async def _onReady(ready_event: EventData):
|
|||||||
logging.info('Bot Twitch prêt')
|
logging.info('Bot Twitch prêt')
|
||||||
with webapp.app_context():
|
with webapp.app_context():
|
||||||
await ready_event.chat.join_room(ConfigurationHelper().getValue('twitch_channel'))
|
await ready_event.chat.join_room(ConfigurationHelper().getValue('twitch_channel'))
|
||||||
|
asyncio.get_event_loop().create_task(twitchBot._checkOnlineStreamers())
|
||||||
|
|
||||||
|
|
||||||
async def _onMessage(msg: ChatMessage):
|
async def _onMessage(msg: ChatMessage):
|
||||||
logging.info(f'Dans {msg.room.name}, {msg.user.name} a dit : {msg.text}')
|
logging.info(f'Dans {msg.room.name}, {msg.user.name} a dit : {msg.text}')
|
||||||
@@ -48,6 +50,16 @@ class TwitchBot() :
|
|||||||
else:
|
else:
|
||||||
logging.info("Twitch n'est pas configuré")
|
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):
|
def begin(self):
|
||||||
asyncio.run(self._connect())
|
asyncio.run(self._connect())
|
||||||
|
|
||||||
|
|||||||
36
twitchbot/live_alert.py
Normal file
36
twitchbot/live_alert.py
Normal 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
|
||||||
|
|
||||||
@@ -2,4 +2,4 @@ from flask import Flask
|
|||||||
|
|
||||||
webapp = Flask(__name__)
|
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
|
||||||
|
|||||||
@@ -3,15 +3,10 @@ from webapp import webapp
|
|||||||
from database import db
|
from database import db
|
||||||
from database.helpers import ConfigurationHelper
|
from database.helpers import ConfigurationHelper
|
||||||
from discordbot import bot
|
from discordbot import bot
|
||||||
from discord import TextChannel
|
|
||||||
|
|
||||||
@webapp.route("/configurations")
|
@webapp.route("/configurations")
|
||||||
def openConfigurations():
|
def openConfigurations():
|
||||||
channels = []
|
return render_template("configurations.html", configuration = ConfigurationHelper(), channels = bot.getAllTextChannel())
|
||||||
for channel in bot.get_all_channels():
|
|
||||||
if isinstance(channel, TextChannel):
|
|
||||||
channels.append(channel)
|
|
||||||
return render_template("configurations.html", configuration = ConfigurationHelper(), channels = channels)
|
|
||||||
|
|
||||||
@webapp.route("/configurations/update", methods=['POST'])
|
@webapp.route("/configurations/update", methods=['POST'])
|
||||||
def updateConfiguration():
|
def updateConfiguration():
|
||||||
|
|||||||
54
webapp/live_alert.py
Normal file
54
webapp/live_alert.py
Normal 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"))
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
from flask import render_template
|
|
||||||
from webapp import webapp
|
|
||||||
|
|
||||||
@webapp.route("/messages")
|
|
||||||
def messages():
|
|
||||||
return render_template("messages.html")
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
from flask import render_template
|
|
||||||
from webapp import webapp
|
|
||||||
|
|
||||||
@webapp.route("/moderation")
|
|
||||||
def moderation():
|
|
||||||
return render_template("moderation.html")
|
|
||||||
@@ -7,7 +7,13 @@ table td {
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
max-width: 250px;
|
}
|
||||||
|
|
||||||
|
table.live-alert tr td:last-child {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.icon {
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
@@ -19,12 +19,12 @@
|
|||||||
<td>{{ commande.trigger }}</td>
|
<td>{{ commande.trigger }}</td>
|
||||||
<td>{{ commande.response }}</td>
|
<td>{{ commande.response }}</td>
|
||||||
<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 '❌' }}
|
{{ '✅' if commande.discord_enable else '❌' }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<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 '❌' }}
|
{{ '✅' if commande.twitch_enable else '❌' }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
76
webapp/templates/live-alert.html
Normal file
76
webapp/templates/live-alert.html
Normal 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 %}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{% extends "template.html" %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>Messages de Mamie</h1>
|
|
||||||
<p>TODO</p>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -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 %}
|
|
||||||
@@ -18,10 +18,9 @@
|
|||||||
<nav>
|
<nav>
|
||||||
<a href="/"><img src="/static/ico/favicon.ico"></a>
|
<a href="/"><img src="/static/ico/favicon.ico"></a>
|
||||||
<ul>
|
<ul>
|
||||||
|
<li><a href="/live-alert">Alerte live</a></li>
|
||||||
<li><a href="/commandes">Commandes</a></li>
|
<li><a href="/commandes">Commandes</a></li>
|
||||||
<li><a href="/humeurs">Humeurs</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="/protondb">ProtonDB</a></li>
|
||||||
<li><a href="/configurations">Configurations</a></li>
|
<li><a href="/configurations">Configurations</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
Reference in New Issue
Block a user