[ 0.000 ] Initializing portfolio...
[ 0.212 ] Loading modules... OK
[ 0.445 ] Mounting filesystem... OK
[ 0.673 ] Starting services... OK
[ 0.891 ] Welcome, user.
Bot Discord FR tout-en-un. 8 mois de dev, 41 cogs, 150+ commandes slash, dashboard web pour configurer le bot sans toucher aux slash. L'idée de départ : remplacer MEE6, Dyno, Tickets Bot et Wick par un seul bot pensé en français, avec une vraie modération IA (pas juste des regex sur des mots interdits). Tourne en prod sur Render, monétisé via codes premium Stripe et PayPal.
Sur les serveurs Discord FR, le problème c'est que tu dois cumuler 3 ou 4 bots anglophones pour avoir le minimum, et leur modération basée sur des listes de mots interdits se fait contourner en cinq minutes. Le verlan, l'argot, les fautes volontaires, rien n'est pris en compte. Et la moitié des fonctions qui servent vraiment sont derrière un paywall.
yuAs69Bots, c'est ma réponse à ça. Un seul bot, en français, avec une modération IA qui comprend le sens et pas juste les mots. Huit mois de dev pour arriver à quelque chose de stable en prod : 41 cogs, 150+ commandes slash, un mute progressif de 5 minutes à une semaine selon les récidives, des codes premium via webhooks Stripe et PayPal, et depuis ce mois-ci un dashboard web pour configurer le tout sans toucher aux slash commands.
$ Fonctionnalités
$ Contexte technique
Update mai 2026. Grosse session : un dashboard web pour configurer le bot dans le navigateur, 4 nouvelles features, et tout ce qu'il faut côté sécu pour exposer un truc sur internet.
$ 1. Dashboard web
Le plus gros morceau. Une vraie interface web où les admins configurent le bot sans utiliser de slash commands. Front en HTML/CSS + Bootstrap, back en Python, connexion via OAuth Discord. Trois onglets par serveur : message d'accueil, modération IA, premium. Quand un admin change un réglage, le bot l'applique direct, sans redémarrer.
$ 2. /joinall et /quitall
Deux commandes que je suis le seul à pouvoir utiliser. Elles permettent d'ajouter ou de retirer en masse des utilisateurs sur un serveur. Pratique pour gérer mon réseau de bots et de serveurs.
$ 3. Compteurs en direct
Des salons vocaux verrouillés en haut du serveur qui affichent les stats (membres, bots, rôles, boosts, etc.) et se mettent à jour tout seuls.
$ 4. Salons vocaux temporaires
Tu rejoins un salon spécial, le bot t'en crée un perso où t'es admin, et ça se supprime tout seul quand il y a plus personne. Tu peux renommer, kick, verrouiller, transférer l'ownership.
$ 5. Sécurité du dashboard
Le dashboard est exposé sur internet, donc il fallait sérieusement le sécuriser. J'ai mis en place les bons headers HTTP, des cookies sécurisés contre les attaques classiques (clickjacking, vol de session), et une limite de 10 tentatives par heure sur l'activation des codes premium pour éviter le bruteforce.
$ 6. Faire découvrir le dashboard
Personne ira sur un dashboard si on le lui dit pas. J'ai ajouté plusieurs points d'entrée : une commande dédiée, un bouton sur la page status du bot, et un DM automatique au propriétaire d'un serveur quand le bot le rejoint.
$ 7. Doc et marketing
PDF de 20 pages mis à jour avec les nouvelles features, et 3 scripts vidéo prêts à filmer (TikTok, demo, YouTube).
# La galère du moment : un changement de version de Python sur Render (l'hébergeur) a cassé la lib qui chiffre les cookies du dashboard. J'ai dû figer la version de Python utilisée et changer de lib de chiffrement.
# Mute progressif selon le nombre de recidives detectees par l'IA.
# Index 0 = 1ere offense, index 8 = 9eme offense et plus.
OFFENSE_TIMEOUTS_SECONDS = [
5 * 60, # 1ere : 5 min
15 * 60, # 2eme : 15 min
30 * 60, # 3eme : 30 min
2 * 3600, # 4eme : 2 h
6 * 3600, # 5eme : 6 h
12 * 3600, # 6eme : 12 h
24 * 3600, # 7eme : 24 h
48 * 3600, # 8eme : 48 h
7 * 24 * 3600, # 9eme+ : 1 semaine
]
async def _analyse_with_mistral(content: str) -> dict:
"""Envoie le message a Mistral Moderation et renvoie les scores 0-1 par categorie."""
async with aiohttp.ClientSession() as session:
async with session.post(
MISTRAL_URL,
headers={"Authorization": f"Bearer {MISTRAL_API_KEY}"},
json={"model": MISTRAL_MODEL, "input": content},
timeout=5,
) as res:
data = await res.json()
return data["results"][0]["category_scores"]
# CSP strict : on autorise jsdelivr (Bootstrap) et cdn.discordapp.com
# (avatars + icones de serveur). Pas d'inline JS sauf 'unsafe-inline' style.
_CSP = (
"default-src 'self'; "
"script-src 'self' https://cdn.jsdelivr.net 'unsafe-inline'; "
"style-src 'self' https://cdn.jsdelivr.net 'unsafe-inline'; "
"img-src 'self' https://cdn.discordapp.com data:; "
"frame-ancestors 'none'; base-uri 'self'; form-action 'self'"
)
@web.middleware
async def _security_headers_mw(request, handler):
response = await handler(request)
response.headers.setdefault("X-Frame-Options", "DENY")
response.headers.setdefault("X-Content-Type-Options", "nosniff")
response.headers.setdefault("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
response.headers.setdefault("Content-Security-Policy", _CSP)
response.headers.setdefault("Referrer-Policy", "same-origin")
response.headers.setdefault("Permissions-Policy", "geolocation=(), microphone=(), camera=()")
return response
# Cookie de session : Secure (HTTPS only) + SameSite=Lax (mitigation CSRF
# basique : cookie pas envoye sur cross-site sauf navigation directe).
session_setup(app, NaClCookieStorage(
nacl_key, cookie_name="yuasbot_dashboard",
secure=True, samesite="Lax", max_age=7 * 24 * 3600,
))
# Quand un user rejoint le "creator channel", on lui cree un salon vocal
# perso avec lui en owner, puis on l'y deplace. Auto-cleanup si vide.
@commands.Cog.listener()
async def on_voice_state_update(self, member, before, after):
if member.bot:
return
cfg = await get_config(member.guild.id)
if cfg is None:
return
# 1) Creation : user vient de rejoindre le creator channel
if after.channel is not None and after.channel.id == cfg["creator_channel_id"]:
category = member.guild.get_channel(cfg["category_id"])
ch = await member.guild.create_voice_channel(
name=f"Salon de {member.display_name}"[:100],
category=category,
user_limit=cfg.get("default_user_limit", 0),
overwrites={
member: discord.PermissionOverwrite(
manage_channels=True,
move_members=True,
connect=True,
),
},
reason=f"tempvoice create for {member.id}",
)
await register_active(ch.id, member.guild.id, member.id)
await member.move_to(ch, reason="tempvoice move to own channel")
# 2) Cleanup : un user quitte un temp -> on supprime si vide
if before.channel is not None and before.channel.id != cfg["creator_channel_id"]:
active = await get_active(before.channel.id)
if active is not None and len(before.channel.members) == 0:
await before.channel.delete(reason="tempvoice empty cleanup")
await delete_active(before.channel.id)