Plateforme active

Crée ton bot,
vis-le en temps réel

Programme un bot Python directement dans ton navigateur. Pas de serveur à gérer, juste du code et de la créativité.

20+
Composants UI
<1s
Hot-reload
100%
Python natif
0
Config serveur

📚 Explorer la documentation


🌟 Ce que tu peux créer

🎮
Bot RPG
Classes, combats en tour par tour, équipements, quêtes et progression d'XP.
📊
Dashboard serveur
Stats en temps réel : membres actifs, messages/heure, top contributeurs.
🛒
Boutique virtuelle
Économie interne, coins, achats, inventaires et récompenses par niveau.
🎵
Bot musique
File d'attente, favoris, radios et contrôles interactifs avec onglets.
🤖
Assistant IA
Connecte une API IA (GPT, Gemini…) et réponds intelligemment aux questions.
🏆
Classement & XP
Niveaux, palmarès, tableaux de scores avec barres de progression animées.
📅
Gestionnaire d'événements
Inscriptions, rappels, timers de compte à rebours et sondages automatiques.
🌐
Bot API externe
Météo, crypto, traduction, news — n'importe quelle API REST en Python.
🔧
Bot modération
Logs, commandes admin, filtres automatiques, rapports d'activité.

🗺️ Par où commencer ?

1
Aller dans l'éditeur de bots
Menu principal → 🤖 Bots IA dans la barre latérale.
2
Créer un nouveau bot
Nom, avatar, description — c'est prêt en 30 secondes.
3
Écrire ton code Python
Définis une classe avec on_message(self, message).
4
Ctrl+S et teste !
Va dans ton serveur et tape ta commande — le bot répond instantanément.

⚡ Code minimal pour démarrer

Python — copie-colle dans l'éditeur
class MonBot:
    def on_ready(self):
        print("🤖 Bot prêt !")

    def on_message(self, message):
        content = message.get('content', '').lower().strip()
        channel = message.get('channel_id')
        author  = message.get('author', {})
        if author.get('is_bot'): return  # anti-boucle obligatoire

        if content == '!ping':
            self.send_message(channel, "🏓 Pong !")
        elif content == '!aide':
            self.send_message(channel, embed={
                "title": "📚 Commandes",
                "description": "• `!ping` — test\n• `!aide` — ce menu",
                "color": "#7c6cf0"
            })
💡
Besoin d'inspiration ?
Consulte les exemples complets pour des bots entiers prêts à copier, ou parcours la doc des boutons, onglets et interactions pour aller plus loin.
🚀 Démarrage

Créer un bot

Configure ton premier bot depuis l'éditeur intégré en quelques étapes. De zéro à un bot actif en moins de 2 minutes.

🏷️
Nom & description
Choisis un nom unique et une description qui explique ce que fait ton bot.
🖼️
Avatar personnalisé
Charge une image ou utilise un avatar par défaut. Visible dans tous les salons.
🖥️
Éditeur Monaco
Basé sur VS Code. Coloration Python, autocomplétion, console et sauvegarde Ctrl+S.
Hot-reload
Sauvegarde et le bot tourne le nouveau code immédiatement. Pas de redémarrage.
🌐
Multi-serveurs
Ajoute le même bot à plusieurs serveurs. Il tourne partout simultanément.
🔒
Sandboxé
Ton code est isolé. Les erreurs n'affectent pas les autres bots ou utilisateurs.
🐍
Python complet
requests, random, datetime, json… tous les modules standards disponibles.
💾
Sauvegarde auto
Ton code est retenu dans l'éditeur même si tu fermes l'onglet accidentellement.

🗺️ Étapes de création

1
Accéder à l'éditeur
Menu principal → 🤖 Bots IA dans la barre latérale.
2
Cliquer sur "Nouveau bot"
Remplis le nom, la description, choisis un avatar. Tout est modifiable après création.
3
Ajouter à un serveur
Dans les paramètres du bot → + Ajouter à un serveur. Tu peux en ajouter plusieurs.
4
Démarrer le bot
Bouton ▶️ Démarrer → statut EN LIGNE. Le bot est actif dans tous ses serveurs.
5
Écrire et tester
Copie un template ci-dessous dans l'éditeur, Ctrl+S, puis tape la commande dans ton serveur !

📋 Templates prêts à l'emploi

Copie un de ces templates dans l'éditeur, sauvegarde avec Ctrl+S, et ton bot est opérationnel immédiatement.

Template 1 — Bot de base (commandes texte)
class MonBot:
    def on_ready(self):
        print("🤖 Bot prêt !")  # visible dans la console de l'éditeur

    def on_message(self, message):
        content = message.get('content', '').lower().strip()
        channel = message.get('channel_id')
        author  = message.get('author', {})
        if author.get('is_bot'): return  # Anti-boucle OBLIGATOIRE

        if content == '!ping':
            self.send_message(channel, "🏓 Pong !")

        elif content == '!bonjour':
            nom = author.get('username', 'toi')
            self.send_message(channel, f"👋 Bonjour {nom} !")

        elif content == '!aide':
            self.send_message(channel, embed={
                "title": "📚 Commandes disponibles",
                "description": "• `!ping` — test de latence\n• `!bonjour` — salutation\n• `!aide` — ce menu",
                "color": "#7c6cf0"
            })
Template 2 — Bot de score (mémoire d'état avec self.*)
class BotScore:
    def __init__(self):
        self.scores = {}  # {user_id: score} — persiste entre les messages

    def on_message(self, message):
        content = message.get('content', '').lower().strip()
        channel = message.get('channel_id')
        author  = message.get('author', {})
        if author.get('is_bot'): return
        uid  = author.get('id')
        name = author.get('username', '?')

        if content == '!+1':
            self.scores[uid] = self.scores.get(uid, 0) + 1
            self.send_message(channel, f"✅ Score de {name} : {self.scores[uid]} pt(s)")

        elif content == '!scores':
            if not self.scores:
                self.send_message(channel, "Aucun score enregistré.")
            else:
                lignes = "\n".join(f"ID {uid}: {s} pts" for uid, s in sorted(
                    self.scores.items(), key=lambda x: -x[1]))
                self.send_message(channel, embed={"title": "🏆 Classement", "description": lignes, "color": "#f59e0b"})
Template 3 — Bot interactif (boutons cliquables)
class BotInteractif:
    def on_message(self, message):
        content = message.get('content', '').lower().strip()
        channel = message.get('channel_id')
        author  = message.get('author', {})
        if author.get('is_bot'): return

        if content == '!menu':
            self.send_message(channel, "Que veux-tu faire ?", components=[
                {"type": "button", "label": "⚔️ Combattre",  "custom_id": "combat",  "style": "danger"},
                {"type": "button", "label": "🏪 Boutique",  "custom_id": "boutique", "style": "primary"},
                {"type": "button", "label": "📊 Mon profil", "custom_id": "profil",   "style": "secondary"},
            ])

    def on_button_click(self, interaction):
        ch  = interaction['channel_id']
        cid = interaction.get('custom_id')
        usr = interaction['user']['username']
        if   cid == 'combat':   self.send_message(ch, f"⚔️ {usr} entre dans l'arène !")
        elif cid == 'boutique': self.send_message(ch, f"🏪 {usr} ouvre la boutique…")
        elif cid == 'profil':   self.send_message(ch, f"📊 Profil de {usr} : niveau 1")
Template 4 — Bot API externe (météo avec requests)
import requests

class BotMeteo:
    API_KEY = "ta_cle_api"  # https://openweathermap.org/api

    def on_message(self, message):
        content = message.get('content', '').strip()
        channel = message.get('channel_id')
        author  = message.get('author', {})
        if author.get('is_bot'): return

        if content.lower().startswith('!meteo '):
            ville = content[7:].strip()
            try:
                r = requests.get(
                    f"https://api.openweathermap.org/data/2.5/weather"
                    f"?q={ville}&appid={self.API_KEY}&lang=fr&units=metric",
                    timeout=5)
                d    = r.json()
                temp = d['main']['temp']
                desc = d['weather'][0]['description']
                self.send_message(channel, f"🌤️ **{ville.title()}** : {temp:.0f}°C, {desc}")
            except:
                self.send_message(channel, "❌ Ville introuvable ou API indisponible.")

⚙️ Paramètres de configuration du bot

ParamètreDescriptionModifiable après ?
NomNom affiché dans les salons et les messages. Max 32 caractères.Oui
DescriptionCourte description du bot, visible dans la liste des bots du serveur.Oui
AvatarImage du bot. Formats acceptés : JPG, PNG, GIF animé. Max 2 Mo.Oui
Code PythonLe code du bot. Éditable à tout moment dans l'éditeur Monaco.Oui
Serveurs associésListe des serveurs où le bot est actif. Ajout/suppression libres.Oui
StatutEN LIGNE / HORS LIGNE. Contrôlé via les boutons ▶️ et ⏹️.Oui

💡 Bonnes pratiques

⚠️
Anti-boucle obligatoire
Sans if author.get('is_bot'): return, ton bot répondra à ses propres messages — boucle infinie assurée.
💡
Ctrl+S = déploiement instantané
Pas besoin de redémarrer. Sauvegarde et le prochain message utilise déjà ton nouveau code.
🧠
Utilise self.* pour mémoriser
Les attributs self.scores, self.etape, etc. persistent entre les messages tant que le bot tourne.
🐛
Débugger avec print()
print(message) affiche l'objet complet dans la console de l'éditeur. Indispensable pour inspecter les données.
🚀
Prêt à aller plus loin ?
Explore les boutons interactifs, les onglets, l'accordéon ou les exemples complets pour des bots entiers.
🚀 Démarrage

L'éditeur de code

Un éditeur Python complet dans le navigateur, basé sur Monaco (VS Code). Coloration syntaxique, autocomplétion, console intégrée et déploiement instantané avec Ctrl+S.

📝
Monaco Editor
Le même moteur que VS Code. Coloration Python, correspondance des parenthèses, mini-carte.
🖥️
Console intégrée
Affiche les sorties print(), erreurs de syntaxe et confirmations de sauvegarde en temps réel.
Déploiement instant
Ctrl+S déploie le code immédiatement. Ton bot est actif en moins d'une seconde.
🎨
Thème sombre
Interface optimisée pour de longues sessions. Confort visuel garanti toute la journée.
🧠
Autocomplétion
Suggestions de méthodes, variables et mots-clés Python pendant la frappe.
🔍
Recherche & remplacement
Ctrl+F pour chercher, Ctrl+H pour remplacer — avec regex.
💾
Sauvegarde auto
L'éditeur retient ton code même si tu fermes l'onglet. Rien n'est perdu.
📄
Multi-curseur
Alt+clic pour placer plusieurs curseurs. Édite plusieurs lignes à la fois.

🖼️ Interface de l'éditeur

⬅️ Zone de code
L'éditeur Monaco occupe la partie principale. Numéros de lignes, coloration syntaxique, erreurs soulignées en rouge.
⬇️ Console
Panel en bas. Affiche les print(), les erreurs Python et les logs du système. Effaçable en 1 clic.
➡️ Barre d'actions
Boutons Sauvegarder, Réinitialiser, et accès aux paramètres du bot dans la barre du haut.

⌨️ Raccourcis clavier

RaccourciActionContexte
💾 Sauvegarde & navigation
Ctrl+SSauvegarder & déployer le botPartout
Ctrl+ZAnnuler la dernière actionPartout
Ctrl+Shift+Z / Ctrl+YRétablirPartout
📝 Édition
Ctrl+/Commenter / décommenter la ligneCode
Ctrl+DSélectionner le mot suivant identiqueCode
Ctrl+Shift+KSupprimer la ligneCode
Alt+ / Déplacer la ligne vers le haut / basCode
Alt+Shift+ / Copier-coller la ligne vers le haut / basCode
TabIndenter (4 espaces)Code
Shift+TabDésindenterCode
🔍 Recherche
Ctrl+FRechercher dans le codeCode
Ctrl+HRechercher & remplacerCode
F3 / Shift+F3Résultat suivant / précédentRecherche
📋 Sélection
Ctrl+ATout sélectionnerCode
Ctrl+LSélectionner la ligne entièreCode
Alt+clicAjouter un curseur supplémentaireCode
Ctrl+Shift+LSélectionner toutes les occurrencesCode

🚀 Ton premier bot en 2 minutes

Copie ce code dans l'éditeur, appuie sur Ctrl+S, et ton bot est actif.

Python — Bot de démarrage
class MonBot:

    def on_ready(self):
        print("🤖 Bot prêt !")  # visible dans la console

    def on_message(self, message):
        content = message.get('content', '').lower().strip()
        channel = message.get('channel_id')
        author  = message.get('author', {})

        if author.get('is_bot'): return  # ignorer les bots

        if content == '!ping':
            self.send_message(channel, "🏓 Pong !")

        elif content == '!aide':
            self.send_message(channel, "Commandes : !ping · !aide · !bonjour")

        elif content.startswith('!bonjour'):
            nom = author.get('username', 'toi')
            self.send_message(channel, f"👋 Bonjour, {nom} !")

💡 Conseils pour bien coder

Toujours filtrer is_bot
Commence chaque on_message par if author.get('is_bot'): return pour éviter les boucles infinies.
📝
Utilise print() pour débugger
print(message) affiche l'objet complet dans la console. Indispensable pour voir la structure des données.
🚀
Ctrl+S = déploiement immédiat
Pas besoin de recharger. Le bot utilise ton nouveau code dès la prochaine interaction.
🧠
self.* pour mémoriser
Les attributs self.compteur, self.scores, etc. persistent entre les messages tant que le bot tourne.
⚠️
Erreur de syntaxe
Si ton code contient une erreur Python, la console affichera la ligne concernée en rouge et le bot gardera l'ancienne version jusqu'à la correction.
🚀 Démarrage

Comment ça marche

Comprends le cycle de vie complet — de l'écriture du code à la réponse du bot en temps réel dans le chat.

🔄 Cycle d'exécution — Message

💬
Utilisateur
écrit un message
🖥️
Serveur
Le Navire reçoit
🤖
on_message()
ton code s'exécute
📤
send_message()
appelé
Réponse
affichée

🔄 Cycle d'exécution — Interaction

🔘
Utilisateur
clique un bouton
🖥️
Serveur
détecte le clic
🤖
on_button_click()
ton code s'exécute
📤
Nouvelle réponse
ou mise à jour
Interface
mise à jour
Temps réel
Dès qu'un message arrive, on_message() est appelé avec toutes les données en quelques millisecondes.
🐍
Python pur
Ton code tourne côté serveur. Imports, variables, boucles, conditions — tout Python est disponible.
🔄
Hot reload
Ctrl+S → actif immédiatement. Pas de redémarrage, pas d'interruption.
🧠
État mémorisé
self.xxx persiste entre les appels. Compteurs, scores, données — tout reste en mémoire.
📱
Composants riches
Envoie des boutons, menus, graphiques, timers, onglets — pas juste du texte.
🔗
APIs externes
Connecte n'importe quelle API REST depuis ton bot avec requests.
🛡️
Sandboxed
Le code tourne dans un environnement sécurisé. Les erreurs n'affectent pas les autres bots.
📦
Multi-méthodes
on_ready, on_message, on_button_click — chaque événement a sa méthode.

📨 L'objet message

Tout ce que contient l'objet reçu dans on_message(self, message) :

CléTypeDescriptionExemple
contentstrTexte du message"!ping"
channel_idintID du salon1042
server_idintID du serveur42
message_idintID unique du message8801
author.idintID de l'auteur485921
author.usernamestrNom d'utilisateur"Léa"
author.is_botboolL'auteur est un bot ?False
attachmentslistFichiers joints au message[{"name":"…"}]

🧪 Exemple complet — de la réception à la réponse

Python — Flux complet
class MonBot:

    def on_ready(self):
        print("🤖 Démarré !")  # s'exécute au démarrage

    def on_message(self, message):
        # 1. Extraire les données utiles
        content = message.get('content', '').lower().strip()
        channel = message.get('channel_id')
        author  = message.get('author', {})

        # 2. Ignorer les messages de bots (anti-boucle)
        if author.get('is_bot'): return

        # 3. Répondre selon le contenu
        if content == '!ping':
            self.send_message(channel, "🏓 Pong !")

        elif content == '!vote':
            # Envoyer des boutons interactifs
            self.send_message(channel, "Vote ici :", components=[
                {"type":"button", "label":"👍 Oui", "custom_id":"vote_oui", "style":"success"},
                {"type":"button", "label":"👎 Non", "custom_id":"vote_non", "style":"danger"},
            ])

    def on_button_click(self, interaction):
        # 4. Répondre au clic sur les boutons
        ch  = interaction['channel_id']
        cid = interaction.get('custom_id')
        usr = interaction['user']['username']

        if cid == 'vote_oui':
            self.send_message(ch, f"👍 {usr} a voté OUI !")
        elif cid == 'vote_non':
            self.send_message(ch, f"👎 {usr} a voté NON.")

🌟 Ce que ton bot peut faire

💬
Commandes texte
!commande → réponse instantanée. Supporte les préfixes, sous-commandes et arguments.
🎲
Aléatoire
Dés, tirages au sort, citations, blagues — avec random.
📊
Visuels riches
Barres de progression, timers, cartes de stats, onglets, accordions, carousels.
🔘
Interactions
Boutons, menus déroulants, sondages, évaluations, glissières, listes de cases.
🧠
Mémoire de session
Scores, préférences, états de jeu dans self.*. Persistant jusqu'au redémarrage.
🔗
APIs externes
Météo, traduction, base de données, IA — tout via requests ou aiohttp.
🎮
Mini-jeux
Quiz, chasse au trésor, duels, RPG — grâce à la mémoire et aux interactions.
🗓️
Automatisations
Messages programmés, rappels, timers, nettoyage automatique des salons.
📈
Tableaux de bord
Statistiques de serveur, classements, suivi d'activité en temps réel.
💡
Pas besoin d'être expert
Le Navire s'occupe de tout ce qui est réseau, WebSocket et sécurité. Toi, tu écris juste ta logique en Python. Une classe, quelques méthodes — c'est tout.
💬 Messages

Structure du code

Tout bot est une classe Python avec des méthodes spéciales reconnues par Le Navire.

Python — Template complet
class MonBot:

    def on_ready(self):
        # Appelé une fois au démarrage
        print("Bot prêt !")

    def on_message(self, message):
        # Appelé à chaque message — méthode PRINCIPALE
        content = message.get('content', '').lower().strip()
        channel = message.get('channel_id')
        author  = message.get('author', {})
        if author.get('is_bot'): return

        if content == '!ping':
            self.send_message(channel, "🏓 Pong !")

    def on_button_click(self, interaction):
        # Appelé sur clic bouton / sélection menu / timer expiré
        cid = interaction.get('custom_id')
        ch  = interaction.get('channel_id')
        self.send_message(ch, f"Tu as cliqué sur `{cid}`")

Méthodes reconnues

MéthodeDéclencheurObligatoire
on_ready(self)Démarrage du botNon
on_message(self, message)Chaque message dans un salon du botOui
on_button_click(self, interaction)Clic bouton / menu / timer — handler universelNon
on_select_menu(self, interaction)Sélection menu déroulant (prioritaire)Non
on_timer_expired(self, interaction)Timer arrivé à zéro (prioritaire)Non
ℹ️
Une seule classe détectée
Le Navire cherche la première classe dans ton fichier et appelle ses méthodes. Donne-lui un nom parlant.
💬 Messages

Recevoir des messages

on_message(self, message) est appelée pour chaque message dans les salons où ton bot est présent.

L'objet message

CléTypeDescriptionExemple
contentstrTexte du message"!bonjour"
channel_idintID du salon42
server_idintID du serveur7
message_idintID unique du message1234
author.idintID de l'auteur3
author.usernamestrNom d'utilisateur"Alice"
author.is_botboolVrai si c'est un botFalse
Python — Patterns courants
def on_message(self, message):
    content  = message.get('content', '')
    clean    = content.lower().strip()        # normalisé
    channel  = message.get('channel_id')
    author   = message.get('author', {})
    username = author.get('username', 'Inconnu')
    if author.get('is_bot'): return

    # Commande exacte
    if clean == '!ping':
        self.send_message(channel, "🏓 Pong !")

    # Commande avec arguments : "!dis Bonjour le monde"
    elif clean.startswith('!dis '):
        texte = content[5:]
        self.send_message(channel, f"📢 {username} dit : {texte}")

    # Mot-clé dans le message
    elif 'bonjour' in clean:
        self.send_message(channel, f"👋 Salut {username} !")
💡
Toujours .lower().strip()
L'utilisateur peut écrire !Bonjour, !bonjour ou !BONJOUR. Normalise pour éviter les faux négatifs.
💬 Messages

Envoyer des messages

self.send_message() permet d'envoyer n'importe quel type de contenu depuis ton bot.

Signature complète

Python
self.send_message(
    channel_id,          # int  — message.get('channel_id')
    content   = None,   # str  — Texte (supporte Markdown)
    embed     = None,   # dict — Carte embed colorée
    image_url = None,   # str  — URL d'une image
    components= None,   # list — Boutons, menus, barres, timers
)
Python — Les 4 types de messages
# 1. Texte simple (Markdown supporté)
self.send_message(channel, "**Gras**, *italique*, `code`, ~~barré~~")

# 2. Embed (carte colorée)
self.send_message(channel, embed={
    "title": "🌟 Mon embed",
    "description": "Un texte formaté",
    "color": "#7c6cf0"
})

# 3. Image
self.send_message(channel, image_url="https://example.com/image.png")

# 4. Tout combiné
self.send_message(
    channel,
    content="Voici les résultats :",
    embed={"title": "📊 Stats", "color": "#00d9a3"},
    components=[{
        "type": "action_row", "components": [
            {"type": "button", "label": "Voir détails", "style": "primary", "custom_id": "details"}
        ]
    }]
)
💡
Multi-envoi possible
Tu peux appeler self.send_message() plusieurs fois dans le même handler pour envoyer plusieurs messages à la suite.
🎨 Messages riches

Embeds

Des cartes visuelles colorées pour afficher des informations structurées et attrayantes.

🧪 Démo interactive

Aperçu en temps réel — le Markdown est rendu dans la prévisualisation
Titre : Couleur : Footer :
Description :
Presets :
💡
Markdown supporté dans les embeds
**gras***italique****gras+ital***__souligné__~~barré~~||spoiler||`code````bloc```> citation# Titre- liste

Tous les champs

Apparence

ChampTypeDescription
colorstrCouleur accent ("#hex") — barre, titres, glow
themestr"default" · "glass" · "solid" · "dark" · "minimal"
backgroundstrFond CSS custom (hex, rgba, gradient). Écrase theme.
border_positionstr"left" (défaut) · "top" · "bottom" · "all" · "none"
border_widthintÉpaisseur de la bordure en px (défaut : 4)
glowboolAjoute un halo lumineux autour de la carte
compactboolPadding réduit, police plus petite
max_widthintLargeur max en px (défaut : 520)

Contenu

ChampTypeDescription
titlestrTitre principal (Markdown supporté)
descriptionstrCorps du texte (Markdown complet)
urlstrLien sur le titre
authordict{ name, icon_url, url, badge } — ligne d'auteur avec badge optionnel
statusdict{ text, color, dot } — pastille de statut colorée
fieldslistListe de champs (voir ci-dessous)
badgeslist[{ label, color, icon, filled }] — rangée de badges
footerstr / dictTexte ou { text, icon_url }
timestampstrDate ISO 8601 affichée à côté du footer

Images

ChampTypeDescription
bannerstr / dictImage pleine-largeur en haut de la carte. Str = URL, dict = { url, height }
thumbnailstr / dictPetite image à droite. Dict : { url, round, radius }
imagestr / dictImage principale. Dict : { url, radius }
imageslistGrille de 2–4 images : ["url1","url2"] ou [{ url }]

Champs spéciaux (fields)

PropriétéDescription
type: "progress"Affiche une barre de progression. Utilise value(0-100), bar_color, height, label
type: "divider"Ligne de séparation fine entre deux champs
inline: TrueDeux champs côte à côte (50% chacun)
Python — Embed avec fields
self.send_message(channel, embed={
    "title":  "📊 Stats du serveur",
    "color":  "#00d9a3",
    "fields": [
        {"name": "Membres",    "value": "42",   "inline": True},
        {"name": "Messages",   "value": "1337", "inline": True},
        {"name": "Bots actifs", "value": "3",    "inline": True},
    ],
    "footer": "Mis à jour à l'instant"
})

Exemples avancés

Python — Thème glass + glow + status
self.send_message(channel, embed={
    "title"  : "🛡️ Profil Joueur",
    "color"  : "#a855f7",
    "theme"  : "glass",
    "glow"   : True,
    "status" : {"text": "En ligne", "color": "#00d9a3"},
    "author" : {"name": "Sylvain42", "badge": "VIP"},
    "description": "Un grand guerrier du désert numérique.",
    "badges": [
        {"label": "Niveau 42", "color": "#a855f7", "icon": "⭐"},
        {"label": "Top Joueur", "color": "#f59e0b", "icon": "🏆"},
    ],
    "fields": [
        {"name": "XP",     "type": "progress", "value": 73, "bar_color": "#a855f7"},
        {"name": "Santé",  "type": "progress", "value": 55, "bar_color": "#ef4444"},
        {"type": "divider"},
        {"name": "Kills",   "value": "284", "inline": True},
        {"name": "Morts",   "value": "91",  "inline": True},
    ],
    "footer": {"text": "Dernière connexion : il y a 5 min"}
})
Python — Thème solid + banner + images grid
self.send_message(channel, embed={
    "title"  : "🏰 Annonce du serveur",
    "color"  : "#f59e0b",
    "theme"  : "dark",
    "banner" : {"url": "https://example.com/banner.jpg", "height": 100},
    "border_position": "top",
    "border_width"   : 3,
    "description": "Découvrez nos nouvelles zones !",
    "images": [
        "https://example.com/zone1.jpg",
        "https://example.com/zone2.jpg",
        "https://example.com/zone3.jpg",
    ],
    "footer": "Patch 2.4 — Aujourd'hui"
})
🎨 Messages riches

Images

Affiche des images directement dans les messages avec image_url ou dans un embed avec le champ image.

Python — 3 façons d'afficher une image
# 1. Image seule (pleine largeur)
self.send_message(channel, image_url="https://example.com/image.png")

# 2. Image dans un embed (avec titre et couleur)
self.send_message(channel, embed={
    "title": "📸 Photo du jour",
    "image": "https://example.com/photo.jpg",
    "color": "#f97316"
})

# 3. Image + bouton
self.send_message(
    channel,
    content="Regarde cette image 👇",
    image_url="https://example.com/img.png",
    components=[{
        "type": "action_row", "components": [
            {"type": "button", "label": "❤️ J'aime", "style": "danger", "custom_id": "like"}
        ]
    }]
)
💡
Formats supportés
PNG, JPG, GIF, WebP. L'URL doit être publiquement accessible. Pour les GIFs animés, image_url donne un meilleur rendu.
🔘 Composants interactifs

Boutons

Les boutons se placent dans des ActionRow (max 5 par ligne). Styles prédéfinis ou couleurs hex totalement custom.

🧪 Démo interactive — clique sur les boutons !

Simulation chat — clique pour voir l'interaction déclenchée
DemoBot Aujourd'huiBOT
Clique sur un bouton pour déclencher on_button_click !
← En attente d'un clic...

Champs d'un bouton

ChampTypeDescription
typestrToujours "button"
labelstrTexte affiché sur le bouton
stylestrprimary / secondary / success / danger / link
colorstrCouleur hex custom "#ff6b9d" — remplace style
text_colorstrCouleur du texte (auto-détecté si absent)
custom_idstrIdentifiant reçu dans on_button_click
emojistrEmoji affiché avant le label
disabledboolBouton grisé dès l'envoi (statique)
— Comportement interactif —
expires_inintGrisé après N secondes (30 = 30s après l'envoi)
lock_on_clickboolGrisé définitivement après le 1er clic (usage unique)
max_usesintGrisé après N clics au total
exclusiveboolCliquer grise tous les autres boutons de la ligne (choix unique)

Couleur hex custom — testeur

Crée un bouton avec ta couleur
Label : Couleur : Texte :
Python — Styles prédéfinis & couleurs custom
if content == '!vote':
    self.send_message(channel,
        content="🗳️ Quel est ton OS préféré ?",
        components=[{
            "type": "action_row", "components": [
                {"type": "button", "label": "🐧 Linux",   "style": "primary",   "custom_id": "vote_linux"},
                {"type": "button", "label": "🪟 Windows", "style": "secondary", "custom_id": "vote_windows"},
                {"type": "button", "label": "🍎 macOS",  "color": "#c0c0c0",   "custom_id": "vote_mac"},
            ]
        }]
    )

# exclusive — cliquer un bouton grise immédiatement les autres
elif content == '!sondage':
    self.send_message(channel,
        content="🗳️ Pour ou contre ?",
        components=[{"type": "action_row", "components": [
            {"type": "button", "label": "✅ Pour",  "style": "success", "custom_id": "vote_pour",   "exclusive": True},
            {"type": "button", "label": "❌ Contre", "style": "danger",  "custom_id": "vote_contre", "exclusive": True},
        ]}]
    )

# expires_in + lock_on_click — disponible 60s, 1 seul clic
elif content == '!offre':
    self.send_message(channel,
        embed={"title": "⚡ Offre flash — 60s !", "color": "#f1c40f"},
        components=[{"type": "action_row", "components": [
            {"type": "button", "label": "🎁 Réclamer", "style": "primary",
             "custom_id": "claim", "expires_in": 60, "lock_on_click": True},
        ]}]
    )
🔘 Composants interactifs

Menu déroulant

Permet à l'utilisateur de choisir une ou plusieurs options dans une liste. Se place dans une ActionRow.

1️⃣
Mono-sélection
Une option à la fois. Usage unique avec lock_on_select.
🔢
Multi-sélection
min_values / max_values pour forcer N choix simultanés.
🏷️
Emoji + description
Chaque option peut avoir une icône et un sous-texte descriptif.
⏱️
Expiration / verrou
Grisé après N secondes, N clics ou à la 1ère sélection.

🧪 Démo interactive — sélectionne une option !

Simulation chat — sélectionne pour déclencher on_button_click
DemoBot Aujourd'huiBOT
🌍 Quelle langue parles-tu ?
DemoBot Aujourd'huiBOT
🎯 Quels topics t'intéressent ? (max 3) — Ctrl+clic pour multi-sélection
← En attente d'une sélection...

Champs d'un select menu

ChampTypeDescription
typestrToujours "select_menu"
custom_idstrIdentifiant reçu dans on_button_click
placeholderstrTexte affiché avant sélection
optionslistListe d'objets {"label", "value", "emoji", "description", "default"}
min_valuesintMin. d'options à sélectionner (défaut : 1)
max_valuesintMax. d'options à sélectionner (défaut : 1)
— Comportement interactif —
expires_inintGrisé après N secondes
lock_on_selectboolGrisé après la première sélection (usage unique)
max_usesintGrisé après N sélections
Python — Select simple & multi-sélection
if content == '!langue':
    self.send_message(channel,
        content="🌍 Quelle langue parles-tu ?",
        components=[{"type": "action_row", "components": [{
            "type":        "select_menu",
            "custom_id":   "lang_select",
            "placeholder": "Choisir une langue...",
            "lock_on_select": True,  # se grise après 1 sélection
            "options": [
                {"label": "Français", "value": "fr", "emoji": "🇫🇷", "default": True},
                {"label": "English",  "value": "en", "emoji": "🇬🇧"},
                {"label": "Español",  "value": "es", "emoji": "🇪🇸"},
            ]
        }]}]
    )

elif content == '!topics':
    # Multi-sélection (max 3 choix)
    self.send_message(channel,
        content="🎯 Quels topics t'intéressent ? (max 3)",
        components=[{"type": "action_row", "components": [{
            "type":        "select_menu",
            "custom_id":   "topics_select",
            "placeholder": "Sélectionne jusqu'à 3 topics",
            "min_values":  1, "max_values": 3,
            "options": [
                {"label": "Gaming", "value": "gaming", "emoji": "🎮"},
                {"label": "Musique", "value": "music", "emoji": "🎵"},
                {"label": "Tech",    "value": "tech",   "emoji": "💻"},
                {"label": "Art",     "value": "art",    "emoji": "🎨"},
                {"label": "Sport",   "value": "sport",  "emoji": "⚽"},
            ]
        }]}]
    )

def on_button_click(self, interaction):
    itype = interaction.get('type')
    cid   = interaction.get('custom_id')
    ch    = interaction.get('channel_id')
    user  = interaction.get('user', {}).get('username', '?')

    if itype == 'select_menu' and cid == 'lang_select':
        lang = interaction.get('values', ['?'])[0]
        self.send_message(ch, f"✅ {user} a choisi : **{lang}**")

    elif itype == 'select_menu' and cid == 'topics_select':
        topics = interaction.get('values', [])
        self.send_message(ch, embed={
            "title": f"🎯 Topics de {user}",
            "description": "• " + "\n• ".join(topics),
            "color": "#5865f2"
        })
💡
values = toujours une liste
interaction.get('values') est une liste Python même pour un select mono-choix. Utilise values[0] pour récupérer la valeur unique.

🏷️ Démo — Options avec description & option désactivée

select avec sous-texte et option grisée (disabled)
DemoBot Aujourd'huiBOT
🏆 Choisir un abonnement
← Sélectionne un plan

Champs d'une option

ChampTypeReq.Description
labelstrTexte affiché dans la liste
valuestrValeur reçue dans interaction['values']
emojistrIcône Unicode devant le label
descriptionstrSous-texte grisé sous le label (≤ 100 car.)
defaultboolTrue pour pré-sélectionner cette option
disabledboolTrue pour griser l'option (non-sélectionnable)
Python — Options avec description & disabled
self.send_message(channel,
    content="🏆 Choisir un abonnement",
    components=[{"type": "action_row", "components": [{
        "type":        "select_menu",
        "custom_id":   "plan_select",
        "placeholder": "— Choisir un plan —",
        "options": [
            {"label": "Free",       "value": "free",       "emoji": "🆓", "description": "Gratuit, jusqu'à 3 bots"},
            {"label": "Pro",        "value": "pro",        "emoji": "⭐", "description": "9,99 €/mois — bots illimités"},
            {"label": "Business",   "value": "business",   "emoji": "🚀", "description": "29,99 €/mois — priorité"},
            {"label": "Enterprise", "value": "enterprise", "emoji": "🏢",
             "description": "Sur devis", "disabled": True},
        ]
    }]}])
Python — Expiration automatique & lock_on_select
# Se grise automatiquement après 30 s ET après 1 clic
self.send_message(channel,
    content="⚡ Vote rapide — ferme dans 30 s",
    components=[{"type": "action_row", "components": [{
        "type":           "select_menu",
        "custom_id":      "vote_select",
        "placeholder":   "Ton vote...",
        "expires_in":     30,     # grisé après 30 s
        "lock_on_select": True,  # grisé après la 1ère sélection
        "options": [
            {"label": "👍 Pour",     "value": "yes"},
            {"label": "👎 Contre",  "value": "no"},
            {"label": "🤷 Abstain", "value": "abs"},
        ]
    }]}]
)

def on_button_click(self, interaction):
    if interaction.get('custom_id') == 'vote_select':
        vote = interaction['values'][0]
        user = interaction['user']['username']
        self.send_message(interaction['channel_id'],
            f"🗳️ {user} a voté : **{vote}**")
Python — Pattern cascade (2 selects liés)
# Étape 1 — sélection de catégorie
if content == '!shop':
    self.send_message(channel,
        content="🛍️ Étape 1 — Choisir une catégorie",
        components=[{"type": "action_row", "components": [{
            "type": "select_menu", "custom_id": "shop_cat",
            "placeholder": "Catégorie...",
            "options": [
                {"label": "Armes",   "value": "weapons", "emoji": "⚔️"},
                {"label": "Armures", "value": "armor",   "emoji": "🛡️"},
                {"label": "Potions", "value": "potions", "emoji": "🧪"},
            ]
        }]}]
    )

# Étape 2 — on_button_click envoie le sous-menu
def on_button_click(self, interaction):
    if interaction.get('custom_id') == 'shop_cat':
        cat   = interaction['values'][0]
        ch    = interaction['channel_id']
        items = {
            "weapons": [{"label": "Épée",    "value": "sword",  "emoji": "⚔️"},
                        {"label": "Arc",     "value": "bow",    "emoji": "🏹"}],
            "armor":   [{"label": "Casque",  "value": "helm",   "emoji": "⛑️"},
                        {"label": "Bouclier","value": "shield", "emoji": "🛡️"}],
            "potions": [{"label": "Santé",   "value": "hp",     "emoji": "❤️"},
                        {"label": "Mana",    "value": "mp",     "emoji": "💙"}],
        }.get(cat, [])
        self.send_message(ch,
            content=f"🛍️ Étape 2 — Articles dans {cat}",
            components=[{"type": "action_row", "components": [{
                "type": "select_menu", "custom_id": "shop_item",
                "placeholder": "Choisir un article...",
                "options": items
            }]}]
        )
⚠️
Maximum 25 options
Un select_menu accepte au maximum 25 options. Au-delà, utilise une pagination ou un modal avec champ texte libre.
💡
Cascade en 2 niveaux
Pattern le plus courant : le 1er select déclenche on_button_click → ton bot envoie un 2ème message avec un nouveau select adapté. Combinable avec edit_message pour modifier le message original.
🔘 Composants interactifs

Barre de progression

Composant visuel — affiche des barres colorées, inversées, verticales, segmentées, avec timer ou lecteur multimédia intégré. Combinable avec edit_message pour animer en temps réel.

🎨
Couleur / gradient
Hex, rgba, ou dégradé CSS sur le fill.
↔️
Normal / Inverse
Remplissage de gauche ou de droite.
↕️
Verticale
Orientation colonne, hauteur libre.
🎵
Player / Timer
Lecteur musique/vidéo/son + compte à rebours.

🧪 Démo — Barres classiques (RPG)

Glisse les curseurs
⭐ XP 73% ❤️ HP 45% 💙 Mana 88%

🎵 Démo — Lecteur musique / vidéo / son

Player interactif
Titre : Artiste : Couleur : Durée (s) : ▶ icône : ⏸ icône : 🔊 vol :
← Clique ▶ pour simuler la lecture

🎨 Démo — Variantes visuelles

Styles, formes, animations

↔️↕️ Démo — Inverse & Vertical

Barres inversées et colonnes verticales

🟦 Démo — Barres segmentées

Segments / vies / niveaux discrets

Champs — progress_bar

ChampTypeRequisDéfautDescription
typestrToujours "progress_bar"
valueintPourcentage rempli — 0 à 100 (clampé auto)
labelstrNoneTexte au-dessus — supporte les emojis
colorstr#00d9a3Couleur hex ou rgba du remplissage
heightint8Hauteur en pixels (2–20 recommandé)
shapestr"round""round" / "square" / "flat" — style des bords
directionstr"ltr""ltr" normal · "rtl" inverse · "vertical" colonne
animatedboolFalseActive les rayures animées en mouvement
pulseboolFalseFait pulser (opacité) la barre — utile pour "en cours"
glowboolFalseAjoute un halo lumineux autour du remplissage
segmentsintNoneDivise la barre en N segments discrets (vies, niveaux…)
track_colorstrrgba(255,255,255,.07)Couleur du fond de piste

Exemple — Stats RPG multi-barres

Python
if content == '!stats':
    xp, hp, mana = 73, 45, 88
    self.send_message(channel,
        embed={"title": "🧙 Tes stats", "color": "#5865f2"},
        components=[
            {"type": "progress_bar", "label": "⭐ XP — Niveau 7",
             "value": xp,   "color": "#10d9a3", "height": 10, "glow": True},
            {"type": "progress_bar", "label": f"❤️ HP  {hp}/100",
             "value": hp,   "color": "#ef4444", "height": 6},
            {"type": "progress_bar", "label": f"💙 Mana  {mana}/100",
             "value": mana, "color": "#5865f2", "height": 6},
            {"type": "action_row", "components": [
                {"type": "button", "label": "⚔️ Attaquer",    "style": "primary",   "custom_id": "attack"},
                {"type": "button", "label": "🏥 Se soigner",   "style": "success",   "custom_id": "heal"},
                {"type": "button", "label": "✨ Lancer un sort", "style": "secondary", "custom_id": "cast"},
            ]},
        ]
    )

Exemple — Lecteur musique / vidéo / son

Python — Player avec progression
import threading, time

if content == '!play':
    title    = "Midnight Drive"
    artist   = "Synthwave King"
    duration = 210  # secondes

    # Message initial — barre à 0
    msg = self.send_message(channel,
        embed={"title": f"🎵 {title}", "description": f"👤 {artist}", "color": "#8875f5"},
        components=[
            {"type": "progress_bar", "label": "0:00 ◄━━━━━━━━━━━━━━━━━━ 3:30",
             "value": 0, "color": "#8875f5", "height": 4},
            {"type": "action_row", "components": [
                {"type": "button", "label": "⏮", "style": "secondary", "custom_id": "prev"},
                {"type": "button", "label": "⏸ Pause", "style": "primary",   "custom_id": "pause"},
                {"type": "button", "label": "⏭", "style": "secondary", "custom_id": "next"},
                {"type": "button", "label": "🔁", "style": "secondary", "custom_id": "loop"},
            ]},
        ]
    )
    mid = msg.get('id')

    def _fmt(s): return f"{s//60}:{s%60:02d}"

    def run_player():
        for elapsed in range(1, duration + 1):
            time.sleep(1)
            pct = int(elapsed / duration * 100)
            lbl = f"{_fmt(elapsed)} ◄{'━'*int(pct/5)}{'─'*(20-int(pct/5))} {_fmt(duration)}"
            self.edit_message(channel, mid,
                embed={"title": f"🎵 {title}", "description": f"👤 {artist}", "color": "#8875f5"},
                components=[
                    {"type": "progress_bar", "label": lbl,
                     "value": pct, "color": "#8875f5", "height": 4},
                    {"type": "action_row", "components": [
                        {"type": "button", "label": "⏮", "style": "secondary", "custom_id": "prev"},
                        {"type": "button", "label": "⏸ Pause", "style": "primary",   "custom_id": "pause"},
                        {"type": "button", "label": "⏭", "style": "secondary", "custom_id": "next"},
                        {"type": "button", "label": "🔁", "style": "secondary", "custom_id": "loop"},
                    ]},
                ]
            )

    threading.Thread(target=run_player, daemon=True).start()

Exemple — Timer avec barre inverse (compte à rebours)

Python — Barre qui se vide (inverse)
import threading, time

if content == '!vote':
    duration = 60  # secondes
    msg = self.send_message(channel,
        embed={"title": "🗳️ Vote en cours !", "color": "#f1c40f"},
        components=[
            {"type": "progress_bar", "label": "⏳ 60s restantes",
             "value": 100, "color": "#f1c40f", "height": 8,
             "direction": "rtl", "animated": True},
            {"type": "action_row", "components": [
                {"type": "button", "label": "✅ Pour",    "style": "success",   "custom_id": "vote_yes"},
                {"type": "button", "label": "❌ Contre",  "style": "danger",    "custom_id": "vote_no"},
            ]},
        ]
    )
    mid = msg.get('id')

    def run_timer():
        for remaining in range(duration - 1, -1, -1):
            time.sleep(1)
            pct = int(remaining / duration * 100)
            color = "#ef4444" if remaining <= 10 else ("#f59e0b" if remaining <= 20 else "#f1c40f")
            self.edit_message(channel, mid,
                embed={"title": "🗳️ Vote en cours !", "color": color},
                components=[
                    {"type": "progress_bar",
                     "label": f"⏳ {remaining}s restantes",
                     "value": pct, "color": color, "height": 8,
                     "direction": "rtl", "pulse": remaining <= 10},
                    {"type": "action_row", "components": [
                        {"type": "button", "label": "✅ Pour",   "style": "success", "custom_id": "vote_yes"},
                        {"type": "button", "label": "❌ Contre", "style": "danger",  "custom_id": "vote_no"},
                    ]},
                ]
            )
        self.send_message(channel, "⏰ Temps écoulé ! Vote terminé.")

    threading.Thread(target=run_timer, daemon=True).start()

Exemple — Barres verticales (comparaison / stats)

Python — Barres verticales
if content == '!classement':
    scores = {"Alice": 85, "Bob": 62, "Carla": 94, "Dan": 41}
    colors = ["#10d9a3", "#5865f2", "#f1c40f", "#ef4444"]
    comps  = []
    for i, (name, score) in enumerate(scores.items()):
        comps.append({
            "type": "progress_bar",
            "label": f"{name} — {score}pts",
            "value": score,
            "color": colors[i],
            "height": 6,
            "direction": "vertical",
        })
    self.send_message(channel,
        embed={"title": "📊 Classement de la semaine", "color": "#10d9a3"},
        components=comps
    )

Exemple — Barre segmentée (vies / niveaux)

Python — Segments discrets
# 3 vies sur 5 restantes
{"type": "progress_bar", "label": "❤️ Vies",
 "value": 60, "color": "#ef4444", "height": 14,
 "segments": 5, "shape": "square"}

# Niveau 4 sur 10 débloqués
{"type": "progress_bar", "label": "🏅 Niveaux débloqués",
 "value": 40, "color": "#f1c40f", "height": 10,
 "segments": 10, "shape": "round"}

# Chargement par étapes (5 blocs)
{"type": "progress_bar", "label": "⚙️ Chargement",
 "value": 80, "color": "#8875f5", "height": 12,
 "segments": 5, "animated": True}

Exemple — Palette de couleurs & styles

Python — Référence couleurs
# Vert    XP / succès
{"type": "progress_bar", "label": "⭐ XP",       "value": 73, "color": "#10d9a3"}
# Rouge   HP / danger
{"type": "progress_bar", "label": "❤️ HP",       "value": 45, "color": "#ef4444"}
# Bleu    Mana / énergie
{"type": "progress_bar", "label": "💙 Mana",     "value": 88, "color": "#5865f2"}
# Violet  Rang / niveau
{"type": "progress_bar", "label": "🔮 Rang",     "value": 60, "color": "#8875f5"}
# Or      Score / coins
{"type": "progress_bar", "label": "🪙 Score",    "value": 55, "color": "#f1c40f"}
# Orange  Chaleur / temps
{"type": "progress_bar", "label": "🔥 Stamina",  "value": 30, "color": "#f59e0b"}
# Rayures animées  — téléchargement, traitement
{"type": "progress_bar", "label": "⬇️ Download", "value": 65, "color": "#10d9a3", "animated": True}
# Pulsation  — en attente
{"type": "progress_bar", "label": "⏳ En attente","value": 50, "color": "#f59e0b", "pulse": True}
# Halo glow  — barre principale
{"type": "progress_bar", "label": "✨ Pouvoir",   "value": 95, "color": "#a78bfa", "glow": True}
💡
Valeur hors limites
Si tu passes value > 100 ou value < 0, le SDK la clamp automatiquement — pas d'erreur.
🚀
Animer une barre avec edit_message
Lance un threading.Thread qui appelle edit_message à chaque étape. Fonctionne pour lecteurs audio/vidéo, timers, téléchargements — à environ 1 update/sec pour rester fluide sans rate-limit.
⚠️
Composant visuel uniquement
La progress_bar n'émet jamais d'interaction. Pour réagir à 100 %, combine avec un Timer ou un bouton.
🔘 Composants interactifs

Timer

Compte à rebours animé entièrement personnalisable — format, style, barre liée, alerte couleur, fond, emoji. Déclenche "timer_expired" dans on_button_click quand il atteint zéro.

🕐
Formats flexibles
mm:ss, h:mm:ss, secondes seules.
🎨
Style visuel
Large, normal, compact, minimal, avec emoji, fond et glow.
📊
Barre liée
Barre de progression qui se vide avec le timer, inversable.
Alerte + callback
Couleur d'alerte à X% restant + timer_expired à zéro.

🧪 Démo — Timer interactif complet

Personnalise chaque paramètre en temps réel
Durée (s) : Label : Couleur : Emoji : Format : Style :
Barre : Fond : Alerte à : %
← Configure et clique Démarrer

📊 Démo — Barre de progression liée au timer

Barre inverse qui se vide + chiffre dégressif
Durée (s) : Label : Couleur :
← Clique Démarrer pour lancer

Champs

ChampTypeDéfautDescription
typestrToujours "timer"
durationintDurée totale en secondes
labelstrTexte au-dessus du compte à rebours
colorhex#f1c40fCouleur du chiffre et de la barre
emojistrIcône affichée devant le chiffre
formatstr"mm:ss"Format : "mm:ss", "h:mm:ss", "seconds"
stylestr"large"Taille : "large", "normal", "compact", "minimal"
progress_barstr/boolfalsetrue / "above" / "inverse" pour afficher une barre liée
backgroundhextransparentCouleur de fond du bloc timer
alert_atint% restant déclenchant la couleur d'alerte (ex: 25)
alert_colorhex#ef4444Couleur quand alert_at est atteint
pulsebooltruePulsation de la barre/chiffre en mode alerte
custom_idstrSi fourni, déclenche timer_expired dans on_button_click
Python — Timer minimal
self.send_message(channel,
    content="⏳ Le bot sera prêt dans :",
    components=[{
        "type":     "timer",
        "duration": 30,
        "color":    "#10d9a3",
        "emoji":    "🟢",
    }])
Python — Timer stylisé avec barre + alerte
self.send_message(channel,
    content="⚡ Offre flash ! Plus que :",
    components=[{
        "type":         "timer",
        "duration":     120,
        "label":        "Offre expire dans",
        "color":        "#f59e0b",
        "emoji":        "🔥",
        "format":       "mm:ss",
        "style":        "large",
        "progress_bar": True,
        "background":   "#1a1208",
        "alert_at":     20,
        "alert_color":  "#ef4444",
        "pulse":        True,
        "custom_id":    "flash_sale",
    }])

def on_button_click(self, interaction):
    if interaction.get('type') == 'timer_expired':
        ch = interaction['channel_id']
        self.send_message(ch, embed={"title": "❌ Offre expirée !", "color": "#ef4444"})
Python — Quiz chrono (timer + boutons)
if content == '!quiz':
    self.send_message(channel,
        embed={"title": "❓ Capitale de la France ?", "color": "#9b59b6"},
        components=[
            {"type": "timer", "label": "Temps restant", "duration": 20,
             "color": "#e74c3c", "progress_bar": True, "alert_at": 30,
             "custom_id": "quiz_timeout"},
            {"type": "action_row", "components": [
                {"type": "button", "label": "Lyon",  "style": "secondary", "custom_id": "quiz_lyon"},
                {"type": "button", "label": "Paris", "style": "primary",   "custom_id": "quiz_paris"},
                {"type": "button", "label": "Nice",  "style": "secondary", "custom_id": "quiz_nice"},
            ]},
        ]
    )

def on_button_click(self, interaction):
    itype = interaction.get('type'); cid = interaction.get('custom_id')
    ch = interaction.get('channel_id')
    user = interaction.get('user', {}).get('username', '?')
    if itype == 'timer_expired' and cid == 'quiz_timeout':
        self.send_message(ch, embed={"title": "⏰ Temps écoulé !", "description": "C'était **Paris** 🗼", "color": "#ef4444"})
    elif cid == 'quiz_paris':
        self.send_message(ch, f"🎉 {user} a trouvé ! C'est Paris !")
    elif cid in ('quiz_lyon', 'quiz_nice'):
        self.send_message(ch, f"❌ {user}, mauvaise réponse ! C'était Paris 🗼")
Python — Compte à rebours h:mm:ss (live event)
import datetime

if content == '!event':
    now    = datetime.datetime.now()
    target = now.replace(hour=20, minute=0, second=0) + datetime.timedelta(days=1)
    secs   = int((target - now).total_seconds())
    self.send_message(channel,
        content="🎤 Prochain live dans :",
        components=[{
            "type":         "timer",
            "duration":     secs,
            "label":        "📡 Live dans",
            "format":       "h:mm:ss",
            "style":        "large",
            "progress_bar": "inverse",
            "color":        "#8875f5",
            "background":   "#0f0820",
            "alert_at":     5,
            "alert_color":  "#10d9a3",
            "custom_id":    "event_start",
        }]
    )

def on_button_click(self, interaction):
    if interaction.get('type') == 'timer_expired' and interaction.get('custom_id') == 'event_start':
        self.send_message(interaction['channel_id'], "🟢 @everyone Le live commence MAINTENANT !")
⚠️
Timer côté client
Le décompte s'anime dans chaque navigateur. L'interaction timer_expired est envoyée par chaque client qui voit le message. Gère les doublons côté bot (ex: dict Python pour ignorer les appels dupliqués).
💡
Combo Timer + progress_bar native
Le champ progress_bar: true génère automatiquement une barre synchronisée. Pas besoin de 2 composants séparés — la barre et le timer partagent le même objet JSON.
🔤 Composant

Affichage texte — text_display

Bloc de texte ultra-personnalisable : taille, police, gras, italique, souligné, barré, espacement, fond plein ou dégradé, bordure flexible (côté, style, épaisseur), ombre et opacité.

🔤
Typographie complète
Taille xs→xl, police mono/serif, gras, italique, souligné, barré, espacement des lettres.
🎨
Fond riche
Couleur pleine ou dégradé 2 couleurs, border-radius et padding personnalisés.
📐
Bordure flexible
Côté gauche/droite/haut/bas/tout, style solid/dashed/dotted/double, épaisseur libre.
Effets visuels
Ombre portée, opacité 0→1, icône préfixe, alignement gauche/centre/droite.

🧪 Démo — Personnalise en temps réel

Tous les paramètres disponibles
Texte : Couleur : Taille : Police : Align :
Espacement : Opacité : Icône :
Fond : Dégradé : Rayon : Bordure : px
← Modifie les paramètres pour voir le résultat

Champs

ChampTypeDéfautDescription
contentstrTexte à afficher (supporte \n pour sauts de ligne)
colorhex#fffCouleur du texte
sizestr"md""xs" 11px / "sm" 12px / "md" 14px / "lg" 17px / "xl" 20px
fontstrPolice : "mono", "serif" (défaut : sans-serif)
boldboolFalseGras
italicboolFalseItalique
underlineboolFalseSouligné
strikethroughboolFalseBarré (texte rayé)
alignstr"left"Alignement : "left", "center", "right"
letter_spacingfloatEspacement des lettres en em (ex: 0.1)
iconstrEmoji ou icône affiché avant le texte
backgroundhexCouleur de fond du bloc
gradient_colorslistDégradé de fond : ["#color1", "#color2"] (prioritaire sur background)
border_radiusint7Rayon des coins du fond (px)
border_colorhexCouleur de la bordure
border_sidestr"left"Côté : "left", "right", "top", "bottom", "all"
border_stylestr"solid"Style : "solid", "dashed", "dotted", "double"
border_widthint3Épaisseur de la bordure (px)
shadowboolFalseOmbre portée sous le bloc
opacityfloat1Opacité du bloc (0.0 → 1.0)
Python — Citation stylée
self.send_message(channel,
    components=[{
        "type":         "text_display",
        "content":      "Chaque jour est une nouvelle chance.",
        "italic":       True,
        "color":        "#a3cfff",
        "border_color": "#5865f2",
        "icon":         "💬",
    }])
Python — Titre XL centré avec fond + bordure complète + ombre
self.send_message(channel,
    components=[{
        "type":          "text_display",
        "content":       "⚔️ Combat débuté !",
        "size":          "xl",
        "bold":          True,
        "align":         "center",
        "color":         "#f59e0b",
        "background":    "#1a0a00",
        "border_color":  "#f59e0b",
        "border_side":   "all",
        "border_width":  2,
        "shadow":        True,
    }])
Python — Fond dégradé + police mono + espacement
self.send_message(channel,
    components=[{
        "type":              "text_display",
        "content":           "Joueur  XP: 12 450  Rang: #14",
        "font":              "mono",
        "color":             "#e2e8f0",
        "gradient_colors":   ["#1e1b4b", "#312e81"],
        "border_color":      "#8875f5",
        "border_width":      4,
        "border_radius":     10,
        "letter_spacing":    0.05,
        "shadow":            True,
    }])
Python — Liste d'objectifs (complété / barré / discret)
self.send_message(channel,
    components=[
        {"type": "text_display", "content": "Vaincre 10 ennemis",
         "bold": True, "color": "#10d9a3", "icon": "✅"},
        {"type": "text_display", "content": "Collecter 5 pièces",
         "strikethrough": True, "color": "#666", "opacity": 0.55},
        {"type": "text_display", "content": "🔒 Atteindre le niveau 10",
         "color": "#555", "italic": True, "size": "sm"},
    ])
💡
Combiner avec d'autres composants
text_display s'affiche dans la liste components avec les boutons, barres de progression, timers, etc. Empile plusieurs blocs consécutifs pour construire des mises en page riches.
➖ Composant

Séparateur — separator

Ligne de séparation visuelle entre les autres composants. Optionnellement agrémentée d'un label centré.

Démonstration

🤖

Paramètres

ParamètreTypeDéfautDescription
stylesolid / dashed / dotted / doublesolidStyle de la ligne
colorhex / rgbargba(255,255,255,.12)Couleur de la ligne
thicknessint (px)1Épaisseur de la ligne
labelstrNoneTexte affiché au centre
marginint (px)7Marge verticale

⚡ Bonnes pratiques

💡 Combine title + msg_color systématiquement pour identifier visuellement l'origine du message en un coup d'œil — sans lire le contenu.
⚠️ Réserve priority="urgent" aux vraies urgences — le badge clignote et attire immédiatement l'attention. Si tout est urgent, plus rien ne l'est.
💡 Utilise delete_after + ephemeral=True ensemble pour les messages d'erreur ou d'aide : ils disparaissent automatiquement et ne polluent pas l'historique.
🎯 color_scheme="neon" avec msg_color coordonné donne un rendu très impactant pour les annonces importantes — mais utilise-le avec parcimonie pour garder l'effet "wow".
🎯 Les tags sont cliquables côté client — pense à les utiliser pour filtrer les messages dans l'UI. Nomme-les en lowercase sans espaces pour la cohérence.
⚠️ Ne combine pas embeds et components dans le même send_message — les embeds sont pour le message texte enrichi, les components sont pour les cartes interactives. Utilise l'un ou l'autre.

📝 Exemples

Python — titre + bordure colorée
# Titre stylé + bande colorée gauche
self.send_message(channel_id,
    "Voici les dernières nouvelles du serveur.",
    title="📰 Annonce",
    msg_color="#f59e0b",
    category="Annonce",
    tags=["news", "important"],
    pin=True
)
Python — message urgent animé
# Badge URGENT clignotant + footer + TTS
self.send_message(channel_id,
    "⚠️ Le serveur redémarre dans 5 minutes ! Sauvegardez vos données.",
    priority="urgent",
    msg_color="#ef4444",
    footer_text="Maintenance planifiée · 22:00 UTC",
    tts=True
)
Python — message éphémère auto-supprimé
# Visible uniquement par le destinataire, supprimé après 10s
self.send_message(channel_id,
    "Cette commande n'existe pas. Essaie /help pour voir toutes les commandes.",
    ephemeral=True,
    delete_after=10
)
Python — mention + réactions suggérées
# Féliciter un utilisateur avec mention et réactions prédéfinies
self.send_message(channel_id,
    "Bravo pour ta contribution sur la PR #142 !",
    mention="alice",
    reactions_preset=["🎉", "🔥", "👏", "❤️"]
)
Python — thread + bloc de code intégré
# Discussion avec code Python intégré dans le message
self.send_message(channel_id,
    "Voici le résultat de l'analyse :",
    thread_title="Discussion #42",
    priority="low",
    code={
        "language": "python",
        "content": "score = analyse_logs()
print(f'Score: {score:.2f}')"
    }
)
Python — multi-embed dans un seul message
# Résumé multi-sections dans un seul message
self.send_message(channel_id,
    "Résumé de la journée :",
    title="📋 Daily recap",
    msg_color="#6366f1",
    embeds=[
        {"title": "🌅 Matin", "description": "Réunion standup à 9h30 — 5 participants.",   "color": "#3b82f6"},
        {"title": "☀️ Après-midi", "description": "Code review + 3 PRs mergées.",              "color": "#f59e0b"},
        {"title": "🌙 Soir", "description": "Démo client à 18h — feedback positif.",      "color": "#8b5cf6"},
    ]
)
Python — banner + tags + scheme neon
# Release avec bannière image, tags et scheme néon
self.send_message(channel_id,
    "Nouvelle version disponible — mets à jour maintenant !",
    title="🚀 v2.1 publiée",
    banner_url="https://example.com/release-banner.png",
    category="Release",
    tags=["python", "api", "v2"],
    color_scheme="neon",
    msg_color="#00d9a3",
    priority="high"
)
Python — épinglé avec compte à rebours
# Offre limitée : épinglée, expire dans 1 heure avec compte à rebours en direct
import datetime
exp = (datetime.datetime.utcnow() + datetime.timedelta(hours=1)).isoformat()

self.send_message(channel_id,
    "🎁 Offre spéciale — durée limitée !",
    title="🎯 Deal exclusif",
    pin=True,
    priority="high",
    expires_at=exp,
    msg_color="#f59e0b"
)
Python — rapport silencieux automatique
# Rapport nocturne : silencieux, dark scheme, low priority
self.send_message(channel_id,
    "📊 Rapport nocturne généré automatiquement.",
    title="Rapport 03:00",
    msg_color="#3b82f6",
    priority="low",
    color_scheme="dark",
    category="Rapport",
    tags=["rapport", "auto"],
    footer_text="Généré automatiquement",
    silent=True
)
Python — combinaison maximale
# Tous les paramètres avancés ensemble pour voir les interactions visuelles
self.send_message(channel_id,
    "Voici un résumé complet avec toutes les options activées.",
    title="🎨 Message ultra-enrichi",
    msg_color="#8875f5",
    priority="high",
    color_scheme="glass",
    category="Demo",
    tags=["test", "showcase"],
    thread_title="Discussion ouverte",
    footer_text="LeNavire SDK · v2.1",
    tts=True,
    reactions_preset=["🚀", "🎉", "💜"]
)
📈 Composant

Carte de statistique — stat_card

Affiche une métrique clé avec valeur, icône, tendance, barre de progression et couleur de fond configurables.

📊
Valeur + icône
Titre, valeur en gros plan, icône emoji au choix et couleur d'accentuation.
📈
Tendance
Flèche ▲ hausse ou ▼ baisse avec sous-titre et couleur automatique.
🎨
Fond configurable
Auto (teinté), couleur pleine, dégradé 2 couleurs ou transparent.
Barre de progression
Barre d'avancement optionnelle avec valeur et couleur indépendantes.

🧪 Démo — Personnalise en temps réel

Paramètres de la carte
Titre : Valeur : Icône : Couleur : Taille : Tendance :
Sous-titre : Footer :
Fond :
← Modifie les paramètres pour voir le JSON

Champs

ChampTypeDéfautDescription
titlestrLabel au-dessus de la valeur
valuestrValeur principale affichée en gros
iconstrEmoji affiché à gauche
colorhex#5865f2Couleur d'accentuation (valeur, barre, fond auto)
value_colorhex=colorCouleur de la valeur si différente de color
sizestr"md""sm", "md", "lg"
subtitlestrTexte sous la valeur (ex: delta)
trendstr"up" ▲ vert, "down" ▼ rouge, "neutral"
footerstrTexte discret en bas
backgroundhex/boolautoCouleur de fond, ou false pour transparent
gradient_colorslistFond dégradé : ["#c1","#c2"]
border_colorhexCouleur de la bordure
shadowboolFalseOmbre portée
progress_barintValeur 0–100 de la barre de progression
progress_colorhex=colorCouleur de la barre de progression
Python — XP d'un joueur
self.send_message(channel,
    components=[{
        "type":         "stat_card",
        "title":        "XP Totaux",
        "value":        "12 450",
        "icon":         "⭐",
        "color":        "#f59e0b",
        "progress_bar": 72,
        "subtitle":     "+320 cette semaine",
        "trend":        "up",
    }])
Python — Métrique serveur
self.send_message(channel,
    components=[{
        "type":             "stat_card",
        "title":            "Membres actifs",
        "value":            "1 284",
        "icon":             "👥",
        "color":            "#00d9a3",
        "gradient_colors":  ["#0a2018", "#0d3020"],
        "shadow":           True,
        "size":             "lg",
    }])
🏷️ Composant

Rangée de badges — badge_row

Affiche une ou plusieurs lignes de badges colorés avec icône et label. Idéal pour rôles, statuts, récompenses.

🏷️
Badges colorés
Chaque badge a sa propre couleur, icône et label. Style rempli ou contour.
📋
Libellé de groupe
Texte descriptif optionnel affiché à côté du groupe de badges.
↔️
Alignement
Gauche (défaut), centré ou droite. Les badges s'enroulent sur plusieurs lignes.
♾️
Illimité
Autant de badges par rangée que nécessaire. Plusieurs rangées possibles.

Démonstration

🤖

Champs

ChampTypeDéfautDescription
badgeslistListe de badges (voir ci-dessous)
labelstrTexte de groupe affiché en fin de ligne
alignstr"left""left", "center", "right"

Champs d'un badge

ChampTypeDéfautDescription
labelstrTexte du badge
colorhex#5865f2Couleur d'accentuation
iconstrEmoji affiché avant le label
filledboolTrueTrue = fond coloré, False = contour
Python — Rôles d'un joueur
self.send_message(channel,
    components=[{
        "type":   "badge_row",
        "label":  "Rôles",
        "badges": [
            {"label": "Fondateur",  "color": "#f1c40f", "icon": "👑"},
            {"label": "Niveau 42",  "color": "#5865f2", "icon": "⭐"},
            {"label": "Actif",      "color": "#00d9a3", "filled": False},
        ]
    }])
Python — Rangées multiples centrées
self.send_message(channel,
    components=[
        {"type": "badge_row", "align": "center",
         "badges": [{"label": "Speed Runner", "color": "#f59e0b"},
                    {"label": "Top 10",      "color": "#00d9a3"},
                    {"label": "OG",          "color": "#5865f2", "icon": "🏅"}]},
    ])
🖼️ Composant

Image embarquée — image

Affiche une image avec coins arrondis, légende optionnelle, largeur et hauteur configurables.

🖼️
Image inline
URL publique ou base64. Taille max, border-radius et object-fit configurables.
📝
Légende
Texte discret affiché sous l'image, stylé automatiquement.
🔗
Cliquable
Ouvre l'image en plein écran ou une URL de destination au clic.
📐
Dimensions
Largeur et hauteur max paramétrables. L'image est toujours proportionnelle.

Démonstration

🤖

Champs

ChampTypeDéfautDescription
urlstrURL de l'image (publique ou base64)
altstrTexte alternatif
captionstrLégende affichée sous l'image
widthintLargeur max en px
heightintHauteur max en px
border_radiusint10Arrondi des coins (px)
link_urlstrURL ouverte au clic sur l'image
Python — Image avec légende
self.send_message(channel,
    components=[{
        "type":          "image",
        "url":           "https://example.com/banner.png",
        "caption":       "Bannière du serveur — saison 3",
        "width":         300,
        "border_radius": 12,
    }])
🚨 Composant

Alerte — alert

Bloc d'alerte coloré avec icône, titre, message et types prédéfinis (success, warning, danger, tip, info).

5 types prédéfinis
success, warning, danger, tip, info — couleur et icône automatiques.
🎨
Couleur libre
Surcharge de couleur, icône, fond et style de bordure entièrement configurables.
📐
Bordure flexible
Côté (gauche par défaut), épaisseur, rayon, style et fond personnalisables.
Dismissible
Bouton de fermeture optionnel. Mode compact et footer texte disponibles.

🧪 Démo — Personnalise en temps réel

Paramètres de l'alerte
Type : Titre : Message :
Icône : Côté bordure : Épais : Rayon :
Taille titre : Footer :
← Modifie les paramètres pour voir le JSON

Champs

ChampTypeDéfautDescription
alert_typestr"info"Type prédéfini : success, warning, danger, tip, info
titlestrTitre de l'alerte
messagestrContenu de l'alerte (supporte \n)
iconstrautoEmoji d'icône (surcharge le défaut du type)
colorhexautoCouleur d'accentuation (surcharge le type)
border_sidestr"left"left, right, top, bottom, all
border_widthint3Épaisseur de la bordure (px)
border_radiusint8Rayon des coins (px)
backgroundhexautoCouleur de fond personnalisée
shadowboolFalseOmbre portée
compactboolFalsePadding réduit
dismissibleboolFalseBouton ✕ pour fermer
footerstrNote de bas d'alerte
title_sizestr"md""sm", "md", "lg"
Python — Succès
self.send_message(channel,
    components=[{
        "type":       "alert",
        "alert_type": "success",
        "title":      "Paiement confirmé",
        "message":    "Ta commande #1042 a bien été validée.",
    }])
Python — Danger avec shadow + dismissible
self.send_message(channel,
    components=[{
        "type":        "alert",
        "alert_type":  "danger",
        "title":       "Erreur critique",
        "message":     "La base de données est inaccessible.",
        "shadow":      True,
        "dismissible": True,
        "border_side": "all",
    }])
✏️ Composant

Champ de saisie — input_field

Zone de texte interactive permettant à l'utilisateur de saisir une valeur. Supporte validation, compteur de caractères et placeholder.

✏️
Saisie interactive
L'utilisateur tape directement dans l'interface. Réponse envoyée au bot à la validation.
🔢
Compteur de caractères
Limite et compteur automatiques.
Validation
Bouton de validation configurable. Retour d'erreur si champ vide.
🏷️
Label + placeholder
Texte indicatif au-dessus et placeholder dans le champ.

Démonstration

🤖

Champs

ChampTypeDéfautDescription
labelstrTexte au-dessus du champ
placeholderstrTexte indicatif dans le champ
max_lengthint200Nombre de caractères maximum
submit_labelstr"Valider"Texte du bouton de soumission
multilineboolFalseZone de texte multi-lignes
requiredboolTrueEmpêche la soumission si vide
Python — Demander le pseudo
self.send_message(channel,
    components=[{
        "type":         "input_field",
        "label":         "Ton pseudo",
        "placeholder":   "Ex : Sylvain42",
        "max_length":    32,
        "submit_label":  "Choisir",
    }])
⭐ Composant

Étoiles / Rating — rating

Système de notation par étoiles interactif. L'utilisateur survole et clique pour noter.

Notation étoiles
1 à N étoiles (défaut 5). Survol interactif avec feedback visuel instantané.
🎨
Couleur libre
Couleur des étoiles sélectionnées configurable.
📝
Label
Question ou libellé affiché au-dessus des étoiles.
🔒
Vote unique
Une fois voté, les étoiles se verrouillent. Retour visuel de confirmation.

Démonstration

🤖

Champs

ChampTypeDéfautDescription
labelstrTexte affiché au-dessus des étoiles
maxint5Nombre d'étoiles maximum
colorhex#f59e0bCouleur des étoiles sélectionnées
sizeint28Taille des étoiles (px)
Python — Évaluation d'un service
self.send_message(channel,
    components=[{
        "type":  "rating",
        "label": "Évalue ce service",
        "max":   5,
        "color": "#f59e0b",
    }])
📊 Composant

Sondage / Poll — poll

Sondage interactif avec question, options colorées et résultats en barres animées après vote.

📊
Options illimitées
Autant de choix que nécessaire avec emoji, label et couleur pour chacun.
📈
Résultats animés
Barres de progression CSS animées affichées immédiatement après le vote.
🔒
Vote unique
Un seul vote par utilisateur. Les autres options se désactivent après vote.
🎨
Couleur par option
Chaque option peut avoir sa propre couleur pour les barres et la sélection.

Démonstration

🤖

Champs

ChampTypeDéfautDescription
questionstrQuestion posée aux utilisateurs
optionslistListe d'options (voir ci-dessous)
multipleboolFalseAutoriser plusieurs votes
anonymousboolFalseRésultats anonymes

Champs d'une option

ChampTypeDescription
labelstrTexte de l'option
valuestrValeur retournée lors du vote
emojistrEmoji affiché devant le label
colorhexCouleur de la barre et sélection
Python — Choix de langage
self.send_message(channel,
    components=[{
        "type":     "poll",
        "question": "Quel est ton langage préféré ?",
        "options":  [
            {"label": "Python",     "value": "py", "emoji": "🐍", "color": "#3b82f6"},
            {"label": "JavaScript", "value": "js", "emoji": "🟨", "color": "#f59e0b"},
            {"label": "Rust",       "value": "rs", "emoji": "🦀", "color": "#ef4444"},
        ],
    }])
⏳ Composant

Compte à rebours — countdown

Compte à rebours en temps réel affiché sous forme de blocs heures / minutes / secondes avec couleur personnalisable.

⏱️
Temps réel
Décrémente chaque seconde. S'arrête à zéro avec message de fin personnalisable.
🎨
Couleur libre
Couleur des blocs et valeurs configurables indépendamment.
🏷️
Label
Texte descriptif affiché au-dessus du compte à rebours.
📅
Date cible
Spécifie une timestamp ISO ou un nombre de secondes depuis maintenant.

Démonstration

🤖

Champs

ChampTypeDéfautDescription
targetstr/intTimestamp ISO ou durée en secondes
labelstrTexte affiché au-dessus
colorhex#f59e0bCouleur des blocs et valeurs
end_messagestr"Terminé !"Message affiché à l'expiration
show_hoursboolTrueAfficher les heures
Python — Événement dans 1 heure
import datetime
target = (datetime.datetime.utcnow() + datetime.timedelta(hours=1)).isoformat()

self.send_message(channel,
    components=[{
        "type":        "countdown",
        "target":      target,
        "label":       "🎉 Prochain événement :",
        "color":       "#f59e0b",
        "end_message": "L'événement est commencé !",
    }])
💻 Composant

Bloc de code — code_block

Affiche du code source avec coloration syntaxique, numéros de ligne, bouton copier et header façon éditeur.

💻
Coloration syntaxique
Multi-langages : Python, JS, JSON, SQL, Bash et plus encore.
🔢
Numéros de ligne
Numéros de ligne optionnels non sélectionnables à gauche.
📋
Bouton Copier
Copie dans le presse-papier en un clic avec confirmation visuelle.
🎨
Thème configurable
Thème monokai par défaut, couleur de fond et de texte personnalisables.

Démonstration

🤖

Champs

ChampTypeDéfautDescription
contentstrCode source à afficher
languagestr"text"Langage pour la coloration syntaxique
show_line_numbersboolTrueAfficher les numéros de ligne
show_copy_buttonboolTrueBouton de copie dans le header
filenamestrNom de fichier affiché dans le header
themestr"monokai"Thème couleur : monokai, dark, light
Python — Afficher du code Python
self.send_message(channel,
    components=[{
        "type":      "code_block",
        "language":  "python",
        "filename":  "fibonacci.py",
        "content":   "def fibonacci(n):\n    if n <= 1:\n        return n\n    return fibonacci(n-1) + fibonacci(n-2)",
    }])
📋 Composant

Tableau de données — data_table

Tableau HTML stylé avec en-têtes, lignes alternées, couleur d'accentuation et données structurées.

📋
En-têtes colorés
Première ligne en en-tête avec fond coloré et texte en gras.
🎨
Lignes alternées
Zébrage automatique des lignes pour la lisibilité.
📐
Largeur auto
Le tableau s'adapte automatiquement à la largeur disponible.
🔢
Données libres
N colonnes × M lignes. Chaque cellule accepte texte, emojis et nombres.

Démonstration

🤖

Champs

ChampTypeDéfautDescription
headerslistListe de noms de colonnes
rowslistListe de listes : chaque sous-liste = une ligne
colorhex#5865f2Couleur d'accentuation des en-têtes
stripedboolTrueZébrage des lignes
Python — Classement joueurs
self.send_message(channel,
    components=[{
        "type":    "data_table",
        "color":   "#5865f2",
        "headers": ["Joueur", "Score", "Rang"],
        "rows":    [
            ["Alice", "9 500", "🥇"],
            ["Bob",   "7 200", "🥈"],
            ["Carol", "5 100", "🥉"],
        ],
    }])
🗂️ Composant

Onglets — tabs

Système d'onglets cliquables avec contenu indépendant par onglet. Couleur, style, arrondi, taille, espacement, icônes et interactions entièrement configurables.

🗂️
Onglets cliquables
Navigation fluide. L'onglet actif est mis en surbrillance avec fond coloré.
📝
Contenu libre
Chaque onglet peut contenir du texte multi-lignes totalement indépendant.
🎨
Couleur d'accent
Couleur personnalisable pour la bordure et le fond actif de l'onglet.
🔢
Jusqu'à 6 onglets
Jusqu'à 6 onglets dans le même composant, s'enroulent automatiquement.
📌
Onglet par défaut
Index de l'onglet affiché à l'ouverture, configurable librement.
📐
Style complet
Arrondi, taille de police, padding, espacement et couleur de fond réglables.
🔔
Interaction bot
Événement tab_select déclenché à chaque changement d'onglet.
📱
Responsive
Les onglets s'enroulent sur plusieurs lignes si l'espace manque.

⚡ Préréglages

🧪 Démo — Personnalise en temps réel

Onglets interactifsLIVE
🎨 Couleurs
Accent (onglet actif)
Fond du contenu
Texte contenu
📐 Style des onglets
Arrondi (px)
Taille texte (px)
Padding H (px)
Padding V (px)
Gap entre onglets
Gras actif
📄 Style du contenu
Padding contenu
Taille texte (px)
Hauteur min (px)
Bordure contenu
⚙️ Comportement
Onglet par défaut
custom_id
🗂️ Onglets — icône · label · contenu
1
2
3
4
5
6
Aperçu
JSON

      

Paramètres globaux

ChampTypeDéfautDescription
typestrToujours "tabs"
tabslistListe d'onglets (voir tableau ci-dessous)
colorhex#5865f2Couleur d'accent : bordure et fond de l'onglet actif
bg_colorhex#1e1e2eCouleur de fond de la zone de contenu
text_colorhex#e2e8f0Couleur du texte dans la zone de contenu
default_tabint0Index (0-based) de l'onglet affiché par défaut
border_radiusint7Arrondi des onglets en pixels
font_sizeint13Taille de police des onglets en pixels
padding_hint14Padding horizontal des onglets en pixels
padding_vint6Padding vertical des onglets en pixels
tab_gapint2Espacement entre les onglets en pixels
content_paddingint12Padding intérieur de la zone de contenu
content_font_sizeint13Taille de police du contenu en pixels
full_widthboolfalseOnglets en pleine largeur (répartis équitablement)
show_borderbooltrueAfficher la bordure autour de la zone de contenu
custom_idstrnullDéclenche un événement tab_select à chaque changement d'onglet

Paramètres d'un onglet

ChampTypeDéfautDescription
labelstrTexte affiché dans l'onglet
iconstrnullEmoji ou icône affiché avant le label
contentstrContenu affiché quand l'onglet est actif. Supporte \n
💡
Astuce interaction
Définis custom_id pour recevoir un événement tab_select dans ton bot à chaque fois qu'un utilisateur change d'onglet. values[0] = index, values[1] = label de l'onglet cliqué.

Exemples

Python — Infos serveur
self.send_message(channel,
    components=[{
        "type":  "tabs",
        "color": "#5865f2",
        "tabs":  [
            {"label": "Infos",  "icon": "ℹ️",  "content": "Bienvenue sur le serveur !"},
            {"label": "Stats",  "icon": "📊", "content": "500 membres · 12 salons"},
            {"label": "Règles", "icon": "📜", "content": "1. Sois respectueux\n2. Pas de spam"},
        ],
    }])
Python — Bot status
self.send_message(channel,
    components=[{
        "type":  "tabs",
        "color": "#00d9a3",
        "tabs":  [
            {"label": "Statut",    "icon": "🟢", "content": "En ligne depuis 3j 14h · Latence : 42 ms"},
            {"label": "Commandes", "icon": "⚙️", "content": "32 commandes disponibles · /help pour la liste"},
            {"label": "Logs",      "icon": "📋", "content": "Dernière action : ban #user123 il y a 2 min."},
        ],
    }])
Python — Boutique avec interaction
self.send_message(channel,
    components=[{
        "type":      "tabs",
        "color":     "#f59e0b",
        "custom_id": "shop_tabs",
        "tabs":  [
            {"label": "Packs",      "icon": "🛒", "content": "Starter 2,99 € · Pro 6,99 € · Elite 14,99 €"},
            {"label": "Cosmétiques", "icon": "🎨", "content": "Avatars · Bannières · Effets de pseudo"},
            {"label": "Boosts",     "icon": "⚡", "content": "XP x2 · Coins x3 · Invocations gratuites"},
            {"label": "FAQ",        "icon": "❓", "content": "Paiement sécurisé · Remboursement 7j · Support 24/7"},
        ],
    }])
Python — Fiche RPG
self.send_message(channel,
    components=[{
        "type":  "tabs",
        "color": "#a855f7",
        "tabs":  [
            {"label": "Stats",   "icon": "⚔️", "content": "Force : 87 · Agilité : 65 · Magie : 42",       "open": True},
            {"label": "Équip.",  "icon": "🛡️", "content": "Épée légendaire · Armure en mithril"},
            {"label": "Quêtes",  "icon": "📜", "content": "3 actives · 47 terminées · 2 échouées"},
            {"label": "Guilde",  "icon": "🏰", "content": "Ordre du Soleil — Grade : Capitaine"},
        ],
    }])
Python — Réception interaction
def on_button_click(self, interaction):
    if interaction['custom_id'] == 'shop_tabs':
        idx   = interaction['values'][0]  # index de l'onglet
        label = interaction['values'][1]  # label de l'onglet
        self.send_message(channel, f"Onglet '{label}' ouvert (index {idx})")
🪗 Composant

Accordéon — accordion

Sections dépliables/repliables avec animation fluide. Idéal pour FAQ, aide, règles du serveur ou contenu structuré.

🪗
Sections dépliables
Clic sur le titre pour ouvrir/fermer. Transition max-height fluide.
🎨
Couleur d'accent
Couleur personnalisable pour le fond et la flèche des sections ouvertes.
📌
Section par défaut
Une ou plusieurs sections peuvent être ouvertes à l'affichage.
🔢
N sections libres
De 1 à autant de sections que nécessaire dans le même composant.
📐
Rayon de bordure
Border-radius configurable pour un look carré ou très arrondi.
🔔
Interaction bot
Déclenchement d'un événement côté bot à chaque ouverture de section.
✏️
Icône par section
Emoji ou icône optionnel affiché à gauche du titre de chaque section.
🌓
Gap configurable
Espacement entre sections réglable de 0 px (stack) à 16 px.

⚡ Préréglages

🧪 Démo — Personnalise en temps réel

Accordéon interactifLIVE
Apparence
Couleur
Rayon (px)
Gap (px)
Sections — icône · titre · contenu
Aperçu
JSON

      

Paramètres globaux

ChampTypeDéfautDescription
typestrToujours "accordion"
sectionslistListe de sections (voir tableau ci-dessous)
colorhex#00d9a3Couleur d'accentuation (fond ouvert + flèche)
custom_idstrnullDéclenche un événement bot à l'ouverture si défini

Paramètres d'une section

ChampTypeDéfautDescription
titlestrTitre cliquable de la section
contentstrContenu affiché à l'ouverture
openboolFalseOuverte par défaut à l'affichage
iconstrnullEmoji ou icône affiché avant le titre
💡
Astuce interaction
Définis custom_id pour recevoir un événement accordion_open dans ton bot chaque fois qu'un utilisateur ouvre une section. Utile pour des FAQ analytiques ou du contenu dynamique.

Exemples

Python — FAQ serveur
self.send_message(channel,
    components=[{
        "type":     "accordion",
        "color":    "#00d9a3",
        "sections": [
            {"title": "Comment rejoindre ?",    "icon": "❓", "content": "Clique sur Rejoindre...", "open": True},
            {"title": "Prix de l'abonnement",  "icon": "💳", "content": "4,99 €/mois, annulable."},
            {"title": "Contacter le support",   "icon": "🆘", "content": "DM à @support ou #aide."},
        ],
    }])
Python — Règlement
self.send_message(channel,
    components=[{
        "type":     "accordion",
        "color":    "#ef4444",
        "sections": [
            {"title": "Règle 1 — Respect",   "icon": "🤝", "content": "Traite chaque membre avec respect.",   "open": True},
            {"title": "Règle 2 — Spam",     "icon": "🚫", "content": "Le spam entraîne un mute automatique."},
            {"title": "Règle 3 — NSFW",    "icon": "🔞", "content": "Contenu adulte interdit hors canaux dédiés."},
            {"title": "Règle 4 — Pub",     "icon": "📢", "content": "Toute publicité nécessite l'accord des admins."},
        ],
    }])
Python — Boutique avec interaction
self.send_message(channel,
    components=[{
        "type":      "accordion",
        "color":     "#f59e0b",
        "custom_id": "shop_faq",
        "sections": [
            {"title": "Pack Starter — 2,99 €", "icon": "🥉", "content": "50 coins + badge exclusif + accès salon VIP.",      "open": True},
            {"title": "Pack Pro — 6,99 €",    "icon": "🥇", "content": "200 coins + rôle Pro + avatar animé."},
            {"title": "Pack Elite — 14,99 €", "icon": "💎", "content": "Accès illimité + bot privé + support 24/7."},
        ],
    }])
Python — Aide technique bot
self.send_message(channel,
    components=[{
        "type":     "accordion",
        "color":    "#3b82f6",
        "sections": [
            {"title": "Le bot ne répond pas", "icon": "⚙️", "content": "Vérifie les permissions du canal et redémarre avec /restart."},
            {"title": "Commandes inconnues",  "icon": "❓", "content": "Tape /help pour voir toutes les commandes disponibles."},
            {"title": "Erreur de permission", "icon": "🔒", "content": "Le bot doit avoir les rôles admin et gestionnaire de canaux."},
        ],
    }])
Python — RPG / Lore
self.send_message(channel,
    components=[{
        "type":     "accordion",
        "color":    "#a855f7",
        "sections": [
            {"title": "Faction : Ordre du Soleil", "icon": "☀️", "content": "Paladins et mages de lumière. Bonus : +20% défense.",     "open": True},
            {"title": "Faction : Ombre Nocturne",  "icon": "🌑", "content": "Assassins furtifs. Bonus : +30% vitesse + invisibilité."},
            {"title": "Faction : Ingénieurs",      "icon": "⚙️", "content": "Constructeurs et inventeurs. Bonus : tourelles auto."},
            {"title": "Faction : Gardiens Nature",  "icon": "🌿", "content": "Druides et bêtes sauvages. Bonus : régénération +15%."},
        ],
    }])
🎵 Composant

Lecteur média — media_player

Lecteur audio ou vidéo HTML5 intégré dans le message, avec titre et pochette optionnels.

Démonstration

🤖

Paramètres

ParamètreTypeDéfautDescription
urlstrURL du fichier audio ou vidéo
media_typeaudio / videoauto-détectéType de média
titlestrTitre affiché
coverurlPochette (audio seulement)
colorhex#00d9a3Couleur de la barre de lecture

Exemple

self.send_message(channel_id, components=[
    {"type": "media_player",
     "url": "https://example.com/music.mp3",
     "media_type": "audio",
     "title": "🎵 Ma playlist du soir",
     "cover": "https://example.com/cover.jpg",
     "color": "#8b5cf6"}
])
🎚️ Composant

Curseur — slider

Barre glissante pour sélectionner une valeur numérique. L'interaction slider_change est déclenchée au clic sur ✓.

Démonstration

🤖

Paramètres

ParamètreTypeDéfautDescription
minint0Valeur minimale
maxint100Valeur maximale
stepint1Pas
valueint(min+max)/2Valeur initiale
unitstrUnité affichée après la valeur (ex: "%")
labelstrTexte au-dessus
colorhex#00d9a3Couleur d'accent
custom_idstrautoID pour slider_change

Interaction reçue

ℹ️
Type : slider_change
values[0] = valeur sélectionnée en string.

Exemple

self.send_message(channel_id, components=[
    {"type": "slider",
     "label": "🔊 Volume", "min": 0, "max": 100, "value": 50,
     "unit": "%", "color": "#8b5cf6", "custom_id": "vol"}
])

def on_button_click(self, interaction):
    if interaction['type'] == 'slider_change':
        vol = interaction['values'][0]
        self.send_message(interaction['channel_id'], f"Volume réglé à {vol}% 🔊")
❓ Composant

Confirmation — confirm

Boîte de dialogue avec texte de prompt et deux boutons Confirmer / Annuler.

Démonstration

🤖

Paramètres

ParamètreTypeDéfautDescription
promptstrQuestion / texte explicatif
ok_labelstr"✅ Confirmer"Texte du bouton oui
no_labelstr"❌ Annuler"Texte du bouton non
ok_colorhex#22c55eCouleur du bouton oui
no_colorhex#ef4444Couleur du bouton non
custom_idstrautoID pour les interactions

Interactions reçues

ℹ️
Types : confirm_yes et confirm_no
values[0] = "confirm_yes" ou "confirm_no".

Exemple

self.send_message(channel_id, components=[
    {"type": "confirm",
     "prompt": "⚠️ Veux-tu vraiment réinitialiser ton score ?",
     "ok_label": "🗑️ Oui, supprimer", "ok_color": "#ef4444",
     "no_label": "Annuler",            "no_color": "#5865f2",
     "custom_id": "reset_score"}
])

def on_button_click(self, interaction):
    if interaction['type'] == 'confirm_yes':
        self.send_message(interaction['channel_id'], "Score réinitialisé ! 🗑️")
    elif interaction['type'] == 'confirm_no':
        self.send_message(interaction['channel_id'], "Annulé. Ton score est préservé ✅")
🔢 Composant

Saisie numérique — number_input

Contrôle +/− avec limite min/max, pas configurable et bouton d'envoi.

Démonstration

🤖

Paramètres

ParamètreTypeDéfautDescription
minint0Valeur minimale
maxintValeur maximale
stepint1Incrément
valueintminValeur initiale
unitstrUnité (ex: " pts")
labelstrLabel affiché au-dessus
colorhex#5865f2Couleur d'accent
custom_idstrautoID pour number_input_change

Interaction reçue

ℹ️
Type : number_input_change
values[0] = valeur sélectionnée en string.

Exemple

self.send_message(channel_id, components=[
    {"type": "number_input",
     "label": "Combien de jetons veux-tu utiliser ?",
     "min": 1, "max": 100, "value": 10, "step": 5,
     "unit": " 🪙", "color": "#f59e0b", "custom_id": "jetons"}
])

def on_button_click(self, interaction):
    if interaction['type'] == 'number_input_change':
        n = interaction['values'][0]
        self.send_message(interaction['channel_id'], f"Tu as utilisé {n} jetons 🪙")
📋 Composant

Menu déroulant — dropdown

Composant de sélection stylisé utilisable hors ActionRow Discord. Supporte emoji, descriptions, multi-sélection et placeholder personnalisé.

🎨
Couleur d'accent
Bordure et highlight dans la couleur de ton choix.
🏷️
Label + description
Chaque option peut avoir un sous-texte descriptif.
🔢
Multi-sélection
min_values / max_values pour N choix.
Interaction directe
Type dropdown_select dans on_button_click.

🧪 Démonstration interactive

Sélectionne pour voir l'interaction générée
Couleur : Label :
← Sélectionne une option

Paramètres

ParamètreTypeDéfautDescription
optionslistListe d'options : str ou objet {"label","value","emoji","description","default","disabled"}
placeholderstrTexte affiché si aucune option choisie
labelstrTitre au-dessus du menu
colorhex#00d9a3Couleur de la bordure / highlight
custom_idstrautoID reçu dans on_button_click (type dropdown_select)
min_valuesint1Nombre minimum d'options à sélectionner
max_valuesint1Nombre maximum (>1 = multi-sélection)
— Objet option —
option.labelstrTexte affiché dans la liste
option.valuestrValeur reçue dans interaction['values']
option.emojistrIcône devant le label
option.descriptionstrSous-texte grisé sous le label
option.defaultboolPré-sélectionne cette option
option.disabledboolGriser / rendre non-sélectionnable

Interaction reçue

ℹ️
Type : dropdown_select
values est toujours une liste. Mono-sélection → values[0]. Multi-sélection → liste complète.
Python — Dropdown simple avec descriptions
self.send_message(channel_id, components=[
    {"type": "dropdown",
     "label":       "🌍 Choisir une région",
     "placeholder": "-- Sélectionner --",
     "custom_id":   "region",
     "options": [
         {"label": "Europe",   "value": "eu", "emoji": "🇪🇺", "description": "Paris, Berlin, Madrid..."},
         {"label": "Amérique", "value": "us", "emoji": "🌎", "description": "New York, São Paulo..."},
         {"label": "Asie",     "value": "as", "emoji": "🌏", "description": "Tokyo, Séoul, Singapour..."},
         {"label": "Afrique",  "value": "af", "emoji": "🌍", "description": "Dakar, Le Caire, Nairobi..."},
     ]}
])

def on_button_click(self, interaction):
    if interaction.get('type') == 'dropdown_select':
        region = interaction['values'][0]
        user   = interaction['user']['username']
        self.send_message(interaction['channel_id'],
            f"🌐 {user} a sélectionné **{region}**")
Python — Dropdown multi-sélection
self.send_message(channel_id, components=[
    {"type": "dropdown",
     "label":      "🔔 Notifications à activer",
     "custom_id": "notif_dd",
     "min_values": 1,
     "max_values": 4,
     "options": [
         {"label": "Messages directs", "value": "dm",      "emoji": "💬", "default": True},
         {"label": "Mentions",         "value": "mention", "emoji": "🔔"},
         {"label": "Annonces",        "value": "news",    "emoji": "📢"},
         {"label": "Récompenses",     "value": "reward",  "emoji": "🎁"},
     ]}
])

def on_button_click(self, interaction):
    if interaction.get('custom_id') == 'notif_dd':
        choices = interaction['values']   # liste de valeurs
        self.send_message(interaction['channel_id'], embed={
            "title":       "✅ Préférences sauvegardées",
            "description": "• " + "\n• ".join(choices),
            "color":       "#00d9a3"
        })
Python — Option désactivée + default + couleur
self.send_message(channel_id, components=[
    {"type": "dropdown",
     "label":       "🏆 Plan d'abonnement",
     "placeholder": "— Sélectionner —",
     "custom_id":   "plan",
     "color":       "#8875f5",
     "options": [
         {"label": "Free",       "value": "free",       "emoji": "🆓", "default": True},
         {"label": "Pro",        "value": "pro",        "emoji": "⭐"},
         {"label": "Business",   "value": "business",   "emoji": "🚀"},
         {"label": "Enterprise", "value": "enterprise", "emoji": "🏢",
          "description": "Sur devis — contactez-nous", "disabled": True},
     ]}
])
💡
dropdown vs select_menu
dropdown est un composant autonome rendu dans le chat. Il génère des interactions de type dropdown_select. select_menu s'intègre dans une ActionRow Discord et génère du type select_menu. Les deux partagent la même structure d'options.
🎨 Composant

Sélecteur de couleur — color_picker

Palette de swatches + champ de saisie libre pour choisir une couleur hex.

Démonstration

🤖

Paramètres

ParamètreTypeDéfautDescription
palettelist8 couleursSwatches de raccourci
valuehex#00d9a3Couleur initiale
labelstrLabel au-dessus
colorhex#00d9a3Couleur du bouton Valider
custom_idstrautoID pour color_pick

Interaction reçue

ℹ️
Type : color_pick
values[0] = couleur hex (ex: "#ef4444").

Exemple

self.send_message(channel_id, components=[
    {"type": "color_picker",
     "label": "🎨 Couleur de ton rôle",
     "value": "#8b5cf6",
     "custom_id": "role_color"}
])
📅 Composant

Sélecteur de date — date_picker

Champ date ou date+heure natif avec min/max et validation.

Démonstration

🤖

Paramètres

ParamètreTypeDéfautDescription
include_timeboolfalseInclure l'heure
valuestrValeur initiale (YYYY-MM-DD)
minstrDate minimale sélectionnable
maxstrDate maximale sélectionnable
labelstrLabel au-dessus
colorhex#5865f2Couleur du bouton Valider
custom_idstrautoID pour date_pick

Interaction reçue

ℹ️
Type : date_pick
values[0] = date au format ISO (ex: "2026-03-15T14:30").

Exemple

self.send_message(channel_id, components=[
    {"type": "date_picker",
     "label": "📅 Date du rendez-vous",
     "include_time": True,
     "min": "2026-01-01",
     "custom_id": "rdv"}
])
✅ Composant

Liste à cocher — checklist

Cases à cocher multiples avec sélection pré-remplie et bouton de validation.

Démonstration

🤖

Paramètres

ParamètreTypeDéfautDescription
itemslistItems : str ou {"label","value","emoji"}
checkedlist[]Valeurs pré-cochées
labelstrTitre au-dessus
submit_labelstr"✓ Valider"Texte du bouton
colorhex#00d9a3Couleur d'accent
custom_idstrautoID pour checklist_change

Interaction reçue

ℹ️
Type : checklist_change
values = tableau des valeurs cochées.

Exemple

self.send_message(channel_id, components=[
    {"type": "checklist",
     "label": "⚙️ Notifications à activer",
     "checked": ["dm"],
     "custom_id": "notifs",
     "items": [
         {"label": "Messages directs", "value": "dm",      "emoji": "💬"},
         {"label": "Mentions",         "value": "mention", "emoji": "🔔"},
         {"label": "Annonces",        "value": "news",    "emoji": "📢"},
     ]}
])
🪜 Composant

Étapes — progress_steps

Barre de progression horizontale par étapes numérotées (purement visuelle).

Démonstration

🤖

Paramètres

ParamètreTypeDéfautDescription
stepslistÉtapes : str ou {"label"}
currentint0Index de l'étape courante (0-based)
labelstrTitre au-dessus
colorhex#00d9a3Couleur des étapes complétées

Exemple

self.send_message(channel_id, components=[
    {"type": "progress_steps",
     "label": "Commande #2847",
     "current": 2,
     "color": "#22c55e",
     "steps": [
         {"label": "Reçue"},
         {"label": "En préparation"},
         {"label": "Expédiée"},
         {"label": "Livrée"},
     ]}
])
🏷️ Composant

Saisie de tags — tag_input

Widget d'ajout/suppression de tags dynamique. Chaque action déclenche une interaction en temps réel.

🏷️
Gestion dynamique

Ajout par touche Entrée ou bouton, suppression par ✕ sur chaque tag.

🎨
Style total

Couleur, forme (round/square/pill), taille, pills pleines ou transparentes.

Suggestions rapides

preset_tags : chips cliquables pour ajouter un tag en un clic.

🔒
Validations

Limite max, longueur par tag, doublons optionnels, tri automatique.

🧪 Démonstration interactive

Personnalise en temps réel
Apparence
Couleur
Taille
Forme
Comportement
Max tags
Max/tag (car.)
Contenu
Label
Suggestions
Aperçu
JSON

      

Paramètres

ParamètreTypeDéfautDescription
tagslist[]Tags déjà présents à l'affichage
maxint10Nombre maximal de tags
max_lengthint30Longueur max d'un tag (caractères)
placeholderstr"Ajouter…"Texte indicatif du champ vide
labelstrLabel affiché au-dessus
colorhex#00d9a3Couleur des pills
sizesm / md / lgmdTaille globale du widget
shaperound / square / pillroundForme des pills
filledboolFalsePills à fond plein (au lieu de transparent)
sortedboolFalseTrie les tags alphabétiquement
allow_duplicatesboolFalseAutorise les tags en double
preset_tagslistSuggestions cliquables sous le champ pour ajouter en un clic
custom_idstrautoID reçu dans tag_add / tag_remove

Interactions reçues

ℹ️
Types : tag_add et tag_remove
values[0] = tag concerné. Déclenché à chaque ajout ou suppression individuel.

Exemples

Python — Basique
{"type": "tag_input", "label": "🏷️ Tes centres d'intérêt",
 "tags": ["Python"], "max": 5, "custom_id": "interests"}
Python — Pills remplies avec suggestions
{"type": "tag_input", "label": "🎮 Jeux préférés",
 "color": "#5865f2", "filled": True, "shape": "pill",
 "preset_tags": ["Minecraft", "Valorant", "LoL", "FIFA"],
 "max": 4, "custom_id": "games"}
Python — Réception des interactions
def on_button_click(self, interaction):
    if interaction['custom_id'] == 'interests':
        tag = interaction['values'][0]
        if   interaction['type'] == 'tag_add':
            self.send_message(channel, f"Tag ajouté : {tag} ✅")
        elif interaction['type'] == 'tag_remove':
            self.send_message(channel, f"Tag supprimé : {tag} 🗑")
📎 Composant

Upload fichier — file_upload

Zone glisser-déposer ou clic pour sélectionner un fichier. Déclenche file_upload avec le nom, type et taille.

Démonstration

🤖

Paramètres

ParamètreTypeDéfautDescription
acceptstrtousTypes acceptés ex: ".pdf,.docx" ou "image/*"
multipleboolfalseAutoriser plusieurs fichiers
placeholderstrTexte de la zone de dépôt
labelstrLabel au-dessus
colorhex#5865f2Couleur de la bordure
custom_idstrautoID pour file_upload

Interaction reçue

ℹ️
Type : file_upload
values[0] = nom, values[1] = type MIME, values[2] = taille en octets.

Exemple

self.send_message(channel_id, components=[
    {"type": "file_upload",
     "label": "📄 Envoie ton CV",
     "accept": ".pdf",
     "placeholder": "Glisse ton fichier PDF ici",
     "custom_id": "cv"}
])
👤 Composant

Carte utilisateur — user_card

Profil visuel complet : bannière dégradée, avatar, bio, rôle, badges, stats, niveau avec barre XP.

🖼️
Bannière & Avatar

Couleur, dégradé 2 tons ou image URL. Avatar lettre auto ou image.

📊
Stats & Niveau

Dictionnaire de stats, niveau badge + barre de progression XP.

🏷️
Badges & Rôle

Liste de badges colorés, rôle / titre sous le pseudo, indicateur de statut.

🎨
Style flexible

Mode compact, couleur d'accent indépendante, texte de statut, footer.

🧪 Démonstration interactive

Personnalise en temps réel
Identité
Pseudo
Rôle
Bio
Bannière & Couleurs
Couleur 1
Couleur 2
Accent
Niveau & Options
Niv.XP65%
Aperçu
JSON

      

Paramètres

ParamètreTypeDéfautDescription
usernamestrPseudo affiché
display_namestrNom d'affichage (sous le pseudo)
rolestrRôle / titre coloré affiché sous le pseudo
avatarurllettre autoURL de l'image d'avatar
biostrCourte biographie (max ~120 caractères recommandé)
onlineboolIndicateur vert (en ligne) / gris (hors ligne)
status_textstrTexte de statut (ex : "En train de coder 💻")
banner_colorhex#8b5cf6Couleur unie du bandeau haut
banner_gradient[hex,hex]Dégradé de bannière (prioritaire sur banner_color)
banner_imageurlImage URL pour la bannière (prioritaire sur tout)
accent_colorhex= banner_colorCouleur d'accent des badges, stats et rôle
badgeslistListe de {"label","icon","color"}
statsdictEx: {"Messages": 142, "Niveau": 7}
levelintNiveau affiché sous forme de badge coloré
level_progressint (0-100)Barre de progression XP sous le niveau
join_datestrTexte date d'inscription (affiché en pied)
footerstrTexte de pied de carte
compactboolFalseVersion réduite (bannière et padding réduits)

Exemples

Python — Profil complet
{"type": "user_card", "username": "alice", "role": "Modératrice",
 "bio": "Passionnée de code et d'IA 🤖", "online": True,
 "banner_gradient": ["#8b5cf6", "#3b82f6"],
 "badges": [{"label": "Fondatrice", "icon": "⭐", "color": "#f59e0b"}],
 "stats": {"Messages": 1420, "Niveau": 12},
 "level": 12, "level_progress": 65}
Python — Compact avec join_date
{"type": "user_card", "username": "bob",
 "banner_color": "#ef4444", "compact": True,
 "online": False, "join_date": "Membre depuis janvier 2025",
 "stats": {"Score": 8420, "Rang": "#3"}}
🏆 Composant

Classement — leaderboard

Tableau de scores avec podium, barres de visualisation, icônes par entrée et styles avancés.

🥇
Podium médailles

🥇🥈🥉 automatiques pour le top 3. Numéros #N pour le reste.

📊
Mode barres

bar_mode : barres horizontales proportionnelles au score maximum.

🎨
Entrées riches

Icône emoji, sous-titre, couleur de nom et mise en surbrillance par entrée.

⚙️
Flexible

Mode compact, limite, label de colonne personnalisé, tri automatique.

🧪 Démonstration interactive

Personnalise en temps réel
Configuration
Titre
UnitéCouleur
Limite
Col. label
Style
Aperçu
JSON

      

Paramètres

ParamètreTypeDéfautDescription
entrieslistListe d'entrées (voir propriétés ci-dessous)
titlestrTitre dans l'en-tête
unitstrSuffixe ajouté au score (ex: " pts")
limitint10Nombre max d'entrées affichées
colorhex#00d9a3Couleur d'accent
column_labelstrLabel de la colonne score (affiché dans l'en-tête à droite)
bar_modeboolFalseAffiche les scores sous forme de barres proportionnelles
bar_colorhex= colorCouleur des barres en mode bar_mode
compactboolFalseRéduit le padding de chaque ligne

Propriétés d'une entrée

CléTypeDescription
namestrNom du joueur / participant
scoreint/floatScore numérique
iconemojiIcône affichée avant le nom
subtitlestrPetite ligne sous le nom (rôle, badge, niveau…)
colorhexCouleur du nom pour cette entrée uniquement
highlightboolMet l'entrée en surbrillance ("c'est toi")

Exemples

Python — Standard avec icônes
{"type": "leaderboard", "title": "🏆 Top Serveur", "unit": " pts", "color": "#f59e0b",
 "entries": [
     {"name": "alice", "score": 2340, "highlight": True, "icon": "👑"},
     {"name": "bob",   "score": 1820, "subtitle": "Niveau 8"},
     {"name": "carol", "score": 1650},
 ]}
Python — Mode barres compact
{"type": "leaderboard", "title": "📊 Activité hebdo",
 "bar_mode": True, "compact": True, "color": "#5865f2",
 "column_label": "Messages",
 "entries": [
     {"name": "alice", "score": 342},
     {"name": "bob",   "score": 218},
     {"name": "carol", "score": 187},
 ]}
📋 Composant

Chronologie — timeline

Événements verticaux avec connecteurs colorés, badges, icônes, statuts, descriptions, marqueurs temporels et mode compact. Idéal pour journaux d'activité, parcours utilisateur, historiques de commandes, étapes de build et roadmaps.

🎨
Couleur par événement

Chaque point peut avoir sa propre couleur via event.color — indépendamment de la couleur globale.

🏷️
Badges & icônes

event.badge + badge_color, event.icon remplace le point par un emoji personnalisé.

📐
3 styles de connecteur

solid, dashed, dotted — et 3 tailles de point : sm, md, lg.

🔴
Statut visuel

event.status : done ✅, active ⚡ (pulsant), error ❌, pending ⏳ — affiché automatiquement.

🗜️
Mode compact

compact: true réduit les espacements — parfait pour les timelines denses à nombreux événements.

📝
Description riche

event.text affiche un sous-texte descriptif sous le titre — support multi-lignes.

🕐
Titre global

title affiche un en-tête au-dessus de la timeline, avec la couleur d'accent.

🔗
Lien par événement

event.url rend le titre cliquable — idéal pour pointer vers un commit, ticket ou page.

🧪 Démonstration interactive

Personnalise en temps réel
Scénarios prêts
Style global
Titre
Couleur
Connecteur
Taille point
Événement 1
Titre
Texte
Date
IcôneStatut
Badge
Événement 2
Titre
Texte
Date
IcôneStatut
Couleur
Événement 3
Titre
Texte
Date
IcôneStatut
Badge
Événement 4
Titre
Texte
Date
IcôneStatut
Badge
Couleur
Aperçu
JSON généré

      

Paramètres globaux

ParamètreTypeDéfautDescription
eventslist[]Liste d'événements à afficher dans l'ordre
titlestrEn-tête affiché au-dessus de la timeline, coloré avec line_color
line_colorhex#8875f5Couleur globale du connecteur et des points
connector_stylestrsolidsolid / dashed / dotted
dot_sizestrmdsm (10 px) · md (14 px) · lg (18 px)
compactboolfalseRéduit les espacements entre événements

Paramètres d'un événement

CléTypeDéfautDescription
titlestrTitre principal de l'événement
textstrDescription / sous-texte sous le titre
datestrHorodatage ou date affichée en petit à gauche du badge
colorhexline_colorCouleur du point pour cet événement uniquement
iconemoji/strRemplace le point par un emoji (ex: 🚀)
badgestrÉtiquette affichée à côté de la date
badge_colorhexevent.colorCouleur du badge
statusstrdone · active (pulsant) · pending · error — icône automatique dans le titre
urlstrRend le titre cliquable (lien externe)
💡
Bonnes pratiques
Utilise status: "active" pour l'étape en cours — son point pulse automatiquement. Combine icon + color par événement pour des timelines très colorées. Pour les logs de build, connector_style: "dashed" + compact: true donne un rendu dense et lisible.

Exemples

Parcours utilisateur
self.send_message(channel_id, components=[{
    "type": "timeline",
    "title": "👤 Parcours de alice",
    "line_color": "#8875f5",
    "connector_style": "dashed",
    "events": [
        {"title": "Inscription",        "date": "01/01/2026", "icon": "🎯", "badge": "Nouveau",   "badge_color": "#22c55e", "status": "done"},
        {"title": "Premier message",    "date": "15/01/2026", "text": "Bienvenue sur le serveur !", "color": "#00d9a3",   "status": "done"},
        {"title": "Niveau 10 atteint",  "date": "02/02/2026", "icon": "⭐",  "badge": "XP",       "badge_color": "#f59e0b", "status": "active"},
        {"title": "Badge Fondateur",                          "color": "#8b5cf6", "icon": "🏆",   "status": "pending"},
    ]
}])
Pipeline CI/CD
self.send_message(channel_id, components=[{
    "type": "timeline",
    "title": "🚀 Déploiement v2.1 — pipeline",
    "line_color": "#22c55e",
    "connector_style": "solid",
    "dot_size": "sm",
    "compact": True,
    "events": [
        {"title": "Checkout",        "date": "14:31:02", "icon": "📦", "status": "done",    "text": "Branch: main @ abc1234"},
        {"title": "Install deps",    "date": "14:31:08", "icon": "📥", "status": "done",    "text": "npm ci — 127 packages"},
        {"title": "Tests unitaires", "date": "14:31:44", "icon": "🧪", "status": "done",    "text": "48 passed, 0 failed"},
        {"title": "Build",           "date": "14:32:01", "icon": "🔨", "status": "active",  "text": "Compilation en cours…"},
        {"title": "Deploy prod",     "icon": "🚀",         "status": "pending"},
    ]
}])
Suivi de commande
self.send_message(channel_id, components=[{
    "type": "timeline",
    "title": "📦 Commande #2847",
    "line_color": "#f59e0b",
    "dot_size": "lg",
    "events": [
        {"title": "Commande reçue",      "date": "07/03 10:12", "icon": "✅", "status": "done",    "badge": "Confirmé",   "badge_color": "#22c55e"},
        {"title": "En préparation",      "date": "07/03 11:30", "icon": "📦", "status": "done",    "text": "Entrepôt Paris Nord"},
        {"title": "Expédiée",            "date": "07/03 14:00", "icon": "🚚", "status": "active",  "text": "Transporteur DHL — TRK928374"},
        {"title": "Livraison prévue",    "date": "08/03",       "icon": "🏠", "status": "pending"},
    ]
}])
Rapport d'incident
self.send_message(channel_id, components=[{
    "type": "timeline",
    "title": "🚨 Incident #4821 — Stripe",
    "line_color": "#ef4444",
    "connector_style": "dotted",
    "events": [
        {"title": "Alerte détectée",     "date": "03:12 UTC", "icon": "🔴", "status": "error",  "text": "Timeouts Stripe API > 30s",      "color": "#ef4444"},
        {"title": "Équipe notifiée",     "date": "03:14 UTC", "icon": "📣", "status": "done",  "text": "On-call @devops alerté"},
        {"title": "Diagnostic en cours", "date": "03:17 UTC", "icon": "🔍", "status": "active", "text": "Analyse des logs et traces",      "color": "#f59e0b"},
        {"title": "Résolution",          "icon": "✅",         "status": "pending"},
    ]
}])
Roadmap produit
self.send_message(channel_id, components=[{
    "type": "timeline",
    "title": "🗺️ Roadmap LeNavire SDK",
    "line_color": "#8b5cf6",
    "connector_style": "dashed",
    "dot_size": "lg",
    "events": [
        {"title": "v1.0 — Sortie publique",  "date": "Janv. 2025", "icon": "🚀", "status": "done",    "badge": "Publié",    "badge_color": "#22c55e"},
        {"title": "v1.5 — Composants UI",   "date": "Juin 2025",  "icon": "🧩", "status": "done",    "badge": "Publié",    "badge_color": "#22c55e", "text": "Timeline, Leaderboard, Tag Input…"},
        {"title": "v2.0 — Message enrichi", "date": "Mars 2026",  "icon": "✨", "status": "active",  "badge": "En cours",  "badge_color": "#f59e0b", "text": "meta, sticker, code, banner…"},
        {"title": "v2.5 — Marketplace",     "date": "Été 2026",   "icon": "🛍️","status": "pending", "badge": "Prévu",     "badge_color": "#8b5cf6"},
        {"title": "v3.0 — IA native",       "date": "2027",       "icon": "🤖", "status": "pending", "badge": "Vision",    "badge_color": "#5865f2"},
    ]
}])
🔑 Composant

Clé-Valeur — key_value

Tableau propriétés/valeurs ultra-personnalisable : layout liste ou grille, lignes alternées, couleurs globales et par paire, icônes, compact mode, monospace.

📋
2 layouts

list vertical ou grid sur 2 colonnes pour afficher plus d'infos en moins d'espace.

🦓
Lignes alternées

striped: true — fond subtil sur les lignes paires pour faciliter la lecture en scan.

🎨
4 couleurs globales

key_color, value_color, background, border_color — palette complète.

🖌️
Couleur par paire

pair.color override individuel : mets en rouge les erreurs, en vert les succès.

💡
Valeur en gras

pair.bold_value: true met la valeur d'une paire spécifique en gras pour la mettre en avant.

😀
Icône par paire

pair.icon ajoute un emoji ou symbole devant la clé pour un scan visuel rapide.

📐
Mode compact

compact: true réduit le padding des lignes — idéal pour les tableaux denses avec 6+ paires.

💻
Monospace

monospace: true — police Consolas parfaite pour versions, hashes Git, adresses IP.

🧪 Démonstration interactive

Personnalise en temps réel
Options globales
Titre
Layout
StripedCompact
Monospace
Couleurs globales
FondBordure
CléValeur
Paire 1
Ic.Clé
Valeur
Paire 2
Ic.Clé
Valeur
Paire 3
Ic.Clé
Valeur
Paire 4
Ic.Clé
Valeur
Aperçu
JSON

      

Paramètres globaux

ParamètreTypeDéfautDescription
pairslist[]Liste de paires clé/valeur
titlestrEn-tête affiché en majuscule au-dessus du tableau
layoutstrlistlist (vertical) ou grid (2 colonnes côte à côte)
stripedboolfalseFond subtil sur les lignes paires pour la lisibilité
compactboolfalsePadding réduit — idéal pour afficher 6+ paires
monospaceboolfalsePolice Consolas pour clés et valeurs
backgroundhex/rgbargba(…,.04)Couleur de fond du tableau
border_colorhex/rgbargba(…,.08)Couleur des séparateurs et bordure externe
key_colorhexrgba(…,.45)Couleur du texte de toutes les clés
value_colorhexrgba(…,.85)Couleur globale des valeurs (remplacé par pair.color)

Paramètres par paire

ParamètreTypeDéfautDescription
keystrNom de la propriété (affiché à gauche)
valuestrValeur à afficher (à droite ou en-dessous)
iconstrEmoji/symbole préfixant la clé — ex: "🔴", "✅"
colorhexvalue_colorCouleur individuelle de la valeur (override global)
bold_valueboolfalseMet la valeur en gras (font-weight: 700)
💡 Bonnes pratiquespair.color s'applique seulement si aucun value_color global n'est défini. Pour des couleurs individuelles, laisse value_color vide. Combine compact: true + monospace: true pour les sorties techniques (logs, API).

Exemples

🖥️ Infos système avec striping
self.send_message(channel_id, components=[{
    "type": "key_value",
    "title": "🖥️ Infos système",
    "striped": True,
    "key_color": "#7070a0",
    "pairs": [
        {"key": "Version",  "value": "2.1.0",  "bold_value": True, "icon": "🔖"},
        {"key": "CPU",      "value": "42%",    "color": "#22c55e", "icon": "💻"},
        {"key": "Mémoire",  "value": "1.2 GB", "icon": "🧠"},
        {"key": "Uptime",   "value": "3j 14h", "icon": "⏱️"},
        {"key": "Statut",   "value": "OK",     "color": "#22c55e", "bold_value": True, "icon": "✅"},
    ]
}])
🎮 Stats joueur — grille 2 colonnes
self.send_message(channel_id, components=[{
    "type": "key_value",
    "title": "🎮 Stats de alice",
    "layout": "grid",
    "striped": True,
    "pairs": [
        {"key": "Parties",    "value": "142",      "icon": "🎯"},
        {"key": "Victoires",  "value": "89",        "color": "#22c55e", "bold_value": True, "icon": "🏆"},
        {"key": "Défaites",   "value": "53",        "color": "#ef4444", "icon": "💀"},
        {"key": "KD Ratio",   "value": "2.3",       "bold_value": True, "icon": "⚔️"},
        {"key": "Rang",       "value": "Diamant",   "color": "#60d9fa", "bold_value": True, "icon": "💎"},
        {"key": "Streak",     "value": "5 wins",    "color": "#f59e0b", "icon": "🔥"},
    ]
}])
🤖 Profil bot compact monospace
self.send_message(channel_id, components=[{
    "type": "key_value",
    "title": "🤖 Profil — ModBot",
    "compact": True,
    "monospace": True,
    "background": "#0a0a12",
    "border_color": "#8875f533",
    "key_color": "#8875f5",
    "pairs": [
        {"key": "ID",          "value": "bot_7f2a9c"},
        {"key": "Version",     "value": "v3.2.1",    "bold_value": True},
        {"key": "Latence",     "value": "14ms",       "color": "#22c55e"},
        {"key": "Serveurs",    "value": "1 247"},
        {"key": "Commandes",   "value": "42 680"},
        {"key": "Uptime",      "value": "99.94%",     "color": "#22c55e", "bold_value": True},
    ]
}])
📦 Dernier commit Git
self.send_message(channel_id, components=[{
    "type": "key_value",
    "title": "📦 Dernier commit — main",
    "compact": True,
    "monospace": True,
    "key_color": "#7070a0",
    "pairs": [
        {"key": "Hash",     "value": "a3ef508",    "bold_value": True},
        {"key": "Auteur",   "value": "alice"},
        {"key": "Date",     "value": "07/03 14:31"},
        {"key": "Branch",   "value": "main",       "color": "#22c55e"},
        {"key": "Message",  "value": "fix: timeline preset icons", "bold_value": True},
        {"key": "+/-",      "value": "+42 / -7",   "color": "#f59e0b"},
    ]
}])
🌍 Géolocalisation IP
self.send_message(channel_id, components=[{
    "type": "key_value",
    "title": "🌍 Géolocalisation — 91.198.174.192",
    "striped": True,
    "background": "#0a1015",
    "border_color": "#00d9a333",
    "key_color": "#00d9a3",
    "pairs": [
        {"key": "Pays",       "value": "🇫🇷 France",          "bold_value": True},
        {"key": "Ville",      "value": "Paris"},
        {"key": "FAI",        "value": "Wikimedia Foundation"},
        {"key": "Latitude",   "value": "48.8534",              "icon": "📍"},
        {"key": "Longitude",  "value": "2.3488",               "icon": "📍"},
        {"key": "Timezone",   "value": "Europe/Paris"},
    ]
}])
📝 Composant

Diff de code — code_diff

Affiche un diff Git coloré : lignes ajoutées/supprimées, numéros de ligne, stats, 3 thèmes visuels.

🟢
Diff coloré

Vert = ajout, rouge = suppression, bleu = hunk header, gris = contexte.

🔢
Numéros de ligne

show_line_numbers : numéros à gauche, style GitHub.

📊
Stats d'en-tête

show_stats : comptage +X -Y affiché dans l'en-tête du bloc.

🎨
3 thèmes

dark (défaut), github (fond clair), nord (bleu-gris).

🧪 Démonstration interactive

Personnalise en temps réel
En-tête
Fichier
Langage
Thème
Options
Hauteur max px
Contenu diff
Aperçu
JSON

      

Paramètres

ParamètreTypeDéfautDescription
diffstrContenu du diff (+=ajout, -=suppression, @@=hunk)
filenamestrNom du fichier affiché dans l'en-tête
languagestrÉtiquette du langage (ex: "python", "javascript")
themedark / github / norddarkThème visuel du bloc
show_line_numbersboolFalseAffiche les numéros de ligne à gauche
show_statsboolFalseAffiche le comptage +X -Y dans l'en-tête
max_heightint (px)Hauteur max du bloc (défilement si dépassé)
ℹ️
Syntaxe du champ diff
Chaque ligne préfixée par + = ajoutée, - = supprimée, @@ = header de hunk, espace ou rien = contexte. Compatible format git diff standard.

Exemples

Python — Diff simple
{"type": "code_diff", "filename": "app.py",
 "diff": "@@ -10,3 +10,4 @@\n def hello():\n-    print('world')\n+    print('LeNavire')\n+    return True"}
Python — Numéros de ligne, stats, thème github
{"type": "code_diff", "filename": "bot.js",
 "language": "javascript", "theme": "github",
 "show_line_numbers": True, "show_stats": True,
 "diff": "@@ -1,4 +1,5 @@\n const x = 1;\n-const y = 2;\n+const y = 3;\n+const z = 4;\n"}
💬 Composant

Message Riche — rich_message

6 styles de cartes riches — embed, notification, announcement, minimal, warning, success. Auteur + avatar, champs en grille, thumbnail, image pleine largeur, footer et timestamp. Le composant le plus complet et polyvalent du SDK LeNavire.

🎨
6 styles

embed, notification, announcement, minimal, warning, success.

🧩
Champs en grille

fields avec inline pour un affichage 2 colonnes flexible, jusqu'à 10 champs.

🗂️
Méta riche

Auteur + avatar, miniature, image principale, pied de page + timestamp.

🔗
Liens

title_url rend le titre cliquable. button_label/button_url en mode announcement.

Warning & Success

Styles pré-stylisés avec icône automatique, fond teinté et bordure colorée.

🖼️
Images

thumbnail (coin haut-droite) et image (largeur complète) intégrées dans l'embed.

📦
Mode compact

compact=True réduit marges et padding — idéal dans les sidebars et interfaces denses.

🤝
Combinable

Associe rich_message + button_row dans un même appel send_message pour des cards interactives.

🎨 Aperçu des styles

embed
LeNavire Bot
🚀 Déploiement v2.1
La version 2.1 est en ligne sur le serveur de production.
Environnement
Production
Version
2.1.0
Déployé via GitHub Actions14:32
notification
🔔
Maintenance prévue
Le bot sera hors ligne de 02h00 à 02h30.
Planifié · 01:45
announcement
🎉
Tournoi LeNavire ce soir !
Inscris-toi avant 20h. Lots exceptionnels !
S'inscrire
minimal
Connexion détectée
alice@example.com · 185.42.1.1 · Paris, FR
warning
⚠️
Erreur critique — Stripe
Service de paiement inaccessible depuis 03:12.
Incident #4821 · 03:17
success
Commande #2847 confirmée
Ton paiement a été accepté. Expédition sous 24h.
Merci pour ta confiance !

🧪 Démonstration interactive

Personnalise en temps réel
Scénarios prêts
Type & style
Style
Couleur
Contenu
Auteur
Icône auteur
Titre
Lien titre
Description
Icône
Footer
Icône footer
Timestamp
Lien auteur
Images (embed)
Miniature
Image
Bouton (announcement)
Label
URL
Couleur btn
Champs (embed) — clé / valeur / inline
Champ 1
Champ 2
Champ 3
Aperçu
JSON

      

📐 Anatomie d'un embed

LeNavire Bot
author / author_icon
🚀 Déploiement v2.1
title / title_url
La version 2.1 est désormais en production. Temps de build réduit de 40%.
description
Environnement
Production
Version
2.1.0
Durée build
1m 23s
fields[ ] — inline: True / False
Déployé via GitHub Actions 14:32 footer / timestamp
color bande latérale  ·  thumbnail coin haut-droite  ·  image pleine largeur sous la description
Légende
Auteur
author · author_icon · author_url
Titre
title · title_url
Description
description
Champs
fields [{name, value, inline}]
Images
thumbnail · image
Footer
footer · footer_icon · timestamp

Paramètres

ParamètreTypeDéfautDescription
stylestrembedembed / notification / announcement / minimal / warning / success
colorhex#8875f5Couleur d'accent — bande latérale (embed), fond teinté (notification/warning/success)
titlestrTitre principal
title_urlstrRend le titre cliquable (lien hypertexte)
descriptionstrCorps du message (supporte le Markdown de base)
authorstrLigne auteur au-dessus du titre
author_iconurlAvatar circulaire à côté du nom d'auteur
author_urlurlLien sur le nom d'auteur
fieldslist[]Liste de {name, value, inline} — jusqu'à 10 champs en grille 2 col
thumbnailurlPetite image coin haut-droite (mode embed)
imageurlGrande image pleine largeur sous la description
footerstrPied de page (texte)
footer_iconurlIcône ronde dans le pied de page
timestampstrHorodatage affiché à droite du footer (ex: "14:32", "now", ou ISO 8601)
iconemoji/strEmoji principal (notification, announcement, warning, success)
button_labelstrTexte du bouton d'action (mode announcement)
button_urlstrURL du bouton
button_colorhexCouleur personnalisée du bouton announcement
compactboolfalseRéduit les marges internes pour les espaces contraints

💡 Quand utiliser quel style ?

💬 embed
✅ Idéal pour :
  • Résultats CI/CD, déploiements
  • Rapports multi-métriques avec champs
  • Profils utilisateur, résumés d'entité
🔑 fields, author, thumbnail, title_url
🔔 notification
✅ Idéal pour :
  • Alertes basse priorité, rappels
  • Nouveaux messages, événements
  • Maintenance planifiée, mises à jour
🔑 icon, title, description, footer
📢 announcement
✅ Idéal pour :
  • Événements communautaires, concours
  • Nouvelles fonctionnalités, releases
  • Messages avec bouton d'action clair
🔑 icon, button_label, button_url, button_color
➖ minimal
✅ Idéal pour :
  • Logs, audit trail, historique
  • Connexions / déconnexions
  • Actions courtes ne nécessitant pas de détails
🔑 title, description, color
⚠️ warning
✅ Idéal pour :
  • Erreurs critiques, services down
  • Incidents, timeouts, sécurité
  • Actions irréversibles à confirmer
🔑 icon, title, description, footer, timestamp
✅ success
✅ Idéal pour :
  • Confirmations de paiement, commandes
  • Uploads réussis, actions complétées
  • Bienvenues, inscriptions validées
🔑 icon, title, description, footer

🔗 Combiner avec d'autres composants

Le composant rich_message peut être placé avec d'autres composants dans le même appel send_message.

Python — embed + button_row
# rich_message + boutons d'action dans le même message
self.send_message(channel_id, components=[
    {
        "type": "rich_message",
        "style": "embed",
        "color": "#8875f5",
        "title": "🚀 Nouvelle version disponible",
        "description": "LeNavire SDK v3.0 est disponible. Découvre les nouveautés.",
        "fields": [
            {"name": "Version",    "value": "3.0.0",        "inline": True},
            {"name": "Breaking ?", "value": "Non",          "inline": True},
            {"name": "Highlights", "value": "Dark mode, perf +40%, API simplifiée", "inline": False},
        ],
        "footer": "Publié le 7 mars 2026"
    },
    {
        "type": "button_row",
        "buttons": [
            {"label": "📋 Changelog", "id": "changelog", "style": "primary"},
            {"label": "⬇️ Mettre à jour", "id": "update",    "style": "success"},
            {"label": "❌ Ignorer",     "id": "dismiss",   "style": "secondary"}
        ]
    }
])
Python — on_member_join avec announcement
def on_member_join(self, user):
    welcome = self.get_channel_by_name("bienvenue")
    if not welcome:
        return
    self.send_message(welcome["id"], components=[{
        "type": "rich_message",
        "style": "announcement",
        "color": "#10d9a3",
        "icon": "👋",
        "title": f"Bienvenue {user['username']} !",
        "description": "Tu viens de rejoindre LeNavire. Présente-toi dans #général !",
        "button_label": "📖 Règlement",
        "button_url": "https://lenavire.app/rules",
        "button_color": "#10d9a3"
    }])
Python — diffuser un statut sur plusieurs channels
# Envoyer le même rapport sur plusieurs channels (status, alertes...)
STATUS_CHANNELS = ["général", "ops", "monitoring"]

status_color = {"ok": "#22c55e", "warn": "#f59e0b", "error": "#ef4444"}
status_style = {"ok": "success", "warn": "notification", "error": "warning"}

def broadcast_status(self, level="ok", message=""):
    for name in STATUS_CHANNELS:
        ch = self.get_channel_by_name(name)
        if ch:
            self.send_message(ch["id"], components=[{
                "type": "rich_message",
                "style": status_style[level],
                "color": status_color[level],
                "title": f"Statut [{level.upper()}]",
                "description": message,
                "timestamp": "now"
            }])

⚡ Bonnes pratiques

💡 Préfère title_url à un bouton séparé pour les liens simples — ça garde la carte épurée et évite un button_row supplémentaire.
💡 Utilise compact=True dans les sidebars, listes d'activité ou any interface qui affiche plusieurs messages d'affilée — le rendu reste propre sans surcharger l'espace vertical.
⚠️ Limite les fields à 6 maximum en pratique — techniquement 10 sont supportés, mais au-delà la grille 2 colonnes perd en lisibilité. Regroupe les données dans la description si nécessaire.
⚠️ Réserve announcement aux messages user-facing (annonces, concours). Pour les alertes système ou erreurs techniques, utilise warning — ça communique la bonne sémantique visuellement.
🎯 Cohérence des couleurs — choisis une couleur par type de notification et garde-la. Ex: bleu #3b82f6 pour les infos, orange #f59e0b pour les warnings légers, rouge #ef4444 pour les critiques. Les utilisateurs apprennent vite à lire la couleur avant le texte.
🎯 Ajoute toujours un timestamp sur les messages liés à un événement daté (incident, déploiement, rapport) — ça évite la confusion sur "quand ça s'est passé" dans un long historique de messages.

📝 Exemples complets

Python — embed Discord-like
self.send_message(channel_id, components=[{
    "type": "rich_message",
    "style": "embed",
    "color": "#8875f5",
    "author": "LeNavire Bot",
    "title": "🚀 Déploiement v2.1 réussi",
    "description": "La version 2.1 est en ligne sur le serveur de production.",
    "fields": [
        {"name": "Environnement", "value": "Production", "inline": True},
        {"name": "Version",       "value": "2.1.0",       "inline": True},
        {"name": "Durée build",  "value": "1m 23s",      "inline": False},
    ],
    "footer": "Déployé via GitHub Actions",
    "timestamp": "14:32"
}])
Python — notification
self.send_message(channel_id, components=[{
    "type": "rich_message",
    "style": "notification",
    "color": "#f59e0b",
    "icon": "🔔",
    "title": "Maintenance prévue",
    "description": "Le bot sera hors ligne de 02h00 à 02h30.",
    "footer": "Planifié",
    "timestamp": "01:45"
}])
Python — announcement + bouton
self.send_message(channel_id, components=[{
    "type": "rich_message",
    "style": "announcement",
    "color": "#10d9a3",
    "icon": "🎉",
    "title": "Tournoi LeNavire ce soir !",
    "description": "Inscris-toi avant 20h. Lots exceptionnels à gagner !",
    "button_label": "S'inscrire",
    "button_url": "https://lenavire.app/tournoi",
    "button_color": "#10d9a3"
}])
Python — warning critique
self.send_message(channel_id, components=[{
    "type": "rich_message",
    "style": "warning",
    "color": "#ef4444",
    "icon": "⚠️",
    "title": "Erreur critique — Stripe inaccessible",
    "description": "Le service de paiement répond en timeout depuis 03:12 UTC.",
    "footer": "Incident #4821",
    "timestamp": "03:17"
}])
Python — success
self.send_message(channel_id, components=[{
    "type": "rich_message",
    "style": "success",
    "color": "#22c55e",
    "icon": "✅",
    "title": "Commande #2847 confirmée",
    "description": "Ton paiement a été accepté. Expédition sous 24h.",
    "footer": "Merci pour ta confiance !"
}])
Python — embed rapport
self.send_message(channel_id, components=[{
    "type": "rich_message",
    "style": "embed",
    "color": "#f59e0b",
    "author": "Système de monitoring",
    "author_icon": "https://example.com/bot.png",
    "title": "📊 Rapport journalier",
    "title_url": "https://example.com/dashboard",
    "description": "Résumé des métriques des dernières 24 heures.",
    "fields": [
        {"name": "Requêtes",    "value": "48 201",      "inline": True},
        {"name": "Erreurs",     "value": "3 (0.006%)", "inline": True},
        {"name": "Uptime",      "value": "99.98%",     "inline": True},
        {"name": "Latence P99", "value": "42ms",       "inline": True},
        {"name": "Détails", "value": "Consulte le dashboard pour les graphiques complets.", "inline": False},
    ],
    "footer": "Généré automatiquement",
    "timestamp": "now"
}])
Python — minimal
self.send_message(channel_id, components=[{
    "type": "rich_message",
    "style": "minimal",
    "color": "#8875f5",
    "title": "Connexion détectée",
    "description": "alice@example.com · 185.42.1.1 · Paris, FR"
}])
Python — embed compact
# compact=True réduit marges + padding pour espaces contraints
self.send_message(channel_id, components=[{
    "type": "rich_message",
    "style": "embed",
    "color": "#6366f1",
    "compact": True,
    "author": "Bot",
    "title": "Mise à jour disponible",
    "description": "v3.0.0 est disponible.",
    "fields": [{"name": "Nouveautés", "value": "Dark mode, perf +40%", "inline": False}],
    "footer": "Changelog complet sur GitHub"
}])
Python — embed avec image pleine largeur
# thumbnail = coin haut-droite (petit)  |  image = pleine largeur sous la description
self.send_message(channel_id, components=[{
    "type": "rich_message",
    "style": "embed",
    "color": "#ec4899",
    "author": "LeNavire Design",
    "author_icon": "https://lenavire.app/assets/design-bot.png",
    "title": "🎨 Nouvelles illustrations disponibles",
    "description": "Le pack Saison 2 vient d'être mis en ligne. 42 nouveaux visuels haute résolution.",
    "thumbnail": "https://lenavire.app/assets/pack-thumb.png",
    "image": "https://lenavire.app/assets/pack-banner.png",
    "footer": "Design by @lenaart",
    "timestamp": "now"
}])
Python — embed lien auteur + lien titre cliquable
# author_url rend le nom d'auteur cliquable  |  title_url rend le titre cliquable
self.send_message(channel_id, components=[{
    "type": "rich_message",
    "style": "embed",
    "color": "#6366f1",
    "author": "@lenavire",
    "author_url": "https://github.com/lenavire",
    "author_icon": "https://lenavire.app/assets/github.png",
    "title": "Pull Request #142 mergée",
    "title_url": "https://github.com/lenavire/sdk/pull/142",
    "description": "feat: ajout du composant rich_message v2 avec 6 styles.",
    "fields": [
        {"name": "Branche",     "value": "main ← feat/rich-msg",  "inline": True},
        {"name": "Reviewers",   "value": "@alice, @bob",          "inline": True},
        {"name": "Commits",     "value": "8 commits, +423 / -71", "inline": False},
    ],
    "timestamp": "now"
}])
Python — icône footer + compact dans une liste d'activité
# footer_icon + compact=True : parfait pour afficher une liste d'événements
EVENTS = [
    ("alice", "A ouvert un ticket",   "🎫", "14:01"),
    ("bob",   "A fusionné la PR #142", "✅", "14:12"),
    ("bot",   "Déploiement lancé",     "🚀", "14:13"),
]

for (user, action, icon, ts) in EVENTS:
    self.send_message(log_channel_id, components=[{
        "type": "rich_message",
        "style": "minimal",
        "color": "#8875f5",
        "compact": True,
        "title": f"{icon} {action}",
        "description": f"Par @{user}",
        "footer_icon": f"https://lenavire.app/avatar/{user}.png",
        "footer": user,
        "timestamp": ts
    }])
✨ Messages riches

Message enrichi — meta

Enrichis n'importe quel message avec des métadonnées visuelles puissantes : titre coloré, priorité animée, thread, tags, catégorie, TTS, éphémère, épinglé, silent, code intégré, multi-embed, compte à rebours — tout ça directement dans send_message().

🏷️
Titre + couleur

title + msg_color — en-tête stylé avec bande colorée gauche.

Priorité

low, high, urgent — badge rouge animé en mode urgent.

🔊
Synthèse vocale

tts=True ajoute un bouton 🔊 pour lire le message à voix haute.

🎭
4 schemes visuels

glass, neon, dark, light — style global du message.

🧵
Thread + tags

thread_title, category, tags organisent visuellement le contenu.

👻
Éphémère & silencieux

ephemeral, silent, delete_after, pin — contrôle visibilité et durée de vie.

🧪 Démonstration interactive

Personnalise en temps réel
Scénarios prêts
Contenu
Message
Titre
Footer
Couleur
Style & badges
Priorité
Scheme
Catégorie
Thread
Tags
Options
Suppr. après (s)
Mentions & Réactions
Mention
Réactions
Réponse à
Visuels avancés
Banner URL
Sticker URL
Bloc de code intégré
Langage
Contenu
Planification
Expire le
Prévu le
Aperçu
Généré

      

Paramètres de send_message

ParamètreTypeDescription
titlestrTitre stylé au-dessus du contenu
msg_colorhexCouleur de la bordure gauche du message
stickerurlGrande image décorative (128 px) après le texte
codedict{"language": "py", "content": "..."} — bloc de code
ttsboolBouton 🔊 pour lire le message à voix haute
embedslistListe d'embeds supplémentaires (multi-embed)
reply_tointID d'un message cité (affiche un aperçu au-dessus)
mentionstrUsername mis en évidence dans le message
delete_afterint (sec)Auto-suppression après N secondes (badge décompte)
ephemeralboolMessage discret (opacité réduite + 👻 notice)
footer_textstrTexte gris en pied de message
thread_titlestrBadge 🧵 avec titre de fil
prioritylow/high/urgentBadge coloré (urgent = animation pulsante)
reactions_presetlistEmojis suggérés à cliquer sous le message
— PARAMÈTRES AVANCÉS —
banner_urlurlImage pleine largeur (100px) en haut du message
tagslistListe de tags affichés sous le titre (ex: ["python","api"])
categorystrBadge de catégorie teal (ex: "Annonce")
color_schemeglass/neon/dark/lightStyle visuel global du message
expires_atISO str / timestampCompte à rebours en direct ⏳ jusqu'à expiration
silentboolBadge 🔕 Silencieux — sans notification
pinboolBadge 📌 Épinglé
scheduled_atISO strBadge 📅 date/heure de publication planifiée

⚡ Bonnes pratiques

💡 Combine title + msg_color systématiquement — identifier visuellement l'origine d'un message en un coup d'œil, sans lire le contenu.
⚠️ Réserve priority="urgent" aux vraies urgences — le badge clignote et attire immédiatement l'attention. Si tout est urgent, plus rien ne l'est.
💡 Utilise delete_after + ephemeral=True ensemble pour les messages d'erreur ou d'aide : ils disparaissent automatiquement et ne polluent pas l'historique.
🎯 color_scheme="neon" avec msg_color coordonné donne un rendu très impactant pour les annonces — mais utilise-le avec parcimonie pour garder l'effet "wow".
🎯 Les tags sont cliquables côté client — nomme-les en lowercase sans espaces pour une cohérence optimale dans les filtres de l'UI.
⚠️ Ne combine pas embeds et components dans le même appel — les embeds enrichissent le message texte, les components sont pour les cartes interactives.

📝 Exemples

Python — titre + bordure colorée
# Titre stylé + bande colorée gauche + tags + épinglé
self.send_message(channel_id,
    "Voici les dernières nouvelles du serveur.",
    title="📰 Annonce",
    msg_color="#f59e0b",
    category="Annonce",
    tags=["news", "important"],
    pin=True
)
Python — message urgent animé
# Badge URGENT clignotant + footer + TTS
self.send_message(channel_id,
    "⚠️ Le serveur redémarre dans 5 minutes ! Sauvegardez vos données.",
    priority="urgent",
    msg_color="#ef4444",
    footer_text="Maintenance planifiée · 22:00 UTC",
    tts=True
)
Python — message éphémère auto-supprimé
# Visible uniquement par le destinataire, supprimé après 10s
self.send_message(channel_id,
    "Cette commande n'existe pas. Essaie /help pour voir toutes les commandes.",
    ephemeral=True,
    delete_after=10
)
Python — mention + réactions suggérées
# Féliciter un utilisateur avec mention et réactions prédéfinies
self.send_message(channel_id,
    "Bravo pour ta contribution sur la PR #142 !",
    mention="alice",
    reactions_preset=["🎉", "🔥", "👏", "❤️"]
)
Python — thread + bloc de code intégré
# Discussion avec code Python intégré dans le message
self.send_message(channel_id,
    "Voici le résultat de l'analyse :",
    thread_title="Discussion #42",
    priority="low",
    code={
        "language": "python",
        "content": "score = analyse_logs()\nprint(f'Score: {score:.2f}')"
    }
)
Python — multi-embed dans un seul message
# Résumé multi-sections dans un seul message
self.send_message(channel_id,
    "Résumé de la journée :",
    title="📋 Daily recap",
    msg_color="#6366f1",
    embeds=[
        {"title": "🌅 Matin",     "description": "Réunion standup à 9h30 — 5 participants.", "color": "#3b82f6"},
        {"title": "☀️ Après-midi", "description": "Code review + 3 PRs mergées.",             "color": "#f59e0b"},
        {"title": "🌙 Soir",       "description": "Démo client à 18h — feedback positif.",    "color": "#8b5cf6"},
    ]
)
Python — banner + tags + scheme neon
# Release avec bannière image, tags et scheme neon
self.send_message(channel_id,
    "Nouvelle version disponible — mets à jour maintenant !",
    title="🚀 v2.1 publiée",
    banner_url="https://example.com/release-banner.png",
    category="Release",
    tags=["python", "api", "v2"],
    color_scheme="neon",
    msg_color="#00d9a3",
    priority="high"
)
Python — épinglé avec compte à rebours
# Offre limitée : épinglée, expire dans 1 heure avec compte à rebours en direct
import datetime
exp = (datetime.datetime.utcnow() + datetime.timedelta(hours=1)).isoformat()

self.send_message(channel_id,
    "🎁 Offre spéciale — durée limitée !",
    title="🎯 Deal exclusif",
    pin=True,
    priority="high",
    expires_at=exp,
    msg_color="#f59e0b"
)
Python — rapport silencieux automatique
# Rapport nocturne : silencieux, dark scheme, low priority
self.send_message(channel_id,
    "📊 Rapport nocturne généré automatiquement.",
    title="Rapport 03:00",
    msg_color="#3b82f6",
    priority="low",
    color_scheme="dark",
    category="Rapport",
    tags=["rapport", "auto"],
    footer_text="Généré automatiquement",
    silent=True
)
Python — combinaison maximale
# Tous les paramètres avancés combinés
self.send_message(channel_id,
    "Voici un résumé complet avec toutes les options activées.",
    title="🎨 Message ultra-enrichi",
    msg_color="#8875f5",
    priority="high",
    color_scheme="glass",
    category="Demo",
    tags=["test", "showcase"],
    thread_title="Discussion ouverte",
    footer_text="LeNavire SDK · v2.1",
    tts=True,
    reactions_preset=["🚀", "🎉", "💜"]
)
⚡ Interactions

📡 Interactions — référence complète

Chaque clic, sélection, saisie ou timer déclenche une interaction. Toutes arrivent dans on_button_click avec un champ type — ou dans des méthodes dédiées si tu les définis.

🖱️
Boutons & clics
button_click, confirm_accept, confirm_decline
📋
Menus & saisies
select_menu, dropdown_select, input_submit, number_input_change
⏱️
Timers & events
timer_expired, poll_vote, rating_submit, slider_change

L'objet interaction

CléTypeDescription
typestrType d'interaction — voir tableau complet ci-dessous
custom_idstrIdentifiant du composant défini dans ton code
valueslist | NoneDonnées de l'interaction (option choisie, texte saisi, fichier…)
message_idintID du message contenant le composant
channel_idintID du salon où l'interaction a eu lieu
server_idintID du serveur
userdict{"id": int, "username": str, "is_bot": bool} — qui a interagi

Tous les types d'interaction

TypeComposant sourcevaluesDescription
🖱️ CLICS
button_clickbuttonUn bouton a été cliqué
confirm_acceptconfirmL'utilisateur a confirmé
confirm_declineconfirmL'utilisateur a annulé
📋 MENUS & SAISIES
select_menuselect_menu[valeur_choisie]Option sélectionnée dans un menu
dropdown_selectdropdown[valeur_option]Option choisie dans un dropdown
input_submitinput_field[texte_saisi]Texte soumis par l'utilisateur
number_input_changenumber_input[valeur]Valeur numérique modifiée
color_pickcolor_picker["#rrggbb"]Couleur sélectionnée
date_pickdate_picker["YYYY-MM-DDTHH:MM"]Date/heure sélectionnée
checklist_changechecklist[val1, val2, …]Liste des cases cochées
tag_addtag_input[tag_ajouté]Un tag a été ajouté
tag_removetag_input[tag_retiré]Un tag a été supprimé
file_uploadfile_upload[nom, mime, taille]Fichier envoyé par l'utilisateur
⏱️ TIMERS & EVENTS
timer_expiredtimerLe timer a atteint zéro
slider_changeslider[valeur_float]Curseur déplacé
rating_submitrating[note_int]Note soumise (1–5 étoiles)
poll_votepoll[index_option]Vote dans un sondage

Méthodes disponibles

Il y a deux façons de gérer les interactions : un handler universel, ou des méthodes dédiées par type.

ℹ️
Priorité des méthodes
Si ton bot définit on_select_menu ou on_timer_expired, ces méthodes sont appelées en priorité. Sinon, tout passe par on_button_click.

Option A — Handler universel

Python
def on_button_click(self, interaction):
    itype = interaction.get('type')
    ch    = interaction.get('channel_id')
    cid   = interaction.get('custom_id')
    vals  = interaction.get('values') or []
    user  = interaction.get('user', {}).get('username', '?')

    if   itype == 'button_click':        self.send_message(ch, f"🖱️ {user} a cliqué sur `{cid}`")
    elif itype == 'confirm_accept':      self.send_message(ch, "✅ Confirmé !")
    elif itype == 'confirm_decline':     self.send_message(ch, "❌ Annulé.")
    elif itype == 'select_menu':         self.send_message(ch, f"📋 Sélection : {vals[0]}")
    elif itype == 'dropdown_select':     self.send_message(ch, f"📋 Menu → {vals[0]}")
    elif itype == 'input_submit':        self.send_message(ch, f"✏️ Texte reçu : {vals[0]}")
    elif itype == 'number_input_change': self.send_message(ch, f"🔢 Valeur : {vals[0]}")
    elif itype == 'color_pick':          self.send_message(ch, f"🎨 Couleur : {vals[0]}")
    elif itype == 'date_pick':           self.send_message(ch, f"📅 Date : {vals[0]}")
    elif itype == 'slider_change':       self.send_message(ch, f"🎚️ Curseur : {vals[0]}")
    elif itype == 'rating_submit':       self.send_message(ch, f"⭐ Note : {vals[0]}/5")
    elif itype == 'poll_vote':           self.send_message(ch, f"📊 Vote option #{vals[0]}")
    elif itype == 'checklist_change':    self.send_message(ch, f"✅ Cochés : {', '.join(vals)}")
    elif itype == 'tag_add':             self.send_message(ch, f"🏷️ Tag ajouté : {vals[0]}")
    elif itype == 'tag_remove':          self.send_message(ch, f"🏷️ Tag retiré : {vals[0]}")
    elif itype == 'file_upload':         self.send_message(ch, f"📎 Fichier : {vals[0]} ({vals[2]} octets)")
    elif itype == 'timer_expired':       self.send_message(ch, "⏰ Timer terminé !")

Option B — Méthodes dédiées (prioritaires)

Si tu définis ces méthodes, elles sont appelées directement au lieu de passer par on_button_click.

Python
def on_select_menu(self, interaction):
    """Appelée pour select_menu — prioritaire sur on_button_click."""
    ch   = interaction['channel_id']
    vals = interaction.get('values', [])
    self.send_message(ch, f"Tu as sélectionné : **{vals[0]}**")

def on_timer_expired(self, interaction):
    """Appelée quand un timer atteint zéro — prioritaire sur on_button_click."""
    ch  = interaction['channel_id']
    cid = interaction.get('custom_id')
    self.send_message(ch, f"⏰ Timer `{cid}` terminé ! Action déclenchée.")

Accéder à l'utilisateur

Chaque interaction contient un objet user avec les infos de qui a interagi.

Python
def on_button_click(self, interaction):
    user     = interaction.get('user', {})
    user_id  = user.get('id')
    username = user.get('username', 'Inconnu')
    is_bot   = user.get('is_bot', False)
    ch       = interaction['channel_id']

    if is_bot:
        return  # ignorer les bots

    self.send_message(ch, f"Bonjour **{username}** (id={user_id}) !")

Répondre avec un composant mis à jour

Après une interaction, tu peux envoyer un nouveau message avec des composants actualisés (ex: désactiver un bouton, changer un score).

Python
def on_button_click(self, interaction):
    ch  = interaction['channel_id']
    cid = interaction.get('custom_id')

    if cid == 'vote_oui':
        # Mettre à jour l'état interne
        self.votes = getattr(self, 'votes', 0) + 1
        # Renvoyer un message mis à jour
        self.send_message(ch, f"👍 {self.votes} vote(s) pour OUI !", components=[
            {"type": "button", "label": f"👍 OUI ({self.votes})", "custom_id": "vote_oui", "style": "success"},
            {"type": "button", "label": "👎 NON",            "custom_id": "vote_non", "style": "danger"}
        ])

Utiliser l'état entre interactions

Les attributs self.* persistent entre les appels — parfait pour stocker un score, une étape de jeu, etc.

Python
class MonBot:
    def __init__(self):
        self.etape   = 0       # étape courante
        self.scores  = {}      # {user_id: score}
        self.actif   = False  # partie en cours ?

    def on_button_click(self, interaction):
        cid  = interaction.get('custom_id')
        ch   = interaction['channel_id']
        uid  = interaction['user']['id']

        if cid == 'start':
            self.actif = True
            self.etape = 1
            self.send_message(ch, "🎮 Partie lancée ! Étape 1…")

        elif cid == 'bonne_rep' and self.actif:
            self.scores[uid] = self.scores.get(uid, 0) + 10
            self.etape += 1
            self.send_message(ch, f"✅ +10 pts ! Score total : {self.scores[uid]}")

Filtrer par custom_id avec préfixe

Pour gérer beaucoup de boutons proprement, utilise des préfixes dans les custom_id.

Python
def on_button_click(self, interaction):
    cid = interaction.get('custom_id', '')
    ch  = interaction['channel_id']

    # Préfixe "quiz_" → question de quiz
    if cid.startswith('quiz_'):
        rep = cid.split('_', 1)[1]
        self.send_message(ch, f"Tu as répondu : {rep}")

    # Préfixe "nav_" → navigation entre pages
    elif cid.startswith('nav_'):
        page = cid.split('_', 1)[1]
        self._show_page(ch, page)

    # Préfixe "action_" → action générique
    elif cid.startswith('action_'):
        action = cid.removeprefix('action_')
        self._handle_action(ch, action)
⚠️
Éviter les boucles infinies
Si ton bot envoie un message avec des composants en réponse à une interaction, et qu'il reçoit aussi ses propres messages via on_message — filtre avec if message.get('author',{}).get('is_bot'): return.
💡
Astuce : identifier l'interaction
Combine type + custom_id pour un routage ultra-précis. Par exemple type == 'button_click' and custom_id == 'confirm_delete' ne se déclenchera que sur CE bouton précis.

🧪 Simulateur d'interaction

Sélectionne un type et un custom_id pour voir à quoi ressemble l'objet interaction reçu par ton bot.

Simulateur interactifLIVE
Paramètres
Type
custom_id
Username
User ID
Channel ID
Message ID
Valeur
Objet reçu dans on_button_click

        
Code de traitement généré

      

🧩 Patterns avancés

Routeur par type

Centralise la logique avec un dictionnaire de handlers — plus lisible qu'une longue chaîne if/elif.

Python — Routeur
class RouterBot:
    def on_ready(self):
        self.routes = {
            'button_click':        self._on_btn,
            'select_menu':         self._on_select,
            'input_submit':        self._on_input,
            'rating_submit':       self._on_rating,
            'timer_expired':       self._on_timer,
            'file_upload':         self._on_file,
        }

    def on_button_click(self, interaction):
        handler = self.routes.get(interaction.get('type'))
        if handler:
            handler(interaction)

    def _on_btn(self, i):
        self.send_message(i['channel_id'], f"🖱️ Bouton : {i['custom_id']}")

    def _on_select(self, i):
        self.send_message(i['channel_id'], f"📋 Choix : {i['values'][0]}")

    def _on_input(self, i):
        self.send_message(i['channel_id'], f"✏️ Texte : {i['values'][0]}")

    def _on_rating(self, i):
        stars = "⭐" * int(i['values'][0])
        self.send_message(i['channel_id'], f"Note : {stars}")

    def _on_timer(self, i):
        self.send_message(i['channel_id'], "⏰ Temps écoulé !")

    def _on_file(self, i):
        nom, mime, taille = i['values'][:3]
        self.send_message(i['channel_id'], f"📎 {nom} ({taille} octets)")

Confirmation à 2 étapes

Demande d'abord une confirmation avant d'exécuter une action irréversible.

Python — Confirm à 2 étapes
class SafeBot:
    def on_ready(self):
        self.pending = {}  # {user_id: action}

    def on_message(self, message):
        ch  = message['channel_id']
        uid = message['author']['id']
        if message.get('content','').strip() == '!reset':
            self.pending[uid] = 'reset_scores'
            self.send_message(ch, "⚠️ Confirmer la remise à zéro ?", components=[
                {"type": "button", "label": "✅ Confirmer", "custom_id": "do_confirm", "style": "danger"},
                {"type": "button", "label": "❌ Annuler",   "custom_id": "do_cancel",  "style": "secondary"},
            ])

    def on_button_click(self, interaction):
        ch  = interaction['channel_id']
        cid = interaction.get('custom_id')
        uid = interaction['user']['id']

        if cid == 'do_confirm':
            action = self.pending.pop(uid, None)
            if action == 'reset_scores':
                self.scores = {}
                self.send_message(ch, "🗑️ Scores remis à zéro !")
        elif cid == 'do_cancel':
            self.pending.pop(uid, None)
            self.send_message(ch, "↩️ Action annulée.")

Anti-spam par utilisateur

Limite la fréquence d'interactions par utilisateur avec un cooldown en secondes.

Python — Cooldown
import time

class CooldownBot:
    COOLDOWN = 5  # secondes entre chaque interaction

    def on_ready(self):
        self.last_action = {}  # {user_id: timestamp}

    def _check_cooldown(self, uid, ch) -> bool:
        now  = time.time()
        last = self.last_action.get(uid, 0)
        reste = self.COOLDOWN - (now - last)
        if reste > 0:
            self.send_message(ch, f"⏳ Attends encore {reste:.1f}s !")
            return False
        self.last_action[uid] = now
        return True

    def on_button_click(self, interaction):
        uid = interaction['user']['id']
        ch  = interaction['channel_id']
        if not self._check_cooldown(uid, ch):
            return
        # logique principale ici…
        self.send_message(ch, "✅ Action effectuée !")

Middleware de log

Enregistre chaque interaction dans un historique pour débugger ou auditer.

Python — Log d'interactions
import datetime

class AuditBot:
    def on_ready(self):
        self.log = []  # liste des 100 dernières interactions

    def _log(self, interaction):
        entry = {
            'at':    datetime.datetime.now().strftime("%H:%M:%S"),
            'type':  interaction.get('type'),
            'cid':   interaction.get('custom_id'),
            'user':  interaction.get('user',{}).get('username'),
            'vals':  interaction.get('values'),
        }
        self.log.append(entry)
        if len(self.log) > 100:
            self.log.pop(0)

    def on_button_click(self, interaction):
        self._log(interaction)
        ch  = interaction['channel_id']
        cid = interaction.get('custom_id')

        if cid == 'show_log':
            lines = [f"[{e['at']}] {e['type']} · {e['user']} · {e['cid']}" for e in self.log[-5:]]
            self.send_message(ch, "📋 Dernières interactions :\n" + "\n".join(lines))

Machine à états (wizard multi-étapes)

Guide l'utilisateur étape par étape avec des composants différents à chaque étape.

Python — Wizard
class WizardBot:
    def on_ready(self):
        self.state  = {}  # {user_id: {step, data}}

    def on_message(self, message):
        ch  = message['channel_id']
        uid = message['author']['id']
        if message.get('content','').strip() == '!inscription':
            self.state[uid] = {'step': 1, 'data': {}}
            self.send_message(ch, "📝 Étape 1/3 — Choisis ton rôle :", components=[
                {"type":"select_menu", "custom_id":"wiz_role",
                 "options":[{"label":"Développeur","value":"dev"},{"label":"Designer","value":"design"},{"label":"Manager","value":"mgr"}]}
            ])

    def on_button_click(self, interaction):
        ch  = interaction['channel_id']
        cid = interaction.get('custom_id')
        uid = interaction['user']['id']
        s   = self.state.get(uid)

        if not s: return

        if cid == 'wiz_role' and s['step'] == 1:
            s['data']['role'] = interaction['values'][0]
            s['step'] = 2
            self.send_message(ch, "📝 Étape 2/3 — Entre ton pseudo :", components=[
                {"type":"input_field", "custom_id":"wiz_pseudo", "placeholder":"Ton pseudo…"}
            ])

        elif cid == 'wiz_pseudo' and s['step'] == 2:
            s['data']['pseudo'] = interaction['values'][0]
            s['step'] = 3
            role   = s['data']['role']
            pseudo = s['data']['pseudo']
            self.send_message(ch, f"✅ Inscription terminée ! Rôle : **{role}** · Pseudo : **{pseudo}**")
            del self.state[uid]

Interaction conditionnelle par rôle

Restreins certaines actions aux utilisateurs ayant un rôle ou un niveau donné.

Python — Contrôle d'accès
class RoleBot:
    ADMINS = {1001, 1002}  # user IDs autorisés

    def on_button_click(self, interaction):
        ch  = interaction['channel_id']
        cid = interaction.get('custom_id')
        uid = interaction['user']['id']

        if cid == 'admin_reset':
            if uid not in self.ADMINS:
                self.send_message(ch, "🚫 Accès refusé — réservé aux admins.")
                return
            # action admin sécurisée
            self.send_message(ch, "🔑 Reset effectué par admin.")

        elif cid == 'user_action':
            # accessible à tout le monde
            self.send_message(ch, "👤 Action utilisateur OK !")
💡
Récapitulatif des bonnes pratiques
1. Toujours vérifier is_bot pour éviter les boucles.
2. Utiliser des préfixes dans les custom_id pour grouper les interactions.
3. Stocker l'état dans self.* — persistant pendant la session, perdu au redémarrage.
4. Définir des méthodes dédiées (on_select_menu, etc.) pour un code plus propre.
5. Ajouter un cooldown anti-spam sur les boutons publics.
6. Toujours répondre à une interaction — un silence est confusant pour l'utilisateur.
🐍 Avancé

Liberté totale

Ton code Python s'exécute côté serveur. Tu peux importer des modules, répondre à n'importe quel trigger, ou ignorer les messages à ta guise.

🕊️
Liberté totale
Le code s'exécute en Python réel côté serveur. Tu n'es pas limité à des commandes prédéfinies — n'importe quelle logique Python est possible.

N'importe quel trigger

Python — Divers déclencheurs
def on_message(self, message):
    clean = message.get('content', '').lower().strip()
    channel = message.get('channel_id')

    # Commande exacte
    if clean == '!ping': ...

    # Contient un mot-clé
    elif 'merci' in clean:
        self.send_message(channel, "De rien ! 😊")

    # Commence par un préfixe
    elif clean.startswith('!meteo '):
        ville = clean[8:]
        self.send_message(channel, f"🌤️ Météo de {ville} : beau temps !")

    # Tous les messages (attention aux boucles !)
    elif not message.get('author',{}).get('is_bot'):
        # Répondre à tout sans filtrer (à utiliser avec précaution)
        pass

    # Condition complexe
    elif len(clean) > 100 and 'aide' in clean:
        self.send_message(channel, "Ton message est long, mais je vais t'aider !")
🐍 Avancé

Modules Python

Importe des modules Python standard directement dans ton code de bot.

ModuleUtilitéExemple
randomAléatoirerandom.choice(['Pile', 'Face'])
mathCalculsmath.sqrt(16)4.0
jsonJSONjson.dumps({"key": "val"})
datetimeDate/heuredatetime.now().strftime("%H:%M")
reRegexre.search(r'\d+', content)
requestsHTTPrequests.get("https://api.example.com")
hashlibHashhashlib.md5(b"text").hexdigest()
Python — Exemples avec modules
import random, math, datetime

class UtilBot:
    def on_message(self, message):
        clean = message.get('content','').lower().strip()
        ch    = message.get('channel_id')
        if message.get('author',{}).get('is_bot'): return

        if clean == '!pile':
            self.send_message(ch, random.choice(["🟡 Pile !", "⚪ Face !"]))

        elif clean.startswith('!sqrt '):
            n = float(clean[6:])
            self.send_message(ch, f"√{n} = {math.sqrt(n):.4f}")

        elif clean == '!heure':
            now = datetime.datetime.now().strftime("%H:%M:%S")
            self.send_message(ch, f"⏰ Il est {now}")
🐍 Avancé

État & Mémoire

Les attributs self.xxx persistent entre les appels à on_message tant que le bot tourne.

Python — Compteurs et mémoire
import random

class MemoryBot:

    def on_ready(self):
        self.compteur = 0
        self.scores    = {}  # {user_id: score}
        self.blagues   = [
            "Pourquoi les plongeurs plongent-ils toujours en arrière ?",
            "Parce que sinon ils tomberaient dans le bateau !"
        ]

    def on_message(self, message):
        clean   = message.get('content', '').lower().strip()
        channel = message.get('channel_id')
        author  = message.get('author', {})
        uid     = author.get('id')
        name    = author.get('username', '?')
        if author.get('is_bot'): return

        if clean == '!compter':
            self.compteur += 1
            self.send_message(channel, f"Compteur : **{self.compteur}**")

        elif clean == '!point':
            self.scores[uid] = self.scores.get(uid, 0) + 1
            self.send_message(channel, f"✅ {name} a maintenant **{self.scores[uid]}** point(s) !")

        elif clean == '!blague':
            self.send_message(channel, random.choice(self.blagues))
⚠️
Perte de mémoire au redémarrage
self.xxx est perdu si le bot est redémarré. Pour une persistance totale, utilise un fichier JSON ou une base de données.
🌐 Avancé

API externes

Appelle n'importe quelle API depuis ton bot : REST, JSON, authentification, async — documentation complète avec exemples réels.

📡
GET / POST / PUT / DELETE

Toutes les méthodes HTTP avec requests ou aiohttp.

🔑
Auth complète

Bearer token, API Key header/query, Basic Auth, OAuth2 flow.

Async non-bloquant

aiohttp pour ne pas bloquer le bot pendant une requête longue.

🛡️
Gestion d'erreurs

Codes HTTP, timeouts, rate limiting, retries automatiques.

Installation

Bash
pip install requests aiohttp

1 — Requêtes synchrones (requests)

Le plus simple — bloque le thread jusqu'à la réponse. Suffisant pour la plupart des bots.

GET simple
import requests

class ApiBot:
    def on_message(self, message):
        ch = message['channel_id']
        if message.get('content','').strip() == '!prix':
            resp = requests.get('https://api.coindesk.com/v1/bpi/currentprice.json', timeout=5)
            if resp.status_code == 200:
                data = resp.json()
                prix = data['bpi']['EUR']['rate']
                self.send_message(ch, f"💰 Bitcoin : {prix} €")
            else:
                self.send_message(ch, f"❌ Erreur {resp.status_code}")
POST avec body JSON
import requests

resp = requests.post(
    'https://api.example.com/translate',
    json={'text': 'Bonjour', 'target': 'en'},  # body JSON auto
    headers={'Content-Type': 'application/json'},
    timeout=10
)
result = resp.json()
print(result['translation'])  # Hello
PUT / DELETE
import requests

# Mise à jour
resp = requests.put('https://api.example.com/items/42', json={'name': 'Nouveau nom'}, timeout=5)

# Suppression
resp = requests.delete('https://api.example.com/items/42', timeout=5)
print(resp.status_code)  # 204 No Content

2 — Authentification

Bearer Token (OAuth2 / JWT)
import requests

TOKEN = "mon_token_secret"
headers = {"Authorization": f"Bearer {TOKEN}"}

resp = requests.get('https://api.example.com/user/me', headers=headers, timeout=5)
print(resp.json())
API Key (header ou query param)
import requests

API_KEY = "ma_cle_api"

# Via header
resp1 = requests.get('https://api.openweathermap.org/data/2.5/weather',
    params={'q': 'Paris', 'units': 'metric', 'lang': 'fr'},
    headers={'x-api-key': API_KEY},
    timeout=5)

# Via query param (méthode alternative)
resp2 = requests.get('https://api.openweathermap.org/data/2.5/weather',
    params={'q': 'Paris', 'units': 'metric', 'lang': 'fr', 'appid': API_KEY},
    timeout=5)

if resp2.ok:
    d = resp2.json()
    print(f"🌡️ {d['main']['temp']}°C — {d['weather'][0]['description']}")
Basic Auth (login/password)
import requests

resp = requests.get(
    'https://api.exemple.com/protected',
    auth=('mon_login', 'mon_mot_de_passe'),
    timeout=5
)
print(resp.status_code)

3 — Async avec aiohttp

Non-bloquant : le bot continue de traiter les messages pendant la requête.

GET async de base
import asyncio
import aiohttp

class AsyncApiBot:

    def on_message(self, message):
        ch = message['channel_id']
        if '!meteo' in message.get('content', ''):
            asyncio.create_task(self._fetch_meteo(ch))

    async def _fetch_meteo(self, channel_id):
        async with aiohttp.ClientSession() as session:
            async with session.get(
                'https://api.openweathermap.org/data/2.5/weather',
                params={'q': 'Paris', 'units': 'metric', 'lang': 'fr', 'appid': 'CLE_API'},
                timeout=aiohttp.ClientTimeout(total=5)
            ) as resp:
                if resp.status == 200:
                    data = await resp.json()
                    temp = data['main']['temp']
                    desc = data['weather'][0]['description']
                    self.send_message(channel_id, f"🌡️ Paris : {temp}°C, {desc}")
                else:
                    self.send_message(channel_id, f"❌ API erreur {resp.status}")
POST async avec JSON et Bearer
import asyncio, aiohttp

async def appel_openai(prompt: str) -> str:
    API_KEY = "sk-..."
    async with aiohttp.ClientSession() as sess:
        async with sess.post(
            'https://api.openai.com/v1/chat/completions',
            headers={'Authorization': f'Bearer {API_KEY}', 'Content-Type': 'application/json'},
            json={
                'model': 'gpt-4o-mini',
                'messages': [{'role': 'user', 'content': prompt}],
                'max_tokens': 200
            },
            timeout=aiohttp.ClientTimeout(total=15)
        ) as resp:
            data = await resp.json()
            return data['choices'][0]['message']['content']

# Utilisation dans le bot :
class GptBot:
    def on_message(self, message):
        ch = message['channel_id']
        txt = message.get('content','')
        if txt.startswith('!ask '):
            asyncio.create_task(self._reply(ch, txt[5:]))

    async def _reply(self, ch, q):
        answer = await appel_openai(q)
        self.send_message(ch, answer)

4 — Gestion des erreurs et rate limiting

Gestion complète des erreurs HTTP
import requests, time

def appel_api_robuste(url, params=None, headers=None, max_retries=3):
    """Appel avec retry exponentiel sur 429/503."""
    for attempt in range(max_retries):
        try:
            resp = requests.get(url, params=params, headers=headers, timeout=8)
            if resp.status_code == 200:
                return resp.json()
            elif resp.status_code == 429:
                wait = int(resp.headers.get('Retry-After', 2 ** attempt))
                print(f"Rate limit, attente {wait}s")
                time.sleep(wait)
            elif resp.status_code == 401:
                raise PermissionError("Token invalide ou expiré")
            elif resp.status_code == 404:
                return None  # Ressource introuvable
            elif resp.status_code >= 500:
                time.sleep(2 ** attempt)  # Backoff exponentiel
            else:
                resp.raise_for_status()
        except requests.Timeout:
            print(f"Timeout tentative {attempt+1}")
            time.sleep(1)
        except requests.ConnectionError:
            print("Connexion impossible")
            break
    return None

5 — Exemples réels

🌡️ Météo — OpenWeatherMap
import requests

class MeteoBot:
    API_KEY = 'TON_API_KEY'  # https://openweathermap.org/api (gratuit)

    def on_message(self, message):
        content = message.get('content', '').strip()
        ch = message['channel_id']
        if content.startswith('!meteo '):
            ville = content[7:].strip()
            resp = requests.get(
                'https://api.openweathermap.org/data/2.5/weather',
                params={'q': ville, 'units': 'metric', 'lang': 'fr', 'appid': self.API_KEY},
                timeout=5
            )
            if resp.status_code == 200:
                d = resp.json()
                self.send_message(ch, components=[{
                    'type': 'key_value',
                    'title': f'🌡️ Météo — {d["name"]}',
                    'pairs': [
                        {'key': 'Température', 'value': f'{d["main"]["temp"]}°C', 'bold_value': True},
                        {'key': 'Ressenti',    'value': f'{d["main"]["feels_like"]}°C'},
                        {'key': 'Humidité',   'value': f'{d["main"]["humidity"]}%'},
                        {'key': 'Ciel',        'value': d['weather'][0]['description'].capitalize()},
                    ]
                }])
            else:
                self.send_message(ch, f'❌ Ville "{ville}" introuvable')
🤖 OpenAI GPT (chat)
import requests

class GptBot:
    OPENAI_KEY = 'sk-...'  # https://platform.openai.com/api-keys

    def on_message(self, message):
        content = message.get('content', '').strip()
        ch = message['channel_id']
        if content.startswith('!gpt '):
            prompt = content[5:]
            resp = requests.post(
                'https://api.openai.com/v1/chat/completions',
                headers={'Authorization': f'Bearer {self.OPENAI_KEY}'},
                json={'model': 'gpt-4o-mini', 'messages': [{'role': 'user', 'content': prompt}], 'max_tokens': 300},
                timeout=15
            )
            if resp.ok:
                answer = resp.json()['choices'][0]['message']['content']
                self.send_message(ch, answer)
            else:
                self.send_message(ch, f'❌ OpenAI erreur {resp.status_code}')
💹 Crypto — CoinGecko (sans clé)
import requests

class CryptoBot:
    def on_message(self, message):
        content = message.get('content', '').strip().lower()
        ch = message['channel_id']
        coins = {'btc': 'bitcoin', 'eth': 'ethereum', 'sol': 'solana'}
        for sym, cid in coins.items():
            if content == f'!{sym}':
                resp = requests.get(
                    f'https://api.coingecko.com/api/v3/simple/price',
                    params={'ids': cid, 'vs_currencies': 'eur,usd'},
                    timeout=5
                )
                if resp.ok:
                    d = resp.json()[cid]
                    self.send_message(ch, components=[{
                        'type': 'key_value',
                        'title': f'💹 {cid.capitalize()}',
                        'pairs': [
                            {'key': 'EUR', 'value': f'{d["eur"]:,.2f} €', 'bold_value': True},
                            {'key': 'USD', 'value': f'${d["usd"]:,.2f}'},
                        ]
                    }])

6 — Plus d'exemples réels

🔧 GitHub — Derniers commits d'un repo
import requests

class GitHubBot:
    TOKEN = 'ghp_...'  # https://github.com/settings/tokens (optionnel, augmente les limites)

    def on_message(self, message):
        content = message.get('content','').strip()
        ch = message['channel_id']
        if content.startswith('!commits '):
            repo = content[9:].strip()  # ex: "microsoft/vscode"
            headers = {'Authorization': f'Bearer {self.TOKEN}'} if self.TOKEN else {}
            resp = requests.get(f'https://api.github.com/repos/{repo}/commits',
                params={'per_page': 5}, headers=headers, timeout=5)
            if resp.ok:
                commits = resp.json()
                pairs = [{'key': c['sha'][:7], 'value': c['commit']['message'].split('\n')[0][:60]}
                         for c in commits]
                self.send_message(ch, components=[{
                    'type': 'key_value', 'title': f'📦 {repo} — derniers commits',
                    'monospace': True, 'pairs': pairs
                }])
            else:
                self.send_message(ch, f'❌ Repo "{repo}" introuvable ou API limit atteinte')
🌐 Traduction — LibreTranslate (gratuit, self-hostable)
import requests

class TraducteurBot:
    # API gratuite : https://libretranslate.com ou héberge toi-même
    BASE = 'https://libretranslate.com'
    KEY  = ''  # Optionnel sur instance publique

    def on_message(self, message):
        content = message.get('content','').strip()
        ch = message['channel_id']
        if content.startswith('!tr '):
            # Format: !tr en Bonjour tout le monde
            parts = content[4:].split(' ', 1)
            lang_target = parts[0] if len(parts)>1 else 'en'
            text = parts[1] if len(parts)>1 else parts[0]
            resp = requests.post(f'{self.BASE}/translate',
                json={'q': text, 'source': 'auto', 'target': lang_target, 'api_key': self.KEY},
                timeout=8)
            if resp.ok:
                self.send_message(ch, f'🌐 {resp.json()["translatedText"]}')
            else:
                self.send_message(ch, '❌ Erreur de traduction')
🎵 Spotify — Titre en cours (OAuth2)
import requests, base64

class SpotifyBot:
    # https://developer.spotify.com/dashboard — créer une app
    CLIENT_ID     = 'ton_client_id'
    CLIENT_SECRET = 'ton_client_secret'
    REFRESH_TOKEN = 'ton_refresh_token'  # obtenu via OAuth2 flow

    def _get_token(self):
        creds = base64.b64encode(f'{self.CLIENT_ID}:{self.CLIENT_SECRET}'.encode()).decode()
        resp = requests.post('https://accounts.spotify.com/api/token',
            data={'grant_type': 'refresh_token', 'refresh_token': self.REFRESH_TOKEN},
            headers={'Authorization': f'Basic {creds}'}, timeout=5)
        return resp.json().get('access_token')

    def on_message(self, message):
        ch = message['channel_id']
        if message.get('content','').strip() == '!spotify':
            token = self._get_token()
            resp = requests.get('https://api.spotify.com/v1/me/player/currently-playing',
                headers={'Authorization': f'Bearer {token}'}, timeout=5)
            if resp.status_code == 200:
                d = resp.json()
                artists = ', '.join(a['name'] for a in d['item']['artists'])
                self.send_message(ch, f"🎵 {artists} — {d['item']['name']}")
            elif resp.status_code == 204:
                self.send_message(ch, '⏸️ Rien en cours de lecture')
            else:
                self.send_message(ch, '❌ Erreur Spotify')
📺 YouTube — Infos d'une vidéo
import requests, re

class YouTubeBot:
    API_KEY = 'AIza...'  # https://console.cloud.google.com — YouTube Data API v3 (gratuit 10k/j)

    def on_message(self, message):
        content = message.get('content','').strip()
        ch = message['channel_id']
        if 'youtube.com' in content or 'youtu.be' in content:
            vid = re.search(r'(?:v=|youtu\.be/)([A-Za-z0-9_-]{11})', content)
            if not vid: return
            resp = requests.get('https://www.googleapis.com/youtube/v3/videos',
                params={'id': vid.group(1), 'part': 'snippet,statistics', 'key': self.API_KEY},
                timeout=5)
            if resp.ok:
                item = resp.json()['items'][0]
                sn = item['snippet']; st = item['statistics']
                self.send_message(ch, components=[{
                    'type': 'rich_message', 'style': 'embed', 'color': '#ff0000',
                    'author': sn['channelTitle'],
                    'title': sn['title'],
                    'description': sn['description'][:200] + '...',
                    'fields': [
                        {'name': '👁️ Vues',      'value': f"{int(st.get('viewCount',0)):,}", 'inline': True},
                        {'name': '👍 Likes',     'value': f"{int(st.get('likeCount',0)):,}", 'inline': True},
                        {'name': '💬 Commentaires', 'value': f"{int(st.get('commentCount',0)):,}", 'inline': True},
                    ],
                    'thumbnail': sn['thumbnails']['medium']['url'],
                    'footer': 'YouTube Data API'
                }])
🤖 Hugging Face — IA générative (gratuit)
import requests

class HuggingFaceBot:
    # https://huggingface.co/settings/tokens — token gratuit
    HF_TOKEN = 'hf_...'
    MODEL    = 'mistralai/Mistral-7B-Instruct-v0.2'  # ou tout autre modèle

    def on_message(self, message):
        content = message.get('content','').strip()
        ch = message['channel_id']
        if content.startswith('!ia '):
            prompt = content[4:]
            resp = requests.post(
                f'https://api-inference.huggingface.co/models/{self.MODEL}',
                headers={'Authorization': f'Bearer {self.HF_TOKEN}'},
                json={'inputs': prompt, 'parameters': {'max_new_tokens': 200}},
                timeout=30
            )
            if resp.ok:
                result = resp.json()
                if isinstance(result, list):
                    text = result[0].get('generated_text', '?')
                    self.send_message(ch, text.replace(prompt, '').strip()[:500])
                else:
                    self.send_message(ch, '⏳ Modèle en cours de chargement, réessaie dans 20s')
            else:
                self.send_message(ch, f'❌ HuggingFace erreur {resp.status_code}')
🌍 IP Géolocalisation — ip-api.com (gratuit)
import requests

class GeoBot:
    def on_message(self, message):
        content = message.get('content','').strip()
        ch = message['channel_id']
        if content.startswith('!geo '):
            ip = content[5:].strip()
            resp = requests.get(f'http://ip-api.com/json/{ip}?lang=fr', timeout=5)
            if resp.ok:
                d = resp.json()
                if d['status'] == 'success':
                    self.send_message(ch, components=[{
                        'type': 'key_value', 'title': f'🌍 Géolocalisation — {ip}',
                        'striped': True,
                        'pairs': [
                            {'key': 'Pays',     'value': f'{d["country"]} {d.get("countryCode","")}'},
                            {'key': 'Région',   'value': d.get('regionName','?')},
                            {'key': 'Ville',    'value': d.get('city','?')},
                            {'key': 'FAI',      'value': d.get('isp','?')},
                            {'key': 'Timezone', 'value': d.get('timezone','?')},
                        ]
                    }])
                else:
                    self.send_message(ch, f'❌ IP introuvable ou privée')
📰 NewsAPI — Actualités en temps réel
import requests

class NewsBot:
    API_KEY = '...'  # https://newsapi.org (gratuit 100 req/j)

    def on_message(self, message):
        content = message.get('content','').strip()
        ch = message['channel_id']
        if content.startswith('!news '):
            query = content[6:].strip()
            resp = requests.get('https://newsapi.org/v2/everything',
                params={'q': query, 'language': 'fr', 'pageSize': 3, 'sortBy': 'publishedAt', 'apiKey': self.API_KEY},
                timeout=5)
            if resp.ok:
                articles = resp.json().get('articles', [])
                for a in articles[:3]:
                    self.send_message(ch, components=[{
                        'type': 'rich_message', 'style': 'embed', 'color': '#0ea5e9',
                        'author': a.get('source',{}).get('name',''),
                        'title': a['title'],
                        'description': (a.get('description') or '')[:200],
                        'title_url': a['url'],
                        'footer': a.get('publishedAt','')[:10],
                    }])
            else:
                self.send_message(ch, '❌ Erreur NewsAPI')
🔴 Reddit — Posts tendance d'un subreddit
import requests

class RedditBot:
    HEADERS = {'User-Agent': 'LeNavireBot/1.0'}  # Reddit exige un User-Agent

    def on_message(self, message):
        content = message.get('content','').strip()
        ch = message['channel_id']
        if content.startswith('!reddit '):
            sub = content[8:].strip()
            resp = requests.get(f'https://www.reddit.com/r/{sub}/hot.json',
                params={'limit': 5}, headers=self.HEADERS, timeout=5)
            if resp.ok:
                posts = resp.json()['data']['children']
                pairs = [{'key': f'#{i+1}', 'value': p['data']['title'][:60],
                          'color': '#ff4500'} for i, p in enumerate(posts)]
                self.send_message(ch, components=[{
                    'type': 'key_value',
                    'title': f'🔴 r/{sub} — Hot',
                    'pairs': pairs
                }])
            else:
                self.send_message(ch, f'❌ Subreddit "{sub}" introuvable')
🐶 Random Dog / Cat (zéro config)
import requests

class AnimalBot:
    def on_message(self, message):
        content = message.get('content','').strip().lower()
        ch = message['channel_id']
        if content == '!chien':
            r = requests.get('https://dog.ceo/api/breeds/image/random', timeout=5)
            if r.ok:
                self.send_message(ch, r.json()['message'])  # URL directe de l'image
        elif content == '!chat':
            r = requests.get('https://api.thecatapi.com/v1/images/search', timeout=5)
            if r.ok:
                self.send_message(ch, r.json()[0]['url'])
🔐 Discord Webhook — Notifier un salon externe
import requests

# Envoie un message dans un salon Discord via webhook (sans bot)
def notifier_discord(webhook_url: str, titre: str, contenu: str, couleur: int = 0x8875f5):
    resp = requests.post(webhook_url, json={
        'embeds': [{
            'title': titre,
            'description': contenu,
            'color': couleur,
            'footer': {'text': 'LeNavire Bot'}
        }]
    }, timeout=5)
    return resp.status_code == 204  # Discord renvoie 204 si OK

# Utilisation :
# notifier_discord(WEBHOOK_URL, '🚀 Déploiement', 'Version 2.1 en production')
📧 Telegram Bot — Envoyer un message
import requests

class TelegramNotifier:
    # https://core.telegram.org/bots — crée un bot avec @BotFather
    BOT_TOKEN = '123456789:AAH...'
    CHAT_ID   = '-100...'  # ID du groupe ou channel

    def envoyer(self, texte: str):
        requests.post(
            f'https://api.telegram.org/bot{self.BOT_TOKEN}/sendMessage',
            json={'chat_id': self.CHAT_ID, 'text': texte, 'parse_mode': 'HTML'},
            timeout=5
        )

Tableau récapitulatif

APIAuthGratuit ?Lien
OpenWeatherMapAPI Key (query)✅ 60 req/minopenweathermap.org/api
OpenAIBearer Token💳 Payant (crédits)platform.openai.com
CoinGeckoAucune (basique)✅ 30 req/mincoingecko.com/api
REST CountriesAucune✅ Illimitérestcountries.com
The Cat APIAPI Key optionnel✅ 10 req/minthecatapi.com
Dog APIAucune✅ Illimitédog.ceo/api
GitHub APIBearer (PAT optionnel)✅ 60/h anon, 5000/h authdocs.github.com/rest
GiphyAPI Key (query)✅ 100 req/hdevelopers.giphy.com
LibreTranslateAucune/Key optionnel✅ Gratuit (self-host)libretranslate.com
Spotify Web APIOAuth2 (Bearer)✅ Gratuit (limité)developer.spotify.com
YouTube Data API v3API Key (query)✅ 10 000 quota/jconsole.cloud.google.com
Hugging Face InferenceBearer Token✅ Gratuit (limité)huggingface.co/inference-api
ip-api.comAucune✅ 45 req/minip-api.com
NewsAPIAPI Key (query)✅ 100 req/jnewsapi.org
Reddit JSONUser-Agent header✅ Illimitéreddit.com/r/x/hot.json
Discord WebhookURL secrète✅ Illimitédiscord.com/developers
Telegram Bot APIBot Token✅ Illimitécore.telegram.org/bots
Open Trivia DBAucune✅ Illimitéopentdb.com/api_config.php
JokeAPIAucune✅ Illimitéjokeapi.dev
ExchangeRate-APIAPI Key✅ 1500 req/moisexchangerate-api.com
📖 Exemples complets

Bot de commandes

Un bot complet avec aide, info serveur, dé à rouler et easter eggs.

Python — Copie-colle directement
import random

class CommandBot:

    def on_ready(self):
        self.commandes = ["!aide", "!de", "!info", "!8ball"]

    def on_message(self, message):
        clean   = message.get('content', '').lower().strip()
        channel = message.get('channel_id')
        author  = message.get('author', {})
        name    = author.get('username', '?')
        if author.get('is_bot'): return

        if clean == '!aide':
            self.send_message(channel, embed={
                "title": "📚 Commandes disponibles",
                "color": "#7c6cf0",
                "fields": [
                    {"name": "!aide",        "value": "Affiche ce menu",          "inline": True},
                    {"name": "!de [N]",      "value": "Lance un dé à N faces",    "inline": True},
                    {"name": "!8ball [Q]",   "value": "Pose une question au sort", "inline": True},
                ],
                "footer": "CommandBot — Le Navire"
            })

        elif clean.startswith('!de'):
            parts = clean.split()
            faces = int(parts[1]) if len(parts) > 1 and parts[1].isdigit() else 6
            result = random.randint(1, faces)
            self.send_message(channel, embed={
                "title": f"🎲 {name} lance un D{faces}",
                "description": f"Résultat : **{result}**",
                "color": "#f1c40f" if result == faces else "#5865f2"
            })

        elif clean.startswith('!8ball'):
            reponses = ["✅ Oui, certainement", "❌ Non, pas du tout",
                        "🤔 Peut-être...", "🔮 Les étoiles disent oui"]
            self.send_message(channel, f"🎱 **{name}** : {random.choice(reponses)}")
📖 Exemples complets

Quiz interactif

Un vrai quiz avec timer, boutons exclusifs et score, utilisant toutes les fonctionnalités.

Python — Quiz complet avec timer & score
class QuizBot:

    def on_ready(self):
        self.scores = {}
        self.questions = [
            {"q": "Capitale de la France ?", "options": ["Lyon","Paris","Nice"],     "answer": "Paris"},
            {"q": "2 + 2 = ?",               "options": ["3","4","5"],           "answer": "4"},
            {"q": "Couleur du ciel ?",         "options": ["Rouge","Vert","Bleu"], "answer": "Bleu"},
        ]
        self.current = None

    def on_message(self, message):
        import random
        clean = message.get('content','').lower().strip()
        ch    = message.get('channel_id')
        if message.get('author',{}).get('is_bot'): return

        if clean == '!quiz':
            q = random.choice(self.questions)
            self.current = q
            self.send_message(ch,
                embed={"title": f"❓ {q['q']}", "color": "#9b59b6", "footer": "Tu as 15 secondes !"},
                components=[
                    {"type": "timer", "duration": 15, "color": "#e74c3c", "custom_id": "quiz_end"},
                    {"type": "action_row", "components": [
                        {"type": "button", "label": o, "style": "secondary",
                         "custom_id": f"ans_{o}", "exclusive": True}
                        for o in q["options"]
                    ]}
                ]
            )

        elif clean == '!scores':
            lines = [f"• {uid} : {s} pts" for uid, s in self.scores.items()]
            self.send_message(ch, embed={
                "title": "🏆 Scores",
                "description": "\n".join(lines) if lines else "Aucun score encore !",
                "color": "#f1c40f"
            })

    def on_button_click(self, interaction):
        itype = interaction.get('type')
        cid   = interaction.get('custom_id')
        ch    = interaction.get('channel_id')
        user  = interaction.get('user', {})
        uid   = user.get('username', '?')

        if itype == 'timer_expired' and cid == 'quiz_end':
            ans = self.current['answer'] if self.current else '?'
            self.send_message(ch, embed={"title": "⏰ Temps écoulé !", "description": f"C'était **{ans}**", "color": "#ef4444"})

        elif itype == 'button_click' and cid.startswith('ans_') and self.current:
            chosen = cid[4:]
            if chosen == self.current['answer']:
                self.scores[uid] = self.scores.get(uid, 0) + 1
                self.send_message(ch, f"🎉 {uid} a trouvé ! (+1 pt, total : {self.scores[uid]})")
            else:
                self.send_message(ch, f"❌ {uid}, c'était **{self.current['answer']}**")
📖 Exemples complets

Messages riches complets

Toutes les fonctionnalités combinées : embed + barres + boutons + menu déroulant.

Python — Profil joueur complet
if content == '!profil':
    user = author.get('username', 'Joueur')
    self.send_message(channel,
        embed={
            "title": f"🎮 Profil de {user}",
            "description": "Niveau 12 — Rang : **Vétéran**",
            "color": "#7c6cf0",
            "fields": [
                {"name": "⭐ XP",    "value": "1 250 / 2 000", "inline": True},
                {"name": "🏆 Wins", "value": "47",           "inline": True},
                {"name": "💀 KDR",  "value": "2.3",          "inline": True},
            ],
            "footer": "Dernière connexion : aujourd'hui"
        },
        components=[
            {"type": "progress_bar", "label": "⭐ XP vers niveau 13", "value": 63, "color": "#7c6cf0"},
            {"type": "progress_bar", "label": "❤️ Vie",                "value": 80, "color": "#ef4444", "height": 5},
            {"type": "action_row", "components": [
                {"type": "button", "label": "⚔️ Combattre", "style": "danger",   "custom_id": "fight"},
                {"type": "button", "label": "🛒 Boutique",  "style": "primary",  "custom_id": "shop"},
                {"type": "button", "label": "💤 Se reposer", "style": "success", "custom_id": "rest"},
            ]},
            {"type": "action_row", "components": [{
                "type":        "select_menu",
                "custom_id":   "equip_select",
                "placeholder": "Équiper un objet...",
                "options": [
                    {"label": "Épée de feu",   "value": "sword",  "emoji": "🔥"},
                    {"label": "Bouclier d'or", "value": "shield", "emoji": "🛡️"},
                    {"label": "Arc enchanté",  "value": "bow",    "emoji": "🏹"},
                ]
            }]},
        ]
    )
🔧 Workflow

Sauvegarder & déployer

Le déploiement est automatique et instantané. Aucune configuration serveur requise.

Ctrl+S = live instantanément
Appuie sur Ctrl+S dans l'éditeur → code sauvegardé → prochain message utilise le nouveau code. Rien d'autre à faire.
1
Modifier le code dans l'éditeur
Ajoute tes commandes, ajuste la logique.
2
Appuyer sur Ctrl+S
Le message "Fichier sauvegardé" apparaît dans la console.
3
Tester dans le salon
Tape ta commande dans le serveur. Le bot répond immédiatement avec le nouveau comportement.
🔧 Workflow

Déboguer

Référence rapide pour résoudre les problèmes courants.

ProblèmeSolution
Le bot ne répond pas du toutVérifier que le bot est EN LIGNE (bouton ▶️ Démarrer) et ajouté au serveur
Erreur de syntaxe PythonL'éditeur souligne les erreurs en rouge. Corriger avant de sauvegarder
La classe n'est pas détectéeTa classe doit avoir une méthode on_message(self, message)
Boucle infinieAjouter if author.get('is_bot'): return en tout premier
Le code modifié ne s'applique pasVérifier que Ctrl+S a bien affiché le message de succès
Commande non reconnueUtiliser .lower().strip() sur content pour normaliser
Bouton/menu ne déclenche rienVérifier que on_button_click est définie et que le custom_id correspond
🔴
Anti-boucle OBLIGATOIRE
Mets toujours if message.get('author', {}).get('is_bot'): return au début de on_message. Sinon ton bot verra ses propres messages et créera une boucle infinie qui spammera le salon.
🔧 Workflow

Conseils & bonnes pratiques

Les meilleures habitudes pour écrire un bot propre, fiable et fun.

📝
Utilise des listes
Stocke tes réponses dans self.xxx et utilise random.choice() pour varier les réponses.
🔤
.lower().strip()
Normalise toujours le contenu pour éviter les faux négatifs dus à la casse ou aux espaces.
🌿
if / elif / else
Structure avec if/elif/else et utilise return pour sortir après avoir répondu.
🥚
Easter eggs
Ajoute des réponses cachées déclenchées par des phrases secrètes pour surprendre les utilisateurs.
👤
Personnalise
Utilise author.get('username') pour inclure le nom de l'utilisateur dans les réponses.
🧪
Teste immédiatement
Cycle : modifier → Ctrl+S → tester. Le feedback est instantané.
🎨
Couleurs cohérentes
Utilise des couleurs consistantes pour ton bot. Vert = succès, rouge = erreur, bleu = info.
📦
Combine les composants
Embed + barres de progression + boutons + menu dans un seul message pour un rendu propre.
🚢 Le Navire — Documentation Bots
🎮 Mini-jeux simples

Mini-jeux embarqués

Envoie un jeu directement dans le chat. Le joueur joue sans quitter Le Navire. Récupère le score via on_button_click.

🐍
Snake
Le serpent classique. Flèches ou WASD ou swipe. Score = longueur atteinte.
🃏
Memory
Retourne les paires d'émojis. Score = paires trouvées.
Réaction rapide
Clique dès que c'est vert. Score = temps de réaction en ms.
🔢
Devinette
Trouve le nombre caché. Indications chaud/froid.
Pierre Feuille Ciseaux
Best-of configurable contre le bot. Score = manches gagnées.
🖱️
Clic maniaque
Clique le plus de fois possible dans le temps imparti. Score = nb de clics.
📝
Quiz
Q/A à choix multiple entièrement configurable. Score = bonnes réponses.
💡
Comment ça marche
Un composant de type minigame est ajouté dans le tableau components d'un send_message(). Le front-end gère le rendu interactif, le bot reçoit l'événement on_button_click avec type: "minigame_end" quand la partie se termine.

Événements reçus par le bot

ChampValeurDescription
type"minigame_end"Type de l'interaction
custom_idton identifiantL'id que tu as passé dans le composant
values["score"]Score final (liste de chaînes)
channel_idid salonSalon où le jeu a été lancé
Python — Exemple complet
class GameBot:
    def on_message(self, message):
        content = message.get('content', '').lower().strip()
        channel = message.get('channel_id')
        if message.get('author', {}).get('is_bot'): return

        if content == '!snake':
            self.send_message(channel, "🐍 À toi de jouer !", components=[{
                "type": "minigame", "game": "snake",
                "speed": 4, "custom_id": "snake_game"
            }])
        elif content == '!pfc':
            self.send_message(channel, "❓ Qui va gagner ?", components=[{
                "type": "minigame", "game": "pfc",
                "rounds": 3, "custom_id": "pfc_game"
            }])
        elif content == '!quiz':
            self.send_message(channel, "📝 Quiz !'", components=[{
                "type": "minigame", "game": "quiz", "custom_id": "quiz_game"
            }])

    def on_button_click(self, interaction):
        if interaction.get('type') == 'minigame_end':
            score = interaction.get('values', ['?'])[0]
            ch    = interaction.get('channel_id')
            self.send_message(ch, f"🎯 Score : **{score}**")
🎮 Mini-jeux

🐍 Snake

Le serpent classique. Contrôle avec les flèches du clavier ou WASD. Supporte aussi le swipe sur mobile.

Démo live

Snake — démo interactive
🐍
Snake
🏆 Score : 0
↑↓←→ ou WASD
Niveau 4

Code Python

Python
self.send_message(channel, "🐍 À toi de jouer !", components=[{
    "type"      : "minigame",
    "game"      : "snake",
    "label"     : "Snake",         # Titre affiché
    "width"     : 20,             # Colonnes (défaut: 20)
    "height"    : 14,             # Lignes   (défaut: 14)
    "cell_size" : 18,             # Pixels par cellule
    "speed"     : 4,              # 1 (lent) → 8 (rapide)
    "snake_color": "#00d9a3",     # Couleur du serpent
    "food_color" : "#f87171",     # Couleur de la nourriture
    "custom_id"  : "snake_game"   # ID pour on_button_click
}])

Paramètres

ParamètreTypeDéfautDescription
gamestr"snake"
labelstr"Snake"Titre dans l'overlay
widthint20Nombre de colonnes (max 30)
heightint14Nombre de lignes (max 20)
cell_sizeint18Taille d'une cellule en px
speedint3Vitesse 1–8 (1=lent, 8=très rapide)
snake_colorstr#00d9a3Couleur hex du serpent
food_colorstr#f87171Couleur hex de la nourriture
custom_idstrID envoyé dans on_button_click
🎮 Mini-jeux

🃏 Memory

Retourne les cartes et trouve toutes les paires. Les emojis sont entièrement personnalisables.

Démo live

Memory — démo interactive
🃏 Paires : 0/8

Code Python

Python
self.send_message(channel, "🃏 Trouve les paires !", components=[{
    "type"   : "minigame",
    "game"   : "memory",
    "label"  : "Memory",
    "emojis" : ["🎮","🐍","💎","⚡","🎯","🌊","🚀","🍕"],  # max 8 paires
    "cols"   : 4,         # colonnes de la grille
    "custom_id": "memory_game"
}])

Paramètres

ParamètreTypeDéfautDescription
gamestr"memory"
labelstr"Memory"Titre
emojislist8 emojis variésListe d'emojis (max 8). Chaque emoji forme une paire.
colsint4Nombre de colonnes (grille de cols × (2*len/cols))
custom_idstrID pour on_button_click
🎮 Mini-jeux

⚡ Réaction rapide

Attend le signal vert et tape le plus vite possible. Le temps de réaction est mesuré en millisecondes.

Démo live

Réaction — démo interactive
⚡ Réaction rapide
Prêt ?

Code Python

Python
self.send_message(channel, "⚡ Réaction rapide — sois le plus rapide !", components=[{
    "type"     : "minigame",
    "game"     : "reaction",
    "label"    : "Réaction rapide",
    "custom_id": "react_game"  # values = ["temps_ms"]
}])
💡
Score reçu
values[0] contient le temps en millisecondes (ex: "243"). Compare les scores pour faire un classement !
🎮 Mini-jeux

🔢 Devinette de nombre

Le bot choisit un nombre secret. L'utilisateur a N essais pour le deviner, avec des indices chaud/froid.

Démo live

Devinette — démo interactive

Code Python

Python
self.send_message(channel, "🔢 Devine le nombre !", components=[{
    "type"     : "minigame",
    "game"     : "number_guess",
    "label"    : "Devinette",
    "max"      : 100,     # Nombre max (1 à max)
    "tries"    : 7,       # Essais maximum
    "custom_id": "ng_game"  # values = ["nb_essais"]
}])

Paramètres

ParamètreTypeDéfautDescription
gamestr"number_guess"
labelstr"Devinette"Titre
maxint100Borne supérieure (génère 1 à max)
triesint7Nombre max d'essais
custom_idstrID pour on_button_clickvalues[0] = nb d'essais utilisés
🔥 Mini-jeux avancés

Mini-jeux avancés

Des jeux plus complexes avec logique de grille, alphabet, récursivité. Mêmes bases que les jeux simples — un composant, un on_button_click.

🧵
Pendu
Devins les lettres pour trouver le mot caché. Liste de mots personnalisable, max erreurs configurable. SVG pendu animé.
🔢
2048
Le casse-tête de grille classique. Flèches ou swipe. Taille de grille configurable (4×4, 5×5...).
💣
Démineur
Grille de mines, clic gauche pour révéler, clic droit pour poser un drapeau. Taille et nombre de mines configurables.
✖️
Morpion
Tic-tac-toe contre un bot IA. Minimax intégré, le bot joue de façon optimale.
🏓
Casse-brique
Breakout sur canvas. Déplace la raquette à la souris ou au tactile. Brique multi-rangées colorées.
💡
Combine avec le leaderboard
Utilise on_button_click pour récupérer le score et mettre à jour ton classement avec leaderboard ou score_card.
🔥 Mini-jeux avancés

🧵 Pendu

Trouve le mot caché lettre par lettre. Le dessin du pendu se complète à chaque erreur. Liste de mots entièrement personnalisable.

Démo live

Pendu — démo interactive

Code Python

Python
self.send_message(channel, "🧵 Pendu — trouve le mot !", components=[{
    "type"       : "minigame",
    "game"       : "hangman",
    "label"      : "Pendu",
    "words"      : ["PYTHON","DISCORD","NAVIRE","SERVEUR"],
    "hint"       : "Langage ou technologie",  # indice optionnel
    "max_errors" : 6,                          # erreurs avant perdu
    "custom_id"  : "hangman_game"               # values = ["score"] (erreurs évitées)
}])

Paramètres

ParamètreTypeDéfautDescription
gamestr"hangman"
wordslist5 mots techTableau de mots en majuscules (un est choisi aléatoirement)
hintstrIndice affiché sous le titre
max_errorsint6Nombre d'erreurs max avant Game Over
custom_idstrvalues[0] = erreurs évitées (0 si perdu)
🔥 Mini-jeux avancés

🔢 2048

Fusionne les tuiles pour atteindre 2048. Contrôle avec les flèches ou swipe. Grille de taille configurable.

Démo live

2048 — démo interactive
🏆 0

Code Python

Python
self.send_message(channel, "🔢 Atteins 2048 !", components=[{
    "type"      : "minigame",
    "game"      : "2048",
    "label"     : "2048",
    "size"      : 4,       # taille de la grille (3, 4 ou 5)
    "custom_id" : "2048_game"  # values = ["score_final"]
}])

Paramètres

ParamètreTypeDéfautDescription
gamestr"2048"
labelstr"2048"Titre affiché
sizeint4Taille de la grille (3, 4 ou 5)
custom_idstrvalues[0] = score final
🏆 Scores & Classements

🏆 Leaderboard

Affiche un classement complèt et visuel dans le chat. Totalement custom : titre, scores, badges, avatars, unité.

Démo live

Leaderboard — démo interactive
ScoreBot MaintenantBOT

Code Python

Python
self.send_message(channel, components=[{
    "type"   : "leaderboard",
    "title"  : "🏆 Classement",
    "color"  : "#00d9a3",       # couleur accent
    "unit"   : "pts",             # unité affichée après le score
    "max"    : 10,               # nb max de lignes affichées
    "entries": [
        {"name": "Alice",   "score": 4280, "badge": "🔥 Streak"},
        {"name": "Bob",     "score": 3150},
        {"name": "Charlie", "score": 1990, "avatar": "https://..."},
    ]
}])

Paramètres — entrée

ChampTypeDescription
namestrNom du joueur
scoreint/strScore affiché
avatarstrURL d'image de profil (optionnel)
badgestrBadge pill affiché après le nom (ex. "🔥 Streak")
🏆 Scores & Classements

🃏 Carte de score

Affiche la carte perso d'un joueur : nom, rang, score principal et stats secondaires configurable.

Démo live

Carte de score — démo interactive
ScoreBot MaintenantBOT

Code Python

Python
self.send_message(channel, components=[{
    "type"     : "score_card",
    "name"     : author.get('username'),
    "score"    : 4280,
    "rank"     : 1,             # 1=🥇 2=🥈 3=🥉 n=#n
    "unit"     : "pts",
    "badge"    : "🔥 Streak x7",   # optionnel
    "subtitle" : "Meilleur score cette semaine",
    "color"    : "#00d9a3",
    "stats"    : [           # stats secondaires (optionnel)
        {"label": "Parties", "value": "42",   "color": "#a78bfa"},
        {"label": "Victoires","value": "31",   "color": "#00d9a3"},
        {"label": "Win Rate", "value": "74%",  "color": "#fbbf24"},
    ]
}])
🏆 Scores & Classements

🧠 Système de points complet

Crée un système de points persistant avec self. Points par joueur, classement global, commandes d'affichage custom.

💡
Tout dans self
Le bot stocke les scores dans self.scores (dict en mémoire). Les données persistent tant que le bot tourne. Pour une persistance totale, sauvegarde dans un fichier avec le module json.

Bot complet avec scores + classement

Python — Système de score complet
import json, os

class ScoreBot:

    def on_ready(self):
        # Charge les scores depuis un fichier (persistance entre redémarrages)
        try:
            with open('scores.json') as f:
                self.scores = json.load(f)
        except:
            self.scores = {}  # {user_id: {"name":..., "points":..., "games":...}}

    def _save(self):
        with open('scores.json', 'w') as f:
            json.dump(self.scores, f)

    def _add_points(self, uid, name, pts):
        if uid not in self.scores:
            self.scores[uid] = {"name":name, "points":0, "games":0}
        self.scores[uid]["points"] += pts
        self.scores[uid]["games"]  += 1
        self._save()

    def _get_leaderboard(self, limit=10):
        # Tri décroissant par points
        sorted_scores = sorted(
            self.scores.values(),
            key=lambda x: x["points"], reverse=True
        )
        return [{"name": s["name"], "score": s["points"]} for s in sorted_scores[:limit]]

    def on_message(self, message):
        content = message.get('content', '').lower().strip()
        channel = message.get('channel_id')
        author  = message.get('author', {})
        if author.get('is_bot'): return

        uid  = str(author.get('id',''))
        name = author.get('username','')

        if content == '!snake':
            self.send_message(channel, "🐍 Joue et marque des points !",
                components=[{"type":"minigame","game":"snake",
                             "speed":4,"custom_id":f"snake_{uid}"}])

        elif content == '!score':
            s = self.scores.get(uid, {"points":0,"games":0})
            rank = sorted(self.scores.values(), key=lambdax:x["points"],reverse=True)
            rank_n = next((i+1 for i,r in enumerate(rank) if r["name"]==name), None)
            self.send_message(channel, components=[{
                "type"     : "score_card",
                "name"     : name,
                "score"    : s["points"],
                "rank"     : rank_n,
                "unit"     : "pts",
                "subtitle" : f"{s['games']} parties jouées",
                "color"    : "#00d9a3",
                "stats"    : [
                    {"label":"Parties","value":str(s["games"]),"color":"#a78bfa"},
                    {"label":"Points", "value":str(s["points"]),"color":"#00d9a3"},
                ]
            }])

        elif content == '!top':
            lb = self._get_leaderboard(10)
            self.send_message(channel, components=[{
                "type"   : "leaderboard",
                "title"  : "🏆 Top joueurs",
                "color"  : "#00d9a3",
                "unit"   : "pts",
                "entries": lb
            }])

    def on_button_click(self, interaction):
        if interaction.get('type') == 'minigame_end':
            cid   = interaction.get('custom_id','')
            score = int(interaction.get('values',['0'])[0] or 0)
            ch    = interaction.get('channel_id')
            uid   = cid.split('_')[-1]  # custom_id = "snake_{uid}"
            player= self.scores.get(uid,{}).get("name","Joueur")
            self._add_points(uid, player, score)
            self.send_message(ch, f"✅ +{score} pts pour **{player}** ! Total : {self.scores[uid]['points']}")

Commandes disponibles

CommandeAction
!snakeLance une partie de Snake, le score est ajouté automatiquement
!scoreAffiche la carte de score perso du joueur avec rang
!topAffiche le classement global des 10 meilleurs
💡
Personnalise tout
Ajoute des commandes !+pts 100 @user, des rôles selon le score, des multiplicateurs, des saisons... tout est en Python pur. Utilise self.scores comme tu veux.
🎮 Mini-jeux simples

✊ Pierre Feuille Ciseaux

Jeu en manches configurable contre le bot. Simple et rapide, parfait pour relancer l'activité dans un salon.

Démo live

Pierre Feuille Ciseaux — démo interactive
GameBot MaintenantBOT

Code Python

Python
self.send_message(channel, "❓ Qui va gagner ?", components=[{
    "type"      : "minigame",
    "game"      : "pfc",
    "rounds"    : 3,        # nombre de manches
    "color"     : "#8875f5",
    "custom_id" : "pfc_game"   # values = ["nb_manches_gagnées"]
}])

Paramètres

ParamètreTypeDéfautDescription
roundsint3Nombre de manches à jouer
colorstr#8875f5Couleur accent
custom_idstrvalues[0] = manches gagnées par le joueur
🎮 Mini-jeux simples

🖱️ Clic maniaque

Clique le plus vite possible pendant le compte à rebours. Score = nombre de clics. Durée personnalisable.

Démo live

Clic maniaque — démo interactive
GameBot MaintenantBOT

Code Python

Python
self.send_message(channel, "🖱️ Clic maniaque !", components=[{
    "type"      : "minigame",
    "game"      : "clicker",
    "duration"  : 10,       # durée en secondes
    "color"     : "#f59e0b",
    "custom_id" : "clicker_game" # values = ["nb_clics"]
}])

Paramètres

ParamètreTypeDéfautDescription
durationint10Durée du chrono en secondes
colorstr#f59e0bCouleur du bouton
custom_idstrvalues[0] = nombre de clics final
🎮 Mini-jeux simples

📝 Quiz

Q&A à choix multiple entièrement personnalisable. Définis tes propres questions, réponses et bonne réponse.

Démo live

Quiz — démo interactive
QuizBot MaintenantBOT

Code Python

Python
self.send_message(channel, "📝 Quiz culture générale", components=[{
    "type"      : "minigame",
    "game"      : "quiz",
    "color"     : "#8875f5",
    "questions" : [
        {
            "q"      : "Quelle est la capitale de la France ?",
            "answers": ["Lyon","Paris","Marseille","Bordeaux"],
            "correct": 1  # index 0-based
        },
        {
            "q"      : "Combien de coulèures dans un arc-en-ciel ?",
            "answers": ["5","6","7","8"],
            "correct": 2
        },
    ],
    "custom_id" : "quiz_game"  # values = ["score"]
}])

Format d'une question

ChampTypeDescription
qstrTexte de la question
answerslist[str]2 à 4 réponses possibles
correctintIndex de la bonne réponse (base 0)
🔥 Mini-jeux avancés

💣 Démineur

Grille de mines à déminer. Clic gauche pour révéler, clic droit pour poser un drapeau. Taille et mines configurables.

Démo live

Démineur — démo interactive

Code Python

Python
self.send_message(channel, "💣 Déminage en cours...", components=[{
    "type"      : "minigame",
    "game"      : "minesweeper",
    "cols"      : 8,
    "rows"      : 8,
    "mines"     : 10,
    "color"     : "#f87171",
    "custom_id" : "ms_game"  # values = ["temps_en_secondes"] si gagné, ["0"] si perdu
}])

Paramètres

ParamètreTypeDéfautDescription
colsint8Nombre de colonnes
rowsint8Nombre de lignes
minesint10Nombre de mines placées
colorstr#f87171Couleur accent
custom_idstrvalues[0] = temps en s si gagné, "0" si perdu
🔥 Mini-jeux avancés

✖️ Morpion

Tic-tac-toe classique contre un bot IA basé sur minimax. Tu joues en X, le bot joue en O.

Démo live

Morpion — démo interactive
GameBot MaintenantBOT

Code Python

Python
self.send_message(channel, "✖️ Morpion — bats le bot !", components=[{
    "type"      : "minigame",
    "game"      : "tictactoe",
    "color"     : "#8875f5",
    "custom_id" : "tt_game"  # values = ["1"] gagné, ["0"] perdu, ["draw"] égalité
}])

Paramètres

ParamètreTypeDéfautDescription
colorstr#8875f5Couleur des X du joueur
custom_idstrvalues[0] = "1" gagné / "0" perdu / "draw" égalité
🔥 Mini-jeux avancés

🏓 Casse-brique

Breakout sur canvas HTML5. Déplace la raquette à la souris ou au tactile. Brise toutes les briques pour gagner.

Démo live

Casse-brique — démo interactive

Code Python

Python
self.send_message(channel, "🏓 Casse-brique !", components=[{
    "type"      : "minigame",
    "game"      : "breakout",
    "width"     : 320,
    "height"    : 260,
    "color"     : "#00d9a3",
    "custom_id" : "br_game"  # values = ["score_final"]
}])

Paramètres

ParamètreTypeDéfautDescription
widthint320Largeur du canvas en pixels
heightint260Hauteur du canvas en pixels
colorstr#00d9a3Couleur accent (balle + première rangée)
custom_idstrvalues[0] = score final (10 pts par brique)
🎯 Réflexes

🎨 Simon Mémorise

Le bot affiche une séquence de couleurs lumineuses. Le joueur doit la reproduire dans l'ordre exact. La séquence s'allonge à chaque niveau réussi.

Démo live

Simon Mémorise — démo interactive
GameBot MaintenantBOT

Code Python

Python
self.send_message(channel, "🎨 Suis la séquence !", components=[{
    "type"      : "minigame",
    "game"      : "simon",
    "max_level" : 10,      # niveaux à réussir pour gagner
    "speed"     : 600,     # durée du flash en ms
    "color"     : "#8875f5",
    "custom_id" : "simon_game"  # values = ["niveau_atteint"]
}])

Paramètres

ParamètreTypeDéfautDescription
max_levelint10Nombre de niveaux pour gagner
speedint600Durée du flash en ms (plus bas = plus rapide)
colorstr#8875f5Couleur accent
custom_idstrvalues[0] = niveau atteint avant l'erreur
🎯 Réflexes

🐭 Tape-Taupe

Des taupes apparaissent aléatoirement dans une grille 3×3. Clique dessus avant qu'elles disparaissent ! Durée configurable.

Démo live

Tape-Taupe — démo interactive
GameBot MaintenantBOT

Code Python

Python
self.send_message(channel, "🐭 Tape les taupes !", components=[{
    "type"      : "minigame",
    "game"      : "whack_mole",
    "duration"  : 20,      # durée en secondes
    "speed"     : 900,     # intervalle apparition ms
    "color"     : "#f59e0b",
    "custom_id" : "wm_game"    # values = ["nb_taupes"]
}])

Paramètres

ParamètreTypeDéfautDescription
durationint20Durée de la partie en secondes
speedint900Intervalle entre chaque taupe en ms
colorstr#f59e0bCouleur accent
custom_idstrvalues[0] = nombre de taupes tapées
🎲 Hasard & Stratégie

🎰 Machine à Sous

3 rouleaux d'émojis. Une paire rapporte 1 crédit, un triple rapporte 3 à 10 crédits selon le symbole. Parfait pour animer un salon détendu.

Démo live

Machine à Sous — démo interactive
GameBot MaintenantBOT

Code Python

Python
self.send_message(channel, "🎰 Tente ta chance !", components=[{
    "type"      : "minigame",
    "game"      : "slots",
    "credits"  : 10,      # nombre de crédits de départ
    "symbols"  : ["🍒","🍋","🍊","🍇","⭐","💎","🔔"],
    "color"     : "#f59e0b",
    "custom_id" : "slots_game"  # values = ["dernier_gain"]
}])

Paramètres

ParamètreTypeDéfautDescription
creditsint10Nombre de crédits de départ
symbolslist[…]7 émojis utilisés pour les rouleaux
colorstr#f59e0bCouleur du cadre
custom_idstrvalues[0] = dernier gain obtenu
🎲 Hasard & Stratégie

🃏 Plus Haut / Plus Bas

Un jeu de cartes à 52 cartes mélangées. Devine si la prochaine carte est plus haute ou plus basse. Les series consécutives sont indiquées.

Démo live

Plus Haut / Plus Bas — démo interactive
GameBot MaintenantBOT

Code Python

Python
self.send_message(channel, "🃏 Plus haut ou plus bas ?", components=[{
    "type"      : "minigame",
    "game"      : "higher_lower",
    "rounds"    : 13,     # nombre de cartes à deviner
    "color"     : "#06b6d4",
    "custom_id" : "hl_game"    # values = ["nb_bonnes_réponses"]
}])

Paramètres

ParamètreTypeDéfautDescription
roundsint13Nombre de cartes à deviner
colorstr#06b6d4Couleur accent
custom_idstrvalues[0] = bonnes réponses sur le total
🎲 Hasard & Stratégie

🔴 Puissance 4

Grille 7×6. Le joueur joue contre un bot avec IA simple (gagne > bloque > centre). Premier à aligner 4 remporte la partie.

Démo live

Puissance 4 — démo interactive
GameBot MaintenantBOT

Code Python

Python
self.send_message(channel, "🔴 Puissance 4 !", components=[{
    "type"      : "minigame",
    "game"      : "connect4",
    "color"     : "#f87171",   # couleur du joueur
    "custom_id" : "c4_game"   # values = ["1"] gagné / ["0"] perdu / ["draw"]
}])

Paramètres

ParamètreTypeDéfautDescription
colorstr#f87171Couleur des jetons du joueur
custom_idstr"1" gagné, "0" perdu, "draw" égalité
🎲 Hasard & Stratégie

⌨️ Dactylographie

Tape la phrase affichée le plus rapidement possible. Plusieurs phrases enchâinées. Score = meilleur temps en secondes. Phrases entièrement configurables.

Démo live

Dactylographie — démo interactive
GameBot MaintenantBOT

Code Python

Python
self.send_message(channel, "⌨️ Speed typing !", components=[{
    "type"      : "minigame",
    "game"      : "typing",
    "phrases"  : [
        "Vite vite il faut taper !",
        "Le chat dort sur le canapé",
        "Bienvenue sur Le Navire"
    ],
    "color"     : "#10b981",
    "custom_id" : "typing_game"  # values = ["meilleur_temps_s"]
}])

Paramètres

ParamètreTypeDéfautDescription
phraseslist5 phrases défautListe de phrases à taper
colorstr#10b981Couleur accent
custom_idstrvalues[0] = meilleur temps en secondes
🎵 SDK — Audio

🔊 Voice & Musique

Fais rejoindre ton bot dans un salon vocal, lance une musique depuis YouTube / SoundCloud (via yt-dlp) ou depuis Spotify (via spotipy), et contrôle la lecture en temps réel.

Rejoindre / Quitter un vocal

Python
# Rejoindre le salon vocal id=456
self.join_voice(456)

# Quitter le salon vocal
self.leave_voice(456)

Jouer de la musique

Python — YouTube / SoundCloud
if content.startswith('!play '):
    url = content[6:].strip()
    self.join_voice(456)                     # rejoindre d'abord
    track = self.play_music(456, url, volume=0.8)
    self.send_message(channel,
        embed={
            "title": "🎵 En lecture",
            "description": track['title'],
            "color": "#00d9a3",
            "thumbnail": track.get('thumbnail'),
            "footer": f"Durée : {track.get('duration','?')} s"
        }
    )
Python — Arrêter la lecture
if content == '!stop':
    self.stop_music(456)
    self.leave_voice(456)

Recherche de pistes

Python
if content.startswith('!search '):
    query = content[8:].strip()
    results = self.search_music(query, limit=5)
    lines = [f"**{i+1}.** {r['title']} — {r.get('uploader','?')}"
             for i, r in enumerate(results)]
    self.send_message(channel, "\n".join(lines))

Infos d'une piste

Python
info = self.get_track_info("https://www.youtube.com/watch?v=...")
# → {'title': '...', 'uploader': '...', 'duration': 213, 'thumbnail': '...', 'source': 'youtube'}

# Spotify (nécessite spotipy + SPOTIPY_CLIENT_ID / SECRET en env)
info = self.get_track_info("https://open.spotify.com/track/...")
# → {'title': '...', 'artist': '...', 'album': '...', 'duration': 210, 'source': 'spotify'}

Membres dans un vocal

Python
members = self.get_voice_members(456)
for m in members:
    print(m['username'])  # liste des membres en ligne dans ce salon

Référence SDK

MéthodeParamètresRetour
join_voice(channel_id)int{'success': True}
leave_voice(channel_id)int{'success': True}
play_music(channel_id, url, volume=0.8)int, str, floatDict piste (title, duration, thumbnail…)
stop_music(channel_id)int{'success': True}
search_music(query, limit=5)str, intListe de résultats
get_track_info(url)strDict infos piste
get_voice_members(channel_id)intListe membres en ligne
⚙️
Dépendances requises
yt-dlp — lecture YouTube / SoundCloud / 1 000+ sources (pip)
spotipy — support Spotify (pip, optionnel)
Installe-les depuis l'onglet Dépendances de ton bot ou via la commande !installdep ci-dessous.
📦 SDK — Dépendances

📦 Gestionnaire de dépendances

Installe des packages Python (pip) ou JavaScript (npm) directement depuis l'éditeur de ton bot. Chaque bot dispose de son propre espace isolé.

Installer une dépendance

Dans l'interface, onglet Dépendances du bot → tape le nom du package → clic sur Installer.

Python — exemple : installer requests
# Dans ton bot, après installation via l'UI :
import requests
response = requests.get("https://api.example.com/data")
data = response.json()
JavaScript — exemple : installer axios
// Dans ton bot, après installation via l'UI :
const axios = require('axios');
const res = await axios.get('https://api.example.com/data');
console.log(res.data);

Quota de stockage

PlanEspace fichiersDépendances
🆓 Gratuit100 MBIllimitées dans le quota
⭐ Premium500 MBIllimitées dans le quota
⚠️
Filtre de sécurité — packages récents
Les packages publiés depuis moins d'un an sont refusés automatiquement. Cette règle protège contre les packages malveillants récemment soumis sur PyPI / npm.

Commande bot — installer depuis le chat

Python — commande !installdep
import subprocess

if content.startswith('!installdep '):
    pkg = content.split(' ', 1)[1].strip()
    result = subprocess.run(['pip', 'install', pkg],
                            capture_output=True, text=True)
    if result.returncode == 0:
        self.send_message(channel, f"✅ `{pkg}` installé avec succès !")
    else:
        self.send_message(channel, f"❌ Erreur : {result.stderr[:200]}")

Packages populaires

PackageTypeUsage
requestspipRequêtes HTTP
yt-dlppipTéléchargement / info audio-vidéo (requis pour Voice & Musique)
spotipypipAPI Spotify (optionnel pour Voice)
PillowpipTraitement d'images
aiohttppipRequêtes HTTP asynchrones
axiosnpmRequêtes HTTP (JS)
node-fetchnpmFetch API pour Node.js