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.
-
-
-
-
- Commande
- Description
-
-
-
-
- !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 id
- Affiche des informations détaillées sur un utilisateur : création du compte, date d'arrivée, historique de modération
-
-
- !kick @utilisateur raison
- Expulse un utilisateur du serveur
-
-
- !ban @utilisateur raison
- Bannit définitivement un utilisateur du serveur
-
-
- !unban discord_id ou !unban #sanction_id raison
- Révoque le bannissement d'un utilisateur et lui envoie une invitation
-
-
- !banlist
- Affiche 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
-
-
-
- Type
- Utilisateur
- Discord ID
- Date & Heure
- Raison
- Staff
- #
-
-
-
- {% for mod_event in events %}
-
- {{ 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 }}
-
- ✐
- 🗑
-
-
- {% endfor %}
-
-
+
+
+
Événements de modération
+
+
+
+
+
+ Type
+ Utilisateur
+ Date
+ Raison
+ Staff
+ Actions
+
+
+
+ {% for mod_event in events %}
+
+
+ {% 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 }}
+
+
+
+
+
+ {% else %}
+
+
+ Aucun événement de modération
+
+
+ {% endfor %}
+
+
+
+
{% endif %}
{% if event %}
-Editer un événement
-
+
+
Modifier l'événement
+
+
+
{% 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 %}
+
+
-