Version 1.0.0-rc1 - Version initiale (Ajout du serveur, des playlists)
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -144,3 +144,4 @@ test/
 | 
			
		||||
data/
 | 
			
		||||
 | 
			
		||||
__DEBUG.js
 | 
			
		||||
__TEST.js
 | 
			
		||||
							
								
								
									
										49
									
								
								backend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										49
									
								
								backend/package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -1,16 +1,16 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "chopin-backend",
 | 
			
		||||
  "version": "0.3.0",
 | 
			
		||||
  "version": "0.4.0",
 | 
			
		||||
  "lockfileVersion": 3,
 | 
			
		||||
  "requires": true,
 | 
			
		||||
  "packages": {
 | 
			
		||||
    "": {
 | 
			
		||||
      "name": "chopin-backend",
 | 
			
		||||
      "version": "0.3.0",
 | 
			
		||||
      "version": "0.4.0",
 | 
			
		||||
      "license": "ISC",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@discordjs/voice": "^0.18.0",
 | 
			
		||||
        "@distube/ytdl-core": "^4.11.5",
 | 
			
		||||
        "@distube/ytdl-core": "^4.16.8",
 | 
			
		||||
        "@distube/ytsr": "2.0.4",
 | 
			
		||||
        "cors": "^2.8.5",
 | 
			
		||||
        "discord-player": "^7.1.0",
 | 
			
		||||
@@ -19,6 +19,7 @@
 | 
			
		||||
        "ffmpeg-static": "^5.2.0",
 | 
			
		||||
        "ffprobe": "^1.1.2",
 | 
			
		||||
        "ffprobe-static": "^3.1.0",
 | 
			
		||||
        "fluent-ffmpeg": "^2.1.3",
 | 
			
		||||
        "libsodium-wrappers": "^0.7.15",
 | 
			
		||||
        "loguix": "^1.4.2",
 | 
			
		||||
        "nodemon": "^3.1.9",
 | 
			
		||||
@@ -266,9 +267,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@distube/ytdl-core": {
 | 
			
		||||
      "version": "4.16.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@distube/ytdl-core/-/ytdl-core-4.16.4.tgz",
 | 
			
		||||
      "integrity": "sha512-r0ZPMMB5rbUSQSez//dYDWjPSAEOm6eeV+9gyR+1vngGYFUi953Z/CoF4epTBS40X8dR32gyH3ERlh7NbnCaRg==",
 | 
			
		||||
      "version": "4.16.8",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@distube/ytdl-core/-/ytdl-core-4.16.8.tgz",
 | 
			
		||||
      "integrity": "sha512-Vl04TCOiSSwCFmOHVfzIX117tpT/eCobp2hwx4lo2EyeE70FMVrpQpKSEdh+EjywmVAHs/ZXIsaDy+wq1fMb+g==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "http-cookie-agent": "^6.0.8",
 | 
			
		||||
@@ -1573,9 +1574,9 @@
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/axios": {
 | 
			
		||||
      "version": "1.8.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz",
 | 
			
		||||
      "integrity": "sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==",
 | 
			
		||||
      "version": "1.8.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
 | 
			
		||||
      "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "follow-redirects": "^1.15.6",
 | 
			
		||||
@@ -2854,6 +2855,36 @@
 | 
			
		||||
        "node": ">= 0.8"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/fluent-ffmpeg": {
 | 
			
		||||
      "version": "2.1.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.3.tgz",
 | 
			
		||||
      "integrity": "sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "async": "^0.2.9",
 | 
			
		||||
        "which": "^1.1.1"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=18"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/fluent-ffmpeg/node_modules/async": {
 | 
			
		||||
      "version": "0.2.10",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz",
 | 
			
		||||
      "integrity": "sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/fluent-ffmpeg/node_modules/which": {
 | 
			
		||||
      "version": "1.3.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
 | 
			
		||||
      "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
 | 
			
		||||
      "license": "ISC",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "isexe": "^2.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "bin": {
 | 
			
		||||
        "which": "bin/which"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/follow-redirects": {
 | 
			
		||||
      "version": "1.15.9",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "chopin-backend",
 | 
			
		||||
  "version": "0.4.0",
 | 
			
		||||
  "version": "1.0.0",
 | 
			
		||||
  "description": "Discord Bot for music - Fetching everywhere !",
 | 
			
		||||
  "main": "src/main.js",
 | 
			
		||||
  "nodemonConfig": {
 | 
			
		||||
@@ -19,7 +19,7 @@
 | 
			
		||||
  "license": "ISC",
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@discordjs/voice": "^0.18.0",
 | 
			
		||||
    "@distube/ytdl-core": "^4.11.5",
 | 
			
		||||
    "@distube/ytdl-core": "^4.16.8",
 | 
			
		||||
    "@distube/ytsr": "2.0.4",
 | 
			
		||||
    "cors": "^2.8.5",
 | 
			
		||||
    "discord-player": "^7.1.0",
 | 
			
		||||
@@ -28,6 +28,7 @@
 | 
			
		||||
    "ffmpeg-static": "^5.2.0",
 | 
			
		||||
    "ffprobe": "^1.1.2",
 | 
			
		||||
    "ffprobe-static": "^3.1.0",
 | 
			
		||||
    "fluent-ffmpeg": "^2.1.3",
 | 
			
		||||
    "libsodium-wrappers": "^0.7.15",
 | 
			
		||||
    "loguix": "^1.4.2",
 | 
			
		||||
    "nodemon": "^3.1.9",
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ const dlog = new LogType("Discord")
 | 
			
		||||
 | 
			
		||||
const membersVoices = new Map()
 | 
			
		||||
const timers = new Map()
 | 
			
		||||
const guilds = new Map()
 | 
			
		||||
 | 
			
		||||
const client = new Client({
 | 
			
		||||
    intents:[GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildMembers],
 | 
			
		||||
@@ -22,12 +23,32 @@ function getClient() {
 | 
			
		||||
    return client
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getGuilds() {
 | 
			
		||||
    return guilds
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getMembersVoices() {
 | 
			
		||||
    return membersVoices
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getChannel(guildId, channelId) {
 | 
			
		||||
    return client.guilds.cache.get(guildId).channels.cache.get(channelId)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function init() {
 | 
			
		||||
        
 | 
			
		||||
    client.once('ready', () => {
 | 
			
		||||
        dlog.log("Connexion au Bot Discord réussi ! Connecté en tant que : " + client.user.tag)
 | 
			
		||||
 | 
			
		||||
        // Add all guilds to the guilds map
 | 
			
		||||
        client.guilds.cache.forEach(guild => {
 | 
			
		||||
            guilds.set(guild.id, {
 | 
			
		||||
                id: guild.id,
 | 
			
		||||
                name: guild.name,
 | 
			
		||||
                members: guild.members.cache.map(member => member.user.username),
 | 
			
		||||
            })
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        const Activity = require("./Activity")
 | 
			
		||||
        Activity.idleActivity()
 | 
			
		||||
 | 
			
		||||
@@ -72,6 +93,16 @@ function init() {
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
   // If a new guild is added, we will add it to the guilds map
 | 
			
		||||
    client.on("guildCreate", (guild) => {
 | 
			
		||||
        dlog.log("Nouvelle guilde ajoutée : " + guild.name)
 | 
			
		||||
        guilds.set(guild.id, {
 | 
			
		||||
            id: guild.id,
 | 
			
		||||
            name: guild.name,
 | 
			
		||||
            members: guild.members.cache.map(member => member.user.username),
 | 
			
		||||
        })
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    client.on("voiceStateUpdate", (oldMember, newMember) => {
 | 
			
		||||
        membersVoices.set(newMember.id, {
 | 
			
		||||
            guildId: newMember.guild.id,
 | 
			
		||||
@@ -95,7 +126,7 @@ function init() {
 | 
			
		||||
                            dlog.log("[Automatic Task] Guild Id :" + newMember.guild.id + " - Player supprimé : " + channel.name)
 | 
			
		||||
                        }
 | 
			
		||||
                 
 | 
			
		||||
                    }, 10000))
 | 
			
		||||
                    }, 600000))
 | 
			
		||||
                    dlog.log("[Automatic Task] Guild Id :" + newMember.guild.id + " -  Player supprimé dans 10 minutess : " + channel.name)
 | 
			
		||||
                } else {
 | 
			
		||||
                    dlog.log("[Automatic Task] Guild Id :" + newMember.guild.id + " -  Player n'est pas seul dans le channel : " + channel.name)
 | 
			
		||||
@@ -115,6 +146,6 @@ function init() {
 | 
			
		||||
    client.login(config.getToken())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {init, getClient}
 | 
			
		||||
module.exports = {init, getClient, getGuilds, getMembersVoices, getChannel}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										26
									
								
								backend/src/discord/Button.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								backend/src/discord/Button.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
const { ButtonBuilder, ButtonStyle } = require('discord.js');
 | 
			
		||||
 | 
			
		||||
class Button extends ButtonBuilder {
 | 
			
		||||
    constructor(label, customId, style = ButtonStyle.Primary, link = null) {
 | 
			
		||||
        super()
 | 
			
		||||
            .setLabel(label)
 | 
			
		||||
            if (link) {
 | 
			
		||||
                this.setURL(link);
 | 
			
		||||
                this.setStyle(ButtonStyle.Link);
 | 
			
		||||
            } else{
 | 
			
		||||
                this.setCustomId(customId)
 | 
			
		||||
            }
 | 
			
		||||
            this.setStyle(style);
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setDisabled(disabled) {
 | 
			
		||||
        return this.setDisabled(disabled);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setEmoji(emoji) {
 | 
			
		||||
        return this.setEmoji(emoji);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = { Button };
 | 
			
		||||
@@ -20,13 +20,16 @@ const command = new Command("about", "Affiche des informations sur le bot", (cli
 | 
			
		||||
    embed.addField("Ping", `${client.ws.ping} ms    `, true)
 | 
			
		||||
    embed.addField("Réalisé par", "Raphix - 2025", true)
 | 
			
		||||
    embed.addColumn()
 | 
			
		||||
    embed.addField('Versions',"")
 | 
			
		||||
    embed.addField('Versions :',"")
 | 
			
		||||
    embed.addField('Node.js', process.version,true)
 | 
			
		||||
    embed.addField('Discord.js', packageJson.dependencies["discord.js"].replace("^", ""),true)
 | 
			
		||||
    embed.addColumn()
 | 
			
		||||
    embed.addField('Webmetrik', packageJson.dependencies["webmetrik"].replace("^", ""),true)
 | 
			
		||||
    embed.addField('Loguix', packageJson.dependencies["loguix"].replace("^", ""),true)
 | 
			
		||||
    embed.addColumn()
 | 
			
		||||
    embed.addField('FFmpeg', packageJson.dependencies["ffmpeg-static"].replace("^", ""),true)
 | 
			
		||||
    embed.addField('Ytdl', packageJson.dependencies["@distube/ytdl-core"].replace("^", ""),true)
 | 
			
		||||
    embed.addColumn()
 | 
			
		||||
 | 
			
		||||
    embed.send(interaction)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										20
									
								
								backend/src/discord/Commands/Invite.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								backend/src/discord/Commands/Invite.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
const {Command } = require("../Command")
 | 
			
		||||
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()
 | 
			
		||||
    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.')
 | 
			
		||||
    embed.addBotPicture(client)
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    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)
 | 
			
		||||
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
module.exports = {command}  
 | 
			
		||||
@@ -43,7 +43,6 @@ const command = new Command("media", "Lire un média MP3/WAV dans un salon vocal
 | 
			
		||||
    embed.send(interaction)
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}, [{type: "FILE", name: "media", description: "Fichier audio à lire", required: true},
 | 
			
		||||
    {type:"BOOLEAN", name: "now", description: "Lire le média maintenant", required: false}]
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ const { Command } = require("../Command");
 | 
			
		||||
const { Embed, EmbedError } = require("../Embed");
 | 
			
		||||
const { Player } = require("../../player/Player");
 | 
			
		||||
const Finder = require("../../player/Finder");
 | 
			
		||||
const { Playlist } = require("../../player/Playlist");
 | 
			
		||||
const { Playlist } = require("../../playlists/Playlist");
 | 
			
		||||
const spotify = require("../../media/SpotifyInformation");
 | 
			
		||||
 | 
			
		||||
const command = new Command("play", "Jouer une musique à partir d'un lien dans un salon vocal", async (client, interaction) => {
 | 
			
		||||
@@ -44,6 +44,7 @@ const command = new Command("play", "Jouer une musique à partir d'un lien dans
 | 
			
		||||
            embed.addField('**Durée : **', song.readduration)
 | 
			
		||||
            embed.addField("**Artiste : **",song.author)
 | 
			
		||||
            embed.addField('**Demandé par **' + interaction.member.user.username, "")
 | 
			
		||||
            embed.addField("**Lien :** ", song.url)
 | 
			
		||||
            embed.setThumbnail(song.thumbnail)
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@ 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
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,17 @@
 | 
			
		||||
const {Embed} = require("../Embed")
 | 
			
		||||
const {Command} = require("../Command")
 | 
			
		||||
const {restart} = require("../../utils/Maintenance")
 | 
			
		||||
const users = require("../../server/auth/User")  
 | 
			
		||||
 | 
			
		||||
// Nécéssite une raison pour redémarrer le bot
 | 
			
		||||
 | 
			
		||||
const command = new Command("restart", "Redémarre le bot", (client, interaction) => {
 | 
			
		||||
    // Check if user is admin from users list 
 | 
			
		||||
    const user = users.getUserById(interaction.user.id)
 | 
			
		||||
    if(!user || !user.isAdmin()) {
 | 
			
		||||
        interaction.reply({content: "Vous n'êtes pas admin", ephemeral: true})
 | 
			
		||||
        return
 | 
			
		||||
    }
 | 
			
		||||
    const reason = interaction.options.getString("reason")
 | 
			
		||||
    restart(reason)
 | 
			
		||||
    const embed = new Embed()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,18 @@
 | 
			
		||||
const { Command } = require('../Command');
 | 
			
		||||
const { Button } = require('../Button');
 | 
			
		||||
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()
 | 
			
		||||
    embed.setColor(0xffffff)
 | 
			
		||||
    embed.setTitle('Subsonics - Chopin')
 | 
			
		||||
    embed.addBotPicture(client)
 | 
			
		||||
    embed.addField('Lien',"https://subsonics.raphix.fr/")
 | 
			
		||||
    
 | 
			
		||||
    embed.setDescription('Vous pouvez contrôler le bot depuis le site web ! \n Nécéssite une connexion avec votre compte Discord.')
 | 
			
		||||
    
 | 
			
		||||
    const linkButton = new Button("Site web", null, 5, config.getWebsiteLink())
 | 
			
		||||
    embed.addButton(linkButton)
 | 
			
		||||
    embed.send(interaction)
 | 
			
		||||
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,12 @@
 | 
			
		||||
const { EmbedBuilder } = require("discord.js");
 | 
			
		||||
const { EmbedBuilder, ActionRowBuilder } = require("discord.js");
 | 
			
		||||
 | 
			
		||||
class Embed {
 | 
			
		||||
    fields;
 | 
			
		||||
    buttons;
 | 
			
		||||
    constructor() {
 | 
			
		||||
        this.embed = new EmbedBuilder().setTimestamp()
 | 
			
		||||
        this.fields = []
 | 
			
		||||
        this.buttons = []
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setTitle(title) {
 | 
			
		||||
@@ -75,18 +77,24 @@ class Embed {
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    addButton(button) {
 | 
			
		||||
        this.buttons.push(button)
 | 
			
		||||
        return this
 | 
			
		||||
    }   
 | 
			
		||||
 | 
			
		||||
    build() {
 | 
			
		||||
        //Add Fields to an object 
 | 
			
		||||
        this.embed.addFields(this.fields)
 | 
			
		||||
        if(this.buttons.length > 0) {
 | 
			
		||||
            this.actionRow = new ActionRowBuilder()
 | 
			
		||||
			.addComponents(this.buttons);
 | 
			
		||||
        }
 | 
			
		||||
        return this.embed
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    send(interaction, ephemeral) {
 | 
			
		||||
        if(ephemeral) {
 | 
			
		||||
            interaction.reply({embeds: [this.build()], ephemeral: true})
 | 
			
		||||
        } else {
 | 
			
		||||
            interaction.reply({embeds: [this.build()]})
 | 
			
		||||
        }
 | 
			
		||||
        if(ephemeral === undefined) ephemeral = false;
 | 
			
		||||
        interaction.reply({ embeds: [this.build()], ephemeral: ephemeral, components: this.buttons.length > 0 ? [this.actionRow] : [] })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,7 @@ class Report {
 | 
			
		||||
       
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {Report}
 | 
			
		||||
@@ -21,4 +21,6 @@ setup();
 | 
			
		||||
async function setup() {
 | 
			
		||||
    const DiscordBot = require("./discord/Bot")
 | 
			
		||||
    DiscordBot.init()
 | 
			
		||||
    const Server = require("./server/Server")
 | 
			
		||||
    Server.init()
 | 
			
		||||
}
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
const {LogType} = require('loguix');
 | 
			
		||||
const clog = new LogType("SoundcloudInformation");
 | 
			
		||||
const {Song} = require('../player/Song');
 | 
			
		||||
const {Playlist} = require('../player/Playlist');
 | 
			
		||||
const {Playlist} = require('../playlists/Playlist');
 | 
			
		||||
const {Soundcloud} = require('soundcloud.ts')
 | 
			
		||||
const {getReadableDuration} = require('../utils/TimeConverter');
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ const config = require('../utils/Database/Configuration');
 | 
			
		||||
const SPOTIFY_CLIENT_ID = config.getSpotifyClientId()
 | 
			
		||||
const SPOTIFY_CLIENT_SECRET = config.getSpotifyClientSecret()
 | 
			
		||||
const SpotifyWebApi = require('spotify-web-api-node');
 | 
			
		||||
const {Playlist} = require('../player/Playlist');
 | 
			
		||||
const {Playlist} = require('../playlists/Playlist');
 | 
			
		||||
const {Song} = require('../player/Song');
 | 
			
		||||
const youtube = require("../media/YoutubeInformation");
 | 
			
		||||
const {getReadableDuration} = require('../utils/TimeConverter');
 | 
			
		||||
 
 | 
			
		||||
@@ -1,28 +1,29 @@
 | 
			
		||||
const { LogType } = require('loguix');
 | 
			
		||||
const clog = new LogType("YoutubeInformation");
 | 
			
		||||
const { Song } = require('../player/Song');
 | 
			
		||||
const { Playlist } = require('../player/Playlist');
 | 
			
		||||
const { Playlist } = require('../playlists/Playlist');
 | 
			
		||||
const { getReadableDuration } = require('../utils/TimeConverter');
 | 
			
		||||
const ytsr = require('@distube/ytsr');
 | 
			
		||||
const ytfps = require('ytfps');
 | 
			
		||||
 | 
			
		||||
async function getQuery(query) {
 | 
			
		||||
    if (query === null || typeof query !== 'string') {
 | 
			
		||||
async function getQuery(query, multiple) {
 | 
			
		||||
    if (!query || typeof query !== 'string') {
 | 
			
		||||
        clog.error("Impossible de rechercher une vidéo YouTube, car la requête est nulle");
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        const searchResults = await ytsr(query, { limit: 1 });
 | 
			
		||||
        const video = searchResults.items.find(item => item.type === 'video');
 | 
			
		||||
        const limit = multiple ? 25 : 1;
 | 
			
		||||
        const searchResults = await ytsr(query, { limit });
 | 
			
		||||
        const videos = searchResults.items.filter(item => item.type === 'video');
 | 
			
		||||
 | 
			
		||||
        if (!video) {
 | 
			
		||||
        if (videos.length === 0) {
 | 
			
		||||
            clog.error("Impossible de récupérer le lien de la vidéo YouTube à partir de la requête");
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const song = await getVideo(video.url);
 | 
			
		||||
        return song;
 | 
			
		||||
        const songs = await Promise.all(videos.map(video => getVideo(video.url)));
 | 
			
		||||
        return multiple ? songs.filter(song => song !== null) : songs[0];
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        clog.error('Erreur lors de la recherche YouTube: ' + error);
 | 
			
		||||
        return null;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,11 +6,10 @@ const spotify = require("../media/SpotifyInformation")
 | 
			
		||||
const soundcloud = require("../media/SoundcloudInformation")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async function search(query) {
 | 
			
		||||
async function search(query, multiple) {
 | 
			
		||||
    const type = Resolver.getQueryType(query)
 | 
			
		||||
    if(type == QueryType.YOUTUBE_SEARCH) {
 | 
			
		||||
 | 
			
		||||
        return await youtube.getQuery(query)
 | 
			
		||||
        return await youtube.getQuery(query, multiple)
 | 
			
		||||
       
 | 
			
		||||
    }
 | 
			
		||||
    if(type == QueryType.YOUTUBE_VIDEO) {
 | 
			
		||||
 
 | 
			
		||||
@@ -41,6 +41,7 @@ class List {
 | 
			
		||||
        } else {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    nextSong() {
 | 
			
		||||
@@ -58,30 +59,36 @@ class List {
 | 
			
		||||
           
 | 
			
		||||
        }
 | 
			
		||||
        this.setCurrent(song)
 | 
			
		||||
        process.emit("PLAYERS_UPDATE")
 | 
			
		||||
        return song
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    clearNext() {
 | 
			
		||||
        this.next = new Array();
 | 
			
		||||
        process.emit("PLAYERS_UPDATE")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    addNextSong(song) {
 | 
			
		||||
        this.next.push(song)
 | 
			
		||||
        process.emit("PLAYERS_UPDATE")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    firstNext(song) {
 | 
			
		||||
        this.next.unshift(song)
 | 
			
		||||
        process.emit("PLAYERS_UPDATE")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    removeNextByIndex(index) {
 | 
			
		||||
        this.next.splice(index, 1)
 | 
			
		||||
        process.emit("PLAYERS_UPDATE")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    moveSongToUpNext(index) {
 | 
			
		||||
        const song = this.next[index]
 | 
			
		||||
        this.next.splice(index, 1)
 | 
			
		||||
        this.next.unshift(song)
 | 
			
		||||
        process.emit("PLAYERS_UPDATE")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getPrevious() {
 | 
			
		||||
@@ -100,6 +107,7 @@ class List {
 | 
			
		||||
        } else {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
       
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    previousSong() {
 | 
			
		||||
@@ -115,21 +123,25 @@ class List {
 | 
			
		||||
        } else {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
      
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    clearPrevious() {
 | 
			
		||||
        PreviousDB.data[this.guildId] = new Array();
 | 
			
		||||
        savePrevious();
 | 
			
		||||
        process.emit("PLAYERS_UPDATE")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    addPreviousSongToNextByIndex(index) {
 | 
			
		||||
        const song = PreviousDB.data[this.guildId][index]
 | 
			
		||||
        this.next.push(song)
 | 
			
		||||
        process.emit("PLAYERS_UPDATE")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    addPreviousSong(song) {
 | 
			
		||||
        PreviousDB.data[this.guildId].unshift(song)
 | 
			
		||||
        savePrevious()
 | 
			
		||||
        process.emit("PLAYERS_UPDATE")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getCurrent() {
 | 
			
		||||
@@ -138,6 +150,7 @@ class List {
 | 
			
		||||
 | 
			
		||||
    setCurrent(value) {
 | 
			
		||||
        this.current = value;
 | 
			
		||||
        process.emit("PLAYERS_UPDATE")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    destroy() {
 | 
			
		||||
@@ -145,11 +158,11 @@ class List {
 | 
			
		||||
        this.current = null
 | 
			
		||||
        this.shuffle = false;
 | 
			
		||||
        AllLists.delete(this.guildId)
 | 
			
		||||
       
 | 
			
		||||
        process.emit("PLAYERS_UPDATE")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setShuffle(value) {
 | 
			
		||||
        this.shuffle = value;
 | 
			
		||||
    setShuffle() {
 | 
			
		||||
        this.shuffle = !this.shuffle;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    isShuffle() {
 | 
			
		||||
@@ -175,6 +188,7 @@ class List {
 | 
			
		||||
            return song
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        process.emit("PLAYERS_UPDATE")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    addNextPlaylist(playlist, firstAlreadyPlayed) {
 | 
			
		||||
@@ -185,10 +199,17 @@ class List {
 | 
			
		||||
        for(const song of playlist.songs) {
 | 
			
		||||
            this.addNextSong(song)
 | 
			
		||||
        }
 | 
			
		||||
        process.emit("PLAYERS_UPDATE")
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    moveNext(fromIndex, toIndex) {
 | 
			
		||||
        if(fromIndex == toIndex) return;
 | 
			
		||||
        const song = this.next[fromIndex]
 | 
			
		||||
        this.next.splice(fromIndex, 1)
 | 
			
		||||
        this.next.splice(toIndex, 0, song)
 | 
			
		||||
        process.emit("PLAYERS_UPDATE")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,23 +2,14 @@ const {createAudioResource, VoiceConnectionStatus, createAudioPlayer, StreamType
 | 
			
		||||
const {LogType} = require('loguix')
 | 
			
		||||
const clog = new LogType("Media")
 | 
			
		||||
const plog = require("loguix").getInstance("Player")
 | 
			
		||||
const ffmpeg = require('fluent-ffmpeg') 
 | 
			
		||||
 | 
			
		||||
async function play(instance, song) {
 | 
			
		||||
async function getStream(song) {
 | 
			
		||||
       try {
 | 
			
		||||
          
 | 
			
		||||
            instance.player = createAudioPlayer()
 | 
			
		||||
            instance.generatePlayerEvents()
 | 
			
		||||
            const player = instance.player
 | 
			
		||||
            var resource = await createAudioResource(song.url, {
 | 
			
		||||
                    inputType: StreamType.Arbitrary
 | 
			
		||||
               }) // Remplace par ton fichier audio
 | 
			
		||||
        return song.url;
 | 
			
		||||
           
 | 
			
		||||
 | 
			
		||||
            instance.setCurrentResource(resource)
 | 
			
		||||
            player.play(resource);
 | 
			
		||||
            instance.connection.subscribe(player);
 | 
			
		||||
            clog.log(`GUILD : ${instance.guildId} - Lecture de la musique (Media): ${song.title} - id : ${song.id}`) 
 | 
			
		||||
 | 
			
		||||
       } catch(e) {
 | 
			
		||||
            clog.error("Erreur lors de la lecture de la musique : " + song.title)
 | 
			
		||||
            clog.error(e)
 | 
			
		||||
@@ -27,4 +18,4 @@ async function play(instance, song) {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {play}
 | 
			
		||||
module.exports = {getStream}
 | 
			
		||||
@@ -1,31 +1,25 @@
 | 
			
		||||
const {createAudioResource, VoiceConnectionStatus, createAudioPlayer, StreamType} = require('@discordjs/voice');
 | 
			
		||||
const {LogType} = require('loguix')
 | 
			
		||||
const clog = new LogType("Soundcloud")
 | 
			
		||||
const plog = require("loguix").getInstance("Player")
 | 
			
		||||
const clog = new LogType("Soundcloud-Stream")
 | 
			
		||||
const {Soundcloud} = require('soundcloud.ts')
 | 
			
		||||
const ffmpeg = require('fluent-ffmpeg')
 | 
			
		||||
 | 
			
		||||
const soundcloud = new Soundcloud();
 | 
			
		||||
 | 
			
		||||
async function play(instance, song) {
 | 
			
		||||
async function getStream(song) {
 | 
			
		||||
       try {
 | 
			
		||||
          
 | 
			
		||||
            instance.player = createAudioPlayer()
 | 
			
		||||
            instance.generatePlayerEvents()
 | 
			
		||||
            const player = instance.player
 | 
			
		||||
          var stream = await soundcloud.util.streamTrack(song.url)
 | 
			
		||||
           return stream
 | 
			
		||||
            
 | 
			
		||||
               
 | 
			
		||||
            const stream = await soundcloud.util.streamTrack(song.url)
 | 
			
		||||
            var resource = await createAudioResource(stream)
 | 
			
		||||
            instance.setCurrentResource(resource)
 | 
			
		||||
            player.play(resource);
 | 
			
		||||
            instance.connection.subscribe(player);
 | 
			
		||||
            clog.log(`GUILD : ${instance.guildId} - Lecture de la musique (Soundcloud): ${song.title} - id : ${song.id}`) 
 | 
			
		||||
 | 
			
		||||
       } catch(e) {
 | 
			
		||||
            clog.error("Erreur lors de la lecture de la musique : " + song.title)
 | 
			
		||||
            clog.error("Erreur lors de la récupération du stream : " + song.title)
 | 
			
		||||
            clog.error(e)
 | 
			
		||||
       }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {play}
 | 
			
		||||
module.exports = {getStream}
 | 
			
		||||
@@ -1,16 +1,14 @@
 | 
			
		||||
const {createAudioResource, VoiceConnectionStatus, createAudioPlayer, StreamType} = require('@discordjs/voice');
 | 
			
		||||
const {LogType} = require('loguix')
 | 
			
		||||
const clog = new LogType("Youtube")
 | 
			
		||||
const plog = require("loguix").getInstance("Player")
 | 
			
		||||
const clog = new LogType("Youtube-Stream")
 | 
			
		||||
const ytdl = require('@distube/ytdl-core')
 | 
			
		||||
const ffmpeg = require('fluent-ffmpeg')
 | 
			
		||||
const { getRandomIPv6 } = require("@distube/ytdl-core/lib/utils");
 | 
			
		||||
 | 
			
		||||
async function play(instance, song) {
 | 
			
		||||
async function getStream(song) {
 | 
			
		||||
       try {
 | 
			
		||||
         
 | 
			
		||||
          instance.player = createAudioPlayer()
 | 
			
		||||
          instance.generatePlayerEvents()
 | 
			
		||||
          const player = instance.player
 | 
			
		||||
          const stream = ytdl(song.url, { 
 | 
			
		||||
          let stream = ytdl(song.url, { 
 | 
			
		||||
               quality: 'highestaudio',
 | 
			
		||||
               highWaterMark: 1 << 30,
 | 
			
		||||
               liveBuffer: 20000,
 | 
			
		||||
@@ -19,19 +17,14 @@ async function play(instance, song) {
 | 
			
		||||
 | 
			
		||||
          });
 | 
			
		||||
 | 
			
		||||
                    // Add compressor to the audio resource
 | 
			
		||||
          var resource = createAudioResource(stream);
 | 
			
		||||
        return stream
 | 
			
		||||
    
 | 
			
		||||
          instance.setCurrentResource(resource)
 | 
			
		||||
    } catch(e) {
 | 
			
		||||
        clog.error("Erreur lors de la récupération du stream : " + song.title)
 | 
			
		||||
        clog.error(e)
 | 
			
		||||
               
 | 
			
		||||
          player.play(resource);
 | 
			
		||||
          instance.connection.subscribe(player);
 | 
			
		||||
          clog.log(`GUILD : ${instance.guildId} - Lecture de la musique (Youtube): ${song.title} - id : ${song.id}`) 
 | 
			
		||||
    
 | 
			
		||||
           } catch(e) {
 | 
			
		||||
                clog.error("Erreur lors de la lecture de la musique : " + song.title)
 | 
			
		||||
                clog.error(e)
 | 
			
		||||
           }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {play}
 | 
			
		||||
 | 
			
		||||
module.exports = {getStream}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,9 @@
 | 
			
		||||
const { joinVoiceChannel, getVoiceConnection, entersState, VoiceConnectionStatus, createAudioPlayer, AudioPlayerStatus } = require('@discordjs/voice');
 | 
			
		||||
const { joinVoiceChannel, getVoiceConnection, VoiceConnectionStatus, createAudioPlayer, AudioPlayerStatus, StreamType, createAudioResource } = require('@discordjs/voice');
 | 
			
		||||
const {List} = require('./List')
 | 
			
		||||
const {LogType} = require("loguix");
 | 
			
		||||
const ffmpeg = require('fluent-ffmpeg')
 | 
			
		||||
const fs = require('fs')
 | 
			
		||||
const { PassThrough } = require('stream');
 | 
			
		||||
 | 
			
		||||
const plog = new LogType("Player")
 | 
			
		||||
const clog = new LogType("Signal")
 | 
			
		||||
@@ -13,11 +16,13 @@ const AllPlayers = new Map()
 | 
			
		||||
 | 
			
		||||
class Player {
 | 
			
		||||
    connection;
 | 
			
		||||
    connected = false;
 | 
			
		||||
    player;
 | 
			
		||||
    guildId;
 | 
			
		||||
    channelId;
 | 
			
		||||
    queue;
 | 
			
		||||
    currentResource;
 | 
			
		||||
    loop = false;
 | 
			
		||||
    constructor(guildId) {
 | 
			
		||||
        if(this.guildId === null) {
 | 
			
		||||
            clog.error("Impossible de créer un Player, car guildId est null")
 | 
			
		||||
@@ -39,6 +44,20 @@ class Player {
 | 
			
		||||
            clog.log(`GUILD : ${this.guildId} - Une connexion existe déjà pour ce serveur`)
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        this.joinChannel(channel)
 | 
			
		||||
 | 
			
		||||
        this.player = createAudioPlayer()
 | 
			
		||||
        this.generatePlayerEvents()
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    isConnected() {
 | 
			
		||||
        return this.connected
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    joinChannel(channel) {
 | 
			
		||||
        this.channelId = channel.id 
 | 
			
		||||
        this.connection = joinVoiceChannel({
 | 
			
		||||
            channelId: channel.id,
 | 
			
		||||
            guildId: channel.guild.id,
 | 
			
		||||
@@ -47,11 +66,6 @@ class Player {
 | 
			
		||||
            selfMute: false
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.channelId = channel.id 
 | 
			
		||||
 | 
			
		||||
        this.player = createAudioPlayer()
 | 
			
		||||
        this.generatePlayerEvents()
 | 
			
		||||
    
 | 
			
		||||
        this.connection.on('stateChange', (oldState, newState) => {
 | 
			
		||||
            clog.log(`GUILD : ${this.guildId} - [STATE] OLD : "${oldState.status}" NEW : "${newState.status}"`);
 | 
			
		||||
 | 
			
		||||
@@ -61,7 +75,8 @@ class Player {
 | 
			
		||||
                this.leave()
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.connected = true
 | 
			
		||||
        process.emit("PLAYERS_UPDATE")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    generatePlayerEvents() {
 | 
			
		||||
@@ -71,20 +86,30 @@ class Player {
 | 
			
		||||
        this.player.on('error', error => {
 | 
			
		||||
            plog.error(`GUILD : ${this.guildId} - Une erreur est survenue dans le player`);
 | 
			
		||||
            plog.error(error);
 | 
			
		||||
            console.error(error);
 | 
			
		||||
            process.emit("PLAYERS_UPDATE")
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
       this.player.on(AudioPlayerStatus.Idle, () => {
 | 
			
		||||
            // Si la musique est en boucle, on relance la musique
 | 
			
		||||
            if(this.loop) {
 | 
			
		||||
                this.play(this.queue.current)
 | 
			
		||||
                return
 | 
			
		||||
            }
 | 
			
		||||
            // Si la musique n'est pas en boucle, on passe à la musique suivante
 | 
			
		||||
            Activity.idleActivity()
 | 
			
		||||
            this.queue.setCurrent(null)
 | 
			
		||||
            if(this.queue.next.length > 0) {
 | 
			
		||||
                this.play(this.queue.nextSong())
 | 
			
		||||
            } 
 | 
			
		||||
            process.emit("PLAYERS_UPDATE")
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.player.on(AudioPlayerStatus.Playing, () => {
 | 
			
		||||
        
 | 
			
		||||
            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")
 | 
			
		||||
            
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
@@ -101,6 +126,45 @@ class Player {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getState() {
 | 
			
		||||
        const state = {
 | 
			
		||||
            current: this.queue.current,
 | 
			
		||||
            next: this.queue.next,
 | 
			
		||||
            previous: this.queue.previous,
 | 
			
		||||
            loop: this.loop,
 | 
			
		||||
            shuffle: this.queue.shuffle,
 | 
			
		||||
            paused: this.player.state.status == AudioPlayerStatus.Paused,
 | 
			
		||||
            playing: this.player.state.status == AudioPlayerStatus.Playing,
 | 
			
		||||
            duration: this.getDuration(),
 | 
			
		||||
            playerState: this.player.state.status,
 | 
			
		||||
            connectionState: this.connection.state.status,
 | 
			
		||||
            channelId: this.channelId
 | 
			
		||||
        }
 | 
			
		||||
        return state
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async setLoop() {
 | 
			
		||||
        if(this.checkConnection()) return
 | 
			
		||||
        this.loop = !this.loop
 | 
			
		||||
        if(this.loop) {
 | 
			
		||||
            plog.log(`GUILD : ${this.guildId} - La musique est en boucle`)
 | 
			
		||||
        } else {
 | 
			
		||||
            plog.log(`GUILD : ${this.guildId} - La musique n'est plus en boucle`)
 | 
			
		||||
        }
 | 
			
		||||
        process.emit("PLAYERS_UPDATE")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async setShuffle() {
 | 
			
		||||
        if(this.checkConnection()) return
 | 
			
		||||
        this.queue.shuffle = !this.queue.shuffle
 | 
			
		||||
        if(this.queue.shuffle) {
 | 
			
		||||
            plog.log(`GUILD : ${this.guildId} - La musique est en mode aléatoire`)
 | 
			
		||||
        } else {
 | 
			
		||||
            plog.log(`GUILD : ${this.guildId} - La musique n'est plus en mode aléatoire`)
 | 
			
		||||
        }
 | 
			
		||||
        process.emit("PLAYERS_UPDATE")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async play(song) {
 | 
			
		||||
        if(this.checkConnection()) return
 | 
			
		||||
        if(this.queue.current != null) {
 | 
			
		||||
@@ -108,18 +172,31 @@ class Player {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.queue.setCurrent(song)
 | 
			
		||||
        this.stream = await this.getStream(song)
 | 
			
		||||
 | 
			
		||||
       if(song.type == "attachment") {
 | 
			
		||||
            media.play(this, song)
 | 
			
		||||
       }
 | 
			
		||||
       if(song.type == 'youtube') {
 | 
			
		||||
            youtube.play(this, song)
 | 
			
		||||
       }
 | 
			
		||||
       if(song.type == "soundcloud") {
 | 
			
		||||
            soundcloud.play(this, song)
 | 
			
		||||
       }
 | 
			
		||||
        if(this.stream === null) {
 | 
			
		||||
            plog.error(`GUILD : ${this.guildId} - Impossible de lire la musique : ${song.title} avec le type : ${song.type}`)
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
       // TODO: Créer une méthode pour les autres types de médias
 | 
			
		||||
        this.playStream(this.stream)
 | 
			
		||||
 | 
			
		||||
        plog.log(`GUILD : ${this.guildId} - Lecture de la musique : ${song.title} - Type : ${song.type}`)   
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async getStream(song) {
 | 
			
		||||
        let stream = null
 | 
			
		||||
        if(song.type == "attachment") {
 | 
			
		||||
            stream = await media.getStream(song)
 | 
			
		||||
        }
 | 
			
		||||
        if(song.type == 'youtube') {
 | 
			
		||||
            stream = await youtube.getStream(song)
 | 
			
		||||
        }
 | 
			
		||||
        if(song.type == "soundcloud") {
 | 
			
		||||
            stream = await soundcloud.getStream(song)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return stream
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async add(song) {
 | 
			
		||||
@@ -156,6 +233,7 @@ class Player {
 | 
			
		||||
            plog.log(`GUILD : ${this.guildId} - La musique a été mise en pause`)
 | 
			
		||||
            return true
 | 
			
		||||
        }
 | 
			
		||||
        process.emit("PLAYERS_UPDATE")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async leave() {
 | 
			
		||||
@@ -170,24 +248,80 @@ class Player {
 | 
			
		||||
        this.player = null
 | 
			
		||||
        this.connection = null
 | 
			
		||||
        this.channelId = null
 | 
			
		||||
        this.connected = false
 | 
			
		||||
        Activity.idleActivity()
 | 
			
		||||
        this.queue.destroy()
 | 
			
		||||
        AllPlayers.delete(this.guildId)
 | 
			
		||||
        clog.log("Connection détruite avec le guildId : " + this.guildId)
 | 
			
		||||
        plog.log("Player détruit avec le guildId : " + this.guildId)
 | 
			
		||||
        process.emit("PLAYERS_UPDATE")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async setDuration(duration) {
 | 
			
		||||
 | 
			
		||||
        if (this.checkConnection()) return;
 | 
			
		||||
        if (this.queue.current == null) return;
 | 
			
		||||
        if (this.currentResource == null) return;
 | 
			
		||||
    
 | 
			
		||||
        const maxDuration = this.queue.current.duration;
 | 
			
		||||
        if (duration > maxDuration) {
 | 
			
		||||
            plog.error(`GUILD : ${this.guildId} - La durée demandée dépasse la durée maximale de la musique.`);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.stream = await this.getStream(this.queue.current);
 | 
			
		||||
        if (this.stream === null) {
 | 
			
		||||
            plog.error(`GUILD : ${this.guildId} - Impossible de lire la musique : ${this.queue.current.title} avec le type : ${this.queue.current.type}`);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Si stream est un lien, ouvrir le stream à partir du lien
 | 
			
		||||
 | 
			
		||||
        if(typeof this.stream === "string") {  
 | 
			
		||||
            this.stream = fs.createReadStream(this.stream)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const passThroughStream = new PassThrough();
 | 
			
		||||
        ffmpeg(this.stream)
 | 
			
		||||
            .setStartTime(duration) // Démarrer à la position demandée (en secondes)
 | 
			
		||||
            .outputOptions('-f', 'mp3') // Specify output format if needed
 | 
			
		||||
            .on('error', (err) => {
 | 
			
		||||
                plog.error(`GUILD : ${this.guildId} - Une erreur est survenue avec ffmpeg : ${err.message}`);
 | 
			
		||||
            })
 | 
			
		||||
            .pipe(passThroughStream, { end: true });
 | 
			
		||||
 | 
			
		||||
        this.stream = passThroughStream;
 | 
			
		||||
 | 
			
		||||
        this.playStream(this.stream); // Jouer le nouveau flux
 | 
			
		||||
 | 
			
		||||
        this.currentResource.playbackDuration = duration * 1000; // Mettre à jour la durée de lecture du resource
 | 
			
		||||
    
 | 
			
		||||
        plog.log(`GUILD : ${this.guildId} - Lecture déplacée à ${duration}s.`);
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setDuration(duration) {
 | 
			
		||||
    playStream(stream) {
 | 
			
		||||
        if(this.checkConnection()) return
 | 
			
		||||
        if(this.player !== null) this.player.stop();
 | 
			
		||||
 | 
			
		||||
        this.player = createAudioPlayer()
 | 
			
		||||
        this.generatePlayerEvents()
 | 
			
		||||
        
 | 
			
		||||
        const resource = createAudioResource(stream, { inputType: StreamType.Arbitrary });
 | 
			
		||||
 | 
			
		||||
        this.setCurrentResource(resource)
 | 
			
		||||
        this.player.play(resource);
 | 
			
		||||
        this.connection.subscribe(this.player);
 | 
			
		||||
        process.emit("PLAYERS_UPDATE")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getDuration() {
 | 
			
		||||
       // Return the duration of player
 | 
			
		||||
 | 
			
		||||
        if(this.checkConnection()) return
 | 
			
		||||
        if(this.queue.current == null) return
 | 
			
		||||
        if(this.currentResource == null) return
 | 
			
		||||
        var maxduration = this.queue.current.duration 
 | 
			
		||||
        if(duration > maxduration) return
 | 
			
		||||
        this.player.stop(); // Arrête la lecture actuelle
 | 
			
		||||
        this.player.play(this.currentResource, {
 | 
			
		||||
            startTime: duration * 1000 // Convertit le timecode en millisecondes
 | 
			
		||||
        });
 | 
			
		||||
        return this.currentResource.playbackDuration / 1000
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -195,6 +329,22 @@ class Player {
 | 
			
		||||
        this.currentResource = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    changeChannel(channel) {
 | 
			
		||||
        if(this.checkConnection()) return
 | 
			
		||||
        if(this.connection === null) return
 | 
			
		||||
        if(this.connection.channelId === channel.id) return
 | 
			
		||||
 | 
			
		||||
        this.connection.destroy()
 | 
			
		||||
        this.joinChannel(channel)
 | 
			
		||||
 | 
			
		||||
        // Si la musique est en cours de lecture, on la relance avec le bon timecode
 | 
			
		||||
 | 
			
		||||
        if(this.player) {
 | 
			
		||||
            this.connection.subscribe(this.player);
 | 
			
		||||
        }
 | 
			
		||||
        process.emit("PLAYERS_UPDATE")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async skip() {
 | 
			
		||||
      
 | 
			
		||||
        if(this.checkConnection()) return "no_music"
 | 
			
		||||
@@ -203,6 +353,7 @@ class Player {
 | 
			
		||||
        }
 | 
			
		||||
        const songSkip = this.queue.nextSong()
 | 
			
		||||
        this.play(songSkip)
 | 
			
		||||
        process.emit("PLAYERS_UPDATE")
 | 
			
		||||
        return songSkip
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -215,11 +366,37 @@ class Player {
 | 
			
		||||
       
 | 
			
		||||
        const songPrevious = this.queue.previousSong()
 | 
			
		||||
        this.play(songPrevious)
 | 
			
		||||
        process.emit("PLAYERS_UPDATE")
 | 
			
		||||
        return songPrevious
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {Player, AllPlayers}
 | 
			
		||||
/**
 | 
			
		||||
 * 
 | 
			
		||||
 * @param {string} guildId 
 | 
			
		||||
 * @returns {Player} player
 | 
			
		||||
 */
 | 
			
		||||
function getPlayer(guildId) {
 | 
			
		||||
    if(AllPlayers.has(guildId)) {
 | 
			
		||||
        return AllPlayers.get(guildId)
 | 
			
		||||
    } else {
 | 
			
		||||
        return new Player(guildId)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getAllPlayers() {
 | 
			
		||||
    const players = new Array()
 | 
			
		||||
    AllPlayers.forEach((player) => {
 | 
			
		||||
        players.push(player)
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function isPlayer(guildId) {
 | 
			
		||||
    return AllPlayers.has(guildId)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
module.exports = {Player, AllPlayers, getPlayer, isPlayer, getAllPlayers}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 
 | 
			
		||||
@@ -1,16 +0,0 @@
 | 
			
		||||
class Playlist {
 | 
			
		||||
    title = "Aucun titre";
 | 
			
		||||
    id;
 | 
			
		||||
    url;
 | 
			
		||||
    author = "Auteur inconnu";
 | 
			
		||||
    authorId;
 | 
			
		||||
    songs = [];
 | 
			
		||||
    thumbnail = "https://radomisol.fr/wp-content/uploads/2016/08/cropped-note-radomisol-musique.png" ;
 | 
			
		||||
    duration = 0;
 | 
			
		||||
    readduration;
 | 
			
		||||
    description;
 | 
			
		||||
    type;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {Playlist};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										30
									
								
								backend/src/playlists/Playlist.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								backend/src/playlists/Playlist.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
class Playlist {
 | 
			
		||||
    title = "Aucun titre";
 | 
			
		||||
    id;
 | 
			
		||||
    url;
 | 
			
		||||
    author = "Auteur inconnu";
 | 
			
		||||
    authorId;
 | 
			
		||||
    songs = new Array();
 | 
			
		||||
    thumbnail = "https://radomisol.fr/wp-content/uploads/2016/08/cropped-note-radomisol-musique.png" ;
 | 
			
		||||
    duration = 0;
 | 
			
		||||
    readduration;
 | 
			
		||||
    description;
 | 
			
		||||
    type;
 | 
			
		||||
    constructor(title, url, author, authorId, songs, thumbnail, duration, readduration, description) {
 | 
			
		||||
        this.title = title;
 | 
			
		||||
        this.url = url;
 | 
			
		||||
        this.author = author;
 | 
			
		||||
        this.authorId = authorId;
 | 
			
		||||
        this.songs = songs;
 | 
			
		||||
        this.thumbnail = thumbnail;
 | 
			
		||||
        this.duration = duration;
 | 
			
		||||
        this.readduration = readduration;
 | 
			
		||||
        this.description = description;
 | 
			
		||||
        if(!this.url) {
 | 
			
		||||
            this.type = "playlist";
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {Playlist};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										157
									
								
								backend/src/playlists/PlaylistManager.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								backend/src/playlists/PlaylistManager.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,157 @@
 | 
			
		||||
const {Database} = require('../utils/Database/Database');
 | 
			
		||||
const {__glob} = require('../utils/GlobalVars');
 | 
			
		||||
 | 
			
		||||
const {Playlist} = require('./Playlist');
 | 
			
		||||
const {LogType} = require('loguix');
 | 
			
		||||
const clog = new LogType("PlaylistManager");
 | 
			
		||||
const Finder = require('../player/Finder');
 | 
			
		||||
const spotify = require('../media/SpotifyInformation');
 | 
			
		||||
 | 
			
		||||
const playlistDB = new Database("Playlists", __glob.PLAYLISTFILE, {});
 | 
			
		||||
 | 
			
		||||
/** 
 | 
			
		||||
* @param {string} id 
 | 
			
		||||
* @param {string} name 
 | 
			
		||||
* @returns {Array<Playlist>}
 | 
			
		||||
* @description Renvoie la liste des playlists de l'utilisateur
 | 
			
		||||
*/
 | 
			
		||||
function getPlaylistsOfUser(id) {
 | 
			
		||||
    if (playlistDB.data[id]) {
 | 
			
		||||
        return playlistDB.data[id];
 | 
			
		||||
    } else {
 | 
			
		||||
        // Creaete a key with the user id and an empty array
 | 
			
		||||
        playlistDB.data;[id] = new Array();
 | 
			
		||||
        clog.log(`Création d'une clé pour l'utilisateur : ${id}`);
 | 
			
		||||
        playlistDB.save();
 | 
			
		||||
        return playlistDB.data[id];
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {string} id 
 | 
			
		||||
 * @param {string} name 
 | 
			
		||||
 * @returns {Playlist}
 | 
			
		||||
 */
 | 
			
		||||
function getPlaylistOfUser(id, name) {
 | 
			
		||||
    const playlists = getPlaylistsOfUser(id);
 | 
			
		||||
    const playlist = playlists.find(p => p.name === name);
 | 
			
		||||
    if (!playlist) {
 | 
			
		||||
        clog.warn(`La playlist ${name} n'existe pas pour l'utilisateur ${id}`);
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
    return playlist;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function addPlaylist(id, name, url) {
 | 
			
		||||
    const playlists = getPlaylistsOfUser(id);
 | 
			
		||||
    var playlist = new Playlist(name, url);
 | 
			
		||||
    if (playlists.find(p => p.name === name)) {
 | 
			
		||||
        clog.warn(`La playlist ${name} existe déjà pour l'utilisateur ${id}`);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    if(url) {
 | 
			
		||||
        await Finder.search(url).then(async (playlistFounded) => {
 | 
			
		||||
            if(playlistFounded instanceof Playlist) {
 | 
			
		||||
                playlist = playlistFounded;
 | 
			
		||||
            }   
 | 
			
		||||
            if(playlist.type === "spotify") {
 | 
			
		||||
                playlist.songs = await spotify.getTracks(playlist);
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    playlists.push(playlist);
 | 
			
		||||
    playlistDB.save();
 | 
			
		||||
    clog.log(`Ajout de la playlist ${name} pour l'utilisateur ${id}`);
 | 
			
		||||
    return playlist;
 | 
			
		||||
} 
 | 
			
		||||
 | 
			
		||||
function removePlaylist(id, name) {
 | 
			
		||||
    const playlists = getPlaylistsOfUser(id);
 | 
			
		||||
    const index = playlists.findIndex(p => p.name === name);
 | 
			
		||||
    if (index === -1) {
 | 
			
		||||
        clog.warn(`La playlist ${name} n'existe pas pour l'utilisateur ${id}`);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    playlists.splice(index, 1);
 | 
			
		||||
    playlistDB.save();
 | 
			
		||||
    clog.log(`Suppression de la playlist ${name} pour l'utilisateur ${id}`);
 | 
			
		||||
}
 | 
			
		||||
function getPlaylist(id, name) {
 | 
			
		||||
    const playlists = getPlaylistsOfUser(id);
 | 
			
		||||
    const playlist = playlists.find(p => p.name === name);
 | 
			
		||||
    if (!playlist) {
 | 
			
		||||
        clog.warn(`La playlist ${name} n'existe pas pour l'utilisateur ${id}`);
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
    return playlist;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function copyPlaylist(fromId, toId, name) {
 | 
			
		||||
    const playlists = getPlaylistsOfUser(fromId);
 | 
			
		||||
    const playlist = playlists.find(p => p.name === name);
 | 
			
		||||
    if (!playlist) {
 | 
			
		||||
        clog.warn(`La playlist ${name} n'existe pas pour l'utilisateur ${fromId}`);
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
    const toPlaylists = getPlaylistsOfUser(toId);
 | 
			
		||||
    toPlaylists.push(playlist);
 | 
			
		||||
    playlistDB.save();
 | 
			
		||||
    clog.log(`Copie de la playlist ${name} de l'utilisateur ${fromId} vers l'utilisateur ${toId}`);
 | 
			
		||||
 | 
			
		||||
    return newPlaylist;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function renamePlaylist(id, oldName, newName) {
 | 
			
		||||
    const playlists = getPlaylistsOfUser(id);
 | 
			
		||||
    const playlist = playlists.find(p => p.name === oldName);
 | 
			
		||||
    if (!playlist) {
 | 
			
		||||
        clog.warn(`La playlist ${oldName} n'existe pas pour l'utilisateur ${id}`);
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
    playlist.name = newName;
 | 
			
		||||
    playlistDB.save();
 | 
			
		||||
    clog.log(`Renommage de la playlist ${oldName} en ${newName} pour l'utilisateur ${id}`);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function addSong(id, playlistName, song) {
 | 
			
		||||
    const playlists = getPlaylistsOfUser(id);
 | 
			
		||||
    const playlist = playlists.find(p => p.name === playlistName);
 | 
			
		||||
    if (!playlist) {
 | 
			
		||||
        clog.warn(`La playlist ${playlistName} n'existe pas pour l'utilisateur ${id}`);
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
    playlist.songs.push(song);
 | 
			
		||||
    playlistDB.save();
 | 
			
		||||
    clog.log(`Ajout de la chanson ${song.title} à la playlist ${playlistName} pour l'utilisateur ${id}`);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function removeSong(id, playlistName, songId) {
 | 
			
		||||
    const playlists = getPlaylistsOfUser(id);
 | 
			
		||||
    const playlist = playlists.find(p => p.name === playlistName);
 | 
			
		||||
    if (!playlist) {
 | 
			
		||||
        clog.warn(`La playlist ${playlistName} n'existe pas pour l'utilisateur ${id}`);
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
    const index = playlist.songs.findIndex(s => s.id === songId);
 | 
			
		||||
    if (index === -1) {
 | 
			
		||||
        clog.warn(`La chanson ${songId} n'existe pas dans la playlist ${playlistName} pour l'utilisateur ${id}`);
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
    playlist.songs.splice(index, 1);
 | 
			
		||||
    playlistDB.save();
 | 
			
		||||
    clog.log(`Suppression de la chanson ${songId} de la playlist ${playlistName} pour l'utilisateur ${id}`);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    getPlaylistsOfUser,
 | 
			
		||||
    getPlaylistOfUser,
 | 
			
		||||
    addPlaylist,
 | 
			
		||||
    removePlaylist,
 | 
			
		||||
    getPlaylist,
 | 
			
		||||
    copyPlaylist,
 | 
			
		||||
    renamePlaylist,
 | 
			
		||||
    addSong,
 | 
			
		||||
    removeSong
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										234
									
								
								backend/src/server/Documentation.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										234
									
								
								backend/src/server/Documentation.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,234 @@
 | 
			
		||||
# Documentation des Requêtes `socket.io`
 | 
			
		||||
 | 
			
		||||
Les requêtes sont du point de vue du serveur.
 | 
			
		||||
 | 
			
		||||
Le Client doit être initialisé comme ceci :
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
const socket = io("subsonics.raphix.fr:5000", {
 | 
			
		||||
  auth: {
 | 
			
		||||
    token: "TOKEN_HERE",
 | 
			
		||||
    auth_code: "AUTH_FROM_DISCORD_HERE",
 | 
			
		||||
    session: "SESSION_ID_HERE"
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
REDIRECT_CALLBACK = `/callback`
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
## Requêtes Envoyées
 | 
			
		||||
 | 
			
		||||
### Événement : `NEW_SESSION`
 | 
			
		||||
 | 
			
		||||
- **Description** : `/login` et `/` : Envoie un jeton de session utile pour la traçabilité de la connexion par Discord. Dès réception, le client le stocke dans les cookies sous le nom `session`. Si l'utilisateur n'est pas sur `/login`, il doit y être redirigé. Supprime également le cookie `token` s’il existe.
 | 
			
		||||
- **Données envoyées** :
 | 
			
		||||
```json
 | 
			
		||||
"SESSION_ID"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Événement : `NEW_TOKEN`
 | 
			
		||||
 | 
			
		||||
- **Description** : `/callback` : Lors de la redirection depuis Discord, ce jeton est généré après vérification du code d'autorisation. Il est envoyé au client qui est ensuite redirigé vers `/`.
 | 
			
		||||
- **Données envoyées** :
 | 
			
		||||
```json
 | 
			
		||||
"TOKEN_ID"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Événement : `BANNED`
 | 
			
		||||
 | 
			
		||||
- **Description** : `/callback` et `/` : Si reçu, le client est redirigé vers la page de connexion avec l'erreur "BANNI".
 | 
			
		||||
 | 
			
		||||
### Événement : `AUTH_ERROR`
 | 
			
		||||
 | 
			
		||||
- **Description** : `/callback` et `/` : Erreur lors de l’authentification (ex. code Discord invalide ou accès refusé).
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
## Requêtes Reçues
 | 
			
		||||
 | 
			
		||||
> Toutes les requêtes commencent par l’événement `socket.on("EVENT_NAME", callback)` côté client, et sont traitées côté serveur par `IORequest("EVENT_NAME", callback)`.
 | 
			
		||||
 | 
			
		||||
### Utilisateur
 | 
			
		||||
 | 
			
		||||
#### `/USER/INFO`
 | 
			
		||||
 | 
			
		||||
- **Description** : Renvoie l’identité Discord, les guildes et les labels de l'utilisateur connecté.
 | 
			
		||||
- **Données envoyées** :
 | 
			
		||||
```json
 | 
			
		||||
{}
 | 
			
		||||
```
 | 
			
		||||
- **Réponse** :
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "identity": { ... },
 | 
			
		||||
  "guilds": [ ... ],
 | 
			
		||||
  "labels": [ "admin", ... ]
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### `/USERS/LIST`
 | 
			
		||||
 | 
			
		||||
- **Description** : Renvoie la liste des utilisateurs connectés à une guilde.
 | 
			
		||||
- **Données envoyées** :
 | 
			
		||||
```json
 | 
			
		||||
"GUILD_ID"
 | 
			
		||||
```
 | 
			
		||||
- **Réponse** :
 | 
			
		||||
```json
 | 
			
		||||
[ { "id": "...", "username": "...", ... }, ... ]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Player (Musique)
 | 
			
		||||
 | 
			
		||||
#### `/PLAYER/STATE`
 | 
			
		||||
 | 
			
		||||
- **Description** : Récupère l'état actuel du player pour une guilde.
 | 
			
		||||
- **Données envoyées** :
 | 
			
		||||
```json
 | 
			
		||||
"GUILD_ID"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### `/PLAYER/JOIN` / `/PLAYER/LEAVE`
 | 
			
		||||
 | 
			
		||||
- **Description** : Rejoint ou quitte l’écoute du player pour une guilde.
 | 
			
		||||
- **Données envoyées** :
 | 
			
		||||
```json
 | 
			
		||||
"GUILD_ID"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### `/PLAYER/PAUSE`, `/PLAYER/BACKWARD`, `/PLAYER/FORWARD`, `/PLAYER/LOOP`, `/PLAYER/SHUFFLE`, `/PLAYER/DISCONNECT`
 | 
			
		||||
 | 
			
		||||
- **Description** : Contrôle du player (pause, chanson précédente/suivante, boucle, aléatoire, déconnexion).
 | 
			
		||||
- **Données envoyées** :
 | 
			
		||||
```json
 | 
			
		||||
"GUILD_ID"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### `/PLAYER/CHANNEL/CHANGE`
 | 
			
		||||
 | 
			
		||||
- **Description** : Change le salon vocal du player vers celui de l’utilisateur.
 | 
			
		||||
- **Données envoyées** :
 | 
			
		||||
```json
 | 
			
		||||
"GUILD_ID"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### `/PLAYER/SEEK`
 | 
			
		||||
 | 
			
		||||
- **Description** : Change la position de la lecture.
 | 
			
		||||
- **Données envoyées** :
 | 
			
		||||
```json
 | 
			
		||||
["GUILD_ID", TEMPS_EN_SECONDES]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Queue
 | 
			
		||||
 | 
			
		||||
#### `/QUEUE/PLAY/NOW`
 | 
			
		||||
 | 
			
		||||
- **Description** : Joue une chanson de la queue immédiatement.
 | 
			
		||||
- **Données envoyées** :
 | 
			
		||||
```json
 | 
			
		||||
["GUILD_ID", "previous"|"next", INDEX]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### `/QUEUE/NEXT/DELETE`, `/QUEUE/NEXT/DELETEALL`, `/QUEUE/NEXT/MOVE`
 | 
			
		||||
 | 
			
		||||
- **Description** : Supprime ou déplace une chanson dans la file d’attente.
 | 
			
		||||
- **Données envoyées** :
 | 
			
		||||
```json
 | 
			
		||||
["GUILD_ID", INDEX (ou NEW_INDEX)]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Recherche
 | 
			
		||||
 | 
			
		||||
#### `/SEARCH`
 | 
			
		||||
 | 
			
		||||
- **Description** : Effectue une recherche de musique.
 | 
			
		||||
- **Données envoyées** :
 | 
			
		||||
```json
 | 
			
		||||
"QUERY"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### `/SEARCH/PLAY`
 | 
			
		||||
 | 
			
		||||
- **Description** : Joue un morceau directement ou l'ajoute à la queue.
 | 
			
		||||
- **Données envoyées** :
 | 
			
		||||
```json
 | 
			
		||||
["GUILD_ID", SONG, now (bool)]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Playlists
 | 
			
		||||
 | 
			
		||||
#### `/PLAYLISTS/CREATE`, `/DELETE`, `/RENAME`, `/ADD_SONG`, `/REMOVE_SONG`, `/SEND`, `/PLAY`
 | 
			
		||||
 | 
			
		||||
- **Description** : Gère les playlists (création, suppression, renommage, ajout, lecture, envoi à un autre utilisateur).
 | 
			
		||||
- **Données envoyées** : Variable selon l'action, ex :
 | 
			
		||||
```json
 | 
			
		||||
["PLAYLIST_NAME", SONG]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### `/PLAYLISTS/LIST`
 | 
			
		||||
 | 
			
		||||
- **Description** : Renvoie la liste des playlists de l'utilisateur.
 | 
			
		||||
- **Données envoyées** :
 | 
			
		||||
```json
 | 
			
		||||
{}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Admin
 | 
			
		||||
 | 
			
		||||
> Nécessite le label `"admin"` dans `socketUser.labels`.
 | 
			
		||||
 | 
			
		||||
#### `/ADMIN/LOGS`
 | 
			
		||||
 | 
			
		||||
- **Description** : Renvoie les logs du serveur.
 | 
			
		||||
 | 
			
		||||
#### `/ADMIN/MAINTENANCE/RESTART`
 | 
			
		||||
 | 
			
		||||
- **Description** : Redémarre le serveur avec une raison.
 | 
			
		||||
- **Données envoyées** :
 | 
			
		||||
```json
 | 
			
		||||
"RAISON"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### `/ADMIN/USERS/SWITCH_ADMIN`, `/FULL_BAN`, `/DELETE`
 | 
			
		||||
 | 
			
		||||
- **Description** : Gère les utilisateurs (promotion admin, ban complet, suppression).
 | 
			
		||||
- **Données envoyées** :
 | 
			
		||||
```json
 | 
			
		||||
"USER_ID"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### `/ADMIN/PLAYER/GETALLSTATE`
 | 
			
		||||
 | 
			
		||||
- **Description** : Renvoie l’état de tous les players.
 | 
			
		||||
 | 
			
		||||
### Owner / Modérateur
 | 
			
		||||
 | 
			
		||||
#### `/OWNER/USERS/SWITCH_MOD`
 | 
			
		||||
 | 
			
		||||
- **Description** : Nomme ou enlève un modérateur.
 | 
			
		||||
- **Données envoyées** :
 | 
			
		||||
```json
 | 
			
		||||
["USER_ID", "GUILD_ID"]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### `/MOD/USERS/BAN`
 | 
			
		||||
 | 
			
		||||
- **Description** : Bannit un utilisateur d’une guilde.
 | 
			
		||||
- **Données envoyées** :
 | 
			
		||||
```json
 | 
			
		||||
["USER_ID", "GUILD_ID"]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Utilitaires
 | 
			
		||||
 | 
			
		||||
#### `/REPORT`
 | 
			
		||||
 | 
			
		||||
- **Description** : Envoie un rapport avec un niveau et une description.
 | 
			
		||||
- **Données envoyées** :
 | 
			
		||||
```json
 | 
			
		||||
["LEVEL", "DESCRIPTION"]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										564
									
								
								backend/src/server/Server.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										564
									
								
								backend/src/server/Server.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,564 @@
 | 
			
		||||
const {LogType} = require('loguix') 
 | 
			
		||||
const wlog = new LogType("Server")
 | 
			
		||||
 | 
			
		||||
const {Server} = require('socket.io')
 | 
			
		||||
const {createServer} = require('http')
 | 
			
		||||
const session = require("../server/auth/Session")
 | 
			
		||||
const users = require("../server/auth/User")
 | 
			
		||||
const players = require("../player/Player")
 | 
			
		||||
const {Player} = require("../player/Player")
 | 
			
		||||
const discordBot = require("../discord/Bot")
 | 
			
		||||
const discordAuth = require("../server/auth/DiscordAuth")
 | 
			
		||||
const {Report} = require("../discord/ReportSender")
 | 
			
		||||
const Finder = require("../player/Finder")
 | 
			
		||||
const fs = require("fs")
 | 
			
		||||
const {__glob} = require("../utils/GlobalVars")
 | 
			
		||||
const playlists = require("../playlists/PlaylistManager")
 | 
			
		||||
 | 
			
		||||
const configuration = require("../utils/Database/Configuration")
 | 
			
		||||
const { List } = require('../player/List')
 | 
			
		||||
const { restart } = require('../utils/Maintenance')
 | 
			
		||||
 | 
			
		||||
const allConnectedUsers = new Array()
 | 
			
		||||
const guildConnectedUsers = new Map()
 | 
			
		||||
 | 
			
		||||
function init() {
 | 
			
		||||
    
 | 
			
		||||
    wlog.step.init("server_init", "Initialisation du serveur Socket.IO")
 | 
			
		||||
 | 
			
		||||
    const httpServer = createServer()   
 | 
			
		||||
    const io = new Server(httpServer, {
 | 
			
		||||
        cors: {
 | 
			
		||||
            origin: "*"
 | 
			
		||||
        },
 | 
			
		||||
    })
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    process.on("PLAYERS_UPDATE", () => {
 | 
			
		||||
        if(io) {
 | 
			
		||||
            // Get all players and send them to client subscribed to the guild
 | 
			
		||||
            for(var guild of discordBot.getGuilds()) {
 | 
			
		||||
                const player = players.getPlayer(guild.id)
 | 
			
		||||
                if(player) {
 | 
			
		||||
                    io.to(guild.id).emit("PLAYER_UPDATE", player.getState())
 | 
			
		||||
                    wlog.log("Envoi de l'état du player de la guilde : " + guild.id + " à tous les utilisateurs connectés")
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    process.on("USERS_UPDATE", () => {
 | 
			
		||||
        if(io) {
 | 
			
		||||
            // Get all players and send them to client subscribed to the guild
 | 
			
		||||
            for(var guild of discordBot.getGuilds()) {
 | 
			
		||||
               if(guildConnectedUsers.has(guild.id)) {
 | 
			
		||||
                    io.sockets.emit("USERS_UPDATE", guildConnectedUsers.get(guild.id))
 | 
			
		||||
                    io.to("admin").emit("ALL_USERS_UPDATE", allConnectedUsers)  
 | 
			
		||||
                    wlog.log("Envoi de la liste des utilisateurs connectés à la guilde : " + guild.id + " à tous les utilisateurs connectés")
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    io.on("connection", async (socket) => {
 | 
			
		||||
 | 
			
		||||
        wlog.log(`Connexion d'un client : ${socket.id}`)
 | 
			
		||||
       
 | 
			
		||||
        if(socket.handshake.auth == undefined || socket.handshake.auth == {}) {
 | 
			
		||||
            wlog.warn("Authentification manquant pour le client :" + socket.id)
 | 
			
		||||
            sendSession()
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var token = socket.handshake.auth.token
 | 
			
		||||
        var sessionId = socket.handshake.auth.sessionId
 | 
			
		||||
        var auth_code = socket.handshake.auth.auth_code
 | 
			
		||||
 | 
			
		||||
        if(sessionId) {
 | 
			
		||||
            if(!session.checkSession(sessionId)) {
 | 
			
		||||
                wlog.warn("Session invalide pour le client :" + socket.id)
 | 
			
		||||
                sendSession()
 | 
			
		||||
                return;
 | 
			
		||||
            } else {
 | 
			
		||||
                if(auth_code) {
 | 
			
		||||
                    const discordUser = await discordAuth.getDiscordUser(sessionId, auth_code)
 | 
			
		||||
                    session.removeSession(sessionId)
 | 
			
		||||
                    if(discordUser == "GUILDS_ERROR" || discordUser == "USER_INFO_ERROR" || discordUser == "ACCESS_TOKEN_ERROR") {
 | 
			
		||||
                     
 | 
			
		||||
                        wlog.warn("Erreur lors de la récupération des informations de l'utilisateur Discord associé à la session : " + sessionId)
 | 
			
		||||
                        socket.emit("AUTH_ERROR")
 | 
			
		||||
                        socket.disconnect()
 | 
			
		||||
                        return
 | 
			
		||||
                    } else {
 | 
			
		||||
                        const loggedUser = users.addUser(discordUser.auth, discordUser.identity, discordUser.guilds)
 | 
			
		||||
                        for(var guild of discordUser.guilds) {
 | 
			
		||||
                            if(guild.owner) {
 | 
			
		||||
                                users.setGuildOwner(loggedUser.identity.id, guild.id)
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        const newToken = loggedUser.createToken()
 | 
			
		||||
                        socket.emit("NEW_TOKEN", newToken)
 | 
			
		||||
                        socket.disconnect()
 | 
			
		||||
                        wlog.log("Utilisateur Discord associé à la session : " + sessionId + " récupéré avec succès")
 | 
			
		||||
                        
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                   
 | 
			
		||||
                } else {
 | 
			
		||||
                    wlog.warn("Code d'authentification manquant pour le client :" + socket.id)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } 
 | 
			
		||||
 | 
			
		||||
        if(!token) {
 | 
			
		||||
            wlog.warn("Token manquant pour le client :" + socket.id)
 | 
			
		||||
            sendSession()
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const socketUser = users.getUserByToken(token)
 | 
			
		||||
 | 
			
		||||
        if(!socketUser) {
 | 
			
		||||
            wlog.warn("Token invalide pour le client :" + socket.id)
 | 
			
		||||
            sendSession()
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
            
 | 
			
		||||
        if(socketUser) {
 | 
			
		||||
            if(allConnectedUsers.includes(socketUser.identity.id)) {
 | 
			
		||||
                wlog.warn("L'utilisateur '" + socketUser.identity.username + "' est déjà connecté sur un autre appareil")
 | 
			
		||||
                return
 | 
			
		||||
            } else {
 | 
			
		||||
                allConnectedUsers.push(socketUser.identity)
 | 
			
		||||
                addGuildConnectedUser(socketUser.identity, socketUser.guilds)
 | 
			
		||||
                process.emit("USERS_UPDATE")
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            wlog.log("Utilisateur connecté : " + socketUser.identity.username + " (" + socketUser.identity.id + ") - Socket : " + socket.id)
 | 
			
		||||
 | 
			
		||||
            if(socketUser.isFullBanned()) {
 | 
			
		||||
                wlog.warn("Utilisateur banni : " + socketUser.identity.username + " (" + socketUser.identity.id + ") - Socket : " + socket.id)
 | 
			
		||||
                socket.emit("BANNED")
 | 
			
		||||
                socket.disconnect()
 | 
			
		||||
            }
 | 
			
		||||
            if(socketUser.labels.includes("admin")) {
 | 
			
		||||
                socket.join("admin")
 | 
			
		||||
                wlog.log("Utilisateur admin identifié : " + socketUser.identity.username + " (" + socketUser.identity.id + ")")
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // USERS
 | 
			
		||||
 | 
			
		||||
            IORequest("/USER/INFO", () => {
 | 
			
		||||
                IOAnswer("/USER/INFO", {
 | 
			
		||||
                    identity: socketUser.identity,
 | 
			
		||||
                    guilds: socketUser.guilds, 
 | 
			
		||||
                    labels: socketUser.labels
 | 
			
		||||
                })
 | 
			
		||||
                wlog.log("Envoi des informations Discord de '" + socketUser.identity.id + "' à '" + socket.id + "'" )
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            IORequest("/USERS/LIST", (guildId) => {
 | 
			
		||||
                if(!checkUserGuild(socketUser, guildId)) return
 | 
			
		||||
                if(!guildConnectedUsers.has(guildId)) return IOAnswer("/USERS/LIST", false)
 | 
			
		||||
                IOAnswer("/USERS/LIST", guildConnectedUsers.get(guildId))
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            // PLAYERS
 | 
			
		||||
 | 
			
		||||
            IORequest("/PLAYER/PREVIOUS/LIST", (guildId) => {   
 | 
			
		||||
                if(!checkUserGuild(socketUser, guildId)) return
 | 
			
		||||
                const list = new List(guildId)
 | 
			
		||||
                return list.getPrevious()
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            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)
 | 
			
		||||
                socket.join(guildId)
 | 
			
		||||
                IOAnswer("PLAYER_STATE", true)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            IORequest("/PLAYER/LEAVE", (guildId) => {
 | 
			
		||||
                if(!checkUserGuild(socketUser, guildId)) return
 | 
			
		||||
                wlog.log("L'utilisateur '" + socketUser.identity.username + "' quitte la liste d'écoute du player de la guilde : " + guildId)
 | 
			
		||||
                socket.leave(guildId)
 | 
			
		||||
                IOAnswer("PLAYER_STATE", false)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            IORequest("/PLAYER/STATE", (guildId) => {
 | 
			
		||||
                handlePlayerAction(guildId, (player) => player.getState(), "/PLAYER/STATE");
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            IORequest("/PLAYER/PAUSE", (guildId) => {
 | 
			
		||||
                handlePlayerAction(guildId, (player) => player.pause(), "/PLAYER/PAUSE");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            IORequest("/PLAYER/BACKWARD", (guildId) => {
 | 
			
		||||
                handlePlayerAction(guildId, (player) => player.previous(), "/PLAYER/BACKWARD");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            IORequest("/PLAYER/FORWARD", (guildId) => {
 | 
			
		||||
                handlePlayerAction(guildId, (player) => player.skip(), "/PLAYER/FORWARD");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            IORequest("/PLAYER/LOOP", (guildId) => {
 | 
			
		||||
                handlePlayerAction(guildId, (player) => player.setLoop(), "/PLAYER/LOOP");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            IORequest("/PLAYER/SHUFFLE", (guildId) => {
 | 
			
		||||
                handlePlayerAction(guildId, (player) => player.setShuffle(), "/PLAYER/SHUFFLE");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            IORequest("/PLAYER/DISCONNECT", (guildId) => {
 | 
			
		||||
                handlePlayerAction(guildId, (player) => player.leave(), "/PLAYER/DISCONNECT");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            IORequest("/PLAYER/CHANNEL/CHANGE", (guildId) => {
 | 
			
		||||
                handlePlayerAction(guildId, (player) => {
 | 
			
		||||
                    const channel = getUserChannel()
 | 
			
		||||
                    if(!channel) {
 | 
			
		||||
                        IOAnswer("/PLAYER/CHANNEL/CHANGE", false)
 | 
			
		||||
                        return
 | 
			
		||||
                    }
 | 
			
		||||
                    player.changeChannel(channel)
 | 
			
		||||
                }, "/PLAYER/CHANNEL/CHANGE");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            IORequest("/PLAYER/SEEK", (guildId, time) => {
 | 
			
		||||
                handlePlayerAction(guildId, (player) => {
 | 
			
		||||
                    // Check if current is not null
 | 
			
		||||
                    if(player.queue.current == null) {
 | 
			
		||||
                        wlog.warn("Le player de la guilde : " + guildId + " n'a pas de musique en cours")
 | 
			
		||||
                        IOAnswer("/PLAYER/SEEK", false)
 | 
			
		||||
                        return
 | 
			
		||||
                    }
 | 
			
		||||
                    player.setDuration(time)
 | 
			
		||||
                }, "/PLAYER/SEEK");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            IORequest("/QUEUE/PLAY/NOW", (guildId, listType, index) => {
 | 
			
		||||
                if(!checkUserGuild(socketUser, guildId)) return
 | 
			
		||||
                const player = new Player(guildId)
 | 
			
		||||
                if(!connectToPlayer(guildId, player)) return IOAnswer("/QUEUE/PLAY", false)
 | 
			
		||||
                if(listType == "previous") {
 | 
			
		||||
                    const previous = player.queue.getPrevious()
 | 
			
		||||
                    song = previous[index]
 | 
			
		||||
                } else if(listType == "next") {
 | 
			
		||||
                    const next = player.queue.getNext()
 | 
			
		||||
                    song = next[index]
 | 
			
		||||
                }
 | 
			
		||||
                if(!song) return IOAnswer("/QUEUE/PLAY", false)
 | 
			
		||||
                player.add(song)
 | 
			
		||||
                IOAnswer("/QUEUE/PLAY/NOW", true)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            IORequest("/QUEUE/NEXT/DELETE", (guildId, index) => {
 | 
			
		||||
                handlePlayerAction(guildId, (player) => {
 | 
			
		||||
                    const next = player.queue.getNext()
 | 
			
		||||
                    if(!next[index]) return IOAnswer("/QUEUE/NEXT/DELETE", false);
 | 
			
		||||
                    player.queue.removeNextByIndex(index)
 | 
			
		||||
                })
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            IORequest("/QUEUE/NEXT/DELETEALL", (guildId) => {
 | 
			
		||||
                handlePlayerAction(guildId, (player) => player.queue.clearNext())
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            IORequest("/QUEUE/NEXT/MOVE", (guildId, index, newIndex) => {
 | 
			
		||||
                handlePlayerAction(guildId, (player) => {
 | 
			
		||||
                    const next = player.queue.getNext()
 | 
			
		||||
                    if(!next[index]) return IOAnswer("/QUEUE/NEXT/MOVE", false);
 | 
			
		||||
                    player.queue.moveNext(index, newIndex)
 | 
			
		||||
                })
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            // SEARCH
 | 
			
		||||
 | 
			
		||||
            IORequest("/SEARCH", async (query) => {
 | 
			
		||||
                IOAnswer("/SEARCH", await Finder.search(query)) 
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            IORequest("/SEARCH/PLAY", async (guildId, song, now) => {
 | 
			
		||||
                if(!checkUserGuild(socketUser, guildId)) return
 | 
			
		||||
                const player = new Player(guildId)
 | 
			
		||||
                if(!connectToPlayer(guildId, player)) return IOAnswer("/SEARCH/PLAY", false)
 | 
			
		||||
                if(now) {
 | 
			
		||||
                    player.play(song)
 | 
			
		||||
                } else {
 | 
			
		||||
                    player.add(song)
 | 
			
		||||
                }
 | 
			
		||||
                IOAnswer("/SEARCH/PLAY", true)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            
 | 
			
		||||
            // PLAYLISTS
 | 
			
		||||
 | 
			
		||||
            IORequest("/PLAYLISTS/CREATE", (name, url) => {
 | 
			
		||||
                if(!name) return IOAnswer("/PLAYLISTS/CREATE", false)
 | 
			
		||||
                const playlist = playlists.addPlaylist(socketUser.identity.id, name, url)
 | 
			
		||||
                if(!playlist) return IOAnswer("/PLAYLISTS/CREATE", false)
 | 
			
		||||
                IOAnswer("/PLAYLISTS/CREATE", true)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            IORequest("/PLAYLISTS/DELETE", (name) => {
 | 
			
		||||
                if(!name) return IOAnswer("/PLAYLISTS/DELETE", false)
 | 
			
		||||
                playlists.removePlaylist(socketUser.identity.id, name)
 | 
			
		||||
                IOAnswer("/PLAYLISTS/DELETE", true)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            IORequest("/PLAYLISTS/LIST", () => {
 | 
			
		||||
                const playlist = playlists.getPlaylistsOfUser(socketUser.identity.id)
 | 
			
		||||
                IOAnswer("/PLAYLISTS/LIST", playlist)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            IORequest("/PLAYLISTS/SEND", (name, toId) => {
 | 
			
		||||
                // Check if toId is in the same guilds as the user
 | 
			
		||||
                // Check if the toId exists and have a playlist with the same name
 | 
			
		||||
                const toUser = users.getUserById(toId)
 | 
			
		||||
                if(!toUser) return IOAnswer("/PLAYLISTS/SEND", false)
 | 
			
		||||
                const toPlaylists = playlists.getPlaylistsOfUser(toUser.identity.id)
 | 
			
		||||
                const fromPlaylist = playlists.getPlaylistOfUser(socketUser.identity.id, name)
 | 
			
		||||
                if(!fromPlaylist) return IOAnswer("/PLAYLISTS/SEND", false)
 | 
			
		||||
                if(toPlaylists.find(p => p.name == name)) return IOAnswer("/PLAYLISTS/SEND", false)
 | 
			
		||||
                playlists.copyPlaylist(socketUser.identity.id, toUser.identity.id, name)
 | 
			
		||||
                IOAnswer("/PLAYLISTS/SEND", true)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            IORequest("/PLAYLISTS/RENAME", (name, newName) => {
 | 
			
		||||
                if(!name || !newName) return IOAnswer("/PLAYLISTS/RENAME", false)
 | 
			
		||||
                const playlist = playlists.getPlaylistOfUser(socketUser.identity.id, name)
 | 
			
		||||
                if(!playlist) return IOAnswer("/PLAYLISTS/RENAME", false)
 | 
			
		||||
                playlists.renamePlaylist(socketUser.identity.id, name, newName)
 | 
			
		||||
                IOAnswer("/PLAYLISTS/RENAME", true) 
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            IORequest("/PLAYLISTS/ADD_SONG", (name, song) => {
 | 
			
		||||
                const playlist = playlists.getPlaylistOfUser(socketUser.identity.id, name)
 | 
			
		||||
                if(!playlist) return IOAnswer("/PLAYLISTS/ADD_SONG", false)
 | 
			
		||||
                playlists.addSong(socketUser.identity.id, name, song)
 | 
			
		||||
                IOAnswer("/PLAYLISTS/ADD_SONG", true)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            IORequest("/PLAYLISTS/REMOVE_SONG", (name, songId) => {
 | 
			
		||||
                const playlist = playlists.getPlaylistOfUser(socketUser.identity.id, name)
 | 
			
		||||
                if(!playlist) return IOAnswer("/PLAYLISTS/REMOVE_SONG", false)
 | 
			
		||||
                playlists.removeSong(socketUser.identity.id, name, songId)
 | 
			
		||||
                IOAnswer("/PLAYLISTS/REMOVE_SONG", true)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            IORequest("/PLAYLISTS/PLAY", (guildId, name, now) => {
 | 
			
		||||
                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)
 | 
			
		||||
                player.readPlaylist(playlist, now)
 | 
			
		||||
                IOAnswer("/PLAYLISTS/PLAY", true)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            // ADMIN
 | 
			
		||||
 | 
			
		||||
            if(socketUser.labels.includes("admin")) {
 | 
			
		||||
                IORequest("/ADMIN/LOGS", () => {
 | 
			
		||||
                    const logs_data = new Array()
 | 
			
		||||
                    const logs_folder = fs.readdirSync(__glob.LOGS)
 | 
			
		||||
                    for(var log of logs_folder) {
 | 
			
		||||
                        logs_data.push({"name":log, "value": fs.readFileSync(__glob.LOGS + path.sep + log).toString()})
 | 
			
		||||
                    }
 | 
			
		||||
                    IOAnswer("/ADMIN/LOGS", logs_data)
 | 
			
		||||
                })
 | 
			
		||||
 | 
			
		||||
                IORequest("/ADMIN/MAINTENANCE/RESTART", (reason) => {
 | 
			
		||||
                   if(!reason) return IOAnswer("/ADMIN/MAINTENANCE/RESTART", false)
 | 
			
		||||
                   restart(reason)
 | 
			
		||||
                })
 | 
			
		||||
 | 
			
		||||
                IORequest("/ADMIN/USERS/SWITCH_ADMIN", (userId) => {
 | 
			
		||||
                    users.setAdmin(userId)
 | 
			
		||||
                    IOAnswer("/ADMIN/USERS/PROMOTE_ADMIN", true)
 | 
			
		||||
                })
 | 
			
		||||
 | 
			
		||||
                IORequest("/ADMIN/USERS/FULL_BAN", (userId) => {
 | 
			
		||||
                    users.setFullBan(userId)
 | 
			
		||||
                    IOAnswer("/ADMIN/USERS/FULL_BAN", true)
 | 
			
		||||
                })
 | 
			
		||||
 | 
			
		||||
                IORequest("/ADMIN/USERS/DELETE", (userId) => {
 | 
			
		||||
                    users.removeUser(userId)
 | 
			
		||||
                    IOAnswer("/ADMIN/USERS/DELETE", true)
 | 
			
		||||
                })
 | 
			
		||||
 | 
			
		||||
                IORequest("/ADMIN/PLAYER/GETALLSTATE", () => {
 | 
			
		||||
                    
 | 
			
		||||
                    const allPlayers = players.getAllPlayers()
 | 
			
		||||
                    const states = new Array()
 | 
			
		||||
                    for(var player of allPlayers) {
 | 
			
		||||
                        states.push(player.getState())
 | 
			
		||||
                    }
 | 
			
		||||
                    IOAnswer("/ADMIN/PLAYER/GETALLSTATE", states)
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            IORequest("/OWNER/USERS/SWITCH_MOD", (userId, guildId) => {
 | 
			
		||||
                if(!socketUser.isOwner(guildId)) return IOAnswer("/OWNER/USERS/SWITCH_MOD", false)
 | 
			
		||||
                users.setGuildMod(userId, guildId)
 | 
			
		||||
                IOAnswer("/OWNER/USERS/SWITCH_MOD", true)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            IORequest("/MOD/USERS/BAN", (userId, guildId) => {
 | 
			
		||||
                if(!socketUser.isMod(guildId)) return IOAnswer("/MOD/USERS/BAN", false)
 | 
			
		||||
                users.setGuildBan(userId, guildId)
 | 
			
		||||
                IOAnswer("/OWNER/USERS/BAN", true)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            // UTILS
 | 
			
		||||
 | 
			
		||||
            IORequest("/REPORT", (level, desc) => {
 | 
			
		||||
                const report = new Report(socketUser.identity.username, level, desc).send()
 | 
			
		||||
                IOAnswer("/REPORT", true)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            // Functions 
 | 
			
		||||
 | 
			
		||||
            function getUserChannel() {
 | 
			
		||||
                const membersVoices = discordBot.getMembersVoices()
 | 
			
		||||
                const member = membersVoices.get(socketUser.identity.id)
 | 
			
		||||
                if(member) {
 | 
			
		||||
                    const channelId = member.channelId
 | 
			
		||||
                    const channel = discordBot.getChannel(guildId, channelId)
 | 
			
		||||
                    if(!channel) {
 | 
			
		||||
                        wlog.warn("Le channel vocal n'existe pas : " + channelId)
 | 
			
		||||
                        return null
 | 
			
		||||
                    }
 | 
			
		||||
                    return channel
 | 
			
		||||
                } else {
 | 
			
		||||
                    wlog.warn("L'utilisateur '" + socketUser.identity.username + "' n'est pas dans un channel vocal")
 | 
			
		||||
                    return null
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * @param {Player} player 
 | 
			
		||||
             */
 | 
			
		||||
            function connectToPlayer(guildId, player) {
 | 
			
		||||
                if(!checkUserGuild(socketUser, guildId)) return false
 | 
			
		||||
                if(player.isConnected()) true
 | 
			
		||||
                const channel = getUserChannel()
 | 
			
		||||
                if(!channel) return false
 | 
			
		||||
                player.join(channel)
 | 
			
		||||
                return true
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            function checkUserGuild(socketUser, guildId) {
 | 
			
		||||
                // Vérifie si l'utilisateur n'est pas banni de la guilde
 | 
			
		||||
                if(socketUser.isBanned(guildId)) {
 | 
			
		||||
                    wlog.warn("L'utilisateur '" + socketUser.identity.username + "' est banni de la guilde : " + guildId)
 | 
			
		||||
                    return false
 | 
			
		||||
                }
 | 
			
		||||
                if(!socketUser.guilds.includes(guildId)) {
 | 
			
		||||
                    wlog.warn("L'utilisateur '" + socketUser.identity.username + "' n'est pas membre de la guilde : " + guildId)
 | 
			
		||||
                    // Si user admin, override
 | 
			
		||||
                    if(!socketUser.isAdmin()) {
 | 
			
		||||
                        return false
 | 
			
		||||
                    }
 | 
			
		||||
                    wlog.log("L'utilisateur '" + socketUser.identity.username + "' est admin donc à le droit d'accéder à la guilde : " + guildId)
 | 
			
		||||
                }
 | 
			
		||||
                return true
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
               /**
 | 
			
		||||
                 * @param {function(Player)} action - The action to perform on the player.
 | 
			
		||||
                 */
 | 
			
		||||
               async function handlePlayerAction(guildId, action, actionName) {
 | 
			
		||||
                if (!checkUserGuild(socketUser, guildId)) return;
 | 
			
		||||
                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);
 | 
			
		||||
                } else {
 | 
			
		||||
                    wlog.warn(`Le player de la guilde : ${guildId} n'existe pas`);
 | 
			
		||||
                    IOAnswer(actionName, false);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
    
 | 
			
		||||
        socket.on("disconnect", () => {
 | 
			
		||||
            allConnectedUsers.splice(allConnectedUsers.indexOf(socketUser.identity), 1)
 | 
			
		||||
            socket.leave("admin")
 | 
			
		||||
            removeGuildConnectedUser(socketUser.identity)
 | 
			
		||||
            process.emit("USERS_UPDATE")
 | 
			
		||||
            clog.log(`Déconnexion du client : ${socket.id}`)
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        function sendSession() {
 | 
			
		||||
            const newSession = session.addSession(socket.id)
 | 
			
		||||
            socket.emit("NEW_SESSION", newSession)
 | 
			
		||||
            wlog.log("Envoi d'une nouvelle session : '" + newSession + "' au client : " + socket.id)
 | 
			
		||||
            socket.disconnect()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        function IORequest(RequestName, RequestCallback) {
 | 
			
		||||
            socket.on(GQname, (value) => {
 | 
			
		||||
                wlog.log(socketUser.identity.username + " - Socket : " + socket.id  + " - POST/" + GQname + " - [RECIEVED]") 
 | 
			
		||||
                GQcallback(value)
 | 
			
		||||
            })
 | 
			
		||||
        
 | 
			
		||||
        }
 | 
			
		||||
        function IOAnswer(AnswerName, AnswerValue) {
 | 
			
		||||
        
 | 
			
		||||
            wlog.log(socketUser.identity.username + " - Socket : " + socket.id  + " - POST/" + GRname + " - [ANSWERED]") 
 | 
			
		||||
            socket.emit("ANSWER/" + GRname, GRvalue)
 | 
			
		||||
            process.emit("PLAYERS_UPDATE")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    httpServer.listen(configuration.getPort(), () => {
 | 
			
		||||
        wlog.log(`Le serveur écoute sur le port ${configuration.getPort()}`)
 | 
			
		||||
        wlog.step.end("server_init")
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    function AdminRequest(GRname, GRvalue) {
 | 
			
		||||
 | 
			
		||||
        io.to("admin").emit("ALWAYS/" + GRname, GRvalue)
 | 
			
		||||
    
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function addGuildConnectedUser(user, guilds) {
 | 
			
		||||
       for(var guild of guilds) {
 | 
			
		||||
            if(!guildConnectedUsers.has(guild)) {
 | 
			
		||||
                guildConnectedUsers.set(guild, new Array())
 | 
			
		||||
            }
 | 
			
		||||
            guildConnectedUsers.get(guild).push(user)
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function removeGuildConnectedUser(user) {
 | 
			
		||||
        for(var guild of guildConnectedUsers.keys()) {
 | 
			
		||||
            const users = guildConnectedUsers.get(guild)
 | 
			
		||||
            if(users.includes(user)) {
 | 
			
		||||
                users.splice(users.indexOf(user), 1)
 | 
			
		||||
                if(users.length == 0) {
 | 
			
		||||
                    guildConnectedUsers.delete(guild)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
module.exports = {init}
 | 
			
		||||
							
								
								
									
										67
									
								
								backend/src/server/auth/DiscordAuth.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								backend/src/server/auth/DiscordAuth.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,67 @@
 | 
			
		||||
const { LogType } = require('loguix');  
 | 
			
		||||
const dlog = new LogType("DiscordAuth");
 | 
			
		||||
const { getPort, getToken, getWebsiteLink, getClientSecret } = require('../../utils/Database/Configuration');
 | 
			
		||||
const discordBot = require("../../discord/Bot")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async function getDiscordUser(sessionId, auth_code) {
 | 
			
		||||
    const discordBotClient = discordBot.getClient()
 | 
			
		||||
    dlog.step.init("discord_auth_" + sessionId, "Authentification Discord de la session :" + sessionId);
 | 
			
		||||
 | 
			
		||||
    dlog.log("Récupération de l'autorisation de récupération des informations de l'utilisateur Discord associé à la session : " + sessionId);
 | 
			
		||||
 | 
			
		||||
    const params = new URLSearchParams();
 | 
			
		||||
    params.append("client_id", discordBotClient.user.id);
 | 
			
		||||
    params.append("client_secret", getClientSecret());
 | 
			
		||||
    params.append("grant_type", "authorization_code");
 | 
			
		||||
    params.append("code", auth_code);
 | 
			
		||||
    params.append("redirect_uri", getWebsiteLink() + "/callback");
 | 
			
		||||
    params.append("scope", "identify guilds");
 | 
			
		||||
 | 
			
		||||
    fetch("https://discord.com/api/oauth2/token", {
 | 
			
		||||
        method: "POST",
 | 
			
		||||
        headers: {
 | 
			
		||||
            "Content-Type": "application/x-www-form-urlencoded",
 | 
			
		||||
        },
 | 
			
		||||
        body: params,
 | 
			
		||||
    }).then(accessTokenResp => accessTokenResp.json()).then(accessToken => {
 | 
			
		||||
        dlog.log("Récupération réussi du token d'accès Discord associé à la session : " + sessionId);
 | 
			
		||||
 | 
			
		||||
        fetch("https://discord.com/api/users/@me", {
 | 
			
		||||
            headers: {
 | 
			
		||||
                authorization: `${accessToken.token_type} ${accessToken.access_token}`,
 | 
			
		||||
            },
 | 
			
		||||
        }).then(userResp => userResp.json()).then(user => {
 | 
			
		||||
            dlog.log("Récupération réussi des informations de l'utilisateur Discord associé à la session : " + sessionId + " avec le nom d'utilisateur : " + user.username + " (" + user.id + ")");
 | 
			
		||||
            // Get the guilds of the user
 | 
			
		||||
            fetch("https://discord.com/api/users/@me/guilds", {
 | 
			
		||||
                headers: {
 | 
			
		||||
                    authorization: `${accessToken.token_type} ${accessToken.access_token}`,
 | 
			
		||||
                },
 | 
			
		||||
            }).then(guildsResp => guildsResp.json()).then(guilds => {
 | 
			
		||||
                dlog.log("Récupération réussi des guildes de l'utilisateur Discord associé à la session : " + sessionId + " avec le nom d'utilisateur : " + user.username + " (" + user.id + ")");
 | 
			
		||||
                dlog.step.end("discord_auth_" + sessionId)
 | 
			
		||||
                const userData = {
 | 
			
		||||
                    auth: accessToken,
 | 
			
		||||
                    identity: user,
 | 
			
		||||
                    guilds: guilds,
 | 
			
		||||
                }
 | 
			
		||||
                return userData;
 | 
			
		||||
            }).catch(err => {
 | 
			
		||||
                dlog.step.error("discord_auth_" + sessionId, "Erreur lors de la récupération des guildes de l'utilisateur Discord" + " avec le nom d'utilisateur : " + user.username + " (" + user.id + ")" + " associé à la session : " + sessionId + " : " + err);
 | 
			
		||||
                return "GUILDS_ERROR";
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        }).catch(err => {
 | 
			
		||||
            dlog.step.error("discord_auth_" + sessionId, "Erreur lors de la récupération des informations de l'utilisateur Discord associé à la session : " + sessionId + " : " + err);
 | 
			
		||||
            return "USER_INFO_ERROR";
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    }).catch(err => {
 | 
			
		||||
        dlog.step.error("discord_auth_" + sessionId, "Erreur lors de la récupération du token d'accès Discord associé à la session : " + sessionId + " : " + err);
 | 
			
		||||
        return "ACCESS_TOKEN_ERROR";
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {getDiscordUser}
 | 
			
		||||
							
								
								
									
										33
									
								
								backend/src/server/auth/Session.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								backend/src/server/auth/Session.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
const { LogType } = require('loguix');
 | 
			
		||||
const { generateSessionId } = require('../../utils/TokenManager');  
 | 
			
		||||
const clog = new LogType("Session");
 | 
			
		||||
 | 
			
		||||
const sessions = new Array();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function checkSession(sessionId) {
 | 
			
		||||
    return sessions.includes(sessionId);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function addSession() {
 | 
			
		||||
    const sessionId = generateSessionId();
 | 
			
		||||
    if (checkSession(sessionId)) {
 | 
			
		||||
        clog.warn(`Session ${sessionId} non trouvée dans la liste des sessions.`);
 | 
			
		||||
        return addSession(); // Recursively generate a new session ID if it already exists
 | 
			
		||||
    }
 | 
			
		||||
    sessions.push(sessionId);
 | 
			
		||||
    clog.log(`Nouvelle session ${sessionId} ajoutée.`);
 | 
			
		||||
    return sessionId;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function removeSession(sessionId) {
 | 
			
		||||
    const index = sessions.indexOf(sessionId);
 | 
			
		||||
    if (index > -1) {
 | 
			
		||||
        sessions.splice(index, 1);
 | 
			
		||||
        clog.log(`Suppression de la session ${sessionId}.`);
 | 
			
		||||
    } else {
 | 
			
		||||
        clog.warn(`Session ${sessionId} non trouvée dans la liste des sessions.`);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {checkSession, addSession, removeSession};
 | 
			
		||||
							
								
								
									
										301
									
								
								backend/src/server/auth/User.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										301
									
								
								backend/src/server/auth/User.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,301 @@
 | 
			
		||||
const { Database } = require('../../utils/Database/Database');
 | 
			
		||||
const { __glob } = require('../../utils/GlobalVars');  
 | 
			
		||||
const { generateToken } = require('../../utils/TokenManager');  
 | 
			
		||||
const { LogType } = require('loguix');
 | 
			
		||||
const clog = new LogType("User");
 | 
			
		||||
 | 
			
		||||
const UserDB = new Database("Users", __glob.USERFILE, []);
 | 
			
		||||
const userList = new Array();
 | 
			
		||||
loadUsers();
 | 
			
		||||
 | 
			
		||||
class User {
 | 
			
		||||
    auth;
 | 
			
		||||
    identity;
 | 
			
		||||
    tokens;
 | 
			
		||||
    labels;
 | 
			
		||||
    guilds; 
 | 
			
		||||
    constructor(auth, identity, tokens, labels, guilds) {
 | 
			
		||||
        this.auth = auth;
 | 
			
		||||
        this.identity = identity;
 | 
			
		||||
        this.tokens = tokens;
 | 
			
		||||
        this.labels = labels;
 | 
			
		||||
        this.guilds = guilds;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setAdmin() {
 | 
			
		||||
        if (this.labels.includes("ADMIN")) {
 | 
			
		||||
            this.labels.splice(this.labels.indexOf("ADMIN"), 1);
 | 
			
		||||
            clog.log(`L'utilisateur ${this.identity.username} n'est plus admin.`);
 | 
			
		||||
        } else {
 | 
			
		||||
            this.labels.push("ADMIN");
 | 
			
		||||
            clog.log(`L'utilisateur ${this.identity.username} est maintenant admin.`);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setBan(guildId) {
 | 
			
		||||
        const banLabel = `BAN_${guildId}`;
 | 
			
		||||
        if (this.labels.includes(banLabel)) {
 | 
			
		||||
            this.labels.splice(this.labels.indexOf(banLabel), 1);
 | 
			
		||||
            clog.log(`L'utilisateur ${this.identity.username} n'est plus banni du serveur ${guildId}.`);
 | 
			
		||||
        } else {
 | 
			
		||||
            this.labels.push(banLabel);
 | 
			
		||||
            clog.log(`L'utilisateur ${this.identity.username} est maintenant banni du serveur ${guildId}.`);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    createToken() {
 | 
			
		||||
        const token = generateToken();
 | 
			
		||||
        this.tokens.push(token);
 | 
			
		||||
        saveUsers();
 | 
			
		||||
        clog.log(`Token créé pour l'utilisateur ${this.identity.username}.`);
 | 
			
		||||
        return token;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    removeToken(token) {
 | 
			
		||||
        const index = this.tokens.indexOf(token);
 | 
			
		||||
        if (index > -1) {
 | 
			
		||||
            this.tokens.splice(index, 1);
 | 
			
		||||
            saveUsers();
 | 
			
		||||
            clog.log(`Token supprimé pour l'utilisateur ${this.identity.username}.`);
 | 
			
		||||
        } else {
 | 
			
		||||
            clog.warn(`Token non trouvé pour l'utilisateur ${this.identity.username}.`);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    isBanned(guildId) {
 | 
			
		||||
        const banLabel = `BAN_${guildId}`;
 | 
			
		||||
        return this.labels.includes(banLabel);
 | 
			
		||||
    }
 | 
			
		||||
    isFullBanned() {
 | 
			
		||||
        return this.labels.includes("BAN");
 | 
			
		||||
    }
 | 
			
		||||
    isAdmin() {
 | 
			
		||||
        return this.labels.includes("ADMIN");
 | 
			
		||||
    }
 | 
			
		||||
    isMod(guildId) {
 | 
			
		||||
        if(this.isOwner(guildId)) return true;
 | 
			
		||||
        const modLabel = `MOD_${guildId}`;
 | 
			
		||||
        return this.labels.includes(modLabel);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    isOwner(guildId) {
 | 
			
		||||
        const ownerLabel = `OWNER_${guildId}`;
 | 
			
		||||
        return this.labels.includes(ownerLabel);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}   
 | 
			
		||||
 | 
			
		||||
// ADD
 | 
			
		||||
 | 
			
		||||
function addUser(auth, identity, guilds) {
 | 
			
		||||
    // Check if the user already exists
 | 
			
		||||
    const existingUser = userList.find(user => user.identity.id === identity.id);
 | 
			
		||||
    if (existingUser) {
 | 
			
		||||
        clog.warn(`L'utilisateur ${identity.username} existe déjà.`);
 | 
			
		||||
        // Update the existing user with new information
 | 
			
		||||
        existingUser.auth = auth;
 | 
			
		||||
        existingUser.identity = identity;
 | 
			
		||||
        existingUser.guilds = guilds;
 | 
			
		||||
        existingUser.tokens = existingUser.tokens || []; // Ensure tokens array exists
 | 
			
		||||
        existingUser.labels = existingUser.labels || []; // Ensure labels array exists
 | 
			
		||||
        saveUsers();
 | 
			
		||||
        clog.log(`Utilisateur ${identity.username} mis à jour.`);
 | 
			
		||||
        return existingUser;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const newUser = new User(auth, identity, [], [], guilds);
 | 
			
		||||
 | 
			
		||||
    userList.push(newUser);
 | 
			
		||||
    saveUsers();
 | 
			
		||||
    return newUser;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function removeUser(id) {
 | 
			
		||||
    const index = userList.findIndex(user => user.identity.id === id);
 | 
			
		||||
    if (index > -1) {
 | 
			
		||||
        userList.splice(index, 1);
 | 
			
		||||
        saveUsers();
 | 
			
		||||
        clog.log(`Utilisateur ${id} supprimé.`);
 | 
			
		||||
    } else {
 | 
			
		||||
        clog.warn(`Utilisateur ${id} non trouvé.`);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function removeToken(token) {
 | 
			
		||||
    const user = getUserByToken(token);
 | 
			
		||||
    if (user) {
 | 
			
		||||
        const index = user.tokens.indexOf(token);
 | 
			
		||||
        if (index > -1) {
 | 
			
		||||
            user.tokens.splice(index, 1);
 | 
			
		||||
            saveUsers();
 | 
			
		||||
            clog.log(`Token ${token} supprimé pour l'utilisateur ${user.identity.username}.`);
 | 
			
		||||
        } else {
 | 
			
		||||
            clog.warn(`Token ${token} non trouvé pour l'utilisateur ${user.identity.username}.`);
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        clog.warn(`Utilisateur avec le token "${token}" non trouvé.`);
 | 
			
		||||
    }
 | 
			
		||||
    return user;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function addToken(id) {
 | 
			
		||||
    const user = getUserById(id);
 | 
			
		||||
    if (user) {
 | 
			
		||||
        const token = generateToken();
 | 
			
		||||
        user.tokens.push(token);
 | 
			
		||||
        saveUsers();
 | 
			
		||||
        clog.log(`Token "${token}" ajouté pour l'utilisateur ${user.identity.username}.`);
 | 
			
		||||
        return token;
 | 
			
		||||
    } else {
 | 
			
		||||
        clog.warn(`Utilisateur ${id} non trouvé.`);
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
   
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GET
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {string} id 
 | 
			
		||||
 * @returns {User} user
 | 
			
		||||
 */
 | 
			
		||||
function getUserById(id) {
 | 
			
		||||
    return userList.find(user => user.identity.id === id) || null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 
 | 
			
		||||
 * @param {string} token 
 | 
			
		||||
 * @returns {User} user
 | 
			
		||||
 */
 | 
			
		||||
function getUserByToken(token) {
 | 
			
		||||
    return userList.find(user => user.tokens.includes(token)) || null;
 | 
			
		||||
}   
 | 
			
		||||
 | 
			
		||||
function getUsers() {
 | 
			
		||||
    return userList;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getSimpleUsers() {
 | 
			
		||||
    return userList.map(user => {
 | 
			
		||||
        return {
 | 
			
		||||
            identity: user.identity,
 | 
			
		||||
            labels: user.labels,
 | 
			
		||||
            guilds: user.guilds
 | 
			
		||||
        };
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getSimpleUser(id) {
 | 
			
		||||
    const user = getUserById(id);
 | 
			
		||||
    if(user) {
 | 
			
		||||
        return {
 | 
			
		||||
            identity: user.identity,
 | 
			
		||||
            labels: user.labels,
 | 
			
		||||
            guilds: user.guilds
 | 
			
		||||
        };
 | 
			
		||||
    } else {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SET LABELS
 | 
			
		||||
 | 
			
		||||
function setAdmin(id) {
 | 
			
		||||
    const user = getUserById(id);
 | 
			
		||||
    if (user) {
 | 
			
		||||
        user.setAdmin();
 | 
			
		||||
        saveUsers();
 | 
			
		||||
    } else {
 | 
			
		||||
        clog.warn(`Utilisateur ${id} non trouvé.`);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function setGuildMod(id, guildId) {
 | 
			
		||||
    const user = getUserById(id);
 | 
			
		||||
    if (user) {
 | 
			
		||||
        const modLabel = `MOD_${guildId}`;
 | 
			
		||||
        if (user.labels.includes(modLabel)) {
 | 
			
		||||
            user.labels.splice(user.labels.indexOf(modLabel), 1);
 | 
			
		||||
            clog.log(`L'utilisateur ${user.identity.username} n'est plus modérateur du serveur ${guildId}.`);
 | 
			
		||||
        } else {
 | 
			
		||||
            user.labels.push(modLabel);
 | 
			
		||||
            clog.log(`L'utilisateur ${user.identity.username} est maintenant modérateur du serveur ${guildId}.`);
 | 
			
		||||
        }
 | 
			
		||||
        saveUsers();
 | 
			
		||||
    } else {
 | 
			
		||||
        clog.warn(`Utilisateur ${id} non trouvé.`);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function setGuildBan(id, guildId) {
 | 
			
		||||
    const user = getUserById(id);
 | 
			
		||||
    if (user) {
 | 
			
		||||
        user.setBan(guildId);
 | 
			
		||||
        saveUsers();
 | 
			
		||||
    } else {
 | 
			
		||||
        clog.warn(`Utilisateur ${id} non trouvé.`);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function setFullBan(id) {
 | 
			
		||||
    const user = getUserById(id);
 | 
			
		||||
    if (user) {
 | 
			
		||||
        user.labels.push("BAN");
 | 
			
		||||
        saveUsers();
 | 
			
		||||
    } else {
 | 
			
		||||
        clog.warn(`Utilisateur ${id} non trouvé.`);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function setGuildOwner(id, guildId) {
 | 
			
		||||
    const user = getUserById(id);
 | 
			
		||||
    if (user) {
 | 
			
		||||
        const ownerLabel = `OWNER_${guildId}`;
 | 
			
		||||
        if (user.labels.includes(ownerLabel)) {
 | 
			
		||||
            user.labels.splice(user.labels.indexOf(ownerLabel), 1);
 | 
			
		||||
            clog.log(`L'utilisateur ${user.identity.username} n'est plus propriétaire du serveur ${guildId}.`);
 | 
			
		||||
        } else {
 | 
			
		||||
            user.labels.push(ownerLabel);
 | 
			
		||||
            clog.log(`L'utilisateur ${user.identity.username} est maintenant propriétaire du serveur ${guildId}.`);
 | 
			
		||||
        }
 | 
			
		||||
        saveUsers();
 | 
			
		||||
    } else {
 | 
			
		||||
        clog.warn(`Utilisateur ${id} non trouvé.`);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// USERS DB
 | 
			
		||||
 | 
			
		||||
function loadUsers() {
 | 
			
		||||
    UserDB.load()
 | 
			
		||||
    userList.length = 0; 
 | 
			
		||||
    for (const user in UserDB.data) {
 | 
			
		||||
        userList.push(new User(user.auth, user.identity, user.tokens, user.labels, user.guilds));
 | 
			
		||||
    }
 | 
			
		||||
    clog.log(`Chargement de ${userList.length} utilisateurs.`);
 | 
			
		||||
    return userList;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function saveUsers() {
 | 
			
		||||
    UserDB.data = userList.map(user => {
 | 
			
		||||
        return {
 | 
			
		||||
            auth: user.auth,
 | 
			
		||||
            identity: user.identity,
 | 
			
		||||
            tokens: user.tokens,
 | 
			
		||||
            labels: user.labels,
 | 
			
		||||
            guilds: user.guilds
 | 
			
		||||
        };
 | 
			
		||||
    });
 | 
			
		||||
    UserDB.save()
 | 
			
		||||
    clog.log(`Sauvegarde de ${userList.length} utilisateurs.`);
 | 
			
		||||
    
 | 
			
		||||
    return loadUsers();    
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
module.exports = {User}   
 | 
			
		||||
module.exports = {addUser, setGuildOwner , setFullBan, removeUser, getUserByToken , getUserById, getUsers, setAdmin, setGuildMod, setGuildBan, addToken, removeToken, getSimpleUsers, getSimpleUser}
 | 
			
		||||
@@ -2,11 +2,13 @@ const {Database} = require("./Database")
 | 
			
		||||
const {__glob} = require("../GlobalVars")
 | 
			
		||||
const {LogType} = require("loguix")
 | 
			
		||||
const path = require("path")
 | 
			
		||||
const { get } = require("http")
 | 
			
		||||
 | 
			
		||||
const clog = new LogType("Configuration")
 | 
			
		||||
 | 
			
		||||
const config = new Database("config", __glob.DATA  + path.sep + "config.json", {
 | 
			
		||||
    token: "",
 | 
			
		||||
    client_secret: "",
 | 
			
		||||
    report: {
 | 
			
		||||
        channel : "",
 | 
			
		||||
        contact : ""
 | 
			
		||||
@@ -17,7 +19,9 @@ const config = new Database("config", __glob.DATA  + path.sep + "config.json", {
 | 
			
		||||
            clientId: "",
 | 
			
		||||
            clientSecret: ""
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    },
 | 
			
		||||
    website: "",
 | 
			
		||||
    server_port: 5000,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
function getToken() {
 | 
			
		||||
@@ -45,9 +49,21 @@ function getSpotifyClientSecret() {
 | 
			
		||||
    return config.data.api.spotify.clientSecret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getWebsiteLink() {
 | 
			
		||||
    return config.data.website
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getPort() {
 | 
			
		||||
    return config.data.server_port
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getClientSecret() {
 | 
			
		||||
    return config.data.client_secret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if(getToken() == "") {
 | 
			
		||||
    clog.error("Impossible de démarrer sans token valide")
 | 
			
		||||
    process.exit(1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {getToken, getReportChannel, getReportContact, getYoutubeApiKey, getSpotifyClientId, getSpotifyClientSecret}
 | 
			
		||||
module.exports = {getToken, getClientSecret, getReportChannel, getReportContact, getYoutubeApiKey, getSpotifyClientId, getSpotifyClientSecret, getWebsiteLink, getPort}
 | 
			
		||||
@@ -10,6 +10,8 @@ const __glob = {
 | 
			
		||||
    COMMANDS: root + path.sep + "src" + path.sep + "discord" + path.sep + "commands",
 | 
			
		||||
    METRIC_FILE: root + path.sep + "data" + path.sep + "metrics.json",
 | 
			
		||||
    PREVIOUSFILE: root + path.sep + "data" + path.sep + "previous.json",
 | 
			
		||||
    USERFILE: root + path.sep + "data" + path.sep + "users.json",
 | 
			
		||||
    PLAYLISTFILE: root + path.sep + "data" + path.sep + "playlists.json",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {__glob}
 | 
			
		||||
							
								
								
									
										18
									
								
								backend/src/utils/TokenManager.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								backend/src/utils/TokenManager.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
function generateToken(userId) {
 | 
			
		||||
    // Generate a token using the user ID with 32 random bytes
 | 
			
		||||
    const crypto = require('crypto');
 | 
			
		||||
    const token = userId + "_" + crypto.randomBytes(32).toString('hex');
 | 
			
		||||
    
 | 
			
		||||
    return token;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function generateSessionId() {
 | 
			
		||||
    // Generate a session ID using 32 random bytes
 | 
			
		||||
    const crypto = require('crypto');
 | 
			
		||||
    const sessionId = "SESSION" + "_" + crypto.randomBytes(32).toString('hex');
 | 
			
		||||
    
 | 
			
		||||
    return sessionId;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {generateToken, generateSessionId}
 | 
			
		||||
							
								
								
									
										19
									
								
								changelog.md
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								changelog.md
									
									
									
									
									
								
							@@ -1,17 +1,2 @@
 | 
			
		||||
# **Changelog**
 | 
			
		||||
 | 
			
		||||
- Express JS
 | 
			
		||||
- Vue JS
 | 
			
		||||
- Discord JS
 | 
			
		||||
- Player Youtube
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## **Légende de version** : 
 | 
			
		||||
 | 
			
		||||
* Version X.Y.Z
 | 
			
		||||
  > **X** : Indique une version de travail (Période d'activité) \
 | 
			
		||||
  > **Y** : Indique l'ajout d'une fonctionnalité \
 | 
			
		||||
  > **Z** : Indique la modification ou la réparation d'une fonctionnalité
 | 
			
		||||
* Tags
 | 
			
		||||
  > **-alpha** : Indique une version de dévelopement inutilisable \
 | 
			
		||||
  > **-rcX** : Indique une sous-version qui ne modifie rien mais qui peux corriger un bug de facon très superficiel 
 | 
			
		||||
- Setup serveur
 | 
			
		||||
- 
 | 
			
		||||
		Reference in New Issue
	
	Block a user