mirror of
https://github.com/skylanix/MamieHenriette.git
synced 2026-02-06 06:40:35 +01:00
Ajout d'un système de notifications YouTube avec une nouvelle table youtube_notification dans la base de données, intégration de la vérification des vidéos YouTube, et création d'une interface web pour gérer les notifications. Le bot Discord enverra des alertes pour les nouvelles vidéos détectées.
This commit is contained in:
@@ -2,4 +2,4 @@ from flask import Flask
|
||||
|
||||
webapp = Flask(__name__)
|
||||
|
||||
from webapp import commandes, configurations, index, humeurs, protondb, live_alert, twitch_auth, moderation
|
||||
from webapp import commandes, configurations, index, humeurs, protondb, live_alert, twitch_auth, moderation, youtube
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<a href="/"><img src="/static/ico/favicon.ico"></a>
|
||||
<ul>
|
||||
<li><a href="/live-alert">Alerte live</a></li>
|
||||
<li><a href="/youtube">YouTube</a></li>
|
||||
<li><a href="/commandes">Commandes</a></li>
|
||||
<li><a href="/humeurs">Humeurs</a></li>
|
||||
<li><a href="/moderation">Modération</a></li>
|
||||
|
||||
113
webapp/templates/youtube.html
Normal file
113
webapp/templates/youtube.html
Normal file
@@ -0,0 +1,113 @@
|
||||
{% extends "template.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Notifications YouTube</h1>
|
||||
|
||||
{% if msg %}
|
||||
<div id="alert-msg" class="alert alert-{{ msg_type }}" style="padding: 10px; margin: 10px 0; border: 1px solid {{ '#f00' if msg_type == 'error' else '#0f0' }}; background-color: {{ '#ffe0e0' if msg_type == 'error' else '#e0ffe0' }};">
|
||||
{{ msg }}
|
||||
</div>
|
||||
<script>
|
||||
setTimeout(function() {
|
||||
var el = document.getElementById('alert-msg');
|
||||
if (el) el.style.display = 'none';
|
||||
}, 5000);
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
<p>
|
||||
Liste des chaînes YouTube surveillées pour les notifications de nouvelles vidéos.
|
||||
|
||||
Le bot vérifie toutes les 5 minutes les nouvelles vidéos des chaînes en dessous.
|
||||
Quand une nouvelle vidéo est détectée, le bot enverra une notification sur Discord.
|
||||
</p>
|
||||
|
||||
{% if not notification %}
|
||||
<h2>Notifications</h2>
|
||||
<table class="live-alert">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Chaîne YouTube</th>
|
||||
<th>Canal Discord</th>
|
||||
<th>Type</th>
|
||||
<th>Message</th>
|
||||
<th>#</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for notification in notifications %}
|
||||
<tr>
|
||||
<td>{{notification.channel_id}}</td>
|
||||
<td>{{notification.notify_channel_name}}</td>
|
||||
<td>
|
||||
{% if notification.video_type == 'all' %}
|
||||
Toutes
|
||||
{% elif notification.video_type == 'video' %}
|
||||
Vidéos uniquement
|
||||
{% elif notification.video_type == 'short' %}
|
||||
Shorts uniquement
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{notification.message}}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('toggleYouTube', id = notification.id) }}" class="icon">{{ '✅' if notification.enable else '❌' }}</a>
|
||||
<a href="{{ url_for('openEditYouTube', id = notification.id) }}" class="icon">✐</a>
|
||||
<a href="{{ url_for('delYouTube', id = notification.id) }}"
|
||||
onclick="return confirm('Êtes-vous sûr de vouloir supprimer cette notification ?')" class="icon">🗑</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
<h2>{{ 'Editer une notification' if notification else 'Ajouter une notification YouTube' }}</h2>
|
||||
<form action="{{ url_for('submitEditYouTube', id = notification.id) if notification else url_for('addYouTube') }}" method="POST">
|
||||
<label for="channel_id">Lien ou ID de la chaîne YouTube</label>
|
||||
<input name="channel_id" type="text" maxlength="256" required="required" value="{{notification.channel_id if notification}}" placeholder="https://www.youtube.com/@513v3 ou https://www.youtube.com/channel/UC... ou UC..."/>
|
||||
<label for="notify_channel">Canal de Notification Discord</label>
|
||||
<select name="notify_channel">
|
||||
{% for channel in channels %}
|
||||
<option value="{{channel.id}}"{% if notification and notification.notify_channel == channel.id %}
|
||||
selected="selected" {% endif %}>{{channel.name}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label for="video_type">Type de vidéo à notifier</label>
|
||||
<select name="video_type">
|
||||
<option value="all"{% if notification and notification.video_type == 'all' %} selected="selected" {% endif %}>Toutes (vidéos + shorts)</option>
|
||||
<option value="video"{% if notification and notification.video_type == 'video' %} selected="selected" {% endif %}>Vidéos uniquement</option>
|
||||
<option value="short"{% if notification and notification.video_type == 'short' %} selected="selected" {% endif %}>Shorts uniquement</option>
|
||||
</select>
|
||||
<label for="message">Message</label>
|
||||
<textarea name="message" rows="5" cols="50" required="required">{{notification.message if notification}}</textarea>
|
||||
<input type="Submit" value="{{ 'Modifier' if notification else 'Ajouter' }}">
|
||||
<p>
|
||||
Vous pouvez coller directement le lien de la chaîne YouTube dans n'importe quel format :
|
||||
<ul>
|
||||
<li><strong>Lien avec handle :</strong> <code>https://www.youtube.com/@513v3</code></li>
|
||||
<li><strong>Lien avec ID :</strong> <code>https://www.youtube.com/channel/UCxxxxxxxxxxxxxxxxxxxxxxxxxx</code></li>
|
||||
<li><strong>ID seul :</strong> <code>UCxxxxxxxxxxxxxxxxxxxxxxxxxx</code></li>
|
||||
<li><strong>Handle seul :</strong> <code>@513v3</code></li>
|
||||
</ul>
|
||||
Le système extraira automatiquement l'ID de la chaîne.
|
||||
<br><br>
|
||||
<strong>Note :</strong> Les notifications utilisent le flux RSS YouTube, aucune clé API n'est nécessaire !
|
||||
</p>
|
||||
<p>
|
||||
Pour le message vous avez accès à ces variables :
|
||||
<ul>
|
||||
<li><code>{channel_name}</code> : nom de la chaîne YouTube</li>
|
||||
<li><code>{video_title}</code> : titre de la vidéo</li>
|
||||
<li><code>{video_url}</code> : lien vers la vidéo</li>
|
||||
<li><code>{video_id}</code> : ID de la vidéo</li>
|
||||
<li><code>{thumbnail}</code> : URL de la miniature</li>
|
||||
<li><code>{published_at}</code> : date de publication</li>
|
||||
<li><code>{is_short}</code> : True si c'est un short, False sinon</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.
|
||||
Exemple : <code>🎥 Nouvelle vidéo de {channel_name} : [{video_title}]({video_url})</code>
|
||||
</p>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
157
webapp/youtube.py
Normal file
157
webapp/youtube.py
Normal file
@@ -0,0 +1,157 @@
|
||||
import re
|
||||
import requests
|
||||
from urllib.parse import urlencode
|
||||
from flask import render_template, request, redirect, url_for
|
||||
|
||||
from webapp import webapp
|
||||
from database import db
|
||||
from database.models import YouTubeNotification
|
||||
from discordbot import bot
|
||||
|
||||
|
||||
def extract_channel_id(channel_input: str) -> str:
|
||||
"""Extrait l'ID de la chaîne YouTube depuis différents formats"""
|
||||
if not channel_input:
|
||||
return None
|
||||
|
||||
channel_input = channel_input.strip()
|
||||
|
||||
if channel_input.startswith('UC') and len(channel_input) == 24:
|
||||
return channel_input
|
||||
|
||||
if '/channel/' in channel_input:
|
||||
match = re.search(r'/channel/([a-zA-Z0-9_-]{24})', channel_input)
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
||||
if '/c/' in channel_input or '/user/' in channel_input:
|
||||
parts = channel_input.split('/')
|
||||
for i, part in enumerate(parts):
|
||||
if part in ['c', 'user'] and i + 1 < len(parts):
|
||||
handle = parts[i + 1].split('?')[0].split('&')[0]
|
||||
channel_id = _get_channel_id_from_handle(handle)
|
||||
if channel_id:
|
||||
return channel_id
|
||||
|
||||
if '@' in channel_input:
|
||||
handle = re.search(r'@([a-zA-Z0-9_-]+)', channel_input)
|
||||
if handle:
|
||||
channel_id = _get_channel_id_from_handle(handle.group(1))
|
||||
if channel_id:
|
||||
return channel_id
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _get_channel_id_from_handle(handle: str) -> str:
|
||||
"""Récupère l'ID de la chaîne depuis un handle en utilisant le flux RSS"""
|
||||
try:
|
||||
url = f"https://www.youtube.com/@{handle}"
|
||||
response = requests.get(url, timeout=10, allow_redirects=True)
|
||||
|
||||
if response.status_code == 200:
|
||||
channel_id_match = re.search(r'"channelId":"([^"]{24})"', response.text)
|
||||
if channel_id_match:
|
||||
return channel_id_match.group(1)
|
||||
|
||||
canonical_match = re.search(r'<link rel="canonical" href="https://www\.youtube\.com/channel/([^"]{24})"', response.text)
|
||||
if canonical_match:
|
||||
return canonical_match.group(1)
|
||||
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
@webapp.route("/youtube")
|
||||
def openYouTube():
|
||||
notifications: list[YouTubeNotification] = YouTubeNotification.query.all()
|
||||
channels = bot.getAllTextChannel()
|
||||
for notification in notifications:
|
||||
for channel in channels:
|
||||
if notification.notify_channel == channel.id:
|
||||
notification.notify_channel_name = channel.name
|
||||
msg = request.args.get('msg')
|
||||
msg_type = request.args.get('type', 'info')
|
||||
return render_template("youtube.html", notifications=notifications, channels=channels, msg=msg, msg_type=msg_type)
|
||||
|
||||
|
||||
@webapp.route("/youtube/add", methods=['POST'])
|
||||
def addYouTube():
|
||||
channel_input = request.form.get('channel_id', '').strip()
|
||||
channel_id = extract_channel_id(channel_input)
|
||||
|
||||
if not channel_id:
|
||||
return redirect(url_for("openYouTube") + "?" + urlencode({'msg': f"Impossible d'extraire l'ID de la chaîne depuis : {channel_input}. Veuillez vérifier le lien.", 'type': 'error'}))
|
||||
|
||||
notify_channel_str = request.form.get('notify_channel')
|
||||
if not notify_channel_str:
|
||||
return redirect(url_for("openYouTube") + "?" + urlencode({'msg': "Veuillez sélectionner un canal Discord. Assurez-vous que le bot Discord est connecté.", 'type': 'error'}))
|
||||
|
||||
try:
|
||||
notify_channel = int(notify_channel_str)
|
||||
except ValueError:
|
||||
return redirect(url_for("openYouTube") + "?" + urlencode({'msg': "Canal Discord invalide.", 'type': 'error'}))
|
||||
|
||||
notification = YouTubeNotification(
|
||||
enable=True,
|
||||
channel_id=channel_id,
|
||||
notify_channel=notify_channel,
|
||||
message=request.form.get('message'),
|
||||
video_type=request.form.get('video_type', 'all')
|
||||
)
|
||||
db.session.add(notification)
|
||||
db.session.commit()
|
||||
return redirect(url_for("openYouTube") + "?" + urlencode({'msg': f"Notification ajoutée avec succès pour la chaîne {channel_id}", 'type': 'success'}))
|
||||
|
||||
|
||||
@webapp.route("/youtube/toggle/<int:id>")
|
||||
def toggleYouTube(id):
|
||||
notification: YouTubeNotification = YouTubeNotification.query.get_or_404(id)
|
||||
notification.enable = not notification.enable
|
||||
db.session.commit()
|
||||
return redirect(url_for("openYouTube"))
|
||||
|
||||
|
||||
@webapp.route("/youtube/edit/<int:id>")
|
||||
def openEditYouTube(id):
|
||||
notification = YouTubeNotification.query.get_or_404(id)
|
||||
channels = bot.getAllTextChannel()
|
||||
msg = request.args.get('msg')
|
||||
msg_type = request.args.get('type', 'info')
|
||||
return render_template("youtube.html", notification=notification, channels=channels, notifications=YouTubeNotification.query.all(), msg=msg, msg_type=msg_type)
|
||||
|
||||
|
||||
@webapp.route("/youtube/edit/<int:id>", methods=['POST'])
|
||||
def submitEditYouTube(id):
|
||||
notification: YouTubeNotification = YouTubeNotification.query.get_or_404(id)
|
||||
|
||||
channel_input = request.form.get('channel_id', '').strip()
|
||||
channel_id = extract_channel_id(channel_input)
|
||||
|
||||
if not channel_id:
|
||||
return redirect(url_for("openEditYouTube", id=id) + "?" + urlencode({'msg': f"Impossible d'extraire l'ID de la chaîne depuis : {channel_input}. Veuillez vérifier le lien.", 'type': 'error'}))
|
||||
|
||||
notify_channel_str = request.form.get('notify_channel')
|
||||
if not notify_channel_str:
|
||||
return redirect(url_for("openEditYouTube", id=id) + "?" + urlencode({'msg': "Veuillez sélectionner un canal Discord. Assurez-vous que le bot Discord est connecté.", 'type': 'error'}))
|
||||
|
||||
try:
|
||||
notify_channel = int(notify_channel_str)
|
||||
except ValueError:
|
||||
return redirect(url_for("openEditYouTube", id=id) + "?" + urlencode({'msg': "Canal Discord invalide.", 'type': 'error'}))
|
||||
|
||||
notification.channel_id = channel_id
|
||||
notification.notify_channel = notify_channel
|
||||
notification.message = request.form.get('message')
|
||||
notification.video_type = request.form.get('video_type', 'all')
|
||||
db.session.commit()
|
||||
return redirect(url_for("openYouTube") + "?" + urlencode({'msg': "Notification modifiée avec succès", 'type': 'success'}))
|
||||
|
||||
|
||||
@webapp.route("/youtube/del/<int:id>")
|
||||
def delYouTube(id):
|
||||
notification = YouTubeNotification.query.get_or_404(id)
|
||||
db.session.delete(notification)
|
||||
db.session.commit()
|
||||
return redirect(url_for("openYouTube"))
|
||||
Reference in New Issue
Block a user