diff --git a/backend/package.json b/backend/package.json index 79fc964..41d1ebc 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "chopin-backend", - "version": "1.0.1", + "version": "1.0.2", "description": "Discord Bot for music - Fetching everywhere !", "main": "src/main.js", "nodemonConfig": { diff --git a/backend/src/discord/Commands/About.js b/backend/src/discord/Commands/About.js index 26093b7..6992a77 100644 --- a/backend/src/discord/Commands/About.js +++ b/backend/src/discord/Commands/About.js @@ -10,7 +10,7 @@ const command = new Command("about", "Affiche des informations sur le bot", (cli const minutes = Math.floor((uptime % 3600) / 60); const seconds = Math.floor(uptime % 60); - const embed = new Embed() + const embed = new Embed(interaction) embed.setColor(237, 12, 91) embed.setThumbnail("https://cdn.discordapp.com/avatars/" + client.user.id + "/" + client.user.avatar + ".png") embed.setTitle('Subsonics - Chopin') @@ -31,7 +31,7 @@ const command = new Command("about", "Affiche des informations sur le bot", (cli embed.addField('Ytdl', packageJson.dependencies["@distube/ytdl-core"].replace("^", ""),true) embed.addColumn() - embed.send(interaction) + embed.send() }) diff --git a/backend/src/discord/Commands/Help.js b/backend/src/discord/Commands/Help.js index ca74872..0a120b5 100644 --- a/backend/src/discord/Commands/Help.js +++ b/backend/src/discord/Commands/Help.js @@ -3,7 +3,7 @@ const { Embed } = require('../Embed'); const command = new Command("help", "Affiche la liste des commandes", (client, interaction) => { - const embed = new Embed() + const embed = new Embed(interaction) embed.setColor(0x03ff2d) embed.setTitle('Comment assister au concert ?') embed.setDescription("**Eh ! Tu as eu ton ticket ? Tant mieux ! Voici la liste des commandes à utiliser dans le salon prévu à cet effet !**") @@ -28,7 +28,7 @@ const command = new Command("help", "Affiche la liste des commandes", (client, i }) embed.addField("La queue et la gestion du redémarrage se fait par le site https://subsonics.raphix.fr/", ":star:" ) embed.setThumbnail("https://static.wikia.nocookie.net/codelyoko/images/9/95/Subdigitals.jpg/revision/latest/scale-to-width-down/180?cb=20120105180510&path-prefix=fr"); - embed.send(interaction) + embed.send() }) module.exports = {command} \ No newline at end of file diff --git a/backend/src/discord/Commands/Invite.js b/backend/src/discord/Commands/Invite.js index 9658d31..382999d 100644 --- a/backend/src/discord/Commands/Invite.js +++ b/backend/src/discord/Commands/Invite.js @@ -3,7 +3,7 @@ const {Embed, EmbedError} = require("../Embed") const {Button} = require("../Button") const command = new Command("invite", "Invite moi sur d'autres serveurs", (client, interaction) => { - const embed = new Embed() + const embed = new Embed(interaction) embed.setColor(0xFF007F) embed.setTitle('**Inviter le bot sur d\'autres serveurs**') embed.setDescription('Vous pouvez m\'inviter sur d\'autres serveurs en cliquant sur le bouton ci-dessous.') @@ -13,7 +13,7 @@ const command = new Command("invite", "Invite moi sur d'autres serveurs", (clien const linkButton = new Button("Invite", null, 5, "https://discord.com/oauth2/authorize?client_id=" + client.user.id + "&scope=bot+applications.commands&permissions=8") embed.addButton(linkButton) - embed.send(interaction) + embed.send() }) diff --git a/backend/src/discord/Commands/Leave.js b/backend/src/discord/Commands/Leave.js index 1713cb7..737846f 100644 --- a/backend/src/discord/Commands/Leave.js +++ b/backend/src/discord/Commands/Leave.js @@ -4,11 +4,15 @@ const {Player, AllPlayers} = require("../../player/Player") const command = new Command("leave", "Quitter le salon vocal", (client, interaction) => { - if(!interaction.member.voice.channel) return new EmbedError("Vous devez rejoindre un salon vocal pour arrêter le bot !").send(interaction) + if(!interaction.member.voice.channel) return new EmbedError("Vous devez rejoindre un salon vocal pour arrêter le bot !", interaction) const channel = interaction.member.voice.channel - var embed = new Embed() + var embed = new Embed(interaction) + if(AllPlayers.has(channel.guildId)) { const player = AllPlayers.get(channel.guildId) + if(!player?.connected) { + return embed.returnError("Le bot n'est pas connecté à ce salon vocal") + } player.leave() @@ -16,12 +20,14 @@ const command = new Command("leave", "Quitter le salon vocal", (client, interact embed.setTitle('**Déconnexion**') embed.setDescription('Déconnexion du salon vocal') embed.setThumbnail("https://www.iconsdb.com/icons/download/white/phone-51-64.png") + + embed.send() } else { - embed = new EmbedError("Le bot n'est pas connecté à ce salon vocal") + embed.returnError("Le bot n'est pas connecté à ce salon vocal") } - embed.send(interaction) + }) diff --git a/backend/src/discord/Commands/Media.js b/backend/src/discord/Commands/Media.js index da95bc6..006299c 100644 --- a/backend/src/discord/Commands/Media.js +++ b/backend/src/discord/Commands/Media.js @@ -5,13 +5,16 @@ const { Song } = require('../../player/Song'); const command = new Command("media", "Lire un média MP3/WAV dans un salon vocal", async (client, interaction) => { - if(!interaction.member.voice.channel) return new EmbedError("Vous devez rejoindre un salon vocal pour jouer un média !").send(interaction, true) + if(!interaction.member.voice.channel) return new EmbedError("Vous devez rejoindre un salon vocal pour jouer un média !", interaction, true) const media = interaction.options.get("media") const now = interaction.options.getBoolean("now") || false - if(media.attachment.contentType != "audio/mpeg" && media.attachment.contentType != "audio/wav") return new EmbedError("Le média doit être un fichier audio MP3 ou WAV !").send(interaction) + if(media.attachment.contentType != "audio/mpeg" && media.attachment.contentType != "audio/wav") return new EmbedError("Le média doit être un fichier audio MP3 ou WAV !", interaction) + + const embed = new Embed(interaction) + embed.setColor(0x15e6ed) const channel = interaction.member.voice.channel const song = new Song() await song.processMedia(media, interaction.user.username) @@ -19,8 +22,7 @@ const command = new Command("media", "Lire un média MP3/WAV dans un salon vocal const player = new Player(channel.guildId) player.join(channel) - const embed = new Embed() - embed.setColor(0x15e6ed) + if(now) { @@ -40,7 +42,7 @@ const command = new Command("media", "Lire un média MP3/WAV dans un salon vocal embed.setThumbnail(song.thumbnail) - embed.send(interaction) + embed.send() }, [{type: "FILE", name: "media", description: "Fichier audio à lire", required: true}, diff --git a/backend/src/discord/Commands/Pause.js b/backend/src/discord/Commands/Pause.js index 1d34963..16b6c69 100644 --- a/backend/src/discord/Commands/Pause.js +++ b/backend/src/discord/Commands/Pause.js @@ -5,20 +5,20 @@ const { Player } = require("../../player/Player") const command = new Command("pause", "Mettre en pause / Reprendre la musique en cours", (client, interaction) => { - if(!interaction.member.voice.channel) return new EmbedError("Vous devez rejoindre un salon vocal pour mettre en pause la musique !").send(interaction) + if(!interaction.member.voice.channel) return new EmbedError("Vous devez rejoindre un salon vocal pour mettre en pause la musique !", interaction) const channel = interaction.member.voice.channel const player = new Player(channel.guildId) const result = player.pause() - var embed = new Embed() + var embed = new Embed(interaction) embed.setColor(0x03ff2d) result.then((pause) => { if(pause == "no_music") { - embed = new EmbedError("Il n'y a pas de musique en cours de lecture") + embed.returnError("Il n'y a pas de musique en cours de lecture") } else if(pause) { embed.setTitle('Musique en pause') @@ -32,7 +32,7 @@ const command = new Command("pause", "Mettre en pause / Reprendre la musique en embed.setThumbnail("https://www.iconsdb.com/icons/download/white/play-64.png") } - embed.send(interaction) + embed.send() }) // Réponse en embed diff --git a/backend/src/discord/Commands/Play.js b/backend/src/discord/Commands/Play.js index 5581847..1debb6e 100644 --- a/backend/src/discord/Commands/Play.js +++ b/backend/src/discord/Commands/Play.js @@ -7,18 +7,19 @@ const spotify = require("../../media/SpotifyInformation"); const command = new Command("play", "Jouer une musique à partir d'un lien dans un salon vocal", async (client, interaction) => { - if(!interaction.member.voice.channel) return new EmbedError("Vous devez rejoindre un salon vocal pour jouer une musique !").send(interaction) + if(!interaction.member.voice.channel) return new EmbedError("Vous devez rejoindre un salon vocal pour jouer une musique !", interaction) const url = interaction.options.get("url") const channel = interaction.member.voice.channel const now = interaction.options.getBoolean("now") || false + const embed = new Embed(interaction) await Finder.search(url.value).then(async (song) => { - if(!song) return new EmbedError("Impossible de trouver la musique à partir du lien donné ou des mots clés donnés").send(interaction) + if(!song) return embed.returnError("Impossible de trouver la musique à partir du lien donné ou des mots clés donnés") const player = new Player(channel.guildId) player.join(channel) - const embed = new Embed() + embed.setColor(0x15e6ed) // Check if song is playlist @@ -56,7 +57,7 @@ const command = new Command("play", "Jouer une musique à partir d'un lien dans embed.setTitle("Ajoutée à la file d'attente") } - embed.send(interaction) + embed.send() if(song instanceof Playlist) { if(song.type == "spotify") { diff --git a/backend/src/discord/Commands/Previous.js b/backend/src/discord/Commands/Previous.js index 80b88bd..9538891 100644 --- a/backend/src/discord/Commands/Previous.js +++ b/backend/src/discord/Commands/Previous.js @@ -4,9 +4,10 @@ const { Player, AllPlayers } = require("../../player/Player") const command = new Command("previous", "Passe à la musique précédente", (client, interaction) => { - if(!interaction.member.voice.channel) return new EmbedError("Vous devez rejoindre un salon vocal pour passer à la musique suivante !").send(interaction) + if(!interaction.member.voice.channel) return new EmbedError("Vous devez rejoindre un salon vocal pour passer à la musique suivante !", interaction) const channel = interaction.member.voice.channel + var embed = new Embed(interaction) if(AllPlayers.has(channel.guildId)) { @@ -14,34 +15,33 @@ const command = new Command("previous", "Passe à la musique précédente", (cli const result = player.previous() - var embed = new Embed() + embed.setColor(0x15e6ed) result.then((song) => { if(song == "no_music") { - embed = new EmbedError("Il n'y a pas de musique précédemment jouée") + embed.returnError("Il n'y a pas de musique précédemment jouée") } else if(song) { // Result is a song - - + embed.setTitle('**Musique précédente !**') embed.setDescription('**Titre : **' + song.title) embed.addField('**Durée : **'+ song.readduration, "") embed.addField("**Artiste : **" + song.author, "") embed.setThumbnail(song.thumbnail) - + embed.send() } - embed.send(interaction) + }) } else { - return new EmbedError("Le bot n'est pas connecté").send(interaction) + return embed.returnError("Le bot n'est pas connecté", interaction) } diff --git a/backend/src/discord/Commands/Queue.js b/backend/src/discord/Commands/Queue.js index 85befb1..f493280 100644 --- a/backend/src/discord/Commands/Queue.js +++ b/backend/src/discord/Commands/Queue.js @@ -5,37 +5,38 @@ const { Player, AllPlayers } = require("../../player/Player") const command = new Command("liste", "Affiche la file d'attente", (client, interaction) => { const channel = interaction.member.voice.channel + var embed = new Embed(interaction) + if(AllPlayers.has(channel.guildId)) { - const player = new Player(channel.guildId) - const queue = player.queue.getNext() - - var embed = new Embed() - embed.setColor(0x15e6ed) - - if(queue.length == 0) { - embed = new EmbedError("Il n'y a pas de musique en file d'attente") + const player = new Player(channel.guildId) + const queue = player.queue.getNext() - } else if(queue.length > 0) { + + + if(queue.length == 0) { + embed.returnError("Il n'y a pas de musique en file d'attente") - // Result is a song - embed.setColor(0x15e6ed) - embed.setThumbnail("https://www.iconsdb.com/icons/download/white/list-2-64.png") - embed.setTitle('**File d\'attente :**') - embed.setDescription('**' + queue.length + ' musiques**') - queue.forEach((song, index) => { - // max 24 fields - if(index > 10) return - embed.addField(`**${index+1} - ${song.title}**`, `**Durée : **${song.readduration}\n**Artiste : **${song.author}`) - }) - - } - - embed.send(interaction) + } else if(queue.length > 0) { + + // Result is a song + embed.setColor(0x15e6ed) + embed.setThumbnail("https://www.iconsdb.com/icons/download/white/list-2-64.png") + embed.setTitle('**File d\'attente :**') + embed.setDescription('**' + queue.length + ' musiques**') + queue.forEach((song, index) => { + // max 24 fields + if(index > 10) return + embed.addField(`**${index+1} - ${song.title}**`, `**Durée : **${song.readduration}\n**Artiste : **${song.author}`) + }) + embed.send() + } + + } else { - return new EmbedError("Le bot n'est pas connecté").send(interaction) + embed.returnError("Le bot n'est pas connecté") } }) diff --git a/backend/src/discord/Commands/Report.js b/backend/src/discord/Commands/Report.js index d423628..c000694 100644 --- a/backend/src/discord/Commands/Report.js +++ b/backend/src/discord/Commands/Report.js @@ -5,7 +5,7 @@ const { Report } = require('../ReportSender'); const command = new Command("report", "Signaler un problème avec le bot", (client, interaction) => { const report = new Report(interaction.user.username, interaction.options.getString("type"), interaction.options.getString("description")) const result = report.send() - const embed = new Embed() + const embed = new Embed(interaction) result.then((res) => { @@ -20,7 +20,7 @@ const command = new Command("report", "Signaler un problème avec le bot", (clie embed.setDescription("Votre rapport a bien été envoyé !") } - embed.send(interaction) + embed.send() }) diff --git a/backend/src/discord/Commands/Restart.js b/backend/src/discord/Commands/Restart.js index 1cc2012..76f6ee0 100644 --- a/backend/src/discord/Commands/Restart.js +++ b/backend/src/discord/Commands/Restart.js @@ -14,12 +14,12 @@ const command = new Command("restart", "Redémarre le bot", (client, interaction } const reason = interaction.options.getString("reason") restart(reason) - const embed = new Embed() + const embed = new Embed(interaction) embed.setColor(150, 20, 20) embed.setTitle('Redémarrage') embed.setDescription("Veuillez patientez, le bot va redémarrer dans un instant ! :arrows_counterclockwise:") embed.addField('Raison', reason) - embed.send(interaction) + embed.send() }, [{type: "STRING", name: "reason", description: "Raison du redémarrage", required: true}] ) diff --git a/backend/src/discord/Commands/Skip.js b/backend/src/discord/Commands/Skip.js index 637074f..b5556a9 100644 --- a/backend/src/discord/Commands/Skip.js +++ b/backend/src/discord/Commands/Skip.js @@ -4,22 +4,21 @@ const { Player, AllPlayers } = require("../../player/Player") const command = new Command("skip", "Passe à la musique suivante", (client, interaction) => { - if(!interaction.member.voice.channel) return new EmbedError("Vous devez rejoindre un salon vocal pour passer à la musique suivante !").send(interaction) + if(!interaction.member.voice.channel) return new EmbedError("Vous devez rejoindre un salon vocal pour passer à la musique suivante !", interaction) const channel = interaction.member.voice.channel + var embed = new Embed(interaction) if(AllPlayers.has(channel.guildId)) { const player = new Player(channel.guildId) const result = player.skip() - - var embed = new Embed() embed.setColor(0x15e6ed) result.then((song) => { if(song == "no_music") { - embed = new EmbedError("Il n'y a pas de musique en file d'attente") + embed.returnError("Il n'y a pas de musique en file d'attente", interaction) } else if(song) { @@ -35,11 +34,11 @@ const command = new Command("skip", "Passe à la musique suivante", (client, int } - embed.send(interaction) + embed.send() }) } else { - return new EmbedError("Le bot n'est pas connecté").send(interaction) + return embed.returnError("Le bot n'est pas connecté", interaction) } diff --git a/backend/src/discord/Commands/State.js b/backend/src/discord/Commands/State.js index b17ecaa..05daaad 100644 --- a/backend/src/discord/Commands/State.js +++ b/backend/src/discord/Commands/State.js @@ -8,14 +8,14 @@ const command = new Command("state", "Affiche la musique en cours", (client, int const player = new Player(channel.guildId) const song = player.queue.getCurrent() - var embed = new Embed() - embed.setColor(0x15e6ed) + + if(!song) { - embed = new EmbedError("Il n'y a pas de musique en cours de lecture") + var embed = new EmbedError("Il n'y a pas de musique en cours de lecture", interaction) } else if(song) { - + var embed = new Embed(interaction) // Result is a song embed.setColor(0x15e6ed) @@ -25,10 +25,10 @@ const command = new Command("state", "Affiche la musique en cours", (client, int embed.addField("**Artiste : **", song.author) embed.setThumbnail(song.thumbnail) - + embed.send() } - embed.send(interaction) + }) module.exports = {command} diff --git a/backend/src/discord/Commands/Web.js b/backend/src/discord/Commands/Web.js index b365149..cf59ec4 100644 --- a/backend/src/discord/Commands/Web.js +++ b/backend/src/discord/Commands/Web.js @@ -4,7 +4,7 @@ const { Embed } = require('../Embed'); const config = require('../../utils/Database/Configuration') const command = new Command("web", "Affiche le lien vers le site web pour contrôler le bot", (client, interaction) => { - const embed = new Embed() + const embed = new Embed(interaction) embed.setColor(0xffffff) embed.setTitle('Subsonics - Chopin') embed.addBotPicture(client) @@ -13,7 +13,7 @@ const command = new Command("web", "Affiche le lien vers le site web pour contr const linkButton = new Button("Site web", null, 5, config.getWebsiteLink()) embed.addButton(linkButton) - embed.send(interaction) + embed.send() }) diff --git a/backend/src/discord/Embed.js b/backend/src/discord/Embed.js index a2dd7de..d56d4b1 100644 --- a/backend/src/discord/Embed.js +++ b/backend/src/discord/Embed.js @@ -3,10 +3,18 @@ const { EmbedBuilder, ActionRowBuilder } = require("discord.js"); class Embed { fields; buttons; - constructor() { + constructor (interaction, ephemeral) { this.embed = new EmbedBuilder().setTimestamp() this.fields = [] this.buttons = [] + this.isSended = false + if(interaction) { + interaction.deferReply({ ephemeral: ephemeral }).then(() => { + this.isSended = true + }) + this.interaction = interaction + this.ephemeral = ephemeral + } } setTitle(title) { @@ -92,19 +100,32 @@ class Embed { return this.embed } - send(interaction, ephemeral) { - if(ephemeral === undefined) ephemeral = false; - interaction.reply({ embeds: [this.build()], ephemeral: ephemeral, components: this.buttons.length > 0 ? [this.actionRow] : [] }) + async send() { + // Add a secutiry check to avoid sending an embed if the interaction is not defined and retry one again + while(!this.isSended) { + await new Promise(resolve => setTimeout(resolve, 50)); + } + if(this.ephemeral === undefined) this.ephemeral = false; + this.interaction.editReply({ embeds: [this.build()], components: this.buttons.length > 0 ? [this.actionRow] : [] }) } -} -class EmbedError extends Embed { - constructor(message) { - super() + async returnError(message) { this.setColor(150, 20, 20) this.setTitle('Erreur') this.setThumbnail("https://upload.wikimedia.org/wikipedia/commons/thumb/9/97/Dialog-error-round.svg/2048px-Dialog-error-round.svg.png") this.setDescription(message) + await this.send() + } +} + +class EmbedError extends Embed { + constructor(message, interaction, ephemeral) { + super(interaction, ephemeral) + this.setColor(150, 20, 20) + this.setTitle('Erreur') + this.setThumbnail("https://upload.wikimedia.org/wikipedia/commons/thumb/9/97/Dialog-error-round.svg/2048px-Dialog-error-round.svg.png") + this.setDescription(message) + this.send() } } diff --git a/backend/src/player/Finder.js b/backend/src/player/Finder.js index d3cb870..2a8f7e6 100644 --- a/backend/src/player/Finder.js +++ b/backend/src/player/Finder.js @@ -76,7 +76,7 @@ async function search(query, multiple, forceType) { return await soundcloud.getPlaylist(query) } - // TODO: Add more providers + //MORELATER: Add more providers } module.exports = {search} \ No newline at end of file diff --git a/backend/src/player/List.js b/backend/src/player/List.js index bb102a5..1bfcc15 100644 --- a/backend/src/player/List.js +++ b/backend/src/player/List.js @@ -204,6 +204,11 @@ class List { } moveNext(fromIndex, toIndex) { + // Check if fromIndex and toIndex are valid + if(fromIndex < 0 || fromIndex >= this.next.length + 1 || toIndex < 0 || toIndex >= this.next.length + 1) { + clog.error("Impossible de déplacer la musique, car l'index est invalide, GuildId : " + this.guildId) + return + } if(fromIndex == toIndex) return; const song = this.next[fromIndex] this.next.splice(fromIndex, 1) diff --git a/backend/src/player/Player.js b/backend/src/player/Player.js index e7731e5..a7b60fc 100644 --- a/backend/src/player/Player.js +++ b/backend/src/player/Player.js @@ -1,6 +1,7 @@ const { joinVoiceChannel, getVoiceConnection, VoiceConnectionStatus, createAudioPlayer, AudioPlayerStatus, StreamType, createAudioResource } = require('@discordjs/voice'); const {List} = require('./List') const {LogType} = require("loguix"); +const songCheck = require('./SongCheck') const ffmpeg = require('fluent-ffmpeg') const fs = require('fs') const { PassThrough } = require('stream'); @@ -91,6 +92,7 @@ class Player { }); this.player.on(AudioPlayerStatus.Idle, () => { + if(this.checkConnection()) return // Si la musique est en boucle, on relance la musique if(this.loop) { this.play(this.queue.current) @@ -106,7 +108,7 @@ class Player { }); this.player.on(AudioPlayerStatus.Playing, () => { - + if(this.checkConnection()) return plog.log(`GUILD : ${this.guildId} - Le player est en train de jouer le contenu suivant : ${this.queue.current.title}`); Activity.setMusicActivity(this.queue.current.title, this.queue.current.author, this.queue.current.thumbnail) process.emit("PLAYERS_UPDATE") @@ -140,7 +142,8 @@ class Player { duration: this.getDuration(), playerState: playerStatus, connectionState: connectionStatus, - channelId: this.channelId + channelId: this.channelId, + guildId: this.guildId, } return state } @@ -168,6 +171,7 @@ class Player { } async play(song) { + if(!songCheck.checkSong(song)) return if(this.checkConnection()) return if(this.queue.current != null) { this.player.stop() @@ -202,7 +206,7 @@ class Player { } async add(song) { - if(this.player.state.status == AudioPlayerStatus.Idle && this.queue.current === null && this.queue.next.length === 0) { + if(this.player?.state?.status == AudioPlayerStatus.Idle && this.queue.current === null && this.queue.next.length === 0) { this.play(song) return } @@ -212,15 +216,14 @@ class Player { } async readPlaylist(playlist, now) { - if(this.player.state.status == AudioPlayerStatus.Idle && this.queue.current === null && this.queue.next.length === 0) { - this.play(playlist.songs[0]) - } - if(now) { + if(this.player?.state?.status == AudioPlayerStatus.Idle && this.queue.current === null && this.queue.next.length === 0) { this.play(playlist.songs[0]) this.queue.addNextPlaylist(playlist, true) - } else { - this.queue.addNextPlaylist(playlist) - } + return + } + if(now) this.play(playlist.songs[0]) + this.queue.addNextPlaylist(playlist, now) + plog.log(`GUILD : ${this.guildId} - La playlist a été ajoutée à la liste de lecture : ${playlist.title}`) } @@ -229,13 +232,15 @@ class Player { if(this.player.state.status == AudioPlayerStatus.Paused) { this.player.unpause() plog.log(`GUILD : ${this.guildId} - La musique a été reprise`) + process.emit("PLAYERS_UPDATE") return false } else { this.player.pause() plog.log(`GUILD : ${this.guildId} - La musique a été mise en pause`) + process.emit("PLAYERS_UPDATE") return true } - process.emit("PLAYERS_UPDATE") + } async leave() { @@ -260,7 +265,7 @@ class Player { } async setDuration(duration) { - + //FIXME: SET DURATION FONCTIONNE TRES LENTEMENT if (this.checkConnection()) return; if (this.queue.current == null) return; if (this.currentResource == null) return; diff --git a/backend/src/player/SongCheck.js b/backend/src/player/SongCheck.js new file mode 100644 index 0000000..3effdf3 --- /dev/null +++ b/backend/src/player/SongCheck.js @@ -0,0 +1,44 @@ +const {LogType} = require("loguix") +const {Song} = require("./Song") +const slog = new LogType("SongCheck") + +function checkSong(song) { + if(!(song instanceof Song)) { + slog.error("La musique n'est pas une instance de la classe Song") + // Check if the song is valid and if it has all the required properties + if(song.title && song.id && song.author && song.url && song.duration && song.readduration && song.type) { + slog.log("Acceptation de la musique : " + song.title) + return true + } else { + slog.error("La musique n'est pas valide") + return false + } + } + if(!song.url) { + slog.error("La musique n'a pas d'url") + return false + } + if(!song.title) { + slog.error("La musique n'a pas de titre") + return false + } + if(!song.author) { + slog.error("La musique n'a pas d'auteur") + return false + } + if(!song.duration) { + slog.error("La musique n'a pas de durée") + return false + } + if(!song.readduration) { + slog.error("La musique n'a pas de durée lisible") + return false + } + if(!song.type) { + slog.error("La musique n'a pas de type") + return false + } + return true +} + +module.exports = {checkSong} \ No newline at end of file diff --git a/backend/src/playlists/Playlist.js b/backend/src/playlists/Playlist.js index b62a468..2531d6e 100644 --- a/backend/src/playlists/Playlist.js +++ b/backend/src/playlists/Playlist.js @@ -1,3 +1,5 @@ +const { getReadableDuration } = require("../utils/TimeConverter"); + class Playlist { title = "Aucun titre"; id; @@ -17,8 +19,11 @@ class Playlist { this.authorId = authorId; this.songs = songs || new Array(); this.thumbnail = thumbnail; - this.duration = duration; - this.readduration = readduration; + // Make the some of durations of the songs + if(this.songs.length > 0) { + this.duration = this.songs.reduce((acc, song) => acc + song.duration, 0); + this.readduration = getReadableDuration(this.duration); + } this.description = description; if(!this.url) { this.type = "playlist"; diff --git a/backend/src/server/Server.js b/backend/src/server/Server.js index b00afe1..a792739 100644 --- a/backend/src/server/Server.js +++ b/backend/src/server/Server.js @@ -42,6 +42,7 @@ function init() { for(var guild of discordBot.getGuilds().keys()) { const player = players.getPlayer(guild) if(player) { + if(!player.isConnected()) continue; io.to(player.guildId).emit("/PLAYER/UPDATE", player.getState()) wlog.log("Envoi de l'état du player de la guilde : " + player.guildId + " à tous les utilisateurs connectés") } @@ -68,8 +69,8 @@ function init() { // Make sure Discord Bot is loaded and make an interruption until it is loaded while(!discordBot.getClient().isReady()) { - wlog.warn("Attente de traitement : "+ socket.id + " : Le bot Discord n'est pas encore chargé, attente de 1 seconde...") - await new Promise(resolve => setTimeout(resolve, 1000)) + wlog.warn("Attente de traitement : "+ socket.id + " : Le bot Discord n'est pas encore chargé, attente de 3 seconde... (Avoid Rate Limit)") + await new Promise(resolve => setTimeout(resolve, 3000)) } wlog.log(`Connexion d'un client : ${socket.id}`) @@ -152,8 +153,13 @@ function init() { return } - await users.updateGuilds(socketUser.identity.id) - await users.updateIdentity(socketUser.identity.id) + if (!(await users.updateGuilds(socketUser.identity.id)) || !(await users.updateIdentity(socketUser.identity.id))) { + wlog.error("Erreur lors de la mise à jour des informations de l'utilisateur : " + socketUser.identity.id); + socket.emit("UPDATE_ERROR", "Error updating user information"); + wlog.log("Déconnexion de l'utilisateur : " + socketUser.identity.username + " (" + socketUser.identity.id + ") - Socket : " + socket.id) + socket.disconnect(); + return; + } } socketUser = users.getUserByToken(token) @@ -220,14 +226,14 @@ function init() { // PLAYERS + //CHECKED : 03/05/2025 IORequest("/PLAYER/PREVIOUS/LIST", (guildId) => { if(!checkUserGuild(socketUser, guildId)) return const list = new List(guildId) IOAnswer("/PLAYER/PREVIOUS/LIST", list.getPrevious()) }) - //TODO: Faire les autres reqêtes sur la liste - + // ChECKED : 03/05/2025 IORequest("/PLAYER/JOIN", (guildId) => { if(!checkUserGuild(socketUser, guildId)) return wlog.log("L'utilisateur '" + socketUser.identity.username + "' rejoint la liste d'écoute du player de la guilde : " + guildId) @@ -238,37 +244,49 @@ function init() { } }) socket.join(guildId) - IOAnswer("PLAYER_STATE", true) + IOAnswer("/PLAYER/JOIN", true) + process.emit("PLAYERS_UPDATE") }) - IORequest("/PLAYER/STATE", (guildId) => { - handlePlayerAction(guildId, async (player) => await player.getState(), "/PLAYER/STATE"); + // CHECKED : 03/05/2025 + IORequest("/PLAYER/STATE", async (guildId) => { + const player = await verifyPlayerAction(guildId) + if(!player) return IOAnswer("/PLAYER/STATE", false) + IOAnswer("/PLAYER/STATE", await player.getState()) }) - + + + // CHECKED : 03/05/2025 IORequest("/PLAYER/PAUSE", (guildId) => { handlePlayerAction(guildId, (player) => player.pause(), "/PLAYER/PAUSE"); }); + + // CHECKED : 03/05/2025 IORequest("/PLAYER/BACKWARD", (guildId) => { handlePlayerAction(guildId, (player) => player.previous(), "/PLAYER/BACKWARD"); }); + + // CHECKED : 03/05/2025 IORequest("/PLAYER/FORWARD", (guildId) => { handlePlayerAction(guildId, (player) => player.skip(), "/PLAYER/FORWARD"); }); + // CHECKED : 03/05/2025 IORequest("/PLAYER/LOOP", (guildId) => { handlePlayerAction(guildId, (player) => player.setLoop(), "/PLAYER/LOOP"); }); + // CHECKED : 03/05/2025 IORequest("/PLAYER/SHUFFLE", (guildId) => { handlePlayerAction(guildId, (player) => player.setShuffle(), "/PLAYER/SHUFFLE"); }); - + // CHECKED : 03/05/2025 IORequest("/PLAYER/DISCONNECT", (guildId) => { handlePlayerAction(guildId, (player) => player.leave(), "/PLAYER/DISCONNECT"); }); - + // CHECKED : 03/05/2025 IORequest("/PLAYER/CHANNEL/CHANGE", (guildId) => { handlePlayerAction(guildId, (player) => { const channel = getUserChannel() @@ -280,6 +298,7 @@ function init() { }, "/PLAYER/CHANNEL/CHANGE"); }); + // CHECKED : 03/05/2025 IORequest("/PLAYER/SEEK", (data) => { if(!data) return IOAnswer("/PLAYER/SEEK", false) const {guildId, time} = data @@ -296,14 +315,17 @@ function init() { }, "/PLAYER/SEEK"); }); + // CHECKED : 04/05/2025 IORequest("/QUEUE/PLAY/NOW", (data) => { if(!data) return IOAnswer("/QUEUE/PLAY/NOW", false) - const {guildId, song} = data - if(!song) return IOAnswer("/QUEUE/PLAY/NOW", false) + const {guildId, index, listType} = data + if(!index) return IOAnswer("/QUEUE/PLAY/NOW", false) if(!guildId) return IOAnswer("/QUEUE/PLAY/NOW", false) + if(!listType) return IOAnswer("/QUEUE/PLAY/NOW", false) if(!checkUserGuild(socketUser, guildId)) return const player = new Player(guildId) if(!connectToPlayer(guildId, player)) return IOAnswer("/QUEUE/PLAY", false) + var song; if(listType == "previous") { const previous = player.queue.getPrevious() song = previous[index] @@ -311,11 +333,13 @@ function init() { const next = player.queue.getNext() song = next[index] } - if(!song) return IOAnswer("/QUEUE/PLAY", false) - player.add(song) + if(!song) return IOAnswer("/QUEUE/PLAY/NOW", false) + if(listType == "next") player.queue.removeNextByIndex(index) + player.play(song) IOAnswer("/QUEUE/PLAY/NOW", true) }) + // CHECKED : 04/05/2025 IORequest("/QUEUE/NEXT/DELETE", (data) => { if(!data) return IOAnswer("/QUEUE/NEXT/DELETE", false) const {guildId, index} = data @@ -325,13 +349,15 @@ function init() { const next = player.queue.getNext() if(!next[index]) return IOAnswer("/QUEUE/NEXT/DELETE", false); player.queue.removeNextByIndex(index) - }) + }, "/QUEUE/NEXT/DELETE") }) + // CHECKED : 04/05/2025 IORequest("/QUEUE/NEXT/DELETEALL", (guildId) => { - handlePlayerAction(guildId, (player) => player.queue.clearNext()) + handlePlayerAction(guildId, (player) => player.queue.clearNext(), "/QUEUE/NEXT/DELETEALL") }) + // CHECKED : 04/05/2025 IORequest("/QUEUE/NEXT/MOVE", (data) => { if(!data) return IOAnswer("/QUEUE/NEXT/MOVE", false) const {guildId, index, newIndex} = data @@ -342,7 +368,7 @@ function init() { const next = player.queue.getNext() if(!next[index]) return IOAnswer("/QUEUE/NEXT/MOVE", false); player.queue.moveNext(index, newIndex) - }) + }, "/QUEUE/NEXT/MOVE") }) // SEARCH @@ -352,10 +378,14 @@ function init() { IOAnswer("/SEARCH", await Finder.search(query, true)) }) + // CHECKED : 03/05/2025 IORequest("/SEARCH/PLAY", async (data) => { if(!data) return IOAnswer("/SEARCH/PLAY", false) - const {guildId, song, now} = data + var {guildId, song, now} = data if(!song) return IOAnswer("/SEARCH/PLAY", false) + if(typeof song == "string") { + song = JSON.parse(song) + } if(!guildId) return IOAnswer("/SEARCH/PLAY", false) if(!checkUserGuild(socketUser, guildId)) return const player = new Player(guildId) @@ -368,6 +398,20 @@ function init() { IOAnswer("/SEARCH/PLAY", true) }) + // CHECKED : 05/05/2025 + IORequest("/SEARCH/PLAYLIST", async (data) => { + if(!data) return IOAnswer("/SEARCH/PLAYLIST", false) + const {url, now, guildId} = data + if(!url) return IOAnswer("/SEARCH/PLAYLIST", false) + if(!guildId) return IOAnswer("/SEARCH/PLAYLIST", false) + const playlist = await Finder.search(url, true, "PLAYLIST") + if(!playlist) return IOAnswer("/SEARCH/PLAYLIST", false) + const player = new Player(guildId) + if(!connectToPlayer(guildId, player)) return IOAnswer("/SEARCH/PLAYLIST", false) + player.readPlaylist(playlist, now) + IOAnswer("/SEARCH/PLAYLIST", true) + }) + // PLAYLISTS @@ -446,7 +490,8 @@ function init() { IOAnswer("/PLAYLISTS/REMOVE_SONG", true) }) - IORequest("/PLAYLISTS/PLAY", (data) => { + // CHECKED : 05/05/2025 + IORequest("/PLAYLISTS/PLAY", async (data) => { if(!data) return IOAnswer("/PLAYLISTS/PLAY", false) const {name, guildId, now} = data if(!name || !guildId) return IOAnswer("/PLAYLISTS/PLAY", false) @@ -454,7 +499,7 @@ function init() { const playlist = playlists.getPlaylistOfUser(socketUser.identity.id, name) if(!playlist) return IOAnswer("/PLAYLISTS/PLAY", false) const player = new Player(guildId) - if(!connectToPlayer(guildId, player)) return IOAnswer("/PLAYLISTS/PLAY", false) + if(!await connectToPlayer(guildId, player)) return IOAnswer("/PLAYLISTS/PLAY", false) player.readPlaylist(playlist, now) IOAnswer("/PLAYLISTS/PLAY", true) }) @@ -575,6 +620,7 @@ function init() { const member = membersVoices.get(socketUser.identity.id) if(member) { const channelId = member.channelId + const guildId = member.guildId const channel = discordBot.getChannel(guildId, channelId) if(!channel) { wlog.warn("Le channel vocal n'existe pas : " + channelId) @@ -599,6 +645,17 @@ function init() { return true } + async function verifyPlayerAction(guildId) { + if (!checkUserGuild(socketUser, guildId)) return null; + const player = players.getPlayer(guildId); + if (player) { + return player; + } else { + wlog.warn(`Le player de la guilde : ${guildId} n'existe pas`); + return null; + } + } + function checkUserGuild(socketUser, guildId) { // Check if the guildId is referenced in the bot guilds @@ -622,20 +679,22 @@ function init() { return true } - /** + /** * @param {function(Player)} action - The action to perform on the player. */ - async function handlePlayerAction(guildId, action, actionName) { + async function handlePlayerAction(guildId, action, actionName) { if (!checkUserGuild(socketUser, guildId)) return; - const player = players.getPlayer(guildId); + const player = players.getPlayer(guildId); if (player) { - await action(player); - wlog.log(`L'utilisateur '${socketUser.identity.username}' effectue l'action '${actionName}' sur le player de la guilde : ${guildId}`); - IOAnswer(actionName, true); + await action(player); + wlog.log(`L'utilisateur '${socketUser.identity.username}' effectue l'action '${actionName}' sur le player de la guilde : ${guildId}`); + IOAnswer(actionName, true); } else { - wlog.warn(`Le player de la guilde : ${guildId} n'existe pas`); - IOAnswer(actionName, false); + wlog.warn(`Le player de la guilde : ${guildId} n'existe pas`); + IOAnswer(actionName, false); } + + } } @@ -683,13 +742,12 @@ function init() { wlog.step.end("server_init") }) - function AdminRequest(GRname, GRvalue) { - - io.to("ADMIN").emit("ALWAYS/" + GRname, GRvalue) - - } - function addGuildConnectedUser(user, guilds) { + // Check if guilds is iterable + if(!guilds || !guilds[Symbol.iterator]) { + wlog.warn("Les guilds ne sont pas itérables") + return + } for(var guild of guilds) { if(!guildConnectedUsers.has(guild)) { guildConnectedUsers.set(guild.id, new Array()) diff --git a/backend/src/server/auth/User.js b/backend/src/server/auth/User.js index 63cd182..165c627 100644 --- a/backend/src/server/auth/User.js +++ b/backend/src/server/auth/User.js @@ -169,59 +169,46 @@ async function refreshAllUserInformation() { await loadUsers(); clog.log("Récupération des informations de tous les utilisateurs..."); for (const user of userList) { - await refreshUserInformation(user.identity.id); + await updateCredientials(user.identity.id); } saveUsers(); } -async function refreshUserInformation(id) { +async function updateCredientials(id) { const user = getUserById(id); if (!user) { clog.warn(`Utilisateur ${id} non trouvé.`); return null; } - clog.log(`Récupération (Refresh) des informations de l'utilisateur ${user.identity.username} (${user.identity.id})...`); + clog.log(`Mise à jour des informations d'authentification Discord de l'utilisateur ${user.identity.username} (${user.identity.id})...`); if (user.auth) { - const refresh_token = user.auth.refresh_token; - const authCredientials = await discordAuth.refreshToken(refresh_token); - if(authCredientials) { - user.auth = authCredientials; - const guilds = await discordAuth.getUserGuilds(authCredientials); - const identity = await discordAuth.getUserIdentity(authCredientials); - if(identity) { - user.identity = identity; - clog.log(`Récupération réussie des informations de l'utilisateur ${user.identity.username} (${user.identity.id})`); - } - else { - clog.warn(`Erreur lors de la récupération des informations de l'utilisateur ${user.identity.username} (${user.identity.id})`); - } - if(guilds) { - user.guilds = guilds; - clog.log(`Récupération réussie des guildes de l'utilisateur ${user.identity.username} (${user.identity.id})`); - } - else { - clog.warn(`Erreur lors de la récupération des guildes de l'utilisateur ${user.identity.username} (${user.identity.id})`); - } - // Update the user in the list - const userInUserList = userList.find(u => u.identity.id === user.identity.id); - if (userInUserList) { - userInUserList.auth = user.auth; - userInUserList.guilds = user.guilds; + // Check if the token is expired + const auth = await discordAuth.refreshToken(user.auth.refresh_token); + if (auth) { + // Check Rate limit by checking if auth.message exists + if(typeof auth.message !== "undefined") { + clog.warn(`Erreur lors de la mise à jour des informations d'authentification de l'utilisateur ${user.identity.username} (${user.identity.id}) : ${auth.message}`); + return null; } + user.auth = auth; + clog.log(`Mise à jour réussie des informations d'authentification de l'utilisateur ${user.identity.username} (${user.identity.id})`); } else { - clog.warn(`Erreur lors de la récupération du token d'accès pour l'utilisateur ${user.identity.username} (${user.identity.id})`); - // Delete tokens to oblige the user to reauthenticate - // Clear auth - - user.destroyAuth(); - return null; + clog.warn(`Erreur lors de la mise à jour des informations d'authentification de l'utilisateur ${user.identity.username} (${user.identity.id})`); } - } else { + // Update the user in the list + const userInUserList = userList.find(u => u.identity.id === user.identity.id); + if (userInUserList) { + userInUserList.auth = user.auth; + } + } + else { clog.warn(`Aucune authentification trouvée pour l'utilisateur ${user.identity.username} (${user.identity.id})`); } - + saveUsers(); + return user.auth; } + async function updateGuilds(id) { const user = getUserById(id); if (!user) { @@ -232,11 +219,16 @@ async function updateGuilds(id) { if (user.auth) { const guilds = await discordAuth.getUserGuilds(user.auth); if(guilds) { + if(typeof guilds.message !== "undefined") { + clog.warn(`Erreur lors de la mise à jour des guildes de l'utilisateur ${user.identity.username} (${user.identity.id}) : ${guilds.message}`); + return null; + } user.guilds = guilds; clog.log(`Mise à jour réussie des guildes de l'utilisateur ${user.identity.username} (${user.identity.id})`); } else { clog.warn(`Erreur lors de la mise à jour des guildes de l'utilisateur ${user.identity.username} (${user.identity.id})`); + return null; } // Update the user in the list const userInUserList = userList.find(u => u.identity.id === user.identity.id); @@ -246,6 +238,7 @@ async function updateGuilds(id) { } } else { clog.warn(`Aucune authentification trouvée pour l'utilisateur ${user.identity.username} (${user.identity.id})`); + return null; } saveUsers(); return user.guilds; @@ -261,11 +254,17 @@ async function updateIdentity(id) { if (user.auth) { const identity = await discordAuth.getUserIdentity(user.auth); if(identity) { + // Check Rate limit by checking if identity.message exists + if(typeof identity.message !== "undefined") { + clog.warn(`Erreur lors de la mise à jour de l'identité de l'utilisateur ${user.identity.username} (${user.identity.id}) : ${identity.message}`); + return null; + } user.identity = identity; clog.log(`Mise à jour réussie de l'identité de l'utilisateur ${user.identity.username} (${user.identity.id})`); } else { clog.warn(`Erreur lors de la mise à jour de l'identité de l'utilisateur ${user.identity.username} (${user.identity.id})`); + return null } // Update the user in the list const userInUserList = userList.find(u => u.identity.id === user.identity.id); @@ -275,6 +274,7 @@ async function updateIdentity(id) { } } else { clog.warn(`Aucune authentification trouvée pour l'utilisateur ${user.identity.username} (${user.identity.id})`); + return null; } saveUsers(); return user.identity; @@ -525,7 +525,7 @@ module.exports = { removeToken, getSimpleUsers, getSimpleUser, - refreshUserInformation, + updateCredientials, refreshAllUserInformation, updateGuilds, updateIdentity