Cet article remplace mon précédent article « Un music bot avec Discord.js en 5 minutes ! » qui utilisait un paquet npm devenu aujourd’hui obsolète.
Je vous propose donc de suivre cette nouvelle méthode afin de créer vous même votre propre bot musical.
L’avantage de cette technique est que vous contrôlez tout de A à Z, tout n’est plus fait de façon automatique comme dans le précédent tuto.
Comme mon précédent article, je vous laisse suivre le tuto « Discord Js: Créer votre propre bot » afin d’avoir une base de travail.
Sans plus attendre, initialisons notre projet !

Installation des dépendances
Tout d’abords, rendons nous dans le dossier de notre bot que vous obtenez après avoir suivi le tuto « Discord Js: Créer votre propre bot« .
Une fois à l’intérieur, nous allons installer les dépendances nécessaire pour que tout fonctionne:
npm install discord.js ffmpeg fluent-ffmpeg ffmpeg-static @discordjs/opus ytdl-core --save
Configuration
Nous allons déplacer les constantes de configuration dans un fichier qui s’appellera config.json et que l’on placera à côté de notre fichier index.js.
On va donc y définir deux valeurs: prefix et token.
Prefix correspond au caractère devant préfixer les commandes du bot pour être reconnues.
Token correspond au token discord obtenu pendant la création du bot sur le site de discord.
Voici le contenu du fichier config.json:
{ "prefix": "&", "token": "votre-token" }
Place au code de notre bot musical
Tout se passe désormais dans le fichier index.js.
Dans un premier temps, importons les dépendances dont nous aurons besoin:
const Discord = require('discord.js'); /// DÉBUT NOUVEAU const { prefix, token} = require('./config.json'); const ytdl = require('ytdl-core'); /// FIN NOUVEAU const client = new Discord.Client();
Puis nous allons devoir traiter les différentes commandes de l’utilisateur, pour cela on reprend notre code (celui du ping) existant et on l’améliore:
client.on('message', msg => { if (msg.content === 'ping') { msg.reply('pong'); } });
Deviens donc dans un premiers temps:
client.on('message', msg => { if (msg.author.bot) return; if (!msg.content.startsWith(prefix)) return; });
Ici on ignore le message si celui viens de notre bot ou si il ne commence pas par notre préfixe.
On créer ensuite une constante représentant la queue du serveur discord grâce à l’id de la guild que l’on récupère sur le message:
const serverQueue = queue.get(message.guild.id);
On continue en en traitant les différentes commandes de notre bot:
if (message.content.startsWith(`${prefix}play`)) { // Code pour la commande play return; } else if (message.content.startsWith(`${prefix}skip`)) { // Code pour la commande skip return; } else if (message.content.startsWith(`${prefix}stop`)) { // Code pour la commande skip return; } else { message.channel.send("Vous avez entrez une commande invalide !"); }
Nous remplirons par la suite le code pour chaque commande, préparons avant ça la variable qui représentera la queue de musique.
On créer donc une variable queue de type map:
const Discord = require("discord.js"); const { prefix, token } = require("./config.json"); const ytdl = require("ytdl-core"); const client = new Discord.Client(); // NOUVEAU const queue = new Map();
Pour information, un objet de type map est itérable dans l’ordre d’arrivé de ses éléments. C’est parfait dans le cas d’une queue de musique à lire !
Vous pouvez consulter la documentation à cette adresse.
Créons ensuite une fonction qui sera appelée lorsque le bot devra jouer une musique. Dans un premier temps, vérifions deux points:
- L’utilisateur est bien dans un salon vocal
- Le bot possède bien les permissions de se connecter au salon vocal et d’y parler
Voici ce que cela donne:
async function execute(message, serverQueue) { const args = message.content.split(" "); // On récupère les arguments dans le message pour la suite const voiceChannel = message.member.voice.channel; if (!voiceChannel) // Si l'utilisateur n'est pas dans un salon vocal return message.channel.send( "Vous devez être dans un salon vocal!" ); const permissions = voiceChannel.permissionsFor(message.client.user); // On récupère les permissions du bot pour le salon vocal if (!permissions.has("CONNECT") || !permissions.has("SPEAK")) { // Si le bot n'a pas les permissions return message.channel.send( "J'ai besoin des permissions pour rejoindre le salon et pour y jouer de la musique!" ); } }
On continue, toujours dans la fonction execute, on récupère les informations de la musique passé en argument dans le message.
On utilise le paquet ytdl pour récupérer les infos de youtube.
Seuls le titre et l’url de la vidéo nous intéresse, on les stock donc dans une variable song:
const songInfo = await ytdl.getInfo(args[1]); const song = { title: songInfo.videoDetails.title, url: songInfo.videoDetails.video_url, };
Nous ajoutons ensuite la musique à notre queue de musique, voyons d’abord le cas où serverQueue existe:
if (!serverQueue) { }else { serverQueue.songs.push(song); console.log(serverQueue.songs); return message.channel.send(`${song.title} has been added to the queue!`); }
Maintenant si serverQueue n’existe pas, on le créer, on l’ajoute à la queue global et on push la musique dedans.
Ensuite on connecte le bot au salon vocal et on lance la musique:
const queueConstruct = { textChannel: message.channel, voiceChannel: voiceChannel, connection: null, songs: [], volume: 1, playing: true, }; // On ajoute la queue du serveur dans la queue globale: queue.set(message.guild.id, queueConstruct); // On y ajoute la musique queueContruct.songs.push(song); try { // On connecte le bot au salon vocal et on sauvegarde l'objet connection var connection = await voiceChannel.join(); queueContruct.connection = connection; // On lance la musique play(message.guild, queueContruct.songs[0]); } catch (err) { //On affiche les messages d'erreur si le bot ne réussi pas à se connecter, on supprime également la queue de lecture console.log(err); queue.delete(message.guild.id); return message.channel.send(err); }
Jouer une musique
On va implémenter la fonction appelé lorsque l’utilisateur tape la commande play.
Tout d’abord on va compléter le passage où l’on surveille les différentes commandes tapées par l’utilisateur en y rajoutant nos appels de fonction:
if (message.content.startsWith(`${prefix}play`)) { execute(message, serverQueue); // On appel execute qui soit initialise et lance la musique soit ajoute à la queue la musique return; } else if (message.content.startsWith(`${prefix}skip`)) { skip(message, serverQueue); // Permettra de passer à la musique suivante return; } else if (message.content.startsWith(`${prefix}stop`)) { stop(message, serverQueue); // Permettra de stopper la lecture return; } else { message.channel.send("You need to enter a valid command!"); }
Voici le contenu de la fonction play:
function play(guild, song) { const serverQueue = queue.get(guild.id); // On récupère la queue de lecture if (!song) { // Si la musique que l'utilisateur veux lancer n'existe pas on annule tout et on supprime la queue de lecture serverQueue.voiceChannel.leave(); queue.delete(guild.id); return; } // On lance la musique const dispatcher = serverQueue.connection .play(ytdl(song.url, { filter: 'audioonly' })) .on("finish", () => { // On écoute l'événement de fin de musique serverQueue.songs.shift(); // On passe à la musique suivante quand la courante se termine play(guild, serverQueue.songs[0]); }) .on("error", error => console.error(error)); dispatcher.setVolume(serverQueue.volume); // On définie le volume serverQueue.textChannel.send(`Démarrage de la musique: **${song.title}**`); }
Passer une musique
C’est maintenant le tour de la fonction skip:
function skip(message, serverQueue) { if (!message.member.voice.channel) // on vérifie que l'utilisateur est bien dans un salon vocal pour skip return message.channel.send( "Vous devez être dans un salon vocal pour passer une musique!" ); if (!serverQueue) // On vérifie si une musique est en cours return message.channel.send("Aucune lecture de musique en cours !"); serverQueue.connection.dispatcher.end(); // On termine la musique courante, ce qui lance la suivante grâce à l'écoute d'événement finish }
Stopper la lecture
La fonction stop enfin ! Elle est quasiment la même que celle pour passer une musique.
On vide juste la liste des musique avant de terminer la courante:
function stop(message, serverQueue) { if (!message.member.voice.channel) // on vérifie que l'utilisateur est bien dans un salon vocal pour skip return message.channel.send( "Vous devez être dans un salon vocal pour stopper la lecture!" ); if (!serverQueue) // On vérifie si une musique est en cours return message.channel.send("Aucune lecture de musique en cours !"); serverQueue.songs = []; serverQueue.connection.dispatcher.end(); }
Conclusion
Au final voici le code que l’on obtiens pour notre bot musical:
const Discord = require("discord.js"); const {prefix, token} = require("./config.json"); const ytdl = require("ytdl-core"); const client = new Discord.Client(); const queue = new Map(); client.once("ready", () => { console.log("Ready!"); }); client.once("reconnecting", () => { console.log("Reconnecting!"); }); client.once("disconnect", () => { console.log("Disconnect!"); }); client.on("message", async message => { if (message.author.bot) { return; } if (!message.content.startsWith(prefix)) { return; } const serverQueue = queue.get(message.guild.id); if (message.content.startsWith(`${prefix}play`)) { execute(message, serverQueue); // On appel execute qui soit initialise et lance la musique soit ajoute à la queue la musique return; } else if (message.content.startsWith(`${prefix}skip`)) { skip(message, serverQueue); // Permettra de passer à la musique suivante return; } else if (message.content.startsWith(`${prefix}stop`)) { stop(message, serverQueue); // Permettra de stopper la lecture return; } else { message.channel.send("You need to enter a valid command!"); } }); async function execute(message, serverQueue) { const args = message.content.split(" "); // On récupère les arguments dans le message pour la suite const voiceChannel = message.member.voice.channel; if (!voiceChannel) // Si l'utilisateur n'est pas dans un salon vocal { return message.channel.send( "Vous devez être dans un salon vocal!" ); } const permissions = voiceChannel.permissionsFor(message.client.user); // On récupère les permissions du bot pour le salon vocal if (!permissions.has("CONNECT") || !permissions.has("SPEAK")) { // Si le bot n'a pas les permissions return message.channel.send( "J'ai besoin des permissions pour rejoindre le salon et pour y jouer de la musique!" ); } const songInfo = await ytdl.getInfo(args[1]); const song = { title: songInfo.videoDetails.title, url : songInfo.videoDetails.video_url, }; if (!serverQueue) { const queueConstruct = { textChannel : message.channel, voiceChannel: voiceChannel, connection : null, songs : [], volume : 1, playing : true, }; // On ajoute la queue du serveur dans la queue globale: queue.set(message.guild.id, queueConstruct); // On y ajoute la musique queueConstruct.songs.push(song); try { // On connecte le bot au salon vocal et on sauvegarde l'objet connection var connection = await voiceChannel.join(); queueConstruct.connection = connection; // On lance la musique play(message.guild, queueConstruct.songs[0]); } catch (err) { //On affiche les messages d'erreur si le bot ne réussi pas à se connecter, on supprime également la queue de lecture console.log(err); queue.delete(message.guild.id); return message.channel.send(err); } } else { serverQueue.songs.push(song); console.log(serverQueue.songs); return message.channel.send(`${song.title} has been added to the queue!`); } } function skip(message, serverQueue) { if (!message.member.voice.channel) // on vérifie que l'utilisateur est bien dans un salon vocal pour skip { return message.channel.send( "Vous devez être dans un salon vocal pour passer une musique!" ); } if (!serverQueue) // On vérifie si une musique est en cours { return message.channel.send("Aucune lecture de musique en cours !"); } serverQueue.connection.dispatcher.end(); // On termine la musique courante, ce qui lance la suivante grâce à l'écoute d'événement // finish } function stop(message, serverQueue) { if (!message.member.voice.channel) // on vérifie que l'utilisateur est bien dans un salon vocal pour skip { return message.channel.send( "Vous devez être dans un salon vocal pour stopper la lecture!" ); } if (!serverQueue) // On vérifie si une musique est en cours { return message.channel.send("Aucune lecture de musique en cours !"); } serverQueue.songs = []; serverQueue.connection.dispatcher.end(); } function play(guild, song) { console.log(song); const serverQueue = queue.get(guild.id); // On récupère la queue de lecture if (!song) { // Si la musique que l'utilisateur veux lancer n'existe pas on annule tout et on supprime la queue de lecture serverQueue.voiceChannel.leave(); queue.delete(guild.id); return; } // On lance la musique const dispatcher = serverQueue.connection .play(ytdl(song.url, { filter: 'audioonly' })) .on("finish", () => { // On écoute l'événement de fin de musique serverQueue.songs.shift(); // On passe à la musique suivante quand la courante se termine play(guild, serverQueue.songs[0]); }) .on("error", error => console.error(error)); dispatcher.setVolume(1); // On définie le volume serverQueue.textChannel.send(`Démarrage de la musique: **${song.title}**`); } client.login(token);
Sources
Articles récents
- GAFA et NATU: Qui sont ces géants ?
- Un serveur web basique en Node.JS
- NVM : Un super gestionnaire de version Node.JS
- Phaser: Créer son propre jeu vidéo
- Déstructuration et création de variable
GG sa marche mais pour le config.json tu peux donner le moyen de mettre le prefix
Merci beaucoup pour ton commentaire positif 🙂
Je rajoute ça demain !
Je viens de rajouter une partie configuration vers le début de l’article afin d’expliquer comment rajouter le fichier config.json avec le prefix à l’intérieur 🙂
Passe une très bonne journée et reviens vers moi si tu as le moindre soucis !
Bonjour/Bonsoir En se moment le bot de musique fonction plus pour quel raison ?
Bonjour !
Peux tu me dire les erreurs que tu as avec ton bot ?
Car je viens d’essayer et il fonctionne toujours pour moi, on va essayer de comprendre ensemble pourquoi cela ne fonctionne plus chez toi 🙂
TypeError [ERR_INVALID_ARG_TYPE]: The « url » argument must be of type string. Received undefined
at validateString (internal/validators.js:120:11)
at Url.parse (url.js:159:3)
at Object.urlParse [as parse] (url.js:154:13)
at Object.exports.getURLVideoID (/home/container/node_modules/ytdl-core/lib/url-utils.js:30:22)
at Object.exports.getVideoID (/home/container/node_modules/ytdl-core/lib/url-utils.js:63:20)
at Function.exports. [as getInfo] (/home/container/node_modules/ytdl-core/lib/info.js:312:23)
at ytdl (/home/container/node_modules/ytdl-core/lib/index.js:19:8)
at play (/home/container/index.js:645:11)
at execute (/home/container/index.js:567:7)
at processTicksAndRejections (internal/process/task_queues.js:97:5) {
code: ‘ERR_INVALID_ARG_TYPE’
}
(node:13) UnhandledPromiseRejectionWarning: DiscordAPIError: Cannot send an empty message
at RequestHandler.execute (/home/container/node_modules/discord.js/src/rest/RequestHandler.js:170:25)
at processTicksAndRejections (internal/process/task_queues.js:97:5)
(node:13) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `–unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:13) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Nan c’est bon j’ai trouver le probleme
Probleme :
const songInfo = await ytdl.getInfo(args[1]);
const song = {
title: songInfo.title,
url: songInfo.video_url
};
Resolution :
const songInfo = await ytdl.getInfo(args[1]);
const song = {
title: songInfo.videoDetails.title,
url : songInfo.videoDetails.video_url,
};
Nan c’est bon j’ai trouver le probleme
Probleme :
const songInfo = await ytdl.getInfo(args[1]);
const song = {
title: songInfo.title,
url: songInfo.video_url
};
Resolution :
const songInfo = await ytdl.getInfo(args[1]);
const song = {
title: songInfo.videoDetails.title,
url : songInfo.videoDetails.video_url,
};
Parfait !
Peux tu me dire si l’erreur venait de l’article s’il te plait ou si ça n’était pas clair ? 🙂
Bonjour Sa serais possible de faire un system de recherche en gros !play rechercheyoutube
est sa met la musique ? si possible je vous remercie
C’est possible mais il faut tout d’abord utiliser l’API de youtube afin de faire des recherches: https://developers.google.com/youtube/v3/docs/search/list
Après en fonction des résultats on peux imaginer proposer les résultats à choisir à l’utilisateur ou bien lancer le résultat le plus pertinent 🙂
Je vais voir à l’avenir à en faire un autre article mais en attendant je suis disponible si jamais tu as des questions !
Bonjour/Bonsoir sa serais possible de m’aider sur la fonction de play car je ne comprend rien merci d’avence
Salut j’ai des erreurs quand je veux installer le module
Salut !
Peux tu me donner les erreurs que tu as s’il te plait ?
moi j’ai un probleme quand je lance un musique
Bonjour dans un premier temps c’est toujours d’actualité et aussi est ce que avec cette command on peux faire r/play (search)
Bonjour est ce que cette commande et d’actualité et deuxiement est ce que on peux faire exemple !play (search) car sur tout les tuto y’avais des commande de musique que avec des URL
Bonjour Abey !
Pour le moment la commande search n’est pas disponible mais je vais écrire un nouvel article pour ça courant janvier
Je te laisse venir checker ça dans une dizaine de jours 🙂
Bonjour j’obtiens sa comme erreur quand je veux joué une musique
(node:19024) UnhandledPromiseRejectionWarning: TypeError: Cannot read property ‘channel’ of undefined
at execute (c:\Users\camba\OneDrive\Bureau\bot\index.js:201:45)
at Client. (c:\Users\camba\OneDrive\Bureau\bot\index.js:181:11)
at Client.emit (events.js:327:22)
at MessageCreateHandler.handle (c:\Users\camba\OneDrive\Bureau\bot\node_modules\discord.js\src\client\websocket\packets\handlers\MessageCreate.js:9:34)
at WebSocketPacketManager.handle (c:\Users\camba\OneDrive\Bureau\bot\node_modules\discord.js\src\client\websocket\packets\WebSocketPacketManager.js:108:65)
at WebSocketConnection.onPacket (c:\Users\camba\OneDrive\Bureau\bot\node_modules\discord.js\src\client\websocket\WebSocketConnection.js:336:35)
at WebSocketConnection.onMessage (c:\Users\camba\OneDrive\Bureau\bot\node_modules\discord.js\src\client\websocket\WebSocketConnection.js:299:17)
at WebSocket.onMessage (c:\Users\camba\OneDrive\Bureau\bot\node_modules\ws\lib\event-target.js:120:16)
at WebSocket.emit (events.js:315:20)
at Receiver.receiverOnMessage (c:\Users\camba\OneDrive\Bureau\bot\node_modules\ws\lib\websocket.js:789:20)
/internal/process/warning.js:32
(node:19024) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `–unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
/internal/process/warning.js:32
(node:19024) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
/internal/process/warning.js:32
Error: Error code: 403 | Response from Google: {
« error »: {
« code »: 403,
« message »: « The request cannot be completed because you have exceeded your \u003ca href=\ »/youtube/v3/getting-started#quota\ »\u003equota\u003c/a\u003e. »,
« errors »: [
{
« message »: « The request cannot be completed because you have exceeded your \u003ca href=\ »/youtube/v3/getting-started#quota\ »\u003equota\u003c/a\u003e. »,
« domain »: « youtube.quota »,
« reason »: « quotaExceeded »
}
]
}
}
at IncomingMessage. (c:\Users\camba\OneDrive\Bureau\bot\node_modules\ytsearcher\lib\struct\YTSearch.js:103:36)
at IncomingMessage.emit (/events.js:327:22)
at endReadableNT (/_stream_readable.js:1201:12)
at processTicksAndRejections (internal/process/task_queues.js:84:21) {stack: ‘Error: Error code: 403 | Response from Google…tions (internal/process/task_queues.js:84:21)’, message: ‘Error code: 403 | Response from Google: {
…eason »: « quotaExceeded »
}
]
}
}
‘}
Bonjour Raeve !
Je vous plusieurs soucis avec ce que tu me montres, mais tout d’abord il semblerait que tu ai épuisé ton quota de recherche youtube.
As tu bien généré une clé spécifique à ce bot ?
(node:17160) UnhandledPromiseRejectionWarning: TypeError: Cannot read property ‘channel’ of undefined
at execute (c:\Users\camba\OneDrive\Bureau\bot\index.js:200:45)
at Client. (c:\Users\camba\OneDrive\Bureau\bot\index.js:180:11)
at Client.emit (events.js:327:22)
at MessageCreateHandler.handle (c:\Users\camba\OneDrive\Bureau\bot\node_modules\discord.js\src\client\websocket\packets\handlers\MessageCreate.js:9:34)
at WebSocketPacketManager.handle (c:\Users\camba\OneDrive\Bureau\bot\node_modules\discord.js\src\client\websocket\packets\WebSocketPacketManager.js:108:65)
at WebSocketConnection.onPacket (c:\Users\camba\OneDrive\Bureau\bot\node_modules\discord.js\src\client\websocket\WebSocketConnection.js:336:35)
at WebSocketConnection.onMessage (c:\Users\camba\OneDrive\Bureau\bot\node_modules\discord.js\src\client\websocket\WebSocketConnection.js:299:17)
at WebSocket.onMessage (c:\Users\camba\OneDrive\Bureau\bot\node_modules\ws\lib\event-target.js:120:16)
at WebSocket.emit (events.js:315:20)
at Receiver.receiverOnMessage (c:\Users\camba\OneDrive\Bureau\bot\node_modules\ws\lib\websocket.js:789:20)
(node:17160) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `–unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
/internal/process/warning.js:32
(node:17160) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Error: Error code: 403 | Response from Google: {
« error »: {
« code »: 403,
« message »: « The request cannot be completed because you have exceeded your \u003ca href=\ »/youtube/v3/getting-started#quota\ »\u003equota\u003c/a\u003e. »,
« errors »: [
{
« message »: « The request cannot be completed because you have exceeded your \u003ca href=\ »/youtube/v3/getting-started#quota\ »\u003equota\u003c/a\u003e. »,
« domain »: « youtube.quota »,
« reason »: « quotaExceeded »
}
]
}
}
at IncomingMessage. (c:\Users\camba\OneDrive\Bureau\bot\node_modules\ytsearcher\lib\struct\YTSearch.js:103:36)
at IncomingMessage.emit (/events.js:327:22)
at endReadableNT (/_stream_readable.js:1201:12)
at processTicksAndRejections (internal/process/task_queues.js:84:21) {stack: ‘Error: Error code: 403 | Response from Google…tions (internal/process/task_queues.js:84:21)’, message: ‘Error code: 403 | Response from Google: {
…eason »: « quotaExceeded »
}
]
}
}
‘}
bonjour j’ai un probleme avec mon bot quand je j’essaie de lancé une musique
Salut sa me met une erreur quand je lance ma commande play
bonsoir j’ai un probleme
Uncaught c:\Users\camba\OneDrive\Bureau\bot\index.js:243
var connection = await voiceChannel.join();
^^^^^
SyntaxError: await is only valid in async function
Hello,
où mettons la clé API Ytbe ?
Bonjour !
Aucune clé API Youtube n’est nécessaire avec ytdl-core, il va juste venir récupérer les informations de la vidéo Youtube sans passer par l’api Youtube 🙂
Bonjour quand le bot bot se co en voc il se deco instantanemant es normal
Bonjour xdela,
Est-ce que tu aurais des erreurs dans ma console de ton bot ?
non aucune
Es-tu toi même bien dans le salon vocal ?
Le bot s’arrête-il dans la console ?