diff --git a/database/__init__.py b/database/__init__.py index 9d1d48e..77700d1 100644 --- a/database/__init__.py +++ b/database/__init__.py @@ -33,7 +33,13 @@ def _set_sqlite_pragma(dbapi_connection, connection_record): except Exception: pass -def _tableHaveColumn(table_name:str, column_name:str, cursor:Cursor) -> bool: +def _tableExists(table_name: str, cursor: Cursor) -> bool: + cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table_name,)) + return cursor.fetchone() is not None + +def _tableHaveColumn(table_name:str, column_name:str, cursor:Cursor) -> bool: + if not _tableExists(table_name, cursor): + return False cursor.execute(f'PRAGMA table_info({table_name})') columns = cursor.fetchall() return any(col[1] == column_name for col in columns) @@ -65,6 +71,25 @@ def _doPostImportMigration(cursor:Cursor): logging.info("suppression de la table temporaire game_bundle_old") _dropTable('game_bundle_old', cursor) + if _tableExists('youtube_notification', cursor): + embed_columns = [ + ('embed_title', 'VARCHAR(256)'), + ('embed_description', 'VARCHAR(2000)'), + ('embed_color', 'VARCHAR(8) DEFAULT "FF0000"'), + ('embed_footer', 'VARCHAR(2048)'), + ('embed_author_name', 'VARCHAR(256)'), + ('embed_author_icon', 'VARCHAR(512)'), + ('embed_thumbnail', 'BOOLEAN DEFAULT 1'), + ('embed_image', 'BOOLEAN DEFAULT 1'), + ] + for col_name, col_type in embed_columns: + if not _tableHaveColumn('youtube_notification', col_name, cursor): + try: + cursor.execute(f'ALTER TABLE youtube_notification ADD COLUMN {col_name} {col_type}') + logging.info(f"Colonne {col_name} ajoutée à youtube_notification") + except Exception as e: + logging.warning(f"Colonne youtube_notification.{col_name}: {e}") + with webapp.app_context(): with open('database/schema.sql', 'r') as f: sql = f.read() diff --git a/database/models.py b/database/models.py index 9f103d1..d144c7e 100644 --- a/database/models.py +++ b/database/models.py @@ -61,3 +61,22 @@ class AntiCheatCache(db.Model): notes = db.Column(db.String(1024)) updated_at = db.Column(db.DateTime) + +class YouTubeNotification(db.Model): + __tablename__ = 'youtube_notification' + id = db.Column(db.Integer, primary_key=True) + enable = db.Column(db.Boolean, default=True) + channel_id = db.Column(db.String(128)) + notify_channel = db.Column(db.Integer) + message = db.Column(db.String(2000)) + video_type = db.Column(db.String(16), default='all') + last_video_id = db.Column(db.String(128)) + embed_title = db.Column(db.String(256)) + embed_description = db.Column(db.String(2000)) + embed_color = db.Column(db.String(8), default='FF0000') + embed_footer = db.Column(db.String(2048)) + embed_author_name = db.Column(db.String(256)) + embed_author_icon = db.Column(db.String(512)) + embed_thumbnail = db.Column(db.Boolean, default=True) + embed_image = db.Column(db.Boolean, default=True) + diff --git a/database/schema.sql b/database/schema.sql index 04cef86..0af5439 100644 --- a/database/schema.sql +++ b/database/schema.sql @@ -76,3 +76,21 @@ CREATE TABLE IF NOT EXISTS `member_invites` ( `inviter_name` VARCHAR(256), `join_date` DATETIME NOT NULL ); + +CREATE TABLE IF NOT EXISTS `youtube_notification` ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + `enable` BOOLEAN NOT NULL DEFAULT TRUE, + `channel_id` VARCHAR(128) NOT NULL, + `notify_channel` INTEGER NOT NULL, + `message` VARCHAR(2000) NOT NULL, + `video_type` VARCHAR(16) NOT NULL DEFAULT 'all', + `last_video_id` VARCHAR(128), + `embed_title` VARCHAR(256), + `embed_description` VARCHAR(2000), + `embed_color` VARCHAR(8) NOT NULL DEFAULT 'FF0000', + `embed_footer` VARCHAR(2048), + `embed_author_name` VARCHAR(256), + `embed_author_icon` VARCHAR(512), + `embed_thumbnail` BOOLEAN NOT NULL DEFAULT TRUE, + `embed_image` BOOLEAN NOT NULL DEFAULT TRUE +); diff --git a/discordbot/__init__.py b/discordbot/__init__.py index 2de81dd..cffafb6 100644 --- a/discordbot/__init__.py +++ b/discordbot/__init__.py @@ -3,6 +3,7 @@ import discord import logging import random +from webapp import webapp from database import db from database.helpers import ConfigurationHelper from database.models import Configuration, Humeur, Commande @@ -22,11 +23,14 @@ from discordbot.moderation import ( handle_say_command ) from discordbot.welcome import sendWelcomeMessage, sendLeaveMessage, updateInviteCache +from discordbot.youtube import checkYouTubeVideos from protondb import searhProtonDb class DiscordBot(discord.Client): async def on_ready(self): logging.info(f'Connecté en tant que {self.user} (ID: {self.user.id})') + webapp.config["BOT_STATUS"]["discord_connected"] = True + webapp.config["BOT_STATUS"]["discord_guild_count"] = len(self.guilds) for c in self.get_all_channels() : logging.info(f'{c.id} {c.name}') @@ -35,6 +39,10 @@ class DiscordBot(discord.Client): self.loop.create_task(self.updateStatus()) self.loop.create_task(self.updateHumbleBundle()) + self.loop.create_task(self.updateYouTube()) + + async def on_disconnect(self): + webapp.config["BOT_STATUS"]["discord_connected"] = False async def updateStatus(self): while not self.is_closed(): @@ -51,6 +59,11 @@ class DiscordBot(discord.Client): await checkHumbleBundleAndNotify(self) await asyncio.sleep(30*60) + async def updateYouTube(self): + while not self.is_closed(): + await checkYouTubeVideos() + await asyncio.sleep(5*60) + def getAllTextChannel(self) -> list[TextChannel]: channels = [] for channel in self.get_all_channels(): diff --git a/discordbot/youtube.py b/discordbot/youtube.py new file mode 100644 index 0000000..aa78962 --- /dev/null +++ b/discordbot/youtube.py @@ -0,0 +1,225 @@ +import logging +import asyncio +import xml.etree.ElementTree as ET +import requests + +from database import db +from database.models import YouTubeNotification +from webapp import webapp + +logger = logging.getLogger('youtube-notification') +logger.setLevel(logging.INFO) + + +async def checkYouTubeVideos(): + with webapp.app_context(): + try: + notifications: list[YouTubeNotification] = YouTubeNotification.query.filter_by(enable=True).all() + + for notification in notifications: + try: + await _checkChannelVideos(notification) + except Exception as e: + logger.error(f"Erreur lors de la vérification de la chaîne {notification.channel_id}: {e}") + continue + except Exception as e: + logger.error(f"Erreur lors de la vérification YouTube: {e}") + + +async def _checkChannelVideos(notification: YouTubeNotification): + try: + channel_id = notification.channel_id + + rss_url = f"https://www.youtube.com/feeds/videos.xml?channel_id={channel_id}" + + response = await asyncio.to_thread(requests.get, rss_url, timeout=10) + + if response.status_code != 200: + logger.error(f"Erreur HTTP {response.status_code} lors de la récupération du RSS pour {channel_id}") + return + + root = ET.fromstring(response.content) + + ns = {'atom': 'http://www.w3.org/2005/Atom', 'yt': 'http://www.youtube.com/xml/schemas/2015', 'media': 'http://search.yahoo.com/mrss/'} + + entries = root.findall('atom:entry', ns) + + if not entries: + logger.warning(f"Aucune vidéo trouvée dans le RSS pour {channel_id}") + return + + videos = [] + for entry in entries: + video_id = entry.find('yt:videoId', ns) + if video_id is None: + continue + video_id = video_id.text + + title_elem = entry.find('atom:title', ns) + video_title = title_elem.text if title_elem is not None else 'Sans titre' + + link_elem = entry.find('atom:link', ns) + video_url = link_elem.get('href') if link_elem is not None else f"https://www.youtube.com/watch?v={video_id}" + + published_elem = entry.find('atom:published', ns) + published_at = published_elem.text if published_elem is not None else '' + + author_elem = entry.find('atom:author/atom:name', ns) + channel_name = author_elem.text if author_elem is not None else 'Inconnu' + + thumbnail = None + media_thumbnail = entry.find('media:group/media:thumbnail', ns) + if media_thumbnail is not None: + thumbnail = media_thumbnail.get('url') + + is_short = False + if video_title and ('#shorts' in video_title.lower() or '#short' in video_title.lower()): + is_short = True + + if notification.video_type == 'all': + videos.append((video_id, { + 'title': video_title, + 'url': video_url, + 'published': published_at, + 'channel_name': channel_name, + 'thumbnail': thumbnail, + 'is_short': is_short + })) + elif notification.video_type == 'short' and is_short: + videos.append((video_id, { + 'title': video_title, + 'url': video_url, + 'published': published_at, + 'channel_name': channel_name, + 'thumbnail': thumbnail, + 'is_short': is_short + })) + elif notification.video_type == 'video' and not is_short: + videos.append((video_id, { + 'title': video_title, + 'url': video_url, + 'published': published_at, + 'channel_name': channel_name, + 'thumbnail': thumbnail, + 'is_short': is_short + })) + + videos.sort(key=lambda x: x[1]['published'], reverse=True) + + if videos: + latest_video_id, latest_video = videos[0] + + if not notification.last_video_id: + notification.last_video_id = latest_video_id + db.session.commit() + return + + if latest_video_id != notification.last_video_id: + logger.info(f"Nouvelle vidéo détectée: {latest_video_id} pour la chaîne {notification.channel_id}") + await _notifyVideo(notification, latest_video, latest_video_id) + notification.last_video_id = latest_video_id + db.session.commit() + + except Exception as e: + logger.error(f"Erreur lors de la vérification des vidéos: {e}") + + +async def _notifyVideo(notification: YouTubeNotification, video_data: dict, video_id: str): + from discordbot import bot + try: + channel_name = video_data.get('channel_name', 'Inconnu') + video_title = video_data.get('title', 'Sans titre') + video_url = video_data.get('url', f"https://www.youtube.com/watch?v={video_id}") + thumbnail = video_data.get('thumbnail', '') + published_at = video_data.get('published', '') + is_short = video_data.get('is_short', False) + + try: + message = notification.message.format( + channel_name=channel_name or 'Inconnu', + video_title=video_title or 'Sans titre', + video_url=video_url, + video_id=video_id, + thumbnail=thumbnail or '', + published_at=published_at or '', + is_short=is_short + ) + except KeyError as e: + logger.error(f"Variable manquante dans le message de notification: {e}") + message = f"🎥 Nouvelle vidéo de {channel_name}: [{video_title}]({video_url})" + + logger.info(f"Envoi de notification YouTube: {message}") + bot.loop.create_task(_sendMessage(notification, message, video_url, thumbnail, video_title, channel_name, video_id, published_at, is_short)) + + except Exception as e: + logger.error(f"Erreur lors de la notification: {e}") + + +def _format_embed_text(text: str, channel_name: str, video_title: str, video_url: str, video_id: str, thumbnail: str, published_at: str, is_short: bool) -> str: + """Formate un texte d'embed avec les variables disponibles""" + if not text: + return None + try: + return text.format( + channel_name=channel_name or 'Inconnu', + video_title=video_title or 'Sans titre', + video_url=video_url, + video_id=video_id, + thumbnail=thumbnail or '', + published_at=published_at or '', + is_short=is_short + ) + except KeyError: + return text + + +async def _sendMessage(notification: YouTubeNotification, message: str, video_url: str, thumbnail: str, video_title: str, channel_name: str, video_id: str, published_at: str, is_short: bool): + from discordbot import bot + try: + discord_channel = bot.get_channel(notification.notify_channel) + if not discord_channel: + logger.error(f"Canal Discord {notification.notify_channel} introuvable") + return + + import discord + + embed_title = _format_embed_text(notification.embed_title, channel_name, video_title, video_url, video_id, thumbnail, published_at, is_short) if notification.embed_title else video_title + embed_description = _format_embed_text(notification.embed_description, channel_name, video_title, video_url, video_id, thumbnail, published_at, is_short) if notification.embed_description else None + + try: + embed_color = int(notification.embed_color or 'FF0000', 16) + except ValueError: + embed_color = 0xFF0000 + + embed = discord.Embed( + title=embed_title, + url=video_url, + color=embed_color + ) + + if embed_description: + embed.description = embed_description + + author_name = _format_embed_text(notification.embed_author_name, channel_name, video_title, video_url, video_id, thumbnail, published_at, is_short) if notification.embed_author_name else channel_name + author_icon = notification.embed_author_icon if notification.embed_author_icon else "https://www.youtube.com/img/desktop/yt_1200.png" + embed.set_author(name=author_name, icon_url=author_icon) + + if notification.embed_thumbnail and thumbnail: + embed.set_thumbnail(url=thumbnail) + + if notification.embed_image and thumbnail: + embed.set_image(url=thumbnail) + + if notification.embed_footer: + footer_text = _format_embed_text(notification.embed_footer, channel_name, video_title, video_url, video_id, thumbnail, published_at, is_short) + if footer_text: + embed.set_footer(text=footer_text) + + if message and message.strip(): + await discord_channel.send(message, embed=embed) + else: + await discord_channel.send(embed=embed) + logger.info(f"Notification YouTube envoyée avec succès") + + except Exception as e: + logger.error(f"Erreur lors de l'envoi du message Discord: {e}") diff --git a/twitchbot/__init__.py b/twitchbot/__init__.py index 529bf0d..45796f6 100644 --- a/twitchbot/__init__.py +++ b/twitchbot/__init__.py @@ -14,8 +14,11 @@ 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') + webapp.config["BOT_STATUS"]["twitch_connected"] = True + webapp.config["BOT_STATUS"]["twitch_channel_name"] = channel with webapp.app_context(): - await ready_event.chat.join_room(ConfigurationHelper().getValue('twitch_channel')) + await ready_event.chat.join_room(channel) asyncio.get_event_loop().create_task(twitchBot._checkOnlineStreamers()) diff --git a/webapp/__init__.py b/webapp/__init__.py index 28f6c3c..0ff86c7 100644 --- a/webapp/__init__.py +++ b/webapp/__init__.py @@ -2,4 +2,12 @@ from flask import Flask webapp = Flask(__name__) -from webapp import commandes, configurations, index, humeurs, protondb, live_alert, twitch_auth, moderation +# État des bots (mis à jour par les bots, lu par le panneau) +webapp.config["BOT_STATUS"] = { + "discord_connected": False, + "discord_guild_count": 0, + "twitch_connected": False, + "twitch_channel_name": None, +} + +from webapp import commandes, configurations, index, humeurs, protondb, live_alert, twitch_auth, moderation, youtube diff --git a/webapp/index.py b/webapp/index.py index 10f735b..ffe42b2 100644 --- a/webapp/index.py +++ b/webapp/index.py @@ -1,6 +1,16 @@ from flask import render_template from webapp import webapp +from database.models import ModerationEvent @webapp.route("/") def index(): - return render_template("index.html") + status = webapp.config["BOT_STATUS"] + sanctions_count = ModerationEvent.query.count() + return render_template( + "index.html", + discord_connected=status["discord_connected"], + discord_guild_count=status["discord_guild_count"], + sanctions_count=sanctions_count, + twitch_connected=status["twitch_connected"], + twitch_channel_name=status["twitch_channel_name"], + ) diff --git a/webapp/moderation.py b/webapp/moderation.py index 5571487..e0d92f4 100644 --- a/webapp/moderation.py +++ b/webapp/moderation.py @@ -3,16 +3,58 @@ from webapp import webapp from database import db from database.models import ModerationEvent +def _top_sanctioned(): + return ( + db.session.query( + ModerationEvent.discord_id, + db.func.max(ModerationEvent.username).label("username"), + db.func.count(ModerationEvent.id).label("count"), + ) + .group_by(ModerationEvent.discord_id) + .order_by(db.func.count(ModerationEvent.id).desc()) + .limit(3) + .all() + ) + +def _top_moderators(): + return ( + db.session.query( + ModerationEvent.staff_id, + db.func.max(ModerationEvent.staff_name).label("staff_name"), + db.func.count(ModerationEvent.id).label("count"), + ) + .group_by(ModerationEvent.staff_id) + .order_by(db.func.count(ModerationEvent.id).desc()) + .limit(3) + .all() + ) + @webapp.route("/moderation") def moderation(): events = ModerationEvent.query.order_by(ModerationEvent.created_at.desc()).all() - return render_template("moderation.html", events=events, event=None) + top_sanctioned = _top_sanctioned() + top_moderators = _top_moderators() + return render_template( + "moderation.html", + events=events, + event=None, + top_sanctioned=top_sanctioned, + top_moderators=top_moderators, + ) @webapp.route("/moderation/edit/") def open_edit_moderation_event(event_id): event = ModerationEvent.query.get_or_404(event_id) events = ModerationEvent.query.order_by(ModerationEvent.created_at.desc()).all() - return render_template("moderation.html", events=events, event=event) + top_sanctioned = _top_sanctioned() + top_moderators = _top_moderators() + return render_template( + "moderation.html", + events=events, + event=event, + top_sanctioned=top_sanctioned, + top_moderators=top_moderators, + ) @webapp.route("/moderation/update/", methods=['POST']) def update_moderation_event(event_id): diff --git a/webapp/templates/index.html b/webapp/templates/index.html index 050b7e3..bb109a5 100644 --- a/webapp/templates/index.html +++ b/webapp/templates/index.html @@ -10,84 +10,64 @@

-
- -
-
- -
-
-

Alertes Live

-

Notifications Twitch

-
- +{# Zone Discord #} +
+
+
+ +

Discord

-
+ + {% if discord_connected %}Connecté{% else %}Déconnecté{% endif %} + +
+
+
+

Serveurs connectés

+

{{ discord_guild_count }}

+
+
+

Sanctions enregistrées

+

{{ sanctions_count }}

+
+ +
+
- -
-
- -
-
-

Commandes

-

Commandes personnalisées

-
- +{# Zone Twitch #} +
+
+
+ +

Twitch

-
- - -
-
- -
-
-

Humeurs

-

Statuts Discord rotatifs

-
- + + {% if twitch_connected %}Connecté{% else %}Déconnecté{% endif %} + +
+
+
+

Canal connecté

+

{% if twitch_channel_name %}{{ twitch_channel_name }}{% else %}—{% endif %}

-
- - -
@@ -98,7 +78,7 @@

À propos

- Mamie Henriette est un bot open source pour Discord et Twitch, développé par la communauté. + Mamie Henriette est un bot open source pour Discord et Twitch, développé par la communauté. Cette interface vous permet de configurer et gérer toutes les fonctionnalités.

diff --git a/webapp/templates/moderation.html b/webapp/templates/moderation.html index 52b8bf8..b35f643 100644 --- a/webapp/templates/moderation.html +++ b/webapp/templates/moderation.html @@ -8,6 +8,53 @@

+
+
+
+

Top 3 sanctions

+

Utilisateurs les plus sanctionnés

+
+
+ {% for row in top_sanctioned %} +
+
+ {{ loop.index }} +
+ {{ row.username or '—' }} + {{ row.discord_id }} +
+
+ {{ row.count }} sanction{{ 's' if row.count > 1 else '' }} +
+ {% else %} +
Aucune sanction enregistrée
+ {% endfor %} +
+
+
+
+

Top 3 modérateurs

+

Staff ayant effectué le plus d'actions

+
+
+ {% for row in top_moderators %} +
+
+ {{ loop.index }} +
+ {{ row.staff_name or '—' }} + {{ row.staff_id }} +
+
+ {{ row.count }} action{{ 's' if row.count > 1 else '' }} +
+ {% else %} +
Aucune action enregistrée
+ {% endfor %} +
+
+
+
diff --git a/webapp/templates/template.html b/webapp/templates/template.html index e1387ae..be5a9c8 100644 --- a/webapp/templates/template.html +++ b/webapp/templates/template.html @@ -77,41 +77,68 @@ @@ -138,6 +165,10 @@ Alerte live + + + YouTube + Commandes diff --git a/webapp/templates/youtube.html b/webapp/templates/youtube.html new file mode 100644 index 0000000..e36fdef --- /dev/null +++ b/webapp/templates/youtube.html @@ -0,0 +1,245 @@ +{% extends "template.html" %} + +{% block content %} +

Notifications YouTube

+ +{% if msg %} +
+ {{ msg }} +
+ +{% endif %} + +

+ 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. +

+ +{% if not notification %} +

Notifications

+ + + + + + + + + + + + {% for notification in notifications %} + + + + + + + + {% endfor %} + +
Chaîne YouTubeCanal DiscordTypeMessage#
{{notification.channel_id}}{{notification.notify_channel_name}} + {% if notification.video_type == 'all' %} + Toutes + {% elif notification.video_type == 'video' %} + Vidéos uniquement + {% elif notification.video_type == 'short' %} + Shorts uniquement + {% endif %} + {{notification.message}} + {{ '✅' if notification.enable else '❌' }} + + 🗑 +
+{% endif %} + +

{{ 'Editer une notification' if notification else 'Ajouter une notification YouTube' }}

+ +
+
+
+
+ Configuration de base + + + + + + + + + + + +
+ +
+ Personnalisation de l'embed Discord + + + + Variables: {video_title}, {channel_name}, {video_url}, {video_id} + + + + Variables: {video_title}, {channel_name}, {video_url}, {published_at}, {is_short} + + + + + Format: FF0000 (rouge YouTube par défaut) + + + + + + + + + + + + + +
+ + +
+
+ +
+

Prévisualisation de l'embed Discord

+
+ Cette prévisualisation est approximative. L'apparence réelle sur Discord peut varier légèrement. +
+
+ + + +

+ Variables disponibles pour l'embed : +

    +
  • {channel_name} : nom de la chaîne YouTube
  • +
  • {video_title} : titre de la vidéo
  • +
  • {video_url} : lien vers la vidéo
  • +
  • {video_id} : ID de la vidéo
  • +
  • {thumbnail} : URL de la miniature
  • +
  • {published_at} : date de publication
  • +
  • {is_short} : True si c'est un short, False sinon
  • +
+

+ +{% endblock %} diff --git a/webapp/youtube.py b/webapp/youtube.py new file mode 100644 index 0000000..6f8ae7b --- /dev/null +++ b/webapp/youtube.py @@ -0,0 +1,181 @@ +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'") +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/") +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/", 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'})) + + embed_color = request.form.get('embed_color', 'FF0000').strip().lstrip('#') + if len(embed_color) != 6: + embed_color = 'FF0000' + + 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') + notification.embed_title = request.form.get('embed_title') or None + notification.embed_description = request.form.get('embed_description') or None + notification.embed_color = embed_color + notification.embed_footer = request.form.get('embed_footer') or None + notification.embed_author_name = request.form.get('embed_author_name') or None + notification.embed_author_icon = request.form.get('embed_author_icon') or None + notification.embed_thumbnail = request.form.get('embed_thumbnail') == 'on' + notification.embed_image = request.form.get('embed_image') == 'on' + db.session.commit() + return redirect(url_for("openYouTube") + "?" + urlencode({'msg': "Notification modifiée avec succès", 'type': 'success'})) + + +@webapp.route("/youtube/del/") +def delYouTube(id): + notification = YouTubeNotification.query.get_or_404(id) + db.session.delete(notification) + db.session.commit() + return redirect(url_for("openYouTube"))