206 lines
6.3 KiB
Python
206 lines
6.3 KiB
Python
from flask import render_template, request, redirect, url_for, flash, jsonify
|
|
from webapp import webapp
|
|
from database import db
|
|
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()
|
|
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()
|
|
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):
|
|
event = ModerationEvent.query.get_or_404(event_id)
|
|
event.reason = request.form.get('reason')
|
|
db.session.commit()
|
|
return redirect(url_for('moderation'))
|
|
|
|
@webapp.route("/moderation/delete/<int:event_id>")
|
|
def delete_moderation_event(event_id):
|
|
event = ModerationEvent.query.get_or_404(event_id)
|
|
db.session.delete(event)
|
|
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)
|
|
|