From 48531690fded395d703d4b33eb48d2e8879cc263 Mon Sep 17 00:00:00 2001 From: Mow910 Date: Sun, 1 Feb 2026 12:55:38 +0100 Subject: [PATCH] =?UTF-8?q?Accueil=20restructur=C3=A9=20(zones=20Discord/T?= =?UTF-8?q?witch,=20stats,=20indicateur=20connexion)=20+=20Top=203=20sanct?= =?UTF-8?q?ions/mod=C3=A9rateurs=20+=20BOT=5FSTATUS=20dans=20config=20Flas?= =?UTF-8?q?k?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- database/__init__.py | 12 +- database/models.py | 1 + discordbot/__init__.py | 9 +- twitchbot/__init__.py | 5 +- webapp/__init__.py | 8 + webapp/index.py | 12 +- webapp/moderation.py | 46 ++++- webapp/templates/index.html | 98 +++++++++- webapp/templates/moderation.html | 301 +++++++++++++++++++++---------- webapp/templates/template.html | 261 ++++++++++++++++++++++++--- 10 files changed, 612 insertions(+), 141 deletions(-) diff --git a/database/__init__.py b/database/__init__.py index 444d20f..77700d1 100644 --- a/database/__init__.py +++ b/database/__init__.py @@ -33,11 +33,11 @@ def _set_sqlite_pragma(dbapi_connection, connection_record): except Exception: pass -def _tableExists(table_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: +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})') @@ -70,9 +70,8 @@ def _doPostImportMigration(cursor:Cursor): cursor.execute('INSERT INTO game_bundle(url, name, json) VALUES (?, ?, ?)', (url, name, json.dumps(json_data))) logging.info("suppression de la table temporaire game_bundle_old") _dropTable('game_bundle_old', cursor) - + if _tableExists('youtube_notification', cursor): - logging.info("Migration de la table youtube_notification: ajout des colonnes d'embed") embed_columns = [ ('embed_title', 'VARCHAR(256)'), ('embed_description', 'VARCHAR(2000)'), @@ -81,7 +80,7 @@ def _doPostImportMigration(cursor:Cursor): ('embed_author_name', 'VARCHAR(256)'), ('embed_author_icon', 'VARCHAR(512)'), ('embed_thumbnail', 'BOOLEAN DEFAULT 1'), - ('embed_image', 'BOOLEAN DEFAULT 1') + ('embed_image', 'BOOLEAN DEFAULT 1'), ] for col_name, col_type in embed_columns: if not _tableHaveColumn('youtube_notification', col_name, cursor): @@ -89,8 +88,7 @@ def _doPostImportMigration(cursor:Cursor): 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.error(f"Impossible d'ajouter la colonne {col_name}: {e}") - raise + logging.warning(f"Colonne youtube_notification.{col_name}: {e}") with webapp.app_context(): with open('database/schema.sql', 'r') as f: diff --git a/database/models.py b/database/models.py index 1562652..d144c7e 100644 --- a/database/models.py +++ b/database/models.py @@ -61,6 +61,7 @@ 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) diff --git a/discordbot/__init__.py b/discordbot/__init__.py index 9c48341..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 @@ -28,6 +29,8 @@ 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}') @@ -37,6 +40,9 @@ 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(): @@ -52,11 +58,10 @@ class DiscordBot(discord.Client): while not self.is_closed(): await checkHumbleBundleAndNotify(self) await asyncio.sleep(30*60) - + async def updateYouTube(self): while not self.is_closed(): await checkYouTubeVideos() - # Vérification toutes les 5 minutes (comme pour Twitch) await asyncio.sleep(5*60) def getAllTextChannel(self) -> list[TextChannel]: 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 cdaaa26..0ff86c7 100644 --- a/webapp/__init__.py +++ b/webapp/__init__.py @@ -2,4 +2,12 @@ from flask import Flask webapp = Flask(__name__) +# É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 eb248da..bb109a5 100644 --- a/webapp/templates/index.html +++ b/webapp/templates/index.html @@ -1,7 +1,97 @@ {% extends "template.html" %} {% block content %} -

Bienvenue sur l'interface d'administration de Mamie.

-

Nous devons définir ce que nous souhaitons afficher sur la page d'accueil. Peut-être l'historique des dernières - modifications ? de la modération ?

-{% endblock %} \ No newline at end of file +
+

+ Panneau d'administration +

+

+ Gérez les fonctionnalités de votre bot Discord et Twitch depuis cette interface. +

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

Discord

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

Serveurs connectés

+

{{ discord_guild_count }}

+
+
+

Sanctions enregistrées

+

{{ sanctions_count }}

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

Twitch

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

Canal connecté

+

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

+
+
+

Sanctions

+

+

À venir

+
+
+

Intégrations du bot Twitch à venir.

+
+
+
+ +
+
+
+ +
+
+

À propos

+

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

+ +
+
+
+{% endblock %} diff --git a/webapp/templates/moderation.html b/webapp/templates/moderation.html index fc1e316..b35f643 100644 --- a/webapp/templates/moderation.html +++ b/webapp/templates/moderation.html @@ -1,110 +1,215 @@ {% extends "template.html" %} {% block content %} -

Modération Discord

+
+

Modération

+

+ Historique des actions de modération sur le serveur Discord. +

+
-

- Historique des actions de modération effectuées sur le serveur Discord. +

+
+
+

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 %} +
+
+
- Le bot enregistre automatiquement les avertissements, exclusions et bannissements. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CommandeDescription
!averto @utilisateur raison
Alias : !warn, !av, !avertissement
Avertit un utilisateur et enregistre l'avertissement dans la base de données
!delaverto id
Alias : !removewarn, !delwarn
Retire un avertissement en utilisant son numéro d'ID
!warnings ou !warnings @utilisateur
Alias : !listevent, !listwarn
Affiche la liste des événements de modération (tous ou pour un utilisateur spécifique)
!inspect @utilisateur ou !inspect idAffiche des informations détaillées sur un utilisateur : création du compte, date d'arrivée, historique de modération
!kick @utilisateur raisonExpulse un utilisateur du serveur
!ban @utilisateur raisonBannit définitivement un utilisateur du serveur
!unban discord_id ou !unban #sanction_id raisonRévoque le bannissement d'un utilisateur et lui envoie une invitation
!banlistAffiche la liste des utilisateurs actuellement bannis du serveur
!aide
Alias : !help
Affiche l'aide avec toutes les commandes disponibles
-

+
+
+ + Commandes de modération disponibles + + +
+
+
+ !averto @user raison + Avertit un utilisateur +
+
+ !delaverto id + Retire un avertissement +
+
+ !warnings [@user] + Liste les événements de modération +
+
+ !inspect @user + Informations sur un utilisateur +
+
+ !kick @user raison + Expulse un utilisateur +
+
+ !ban @user raison + Bannit un utilisateur +
+
+ !unban id + Révoque un bannissement +
+
+ !banlist + Liste des utilisateurs bannis +
+
+
+
+
{% if not event %} -

Événements de modération

- - - - - - - - - - - - - - {% for mod_event in events %} - - - - - - - - - - {% endfor %} - -
TypeUtilisateurDiscord IDDate & HeureRaisonStaff#
{{ mod_event.type }}{{ mod_event.username }}{{ mod_event.discord_id }}{{ mod_event.created_at.strftime('%d/%m/%Y %H:%M') if mod_event.created_at else 'N/A' }}{{ mod_event.reason }}{{ mod_event.staff_name }} - - 🗑 -
+
+
+

Événements de modération

+
+
+ + + + + + + + + + + + + {% for mod_event in events %} + + + + + + + + + {% else %} + + + + {% endfor %} + +
TypeUtilisateurDateRaisonStaffActions
+ {% if mod_event.type == 'ban' %} + Ban + {% elif mod_event.type == 'kick' %} + Kick + {% elif mod_event.type == 'warn' or mod_event.type == 'warning' %} + Warn + {% elif mod_event.type == 'unban' %} + Unban + {% else %} + {{ mod_event.type }} + {% endif %} + +
+ {{ mod_event.username }} + {{ mod_event.discord_id }} +
+
+ {{ mod_event.created_at.strftime('%d/%m/%Y %H:%M') if mod_event.created_at else 'N/A' }} + +
{{ mod_event.reason }}
+
+ {{ mod_event.staff_name }} + + +
+ Aucun événement de modération +
+
+
{% endif %} {% if event %} -

Editer un événement

-
- - - - - - - - - - - - Annuler -
+
+

Modifier l'événement

+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ + Annuler + + +
+
+
{% endif %} - {% endblock %} diff --git a/webapp/templates/template.html b/webapp/templates/template.html index 1539260..be5a9c8 100644 --- a/webapp/templates/template.html +++ b/webapp/templates/template.html @@ -1,40 +1,249 @@ - + - - + + Mamie Henriette - - + + + - -
- -
-
- {% block content %}{% endblock %} + + + + + +
+
+
+ {% block content %}{% endblock %} +
+
-