Introduction
Aujourd’hui, nous allons apprendre les fondamentaux de la programmation de bot Discord en Python avec la bibliothèque Pycord.
Prérequis
Attention : Des bases solides en Python sont nécessaires pour la programmation d’un bot Discord. Comprendre des concepts tels que la programmation asynchrone et les requêtes API est important.
Nous utiliserons les outils suivants :
Pour installer Python, il faut se rendre sur le site officiel : python.org . Une fois Python installé et ajouté au PATH (variables d’environnement), on peut installer Pycord avec la commande suivante :
pip install py-cord
Dans le cas ou la commande précédente ne fonctionne pas :
python -m pip install py-cord
Fonctionnement d’un bot Discord
Un bot Discord fonctionne sur le principe d’API. Notre bot reçoit les informations de Discord sur les actions effectuées par un utilisateur, exécute les fonctions correspondantes puis renvoie à Discord une instruction (envoyer un message, octroyer un rôle…). L’action d’un utilisateur déclenche alors les instructions programmées dans l’application (ici, un bot).
Création de l’application
Un bot Discord est une application Discord. Pour le créer, il faut se rendre sur la page “Développeur” de Discord : discord.com/developers .
Il est également important de configurer les “Intents”, les permissions de notre application : lire des messages, obtenir des informations sur les serveurs…
Pour inviter le bot dans un serveur, il faut générer un lien dans la section “OAuth2” en prenant soin de cocher les cases “bot”, et “applications.commands”. Cela permettra au bot d’ajouter des commandes au serveur qu’il rejoint.
Cet article ne se penche pas en détails sur la création de l’application. Pour plus d’informations, vous pouvez vous référer à ces ressources :
- Créer une application (Anglais, article)
- Créer une application (Anglais, vidéo)
Premier bot
Instanciation d’un objet Client
Dans un premier temps, nous allons créer un bot avec une commande simple. Pour ce faire, il faut d’abord instancier un
object Client
de la bibliothèque discord
:
import discord
intents = discord.Intents.default()
client = discord.Client(intents=intents, status="online")
Ici, on déclare d’abord les “Intents”, celles définies dans le portail développeur de Discord. Cette variable peut prendre plusieurs valeurs selon les options choisies :
discord.Intents.all()
: Toutes les permissions.discord.Intents.none()
: Aucune permission.
Pour choisir d’autoriser certains Intents, il faut définir l’attribut associé à True
. Voici un exemple pour autoriser
la lecture du contenu des messages :
import discord
intents = discord.Intents.default()
intents.message_content = True
Tous les attributs configurables peuvent être trouvés dans la documentation officielle de Pycord .
Une fois les Intents définis, on instancie un objet Client
en passant en paramètres les Intents ainsi que le
statut du bot (En ligne, Occupé, Absent…). Cet objet correspond au bot, ce sont ses attributs et méthodes qui seront
modifiés pour répondre aux actions des utilisateurs.
Première commande
Configurons à présent une commande “slash” (dont le préfixe est “/") basique pour saluer un utilisateur :
@client.slash_command(name="hello", description="Saluer un utilisateur.")
async def hello(msg, utilisateur: str):
await msg.respond(f"Salutation à toi, {utilisateur}.")
Ici, le décorateur @client.slash_command()
sert à déclarer une commande ainsi que ses spécifications : nom,
description, restriction de serveurs…
(voir plus ici
).
La fonction décorée correspond à la fonction appelée lorsqu’un utilisateur entre la commande. Cette dernière comporte
plusieurs paramètres :
msg
: Le premier paramètre correspond au contexte dans lequel est exécutée la commande. On peut notamment accéder à l’auteur du message, à son contenu et d’autres attributs .utilisateur: str
: Un paramètre portant le même nom qui est précisé par l’utilisateur de la commande. Il est important de mentionner le type de cette variable pour que Discord puisse vérifier que l’utilisateur entre les paramètres comme il se doit. Malgré tout, on utilise ici un moyen assez primitif de déclarer des paramètres (nous en étudierons un plus poussé).
Démarrage du bot
À présent, nous pouvons allumer le bot pour le tester en ajoutant cette ligne à la fin du code :
client.run(token)
La variable token
est l’identifiant unique du bot, à ne surtout pas partager et disponible sur le
portail développeur
.
Gestion d’évènements
Il est maintenant temps d’explorer la gestion des évènements basiques tels que :
- Changer le message de statut du bot.
- Message personnalisé lorsqu’un utilisateur rejoint/ quitte un serveur.
Ces évènements sont accessibles via des méthodes existantes trouvables dans la documentation :
on_ready()
: Fonction appelée à la mise en ligne du bot.on_member_join(member)
: Fonction appelée lorsqu’un utilisateur rejoint un serveur.
Changer le statut au démarrage
De manière générale, on utilise la fonction on_ready()
car elle est presque toujours appelée au démarrage du bot. Ce
qui est pratique pour définir le statut du bot. À noter que la définition du statut est, elle, réalisée par
client.change_presence()
.
@client.event
async def on_ready():
await client.change_presence(activity=discord.Game("Un jeu"))
Ce code change donc le statut du bot pour “Joue à Un jeu” à son démarrage.
Note : Il est possible de choisir d’autres activités telles que “Écoute …” ou “Streame …”. Voir la documentation des activités .
Message d’accueil sur un serveur
Pour envoyer un message à l’arrivée d’un membre
,
il faut utiliser on_member_join(member)
pour réagir à l’évènement.
Mais il faut également un canal dans lequel le message sera envoyé. Ici, il sera récupéré grâce à son ID (identifiant
unique).
@client.event
async def on_member_join(member):
channel = client.get_channel(CHANNEL_ID)
await channel.send(f"Bienvenue à {member.name} sur le serveur.")
Note : Cette fonction requiert que l’Intent “Intents.members” soit activé pour être accessible.
Exercice : Faire en sorte que le bot envoie un message lorsqu’un utilisateur quitte un serveur. Vous pouvez vous aider des ressources suivantes :
on_member_remove(member)
: Fonction appelée lorsqu’un utilisateur quitte un serveur.Member
: Objet représentant un membre d’un serveur.
Commandes à paramètres poussés
On a vu plus haut une manière de créer une commande à paramètre. Ici, nous allons étudier le décorateur @option
ainsi
que la classe Option()
. Il sera possible de configurer les paramètres que prend la commande : type, valeur minimum/
maximum, optionnel ou non, description…
Les deux possibilités se ressemblent, dans chaque cas, il sera possible de préciser des détails sur les paramètres. Il est important de toujours mentionner le type de variable attendu (utilisateur, entier, texte…).
Avec le décorateur @option
, il faut passer en premier argument le nom du paramètre associé. Alors que la classe
Option()
sera utilisée pour annoter un paramètre, il n’est donc pas utile de préciser le nom de la variable.
Avec @option
import discord
from discord import option
@client.slash_command(name="gift", description="Offrir une quantité de pièces à un utilisateur.")
@option("utilisateur", type=discord.Member, required=True)
@option("quantite", type=int, min_value=10, max_value=100, default=10, description="Nombre de pièces")
async def gift(msg, utilisateur, quantite):
await msg.respond(f"Vous offrez {quantite} pièces à {utilisateur.mention}")
Avec Option()
import discord
from discord import Option
@client.slash_command(name="gift", description="Offrir une quantité de pièces à un utilisateur.")
async def gift(msg,
utilisateur: Option(discord.Member, required=True),
quantite: Option(int, min_value=10, max_value=100, default=10, description="Nombre de pièces")):
await msg.respond(f"Vous offrez {quantite} pièces à {utilisateur.mention}")
Dans ces deux exemples, la commande prend en paramètre un membre du serveur (discord.Member
) qui est un paramètre
obligatoire (required=True
). Un paramètre pour indiquer le nombre de pièces est configuré pour avoir une valeur par
défaut de 10, ainsi qu’un intervalle de valeurs valides (de 10 à 100). Il est possible de donner une description à un
paramètre, elle est alors affichées dans Discord lorsque l’on rentre la commande.
Il existe de nombreuses options configurations pour spécifier un paramètre, trouvables dans la documentation . Une liste des types acceptables pour un paramètre s’y trouve également.
Les deux méthodes sont équivalentes, utilisez votre favorite !
Note : Il est possible de créer des paramètres prédéterminés parmi lesquels l’utilisateur peut choisir. Cela se fait
avec le paramètre choices
qui
correspond à la liste des choix pour l’utilisateur.
On peut également proposer des choix avec
OptionChoice
pour associer une
valeur à un choix (à l’instar d’un dictionnaire) :
@client.slash_command(name="buy", description="Acheter un kilo de fruit")
async def buy(msg,
fruit: Option(str, required=True, choices=[
OptionChoice("Pomme", "1.8"),
OptionChoice("Banane", "2.25")
])):
await msg.respond(f"Le prix est de {fruit} €")
C’est l’équivalent de :
fruits: dict = {
"Pomme": "1.8",
"Banane": "2.25"
}
@client.slash_command(name="buy", description="Acheter un kilo de fruit")
async def buy(msg,
fruit: Option(str, required=True, choices=["Pomme", "Banane"])):
await msg.respond(f"Le prix est de {fruits[fruit]} €")
Gestion des permissions
À l’instar de l’ajout d’une option, on peut restreindre l’usage de commande aux utilisateurs possédant une permission
en particulier. Cela se fait à l’aide d’un décorateur :
@has_permissions
.
Voici un exemple pour une commande qui supprime un nombre donné de messages :
import discord
from discord.ext.commands import has_permissions
@client.slash_command(name="clear", description="Supprime un nombre donné de message.")
@has_permissions(manage_messages=True)
async def clear(msg,
nombre: Option(int, min_value=1, max_value=100, required=True)):
await msg.channel.purge(limit=nombre)
await msg.respond(f"{nombre} messages supprimés.")
L’usage de cette commande est restreint aux utilisateurs qui possèdent la permission de gérer les messages. La liste complète des permissions se trouve ici .
Exercice : Créer une commande pour rendre un utilisateur muet pendant une durée donnée. Restreindre l’usage de cette commande aux membres qui ont la permission. Vous pouvez vous aider des ressources suivantes :
Permissions.mute_members
> : La permission associée au fait de rendre muet un utilisateur.
Gestion des délais (Cooldown
)
Une fonctionnalité intéressante est de pouvoir limiter l’usage d’une commande avec un délai. On parle alors de
cooldown
. Un cooldown
se définit principalement par deux variables :
- Un intervalle de temps
- Un nombre de commandes autorisées pendant cet intervalle
C’est le décorateur @commands.cooldown(rate, per, type)
qui permet cela :
rate
: Nombre de fois que la commande peut être utilisée avant de déclencher le délai.per
: La durée pendant laquelle la commande n’est plus utilisable.type
: Le type decooldown
.
Voici un exemple d’une commande limitée à 2 usages par intervalle de 10 secondes :
import discord
from discord import Option
from discord.ext import commands
@client.slash_command(name="clear", description="Supprime un nombre donné de message.")
@commands.cooldown(2, 10, commands.BucketType.user)
async def clear(msg, nombre: Option(int, min_value=1, max_value=100, required=True)):
await msg.channel.purge(limit=nombre)
await msg.respond(f"{nombre} messages supprimés.")
La commande précédente est donc limitée par un cooldown
, mais il est aussi intéressant de prévoir un message pour
avertir l’utilisateur de la durée restante.
Pour cela, nous allons utiliser le fait que l’erreur
CommandOnCooldown
est levée lorsqu’un utilisateur tente d’exécuter une commande pendant le délai.
Il existe une fonction déclenchée à chaque erreur de commande : on_application_command_error
. Nous allons donc prévoir
le cas où nous rencontrons l’erreur CommandOnCooldown
pour répondre à l’utilisateur :
@client.event
async def on_application_command_error(msg, erreur):
if isinstance(erreur, commands.CommandOnCooldown): # Vérifier le type de l'erreur
await msg.respond(f"Veuillez patienter {erreur.retry_after:.1f}s")
else:
raise erreur
Envoi d’un Embed
Une autre fonctionnalité intéressante de pouvoir envoyer des intégrations, dites
Embed
.
Dans un premier temps, il faut instancier un objet Embed()
, puis spécifier ses attributs. En voici quelques-uns, la liste
complète se trouve dans la documentation
:
title
: Le titre de l’intégration.color
/colour
: La couleur de la barre à gauche de l’intégration.footer
: Le texte au pied de l’intégration.
Certains de ces attributs peuvent être indiqués à l’instanciation de l’objet (comme title
ou color
), d’autres sont
configurables avec une méthode dédiée (par exemple, footer
est défini avec la méthode
set_footer()
).
Un autre aspect important des intégrations sont les “champs” (fields
). Un champ est une ligne de l’intégration définie
par un nom, ainsi qu’une valeur. Pour ajouter un champ, il faut faire appel à la méthode
add_field()
.
Voici un exemple d’intégration qui contient une image, ainsi qu’un champ :
@client.slash_command(name="info", description="Statistique de l'utilisateur")
async def my_embed(msg):
embed = discord.Embed(title="Informations", description=f"Les informations sur {msg.author.name}", color=0x46e32b)
embed.set_thumbnail(url="https://placehold.co/[email protected]")
embed.add_field(name="Pièces", value="Vous possédez 10 pièces", inline=False)
await msg.respond(embed=embed)
La création d’un Embed
peut parfois s’avérer fastidieuse, il existe de nombreux sites pour facilement configurer et
prévisualiser des Embed
. Par exemple :
Conclusion
Avec tous les éléments précédents, vous possédez dorénavant de bonnes connaissances pour démarrer la création d’un bot Discord. Dans un article prochain, le sujet des composants interactifs sera abordé. Il concernera la mise en place de boutons, menus déroulants, formulaires, réactions…
Crédits et ressources
Documentation officielle de Pycord : docs.pycord.dev .