backend-0.2.0 => main #1

Merged
raphix merged 6 commits from backend-0.2.0 into main 2025-03-01 17:03:17 +00:00
17 changed files with 413 additions and 74 deletions
Showing only changes of commit f99fc24aa9 - Show all commits

2
.gitignore vendored
View File

@@ -142,3 +142,5 @@ docs/_book
test/ test/
data/ data/
__DEBUG.js

View File

@@ -13,10 +13,10 @@ function setMusicActivity(songName, artistName, imageUrl) {
const client = bot.getClient() const client = bot.getClient()
client.user.setActivity(`${songName} - ${artistName}`,{ client.user.setActivity(`${songName} - ${artistName}`,{
type: ActivityType.Listening, type: ActivityType.Listening,
/*assets: { assets: {
largeImage: imageUrl, largeImage: imageUrl,
largeText: songName largeText: songName
}*/ }
}); });
dlog.log(`Activité mise à jour : ${songName} - ${artistName}`) dlog.log(`Activité mise à jour : ${songName} - ${artistName}`)
} }

View File

@@ -0,0 +1,28 @@
const {Command} = require("../Command")
const {Embed, EmbedError} = require("../Embed")
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)
const channel = interaction.member.voice.channel
var embed = new Embed()
if(AllPlayers.has(channel.guildId)) {
const player = AllPlayers.get(channel.guildId)
player.leave()
embed.setColor(200, 20, 20)
embed.setTitle('**Déconnexion**')
embed.setDescription('Déconnexion du salon vocal')
embed.setThumbnail("https://www.iconsdb.com/icons/download/white/phone-51-64.png")
} else {
embed = new EmbedError("Le bot n'est pas connecté à ce salon vocal")
}
embed.send(interaction)
})
module.exports = {command}

View File

@@ -5,7 +5,7 @@ const { Song } = require('../../player/Song');
const command = new Command("media", "Lire un média MP3/WAV dans un salon vocal", async (client, interaction) => { const command = new Command("media", "Lire un média MP3/WAV dans un salon vocal", async (client, interaction) => {
if(!interaction.member.voice.channel) return interaction.reply({content:"Vous devez rejoindre un salon vocal pour lire un(e) titre / playlist !", ephemeral: true}) if(!interaction.member.voice.channel) return new EmbedError("Vous devez rejoindre un salon vocal pour jouer un média !").send(interaction, true)
const media = interaction.options.get("media") const media = interaction.options.get("media")
const now = interaction.options.getBoolean("now") || false const now = interaction.options.getBoolean("now") || false
@@ -34,9 +34,9 @@ const command = new Command("media", "Lire un média MP3/WAV dans un salon vocal
} }
embed.setDescription('**Titre : **' + song.title) embed.setDescription('**Titre : **' + song.title)
embed.addField('**Durée : **'+ song.readduration, "") embed.addField('**Durée : **', song.readduration)
embed.addField("**Artiste : **" + song.author, "") embed.addField("**Artiste : **",song.author)
embed.addField('**Demandé par **' + interaction.member.user.username,"") embed.addField('**Demandé par **' + interaction.member.user.username, "")
embed.setThumbnail(song.thumbnail) embed.setThumbnail(song.thumbnail)

View File

@@ -2,22 +2,42 @@ const {Command} = require("../Command")
const {Embed, EmbedError} = require("../Embed") const {Embed, EmbedError} = require("../Embed")
const { Player } = require("../../player/Player") const { Player } = require("../../player/Player")
const command = new Command("pause", "Mettre en pause la musique en cours", (client, interaction) => { 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 !").send(interaction)
const channel = interaction.member.voice.channel const channel = interaction.member.voice.channel
const player = new Player(channel.guildId) const player = new Player(channel.guildId)
player.pause() const result = player.pause()
var embed = new Embed()
embed.setColor(0x03ff2d)
result.then((pause) => {
if(pause == "no_music") {
embed = new EmbedError("Il n'y a pas de musique en cours de lecture")
} else if(pause) {
embed.setTitle('Musique en pause')
embed.setDescription("La musique a été mise en pause")
embed.setThumbnail("https://www.iconsdb.com/icons/download/white/pause-64.png")
} else {
embed.setTitle('Musique reprise')
embed.setDescription("La musique a été reprise")
embed.setThumbnail("https://www.iconsdb.com/icons/download/white/play-64.png")
}
embed.send(interaction)
})
// Réponse en embed // Réponse en embed
const embed = new Embed()
embed.setColor(0x00ff66)
embed.setTitle('Musique en pause')
embed.setDescription("La musique a été mise en pause")
embed.send(interaction)
}) })

View File

@@ -0,0 +1,52 @@
const {Command} = require("../Command")
const {Embed, EmbedError} = require("../Embed")
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)
const channel = interaction.member.voice.channel
if(AllPlayers.has(channel.guildId)) {
const player = new Player(channel.guildId)
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")
} 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(interaction)
})
} else {
return new EmbedError("Le bot n'est pas connecté").send(interaction)
}
})
module.exports = {command}

View File

@@ -0,0 +1,39 @@
const {Command} = require("../Command")
const {Embed, EmbedError} = require("../Embed")
const { Player, AllPlayers } = require("../../player/Player")
const command = new Command("liste", "Affiche la file d'attente", (client, interaction) => {
const channel = interaction.member.voice.channel
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")
} 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 :**')
queue.forEach((song, index) => {
embed.addField(`**${index+1} - ${song.title}**`, `**Durée : **${song.readduration}\n**Artiste : **${song.author}`)
})
}
embed.send(interaction)
} else {
return new EmbedError("Le bot n'est pas connecté").send(interaction)
}
})
module.exports = {command}

View File

@@ -0,0 +1,49 @@
const {Command} = require("../Command")
const {Embed, EmbedError} = require("../Embed")
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)
const channel = interaction.member.voice.channel
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")
} else if(song) {
// Result is a song
embed.setColor(0x15e6ed)
embed.setTitle('**Musique suivante !**')
embed.setDescription('**Titre : **' + song.title)
embed.addField('**Durée : **'+ song.readduration, "")
embed.addField("**Artiste : **" + song.author, "")
embed.setThumbnail(song.thumbnail)
}
embed.send(interaction)
})
} else {
return new EmbedError("Le bot n'est pas connecté").send(interaction)
}
})
module.exports = {command}

View File

@@ -0,0 +1,34 @@
const {Command} = require("../Command")
const {Embed, EmbedError} = require("../Embed")
const {Player} = require("../../player/Player")
const command = new Command("state", "Affiche la musique en cours", (client, interaction) => {
const channel = interaction.member.voice.channel
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")
} else if(song) {
// Result is a song
embed.setColor(0x15e6ed)
embed.setTitle('**Musique en cours :**')
embed.setDescription('**Titre : **' + song.title)
embed.addField('**Durée : **', song.readduration)
embed.addField("**Artiste : **", song.author)
embed.setThumbnail(song.thumbnail)
}
embed.send(interaction)
})
module.exports = {command}

View File

@@ -81,8 +81,12 @@ class Embed {
return this.embed return this.embed
} }
send(interaction) { send(interaction, ephemeral) {
interaction.reply({embeds: [this.build()]}) if(ephemeral) {
interaction.reply({embeds: [this.build()], ephemeral: true})
} else {
interaction.reply({embeds: [this.build()]})
}
} }
} }

View File

@@ -0,0 +1,32 @@
const ffprobe = require('ffprobe');
const ffprobeStatic = require('ffprobe-static');
const { getReadableDuration } = require('../utils/TimeConverter');
const clog = require("loguix").getInstance("Song")
async function getMediaInformation(instance, media, provider) {
try {
const info = await ffprobe(media.attachment.url, { path: ffprobeStatic.path });
if (info.streams?.[0]?.duration_ts) {
instance.duration = info.streams[0].duration;
instance.readduration = getReadableDuration(instance.duration)
}
// Vérification pour éviter une erreur si `streams[0]` ou `tags` n'existe pas
instance.thumbnail = info.streams?.[0]?.tags?.thumbnail ??
"https://radomisol.fr/wp-content/uploads/2016/08/cropped-note-radomisol-musique.png";
// Obtenir le titre (sinon utiliser le nom du fichier)
instance.title = info.streams?.[0]?.tags?.title ?? media.attachment.name;
// Obtenir l'auteur (s'il existe)
instance.author = info.streams?.[0]?.tags?.artist ?? instance.author;
} catch (err) {
clog.error("Impossible de récupérer les informations de la musique : " + media.attachment.name)
clog.error(err)
return null;
}
}
module.exports = {getMediaInformation}

View File

@@ -3,6 +3,7 @@ const { __glob } = require('../utils/GlobalVars')
const PreviousDB = new Database("previous", __glob.PREVIOUSFILE, {}) const PreviousDB = new Database("previous", __glob.PREVIOUSFILE, {})
const {LogType} = require("loguix") const {LogType} = require("loguix")
const clog = new LogType("List") const clog = new LogType("List")
const { Song } = require('./Song')
const AllLists = new Map() // Map<guildId, List> const AllLists = new Map() // Map<guildId, List>
@@ -43,6 +44,9 @@ class List {
} }
nextSong() { nextSong() {
if(this.current != null) {
this.addPreviousSong(this.current)
}
var song = null; var song = null;
if(!this.shuffle) { if(!this.shuffle) {
song = this.next[0] song = this.next[0]
@@ -53,9 +57,11 @@ class List {
this.next.splice(randomIndex, 1) this.next.splice(randomIndex, 1)
} }
this.setCurrent(song)
return song return song
} }
clearNext() { clearNext() {
this.next = new Array(); this.next = new Array();
} }
@@ -64,6 +70,10 @@ class List {
this.next.push(song) this.next.push(song)
} }
firstNext(song) {
this.next.unshift(song)
}
removeNextByIndex(index) { removeNextByIndex(index) {
this.next.splice(index, 1) this.next.splice(index, 1)
} }
@@ -75,15 +85,33 @@ class List {
} }
getPrevious() { getPrevious() {
return PreviousDB.data[this.guildId]; const previousList = new Array()
for(const song of PreviousDB.data[this.guildId]) {
previousList.push(new Song(song))
}
return previousList;
} }
getPreviousSong() { getPreviousSong() {
if(PreviousDB.data[this.guildId].length > 0) {
return new Song(PreviousDB.data[this.guildId][0])
} else {
return null;
}
}
previousSong() {
if(this.current != null) {
this.firstNext(this.current)
}
if(PreviousDB.data[this.guildId].length > 0) { if(PreviousDB.data[this.guildId].length > 0) {
const song = PreviousDB.data[this.guildId][0] const song = PreviousDB.data[this.guildId][0]
// Remove the song from the previous list
PreviousDB.data[this.guildId].splice(0, 1) PreviousDB.data[this.guildId].splice(0, 1)
savePrevious() savePrevious()
return song return new Song(song)
} else { } else {
return null; return null;
} }
@@ -116,6 +144,8 @@ class List {
this.clearNext(); this.clearNext();
this.current = null this.current = null
this.shuffle = false; this.shuffle = false;
AllLists.delete(this.guildId)
} }
setShuffle(value) { setShuffle(value) {

View File

@@ -1,19 +1,28 @@
const {createAudioResource, VoiceConnectionStatus} = require('@discordjs/voice'); const {createAudioResource, VoiceConnectionStatus, createAudioPlayer} = require('@discordjs/voice');
const {LogType} = require('loguix') const {LogType} = require('loguix')
const clog = new LogType("Media") const clog = new LogType("Media")
const plog = require("loguix").getInstance("Player") const plog = require("loguix").getInstance("Player")
async function play(instance, song) { async function play(instance, song) {
//const resource = await song.getResource()
//Test with a local file
const resource = createAudioResource("C:\\Users\\picot\\Downloads\\Confrontation_Replique_Raphix.mp3")
console.log(resource)
// Wait until connection is ready
instance.connection.once(VoiceConnectionStatus.Ready, async () => {
instance.player.play(resource)
})
plog.log(`GUILD : ${instance.guildId} - Lecture de la musique : ${song.title}`)
try {
instance.player = createAudioPlayer()
instance.generatePlayerEvents()
const player = instance.player
const resource = await song.getResource() // Remplace par ton fichier audio
player.play(resource);
instance.connection.subscribe(player);
clog.log(`GUILD : ${instance.guildId} - Lecture de la musique (Media): ${song.title} - Filename : ${song.filename}`)
} catch(e) {
clog.error("Erreur lors de la lecture de la musique : " + song.title)
clog.error(e)
}
} }
module.exports = {play} module.exports = {play}

View File

@@ -6,6 +6,7 @@ const plog = new LogType("Player")
const clog = new LogType("Signal") const clog = new LogType("Signal")
const media = require('./Method/Media'); const media = require('./Method/Media');
const Activity = require('../discord/Activity');
const AllPlayers = new Map() const AllPlayers = new Map()
@@ -39,33 +40,48 @@ class Player {
channelId: channel.id, channelId: channel.id,
guildId: channel.guild.id, guildId: channel.guild.id,
adapterCreator: channel.guild.voiceAdapterCreator, adapterCreator: channel.guild.voiceAdapterCreator,
selfDeaf: false,
selfMute: false
}); });
this.player = createAudioPlayer() this.player = createAudioPlayer()
this.generatePlayerEvents()
this.connection.subscribe(this.player)
this.connection.on('stateChange', (oldState, newState) => { this.connection.on('stateChange', (oldState, newState) => {
clog.log(`GUILD : ${this.guildId} - [STATE] OLD : "${oldState.status}" NEW : "${newState.status}"`); clog.log(`GUILD : ${this.guildId} - [STATE] OLD : "${oldState.status}" NEW : "${newState.status}"`);
// Si la connection est fermée, on détruit le player
if(newState.status === VoiceConnectionStatus.Disconnected) {
this.leave()
}
}); });
}
generatePlayerEvents() {
this.player.on('error', error => { this.player.on('error', error => {
plog.error(`GUILD : ${this.guildId} - Une erreur est survenue dans le player`); plog.error(`GUILD : ${this.guildId} - Une erreur est survenue dans le player`);
plog.error(error); plog.error(error);
}); });
this.player.on(AudioPlayerStatus.Idle, () => { this.player.on(AudioPlayerStatus.Idle, () => {
Activity.idleActivity()
this.queue.setCurrent(null)
if(this.queue.next.length > 0) { if(this.queue.next.length > 0) {
//TODO : Play next song this.play(this.queue.nextSong())
} }
}); });
this.player.on(AudioPlayerStatus.Playing, () => { this.player.on(AudioPlayerStatus.Playing, () => {
plog.log(`GUILD : ${this.guildId} - Le player est en train de jouer le contenu suivant : ${this.queue.current.title}`); 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)
});
} }
checkConnection() { checkConnection() {
if(this.connection === null) { if(this.connection === null) {
clog.error(`GUILD : ${this.guildId} - La connection n'est pas définie`) clog.error(`GUILD : ${this.guildId} - La connection n'est pas définie`)
@@ -78,43 +94,81 @@ class Player {
} }
async play(song) { async play(song) {
if(this.checkConnection()) return
if(this.queue.current != null) {
this.player.stop()
}
this.queue.setCurrent(song)
if(song.type = "attachment") { if(song.type = "attachment") {
media.play(this, song) media.play(this, song)
} }
} }
async add(song) { 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) this.play(song)
return return
} }
this.queue.addNextSong(song) this.queue.addNextSong(song)
plog.log(`GUILD : ${this.guildId} - La musique a été ajoutée à la liste de lecture : ${song.title}`)
} }
async pause() { async pause() {
if(this.player.state.status = AudioPlayerStatus.Paused) { if(this.checkConnection()) return "no_music"
if(this.player.state.status == AudioPlayerStatus.Paused) {
this.player.unpause() this.player.unpause()
plog.log(`GUILD : ${this.guildId} - La musique a été reprise`)
return false
} else { } else {
this.player.pause() this.player.pause()
plog.log(`GUILD : ${this.guildId} - La musique a été mise en pause`)
return true
} }
} }
async leave() { async leave() {
if(this.checkConnection()) return
if(this.queue.current != null) {
this.queue.addPreviousSong(this.queue.current)
}
// Détruit la connection et le player et l'enlève de la liste des // Détruit la connection et le player et l'enlève de la liste des
this.connection.destroy() this.connection.destroy()
this.player.stop() this.player.stop()
Activity.idleActivity()
this.queue.destroy()
AllPlayers.delete(this.guildId) AllPlayers.delete(this.guildId)
clog.log("Connection détruite avec le guildId : " + this.guildId) clog.log("Connection détruite avec le guildId : " + this.guildId)
plog.log("Player détruit avec le guildId : " + this.guildId) plog.log("Player détruit avec le guildId : " + this.guildId)
} }
async skip() {
if(this.checkConnection()) return "no_music"
if(this.queue.next.length === 0) {
return "no_music"
}
const songSkip = this.queue.nextSong()
this.play(songSkip)
return songSkip
}
async previous() {
if(this.checkConnection()) return "no_music"
if(this.queue.getPrevious().length === 0) {
return "no_music"
}
const songPrevious = this.queue.previousSong()
this.play(songPrevious)
return songPrevious
}
} }
module.exports = {Player} module.exports = {Player, AllPlayers}
/* /*

View File

@@ -1,29 +1,43 @@
const {LogType} = require('loguix') const {LogType} = require('loguix')
const { createAudioResource, StreamType } = require('@discordjs/voice'); const { createAudioResource, StreamType } = require('@discordjs/voice');
const ffprobe = require('ffprobe');
const ffprobeStatic = require('ffprobe-static');
const { getReadableDuration } = require('../utils/TimeConverter');
const clog = new LogType("Song") const clog = new LogType("Song")
const MediaInformation = require('../media/MediaInformation')
class Song { class Song {
title = "Aucun titre"; title = "Aucun titre";
filename = "Aucun fichier";
author = "Auteur inconnu" author = "Auteur inconnu"
url; url;
thumbnail; thumbnail = "https://radomisol.fr/wp-content/uploads/2016/08/cropped-note-radomisol-musique.png" ;
duration; duration;
readduration; readduration;
type; type;
constructor(properties) {
if(properties) {
this.title = properties.title ?? this.title
this.filename = properties.filename ?? this.filename
this.author = properties.author ?? this.author
this.url = properties.url ?? this.url
this.thumbnail = properties.thumbnail ?? this.thumbnail
this.duration = properties.duration ?? this.duration
this.readduration = properties.readduration ?? this.readduration
this.type = properties.type ?? this.type
}
}
async processMedia(media, provider) { async processMedia(media, provider) {
if(provider) this.author = provider if(provider) this.author = provider
// Check if media is a file or a link // Check if media is a file or a link
if(media.attachment) { if(media.attachment) {
this.url = media.attachment.url this.url = media.attachment.url
this.filename = media.attachment.name
this.type = "attachment" this.type = "attachment"
// In face, duration is null, get the metadata of the file to get the duration // In face, duration is null, get the metadata of the file to get the duration
await getMediaInformation(this, media) await MediaInformation.getMediaInformation(this, media)
} else { } else {
clog.error("Impossible de traiter le média") clog.error("Impossible de traiter le média")
@@ -47,31 +61,3 @@ class Song {
} }
module.exports = {Song} module.exports = {Song}
async function getMediaInformation(instance, media, provider) {
try {
const info = await ffprobe(media.attachment.url, { path: ffprobeStatic.path });
if (info.streams?.[0]?.duration_ts) {
instance.duration = info.streams[0].duration;
instance.readduration = getReadableDuration(instance.duration)
}
// Vérification pour éviter une erreur si `streams[0]` ou `tags` n'existe pas
instance.thumbnail = info.streams?.[0]?.tags?.thumbnail ??
"https://radomisol.fr/wp-content/uploads/2016/08/cropped-note-radomisol-musique.png";
// Obtenir le titre (sinon utiliser le nom du fichier)
instance.title = info.streams?.[0]?.tags?.title ?? media.attachment.name;
// Obtenir l'auteur (s'il existe)
instance.author = info.streams?.[0]?.tags?.artist ?? instance.author;
} catch (err) {
clog.error("Impossible de récupérer les informations de la musique : " + this.name)
clog.error(err)
return null;
}
}