Ajout de la gestion des jeux gratuits et des invitations Discord. Création des modèles FreeGame et DiscordInvite, mise à jour de la base de données et des fichiers de configuration. Intégration de nouvelles fonctionnalités pour synchroniser et révoquer les invitations, ainsi que l'ajout d'un flux RSS pour les jeux gratuits. Amélioration de l'interface d'administration avec des statistiques et des options de gestion des serveurs.

This commit is contained in:
Mow
2025-12-06 11:25:45 +01:00
parent 54b014c4c8
commit 42db84f010
23 changed files with 3671 additions and 697 deletions
+179 -4
View File
@@ -1,18 +1,67 @@
from flask import render_template, request, redirect, url_for
from flask import render_template, request, redirect, url_for, flash, jsonify
from webapp import webapp
from database import db
from database.models import ModerationEvent
from database.models import ModerationEvent, DiscordInvite
from datetime import datetime, timedelta, timezone
from collections import Counter
from shared_stats import discord_bridge
def get_moderation_stats():
"""Calcule les statistiques de modération"""
events = ModerationEvent.query.all()
if not events:
return {
'total': 0,
'top_moderators': [],
'type_counts': {},
'recent_24h': 0,
'recent_7d': 0,
'recent_30d': 0,
'top_sanctioned': []
}
now = datetime.now()
# Comptages par modérateur
mod_counts = Counter(e.staff_name for e in events if e.staff_name)
top_moderators = mod_counts.most_common(5)
# Comptages par type
type_counts = Counter(e.type for e in events if e.type)
# Événements récents
recent_24h = sum(1 for e in events if e.created_at and (now - e.created_at) < timedelta(hours=24))
recent_7d = sum(1 for e in events if e.created_at and (now - e.created_at) < timedelta(days=7))
recent_30d = sum(1 for e in events if e.created_at and (now - e.created_at) < timedelta(days=30))
# Top des utilisateurs les plus sanctionnés
user_counts = Counter(e.username for e in events if e.username)
top_sanctioned = user_counts.most_common(5)
return {
'total': len(events),
'top_moderators': top_moderators,
'type_counts': dict(type_counts),
'recent_24h': recent_24h,
'recent_7d': recent_7d,
'recent_30d': recent_30d,
'top_sanctioned': top_sanctioned
}
@webapp.route("/moderation")
def moderation():
events = ModerationEvent.query.order_by(ModerationEvent.created_at.desc()).all()
return render_template("moderation.html", events=events, event=None)
stats = get_moderation_stats()
return render_template("moderation.html", events=events, event=None, stats=stats,
invites=[], invite_stats=None, guilds=[], show_invites=False, show_revoked=False)
@webapp.route("/moderation/edit/<int:event_id>")
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)
stats = get_moderation_stats()
return render_template("moderation.html", events=events, event=event, stats=stats)
@webapp.route("/moderation/update/<int:event_id>", methods=['POST'])
def update_moderation_event(event_id):
@@ -28,3 +77,129 @@ def delete_moderation_event(event_id):
db.session.commit()
return redirect(url_for('moderation'))
def _make_aware(dt):
"""Convertit un datetime naive en aware (UTC)"""
if dt is None:
return None
if dt.tzinfo is None:
return dt.replace(tzinfo=timezone.utc)
return dt
def get_invite_stats():
"""Calcule les statistiques des invitations"""
invites = DiscordInvite.query.filter_by(revoked=False).all()
if not invites:
return {
'total': 0,
'total_uses': 0,
'top_inviters': [],
'permanent': 0,
'temporary': 0,
'expired': 0
}
now = datetime.now(timezone.utc)
# Comptages par inviteur
inviter_uses = Counter()
for inv in invites:
if inv.inviter_name:
inviter_uses[inv.inviter_name] += inv.uses or 0
top_inviters = inviter_uses.most_common(5)
# Total des utilisations
total_uses = sum(inv.uses or 0 for inv in invites)
# Invitations permanentes (max_age = 0) vs temporaires
permanent = sum(1 for inv in invites if inv.max_age == 0)
temporary = sum(1 for inv in invites if inv.max_age > 0)
# Invitations expirées (mais pas encore révoquées)
expired = sum(1 for inv in invites if inv.expires_at and _make_aware(inv.expires_at) < now)
return {
'total': len(invites),
'total_uses': total_uses,
'top_inviters': top_inviters,
'permanent': permanent,
'temporary': temporary,
'expired': expired
}
def is_invite_expired(invite, now):
"""Vérifie si une invitation est expirée"""
if not invite.expires_at:
return False
expires_at = _make_aware(invite.expires_at)
return expires_at < now
@webapp.route("/moderation/invitations")
def moderation_invitations():
"""Affiche la liste des invitations Discord"""
show_revoked = request.args.get('show_revoked', 'false') == 'true'
query = DiscordInvite.query
if not show_revoked:
query = query.filter_by(revoked=False)
invites = query.order_by(DiscordInvite.created_at.desc()).all()
invite_stats = get_invite_stats()
guilds = discord_bridge.get_guilds()
now = datetime.now(timezone.utc)
# Pré-calculer le statut expiré pour chaque invitation
for inv in invites:
inv.is_expired = is_invite_expired(inv, now)
return render_template("moderation.html",
events=[],
event=None,
stats=get_moderation_stats(),
invites=invites,
invite_stats=invite_stats,
guilds=guilds,
show_invites=True,
show_revoked=show_revoked,
now=now
)
@webapp.route("/moderation/invitations/sync")
def sync_invitations():
"""Synchronise les invitations depuis Discord"""
guild_id = request.args.get('guild_id', type=int)
result = discord_bridge.sync_invites(guild_id)
if result.get('success'):
flash(f"✅ Synchronisation réussie : {result.get('synced', 0)} invitation(s) synchronisée(s)", 'success')
else:
flash(f"❌ Erreur : {result.get('message', 'Erreur inconnue')}", 'error')
return redirect(url_for('moderation_invitations'))
@webapp.route("/moderation/invitations/revoke/<invite_code>")
def revoke_invitation(invite_code):
"""Révoque une invitation Discord"""
result = discord_bridge.revoke_invite(invite_code)
if result.get('success'):
flash(f"✅ Invitation {invite_code} révoquée avec succès", 'success')
else:
flash(f"❌ Erreur : {result.get('message', 'Erreur inconnue')}", 'error')
return redirect(url_for('moderation_invitations'))
@webapp.route("/api/invitations/sync", methods=['POST'])
def api_sync_invitations():
"""API pour synchroniser les invitations"""
guild_id = request.json.get('guild_id') if request.is_json else None
result = discord_bridge.sync_invites(guild_id)
return jsonify(result)
@webapp.route("/api/invitations/revoke/<invite_code>", methods=['POST'])
def api_revoke_invitation(invite_code):
"""API pour révoquer une invitation"""
result = discord_bridge.revoke_invite(invite_code)
return jsonify(result)