diff --git a/README.md b/README.md index 9560987..626804f 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,19 @@ -# **Subsonics - Chopin** +# **Subsonics - Chopin** > Cette version est une refonte complète et intégrale de [Subsonics - Web](https://git.raphix.fr/subsonics/web) -### Bienvenue sur Chopin, la nouvelle version de Subsonics. +## Bienvenue sur Chopin, la nouvelle version de Subsonics -### **Fonctionnalités** +## **Fonctionnalités** > - Lecture de vidéos depuis Youtube, Spotify et SoundClound > - Lecture de fichiers locaux *(Uniquement sur le site)* -> - Gestion et lecture de playlist +> - Gestion et lecture de playlist > - Accéder à votre propre historique et à l'historique du Bot > - Affichage des paroles de la musique en cours > - Une interface refaite pour toutes les platformes. > - Récupération de vos recommendations & Playlists Spotify / Youtube - Le FrontEnd est gérée par VueJS ✌️et le BackEnd a été entièrement refait localement pour des réponses plus rapide et plus stable -[CHANGELOG](https://git.raphix.fr/subsonics/chopin/src/branch/main/changelog.md) \ No newline at end of file +[CHANGELOG](https://git.raphix.fr/subsonics/chopin/src/branch/main/changelog.md) diff --git a/TODOS.md b/TODOS.md new file mode 100644 index 0000000..be1e503 --- /dev/null +++ b/TODOS.md @@ -0,0 +1,6 @@ +# List + +TODO: Lecture de fichiers depuis le site, nécéssite hébergement. +TODO: Récupération des recommendations, playlists Youtube et Spotify +TODO: Acces à un historique personnel (LOCAL ?) +TODO: Faire un systême de parole. diff --git a/backend/package-lock.json b/backend/package-lock.json index 4316635..4b697c5 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1,16 +1,16 @@ { "name": "chopin-backend", - "version": "0.4.0", + "version": "1.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "chopin-backend", - "version": "0.4.0", + "version": "1.0.1", "license": "ISC", "dependencies": { "@discordjs/voice": "^0.18.0", - "@distube/ytdl-core": "^4.16.8", + "@distube/ytdl-core": "^4.16.9", "@distube/ytsr": "2.0.4", "cors": "^2.8.5", "discord-player": "^7.1.0", @@ -267,18 +267,18 @@ } }, "node_modules/@distube/ytdl-core": { - "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==", + "version": "4.16.9", + "resolved": "https://registry.npmjs.org/@distube/ytdl-core/-/ytdl-core-4.16.9.tgz", + "integrity": "sha512-eRYM3lDR1/1ZB+k6jzIdR+8m9VsYEqjz9+DstX1S/aW1f2rlbj22WCdrRbE+sE3DJW8DLJEp69akfjWqQ+nKIw==", "license": "MIT", "dependencies": { - "http-cookie-agent": "^6.0.8", + "http-cookie-agent": "^7.0.1", "https-proxy-agent": "^7.0.6", "m3u8stream": "^0.8.6", "miniget": "^4.2.3", "sax": "^1.4.1", - "tough-cookie": "^5.1.0", - "undici": "^7.3.0" + "tough-cookie": "^5.1.2", + "undici": "^7.8.0" }, "engines": { "node": ">=20.18.1" @@ -287,10 +287,34 @@ "url": "https://github.com/distubejs/ytdl-core?sponsor" } }, + "node_modules/@distube/ytdl-core/node_modules/http-cookie-agent": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/http-cookie-agent/-/http-cookie-agent-7.0.1.tgz", + "integrity": "sha512-lZHFZUdPTw64PdksQac5xbUd4NWjUbyDYnvR//2sbLpcC4UqEUW0x/6O+rDntVzJzJ07QvhtL5XZSC+c5EK+IQ==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.3" + }, + "engines": { + "node": ">=20.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/3846masa" + }, + "peerDependencies": { + "tough-cookie": "^4.0.0 || ^5.0.0", + "undici": "^7.0.0" + }, + "peerDependenciesMeta": { + "undici": { + "optional": true + } + } + }, "node_modules/@distube/ytdl-core/node_modules/undici": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.3.0.tgz", - "integrity": "sha512-Qy96NND4Dou5jKoSJ2gm8ax8AJM/Ey9o9mz7KN1bb9GP+G0l20Zw8afxTnY2f4b7hmhn/z8aC2kfArVQlAhFBw==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.8.0.tgz", + "integrity": "sha512-vFv1GA99b7eKO1HG/4RPu2Is3FBTWBrmzqzO0mz+rLxN3yXkE4mqRcb8g8fHxzX4blEysrNZLqg5RbJLqX5buA==", "license": "MIT", "engines": { "node": ">=20.18.1" @@ -3231,30 +3255,6 @@ "license": "ISC", "peer": true }, - "node_modules/http-cookie-agent": { - "version": "6.0.8", - "resolved": "https://registry.npmjs.org/http-cookie-agent/-/http-cookie-agent-6.0.8.tgz", - "integrity": "sha512-qnYh3yLSr2jBsTYkw11elq+T361uKAJaZ2dR4cfYZChw1dt9uL5t3zSUwehoqqVb4oldk1BpkXKm2oat8zV+oA==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.3" - }, - "engines": { - "node": ">=18.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/3846masa" - }, - "peerDependencies": { - "tough-cookie": "^4.0.0 || ^5.0.0", - "undici": "^5.11.0 || ^6.0.0" - }, - "peerDependenciesMeta": { - "undici": { - "optional": true - } - } - }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -6049,21 +6049,21 @@ } }, "node_modules/tldts": { - "version": "6.1.79", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.79.tgz", - "integrity": "sha512-wjlYwK8lC/WcywLWf3A7qbK07SexezXjTRVwuPWXHvcjD7MnpPS2RXY5rLO3g12a8CNc7Y7jQRQsV7XyuBZjig==", + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", "license": "MIT", "dependencies": { - "tldts-core": "^6.1.79" + "tldts-core": "^6.1.86" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "6.1.79", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.79.tgz", - "integrity": "sha512-HM+Ud/2oQuHt4I43Nvjc213Zji/z25NSH5OkJskJwHXNtYh9DTRlHMDFhms9dFMP7qyve/yVaXFIxmcJ7TdOjw==", + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", "license": "MIT" }, "node_modules/to-regex-range": { @@ -6115,9 +6115,9 @@ } }, "node_modules/tough-cookie": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.1.tgz", - "integrity": "sha512-Ek7HndSVkp10hmHP9V4qZO1u+pn1RU5sI0Fw+jCU3lyvuMZcgqsNgc6CmJJZyByK4Vm/qotGRJlfgAX8q+4JiA==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", "license": "BSD-3-Clause", "dependencies": { "tldts": "^6.1.32" diff --git a/backend/package.json b/backend/package.json index a3a994f..79fc964 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "chopin-backend", - "version": "1.0.0", + "version": "1.0.1", "description": "Discord Bot for music - Fetching everywhere !", "main": "src/main.js", "nodemonConfig": { @@ -12,14 +12,16 @@ "delay": "2000000" }, "scripts": { - "start": "nodemon src/main.js" + "start": "nodemon src/main.js", + "stop": "ssh raphix@raphix.fr sudo -S -u gitlab-ci pm2 stop 'Subsonics'", + "restart": "ssh raphix@raphix.fr sudo -S -u gitlab-ci pm2 start 'Subsonics'" }, "keywords": [], "author": "Raphix", "license": "ISC", "dependencies": { "@discordjs/voice": "^0.18.0", - "@distube/ytdl-core": "^4.16.8", + "@distube/ytdl-core": "^4.16.9", "@distube/ytsr": "2.0.4", "cors": "^2.8.5", "discord-player": "^7.1.0", diff --git a/backend/src/discord/Bot.js b/backend/src/discord/Bot.js index e6690a9..db7d516 100644 --- a/backend/src/discord/Bot.js +++ b/backend/src/discord/Bot.js @@ -6,6 +6,7 @@ const { LogType } = require("loguix") const config = require("../utils/Database/Configuration") const metric = require("webmetrik") const { Player } = require("../player/Player") +const {refreshAllUserInformation} = require("../server/auth/User") const dlog = new LogType("Discord") @@ -37,18 +38,22 @@ function getChannel(guildId, channelId) { function init() { - client.once('ready', () => { + client.once('ready', async () => { 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, { + await client.guilds.cache.forEach(async guild => { + var guildMember = await guild.members.fetch() + guildMember = guildMember.map(member => member.user.id) + + await guilds.set(guild.id, { id: guild.id, name: guild.name, - members: guild.members.cache.map(member => member.user.username), + members: guildMember, }) + }) - + refreshAllUserInformation() const Activity = require("./Activity") Activity.idleActivity() diff --git a/backend/src/discord/Commands/Play.js b/backend/src/discord/Commands/Play.js index b22ba8f..5581847 100644 --- a/backend/src/discord/Commands/Play.js +++ b/backend/src/discord/Commands/Play.js @@ -62,13 +62,7 @@ const command = new Command("play", "Jouer une musique à partir d'un lien dans if(song.type == "spotify") { song = await spotify.getTracks(song) } - if(now) { - player.readPlaylist(song, true) - - } else { - player.readPlaylist(song) - - } + player.readPlaylist(song, now) } else { diff --git a/backend/src/media/YoutubeInformation.js b/backend/src/media/YoutubeInformation.js index ce7c198..1451f7e 100644 --- a/backend/src/media/YoutubeInformation.js +++ b/backend/src/media/YoutubeInformation.js @@ -62,13 +62,24 @@ async function getPlaylist(url) { } try { - const playlistId = url.match(/(?:youtu\.be\/|youtube\.com\/|music\.youtube\.com\/)(?:playlist\?list=)?([a-zA-Z0-9_-]{34})/); + + // If watch?v= is present in the url with list?=, remove it and the code behind and transform it to playlist?list= + var playlistId; + + // Get &list= in the url until the first & or ? + + if (url.includes("list=")) { + playlistId = url.match(/(list=)([a-zA-Z0-9_-]+)/); + } + + console.log(playlistId); + if (playlistId === null) { clog.error("Impossible de récupérer l'identifiant de la playlist YouTube à partir de l'URL"); return null; } - const playlistInfo = await ytfps(playlistId[1]); + const playlistInfo = await ytfps(playlistId[2]); if (!playlistInfo) { clog.error("Impossible de récupérer la playlist YouTube à partir de l'identifiant"); @@ -82,8 +93,8 @@ async function getPlaylist(url) { playlist.title = playlistInfo.title; playlist.thumbnail = playlistInfo.thumbnail_url; playlist.description = playlistInfo.description; - playlist.url = `https://www.youtube.com/playlist?list=${playlistId[1]}`; - playlist.id = playlistId[1]; + playlist.url = `https://www.youtube.com/playlist?list=${playlistId[2]}`; + playlist.id = playlistId[2]; for (const video of playlistInfo.videos) { const song = new Song(); diff --git a/backend/src/player/Finder.js b/backend/src/player/Finder.js index eed7b73..d3cb870 100644 --- a/backend/src/player/Finder.js +++ b/backend/src/player/Finder.js @@ -6,7 +6,44 @@ const spotify = require("../media/SpotifyInformation") const soundcloud = require("../media/SoundcloudInformation") -async function search(query, multiple) { +async function search(query, multiple, forceType) { + if(!query) return null + if(!multiple) multiple = false + if(!forceType) forceType = null + if(forceType == "PLAYLIST") { + if(query.includes("spotify")) { + return await spotify.getPlaylist(query) + } else if(query.includes("soundcloud")) { + return await soundcloud.getPlaylist(query) + } else if(query.includes("youtube")) { + return await youtube.getPlaylist(query) + } else { + return null + } + } + + if(forceType == "SONG") { + if(query.includes("spotify")) { + return await spotify.getSong(query) + } else if(query.includes("soundcloud")) { + return await soundcloud.getTrack(query) + } else if(query.includes("youtube")) { + return await youtube.getQuery(query, multiple) + } else { + return null + } + } + + if(forceType == "ALBUM") { + if(query.includes("spotify")) { + return await spotify.getAlbum(query) + } else if(query.includes("youtube")) { + return await youtube.getQuery(query, multiple) + } else { + return null + } + } + const type = Resolver.getQueryType(query) if(type == QueryType.YOUTUBE_SEARCH) { return await youtube.getQuery(query, multiple) diff --git a/backend/src/player/Player.js b/backend/src/player/Player.js index 2a77e69..e7731e5 100644 --- a/backend/src/player/Player.js +++ b/backend/src/player/Player.js @@ -127,17 +127,19 @@ class Player { } getState() { + const playerStatus = this.player?.state?.status ?? false; + const connectionStatus = this.connection?.state?.status ?? false; 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, + paused: playerStatus === AudioPlayerStatus.Paused, + playing: playerStatus === AudioPlayerStatus.Playing, duration: this.getDuration(), - playerState: this.player.state.status, - connectionState: this.connection.state.status, + playerState: playerStatus, + connectionState: connectionStatus, channelId: this.channelId } return state diff --git a/backend/src/player/Song.js b/backend/src/player/Song.js index 3f3404d..0f8f2d1 100644 --- a/backend/src/player/Song.js +++ b/backend/src/player/Song.js @@ -14,6 +14,7 @@ class Song { duration; readduration; type; + userAddedId; constructor(properties) { if(properties) { @@ -27,6 +28,7 @@ class Song { this.readduration = properties.readduration ?? this.readduration this.type = properties.type ?? this.type this.authorId = properties.authorId ?? this.authorId + } } diff --git a/backend/src/playlists/Playlist.js b/backend/src/playlists/Playlist.js index cb4165c..b62a468 100644 --- a/backend/src/playlists/Playlist.js +++ b/backend/src/playlists/Playlist.js @@ -15,7 +15,7 @@ class Playlist { this.url = url; this.author = author; this.authorId = authorId; - this.songs = songs; + this.songs = songs || new Array(); this.thumbnail = thumbnail; this.duration = duration; this.readduration = readduration; diff --git a/backend/src/playlists/PlaylistManager.js b/backend/src/playlists/PlaylistManager.js index 0c37961..bdb597d 100644 --- a/backend/src/playlists/PlaylistManager.js +++ b/backend/src/playlists/PlaylistManager.js @@ -9,6 +9,7 @@ const spotify = require('../media/SpotifyInformation'); const playlistDB = new Database("Playlists", __glob.PLAYLISTFILE, {}); + /** * @param {string} id * @param {string} name @@ -20,7 +21,7 @@ function getPlaylistsOfUser(id) { return playlistDB.data[id]; } else { // Creaete a key with the user id and an empty array - playlistDB.data;[id] = new Array(); + playlistDB.data[id] = new Array(); clog.log(`Création d'une clé pour l'utilisateur : ${id}`); playlistDB.save(); return playlistDB.data[id]; @@ -35,7 +36,7 @@ function getPlaylistsOfUser(id) { */ function getPlaylistOfUser(id, name) { const playlists = getPlaylistsOfUser(id); - const playlist = playlists.find(p => p.name === name); + const playlist = playlists.find(p => p.title === name); if (!playlist) { clog.warn(`La playlist ${name} n'existe pas pour l'utilisateur ${id}`); return null; @@ -46,12 +47,16 @@ function getPlaylistOfUser(id, name) { async function addPlaylist(id, name, url) { const playlists = getPlaylistsOfUser(id); var playlist = new Playlist(name, url); - if (playlists.find(p => p.name === name)) { + if (playlists.find(p => p.title === name)) { clog.warn(`La playlist ${name} existe déjà pour l'utilisateur ${id}`); return; } + var failed; if(url) { - await Finder.search(url).then(async (playlistFounded) => { + await Finder.search(url, false, "PLAYLIST").then(async (playlistFounded) => { + if(!playlistFounded) { + failed = true; + } if(playlistFounded instanceof Playlist) { playlist = playlistFounded; } @@ -61,6 +66,11 @@ async function addPlaylist(id, name, url) { }) } + if(failed) { + clog.error(`Impossible de trouver la playlist ${name} pour l'utilisateur ${id}`); + return null; + } + playlists.push(playlist); playlistDB.save(); clog.log(`Ajout de la playlist ${name} pour l'utilisateur ${id}`); @@ -69,7 +79,7 @@ async function addPlaylist(id, name, url) { function removePlaylist(id, name) { const playlists = getPlaylistsOfUser(id); - const index = playlists.findIndex(p => p.name === name); + const index = playlists.findIndex(p => p.title === name); if (index === -1) { clog.warn(`La playlist ${name} n'existe pas pour l'utilisateur ${id}`); return; @@ -80,7 +90,7 @@ function removePlaylist(id, name) { } function getPlaylist(id, name) { const playlists = getPlaylistsOfUser(id); - const playlist = playlists.find(p => p.name === name); + const playlist = playlists.find(p => p.title === name); if (!playlist) { clog.warn(`La playlist ${name} n'existe pas pour l'utilisateur ${id}`); return null; @@ -90,38 +100,66 @@ function getPlaylist(id, name) { function copyPlaylist(fromId, toId, name) { const playlists = getPlaylistsOfUser(fromId); - const playlist = playlists.find(p => p.name === name); + const playlist = playlists.find(p => p.title === name); if (!playlist) { clog.warn(`La playlist ${name} n'existe pas pour l'utilisateur ${fromId}`); return null; } const toPlaylists = getPlaylistsOfUser(toId); + // Check if the playlist already exists in the target user + if (toPlaylists.find(p => p.title === name)) { + clog.warn(`La playlist ${name} existe déjà pour l'utilisateur ${toId}`); + return null; + } toPlaylists.push(playlist); playlistDB.save(); clog.log(`Copie de la playlist ${name} de l'utilisateur ${fromId} vers l'utilisateur ${toId}`); - return newPlaylist; + return false; } function renamePlaylist(id, oldName, newName) { const playlists = getPlaylistsOfUser(id); - const playlist = playlists.find(p => p.name === oldName); + const playlist = playlists.find(p => p.title === oldName); if (!playlist) { clog.warn(`La playlist ${oldName} n'existe pas pour l'utilisateur ${id}`); return null; } - playlist.name = newName; + // Check if the new name already exists + if (playlists.find(p => p.title === newName)) { + clog.warn(`La playlist ${newName} existe déjà pour l'utilisateur ${id}`); + return null; + } + playlist.title = newName; playlistDB.save(); clog.log(`Renommage de la playlist ${oldName} en ${newName} pour l'utilisateur ${id}`); } function addSong(id, playlistName, song) { + if(typeof song === "string") { + try { + song = JSON.parse(song) + } catch (e) { + clog.error(`La chanson ${song} n'est pas valide`); + return null; + } + } + // Check if the song is a valid object + if (typeof song !== 'object' || !song) { + clog.error(`La chanson ${song} n'est pas valide`); + return null; + } const playlists = getPlaylistsOfUser(id); - const playlist = playlists.find(p => p.name === playlistName); + const playlist = playlists.find(p => p.title === playlistName); if (!playlist) { clog.warn(`La playlist ${playlistName} n'existe pas pour l'utilisateur ${id}`); return null; } + // Check the integrity of the song + if (!song.id || !song.title || !song.url) { + clog.error(`La chanson ${song.title} n'est pas valide`); + return null; + } playlist.songs.push(song); playlistDB.save(); clog.log(`Ajout de la chanson ${song.title} à la playlist ${playlistName} pour l'utilisateur ${id}`); @@ -129,7 +167,7 @@ function addSong(id, playlistName, song) { function removeSong(id, playlistName, songId) { const playlists = getPlaylistsOfUser(id); - const playlist = playlists.find(p => p.name === playlistName); + const playlist = playlists.find(p => p.title === playlistName); if (!playlist) { clog.warn(`La playlist ${playlistName} n'existe pas pour l'utilisateur ${id}`); return null; diff --git a/backend/src/server/Server.js b/backend/src/server/Server.js index f3165ed..b00afe1 100644 --- a/backend/src/server/Server.js +++ b/backend/src/server/Server.js @@ -1,6 +1,7 @@ const {LogType} = require('loguix') const wlog = new LogType("Server") +const path = require("path") const {Server} = require('socket.io') const {createServer} = require('http') const session = require("../server/auth/Session") @@ -21,6 +22,7 @@ const { restart } = require('../utils/Maintenance') const allConnectedUsers = new Array() const guildConnectedUsers = new Map() +const UsersBySocket = new Map() function init() { @@ -37,11 +39,11 @@ function init() { 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) + for(var guild of discordBot.getGuilds().keys()) { + const player = players.getPlayer(guild) 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") + io.to(player.guildId).emit("/PLAYER/UPDATE", player.getState()) + wlog.log("Envoi de l'état du player de la guilde : " + player.guildId + " à tous les utilisateurs connectés") } } } @@ -50,22 +52,37 @@ function init() { 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") + for(var guild of discordBot.getGuilds().keys()) { + if(guildConnectedUsers.has(guild)) { + io.to(guild).emit("/USERS/UPDATE", {"id": guild, "members": guildConnectedUsers.get(guild)} ) + io.to("ADMIN").emit("ALL_USERS_UPDATE", allConnectedUsers) + wlog.log("Envoi de la liste des utilisateurs connectés (" + guildConnectedUsers.get(guild).length +") à la guilde : " + guild + " à tous les utilisateurs connectés") } } } }) - - io.on("connection", async (socket) => { + var socketUser; + + // Make sure Discord Bot is loaded and make an interruption until it is loaded + while(!discordBot.getClient().isReady()) { + wlog.warn("Attente de traitement : "+ socket.id + " : Le bot Discord n'est pas encore chargé, attente de 1 seconde...") + await new Promise(resolve => setTimeout(resolve, 1000)) + } wlog.log(`Connexion d'un client : ${socket.id}`) + + socket.on("disconnect", () => { + handleDisconnect() + wlog.log("Déconnexion du client : " + socket.id) + }) + + socket.on("error", (error) => { + handleDisconnect() + wlog.error("Erreur sur le socket : " + socket.id + " - " + error) + }) if(socket.handshake.auth == undefined || socket.handshake.auth == {}) { wlog.warn("Authentification manquant pour le client :" + socket.id) @@ -79,7 +96,7 @@ function init() { if(sessionId) { if(!session.checkSession(sessionId)) { - wlog.warn("Session invalide pour le client :" + socket.id) + wlog.warn("Session invalide pour le client : " + socket.id) sendSession() return; } else { @@ -87,19 +104,18 @@ function init() { 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.emit("AUTH_ERROR", discordUser) socket.disconnect() return } else { - const loggedUser = users.addUser(discordUser.auth, discordUser.identity, discordUser.guilds) + const loggedUser = await users.addUser(discordUser.auth, discordUser.identity, discordUser.guilds) for(var guild of discordUser.guilds) { if(guild.owner) { - users.setGuildOwner(loggedUser.identity.id, guild.id) + users.setGuildOwner(loggedUser.identity.id, guild.id, true) } } - const newToken = loggedUser.createToken() + const newToken = await loggedUser.createToken() socket.emit("NEW_TOKEN", newToken) socket.disconnect() wlog.log("Utilisateur Discord associé à la session : " + sessionId + " récupéré avec succès") @@ -109,6 +125,8 @@ function init() { } else { wlog.warn("Code d'authentification manquant pour le client :" + socket.id) + socket.disconnect() + return } } } @@ -119,47 +137,81 @@ function init() { return } - const socketUser = users.getUserByToken(token) + socketUser = users.getUserByToken(token) if(!socketUser) { wlog.warn("Token invalide pour le client :" + socket.id) sendSession() return + } else { + if(!socketUser.auth) { + wlog.warn("L'utilisateur '" + socketUser.identity.username + "' n'a pas d'authentification Discord Valide") + socketUser.clearToken() + socket.emit("AUTH_ERROR", "AUTH_ERROR") + socket.disconnect() + return + } + + await users.updateGuilds(socketUser.identity.id) + await users.updateIdentity(socketUser.identity.id) } + + socketUser = users.getUserByToken(token) 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") + UsersBySocket.set(socketUser.identity.id, socket.id) + } wlog.log("Utilisateur connecté : " + socketUser.identity.username + " (" + socketUser.identity.id + ") - Socket : " + socket.id) + process.emit("USERS_UPDATE") 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") + if(socketUser.isAdmin()) { + socket.join("ADMIN") wlog.log("Utilisateur admin identifié : " + socketUser.identity.username + " (" + socketUser.identity.id + ")") } // USERS + // CHECKED : 24/04/2025 IORequest("/USER/INFO", () => { + var guildPresents = new Array(); + var guildsOfBot = discordBot.getGuilds() + for(var guild of guildsOfBot) { + + if(guild[1].members.includes(socketUser.identity.id)) { + guildPresents.push(guild[1].id) + } + } IOAnswer("/USER/INFO", { identity: socketUser.identity, - guilds: socketUser.guilds, - labels: socketUser.labels + guilds: guildPresents, + labels: socketUser.labels, + }) wlog.log("Envoi des informations Discord de '" + socketUser.identity.id + "' à '" + socket.id + "'" ) }) + //CHECKED : 24/04/2025 + IORequest("/USER/SIGNOUT", () => { + socketUser.removeToken(token) + socket.disconnect() + }) + + + // CHECKED : 24/04/2025 IORequest("/USERS/LIST", (guildId) => { if(!checkUserGuild(socketUser, guildId)) return if(!guildConnectedUsers.has(guildId)) return IOAnswer("/USERS/LIST", false) @@ -171,27 +223,28 @@ function init() { IORequest("/PLAYER/PREVIOUS/LIST", (guildId) => { if(!checkUserGuild(socketUser, guildId)) return const list = new List(guildId) - return list.getPrevious() + IOAnswer("/PLAYER/PREVIOUS/LIST", list.getPrevious()) }) + //TODO: Faire les autres reqêtes sur la liste + 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) + // Make him to leave all the other rooms except the ADMIN room if he is admin + socket.rooms.forEach((room) => { + if(room != "ADMIN") { + socket.leave(room) + } + }) 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"); + handlePlayerAction(guildId, async (player) => await player.getState(), "/PLAYER/STATE"); }) - + IORequest("/PLAYER/PAUSE", (guildId) => { handlePlayerAction(guildId, (player) => player.pause(), "/PLAYER/PAUSE"); }); @@ -227,7 +280,11 @@ function init() { }, "/PLAYER/CHANNEL/CHANGE"); }); - IORequest("/PLAYER/SEEK", (guildId, time) => { + IORequest("/PLAYER/SEEK", (data) => { + if(!data) return IOAnswer("/PLAYER/SEEK", false) + const {guildId, time} = data + if(!guildId) return IOAnswer("/PLAYER/SEEK", false) + if(!time) return IOAnswer("/PLAYER/SEEK", false) handlePlayerAction(guildId, (player) => { // Check if current is not null if(player.queue.current == null) { @@ -239,7 +296,11 @@ function init() { }, "/PLAYER/SEEK"); }); - IORequest("/QUEUE/PLAY/NOW", (guildId, listType, index) => { + IORequest("/QUEUE/PLAY/NOW", (data) => { + if(!data) return IOAnswer("/QUEUE/PLAY/NOW", false) + const {guildId, song} = data + if(!song) return IOAnswer("/QUEUE/PLAY/NOW", false) + if(!guildId) return IOAnswer("/QUEUE/PLAY/NOW", false) if(!checkUserGuild(socketUser, guildId)) return const player = new Player(guildId) if(!connectToPlayer(guildId, player)) return IOAnswer("/QUEUE/PLAY", false) @@ -255,7 +316,11 @@ function init() { IOAnswer("/QUEUE/PLAY/NOW", true) }) - IORequest("/QUEUE/NEXT/DELETE", (guildId, index) => { + IORequest("/QUEUE/NEXT/DELETE", (data) => { + if(!data) return IOAnswer("/QUEUE/NEXT/DELETE", false) + const {guildId, index} = data + if(!guildId) return IOAnswer("/QUEUE/NEXT/DELETE", false) + if(!index) return IOAnswer("/QUEUE/NEXT/DELETE", false) handlePlayerAction(guildId, (player) => { const next = player.queue.getNext() if(!next[index]) return IOAnswer("/QUEUE/NEXT/DELETE", false); @@ -267,7 +332,12 @@ function init() { handlePlayerAction(guildId, (player) => player.queue.clearNext()) }) - IORequest("/QUEUE/NEXT/MOVE", (guildId, index, newIndex) => { + IORequest("/QUEUE/NEXT/MOVE", (data) => { + if(!data) return IOAnswer("/QUEUE/NEXT/MOVE", false) + const {guildId, index, newIndex} = data + if(!guildId) return IOAnswer("/QUEUE/NEXT/MOVE", false) + if(!index) return IOAnswer("/QUEUE/NEXT/MOVE", false) + if(!newIndex) return IOAnswer("/QUEUE/NEXT/MOVE", false) handlePlayerAction(guildId, (player) => { const next = player.queue.getNext() if(!next[index]) return IOAnswer("/QUEUE/NEXT/MOVE", false); @@ -277,11 +347,16 @@ function init() { // SEARCH + // CHECKED : 24/04/2025 IORequest("/SEARCH", async (query) => { - IOAnswer("/SEARCH", await Finder.search(query)) + IOAnswer("/SEARCH", await Finder.search(query, true)) }) - IORequest("/SEARCH/PLAY", async (guildId, song, now) => { + IORequest("/SEARCH/PLAY", async (data) => { + if(!data) return IOAnswer("/SEARCH/PLAY", false) + const {guildId, song, now} = data + if(!song) return IOAnswer("/SEARCH/PLAY", false) + if(!guildId) return IOAnswer("/SEARCH/PLAY", false) if(!checkUserGuild(socketUser, guildId)) return const player = new Player(guildId) if(!connectToPlayer(guildId, player)) return IOAnswer("/SEARCH/PLAY", false) @@ -296,25 +371,36 @@ function init() { // PLAYLISTS - IORequest("/PLAYLISTS/CREATE", (name, url) => { + // CHECKED : 30/04/2025 + IORequest("/PLAYLISTS/CREATE", async (data) => { + if(!data) return IOAnswer("/PLAYLISTS/CREATE", false) + const {name, url} = data if(!name) return IOAnswer("/PLAYLISTS/CREATE", false) - const playlist = playlists.addPlaylist(socketUser.identity.id, name, url) + const playlist = await playlists.addPlaylist(socketUser.identity.id, name, url) if(!playlist) return IOAnswer("/PLAYLISTS/CREATE", false) IOAnswer("/PLAYLISTS/CREATE", true) }) - IORequest("/PLAYLISTS/DELETE", (name) => { + // CHECKED : 30/04/2025 + IORequest("/PLAYLISTS/DELETE", (data) => { + if(!data) return IOAnswer("/PLAYLISTS/DELETE", false) + const {name} = data if(!name) return IOAnswer("/PLAYLISTS/DELETE", false) playlists.removePlaylist(socketUser.identity.id, name) IOAnswer("/PLAYLISTS/DELETE", true) }) + // CHECKED : 24/04/2025 IORequest("/PLAYLISTS/LIST", () => { const playlist = playlists.getPlaylistsOfUser(socketUser.identity.id) IOAnswer("/PLAYLISTS/LIST", playlist) }) - IORequest("/PLAYLISTS/SEND", (name, toId) => { + // CHECKED : 30/04/2025 + IORequest("/PLAYLISTS/SEND", (data) => { + if(!data) return IOAnswer("/PLAYLISTS/SEND", false) + const {name, toId} = data + if(!name || !toId) return IOAnswer("/PLAYLISTS/SEND", false) // 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) @@ -327,7 +413,10 @@ function init() { IOAnswer("/PLAYLISTS/SEND", true) }) - IORequest("/PLAYLISTS/RENAME", (name, newName) => { + // CHECKED : 30/04/2025 + IORequest("/PLAYLISTS/RENAME", (data) => { + if(!data) return IOAnswer("/PLAYLISTS/RENAME", false) + const {name, newName} = data if(!name || !newName) return IOAnswer("/PLAYLISTS/RENAME", false) const playlist = playlists.getPlaylistOfUser(socketUser.identity.id, name) if(!playlist) return IOAnswer("/PLAYLISTS/RENAME", false) @@ -335,21 +424,33 @@ function init() { IOAnswer("/PLAYLISTS/RENAME", true) }) - IORequest("/PLAYLISTS/ADD_SONG", (name, song) => { + // CHECKED : 30/04/2025 + IORequest("/PLAYLISTS/ADD_SONG", (data) => { + if(!data) return IOAnswer("/PLAYLISTS/ADD_SONG", false) + const {name, song} = data + if(!name || !song) return IOAnswer("/PLAYLISTS/ADD_SONG", false) 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) => { + // CHECKED : 30/04/2025 + IORequest("/PLAYLISTS/REMOVE_SONG", (data) => { + if(!data) return IOAnswer("/PLAYLISTS/REMOVE_SONG", false) + const {name, songId} = data + if(!name || !songId) return IOAnswer("/PLAYLISTS/REMOVE_SONG", false) 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) => { + IORequest("/PLAYLISTS/PLAY", (data) => { + if(!data) return IOAnswer("/PLAYLISTS/PLAY", false) + const {name, guildId, now} = data + if(!name || !guildId) return IOAnswer("/PLAYLISTS/PLAY", false) + if(!checkUserGuild(socketUser, guildId)) return IOAnswer("/PLAYLISTS/PLAY", false) const playlist = playlists.getPlaylistOfUser(socketUser.identity.id, name) if(!playlist) return IOAnswer("/PLAYLISTS/PLAY", false) const player = new Player(guildId) @@ -361,8 +462,10 @@ function init() { // ADMIN - if(socketUser.labels.includes("admin")) { + if(socketUser.isAdmin()) { + // CHECKED : 24/04/2025 IORequest("/ADMIN/LOGS", () => { + if(!socketUser.isAdmin()) return IOAnswer("/ADMIN/LOGS", false) const logs_data = new Array() const logs_folder = fs.readdirSync(__glob.LOGS) for(var log of logs_folder) { @@ -371,53 +474,94 @@ function init() { IOAnswer("/ADMIN/LOGS", logs_data) }) + // CHECKED : 24/04/2025 IORequest("/ADMIN/MAINTENANCE/RESTART", (reason) => { + if(!socketUser.isAdmin()) return IOAnswer("/ADMIN/MAINTENANCE/RESTART", false) if(!reason) return IOAnswer("/ADMIN/MAINTENANCE/RESTART", false) restart(reason) }) + // CHECKED : 24/04/2025 IORequest("/ADMIN/USERS/SWITCH_ADMIN", (userId) => { + if(!socketUser.isAdmin()) return IOAnswer("/ADMIN/USERS/SWITCH_ADMIN", false) + if(socketUser.identity.id == userId) return IOAnswer("/ADMIN/USERS/SWITCH_ADMIN", false) + if(!users.getUserById(userId)) return IOAnswer("/ADMIN/USERS/SWITCH_ADMIN", false) users.setAdmin(userId) - IOAnswer("/ADMIN/USERS/PROMOTE_ADMIN", true) + IOAnswer("/ADMIN/USERS/SWITCH_ADMIN", true) }) + // CHECKED : 24/04/2025 IORequest("/ADMIN/USERS/FULL_BAN", (userId) => { + if(!socketUser.isAdmin()) return IOAnswer("/ADMIN/USERS/FULL_BAN", false) + if(socketUser.identity.id == userId) return IOAnswer("/ADMIN/USERS/FULL_BAN", false) + if(!users.getUserById(userId)) return IOAnswer("/ADMIN/USERS/FULL_BAN", false) + if(users.getUserById(userId).isAdmin()) return IOAnswer("/ADMIN/USERS/FULL_BAN", false) users.setFullBan(userId) IOAnswer("/ADMIN/USERS/FULL_BAN", true) }) + + // CHECKED : 24/04/2025 IORequest("/ADMIN/USERS/DELETE", (userId) => { + if(!socketUser.isAdmin()) return IOAnswer("/ADMIN/USERS/DELETE", false) + if(socketUser.identity.id == userId) return IOAnswer("/ADMIN/USERS/DELETE", false) + if(!users.getUserById(userId)) return IOAnswer("/ADMIN/USERS/DELETE", false) + if(users.getUserById(userId).isAdmin()) return IOAnswer("/ADMIN/USERS/DELETE", false) + users.removeUser(userId) + const userSocket = UsersBySocket.get(userId) + if(userSocket) { + const socket = io.sockets.sockets.get(userSocket) + if(socket) { + socket.emit("DELETED") + socket.disconnect() + } + } IOAnswer("/ADMIN/USERS/DELETE", true) }) - IORequest("/ADMIN/PLAYER/GETALLSTATE", () => { - + // CHECKED : 24/04/2025 + IORequest("/ADMIN/PLAYER/GETALLSTATE", async () => { + if(!socketUser.isAdmin()) return IOAnswer("/ADMIN/PLAYER/GETTALLSTATE", false) const allPlayers = players.getAllPlayers() const states = new Array() - for(var player of allPlayers) { - states.push(player.getState()) + for(var player in allPlayers) { + await states.push(await player.getState()) } IOAnswer("/ADMIN/PLAYER/GETALLSTATE", states) }) } - IORequest("/OWNER/USERS/SWITCH_MOD", (userId, guildId) => { + // CHECKED : 24/04/2025 + IORequest("/OWNER/USERS/SWITCH_MOD", (data) => { + if(!data["userId"] || !data["guildId"]) return IOAnswer("/OWNER/USERS/SWITCH_MOD", false) + const userId = data["userId"] + const guildId = data["guildId"] + if(socketUser.identity.id == userId) return IOAnswer("/OWNER/USERS/SWITCH_MOD", false) 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) => { + + // CHECKED : 24/04/2025 + IORequest("/MOD/USERS/BAN", (data) => { + if(!data["userId"] || !data["guildId"]) return IOAnswer("/MOD/USERS/BAN", false) + const userId = data["userId"] + const guildId = data["guildId"] + if(socketUser.identity.id == userId) return IOAnswer("/MOD/USERS/BAN", false) if(!socketUser.isMod(guildId)) return IOAnswer("/MOD/USERS/BAN", false) users.setGuildBan(userId, guildId) - IOAnswer("/OWNER/USERS/BAN", true) + IOAnswer("/MOD/USERS/BAN", true) }) // UTILS - IORequest("/REPORT", (level, desc) => { - const report = new Report(socketUser.identity.username, level, desc).send() + // CHECKED : 24/04/2025 + IORequest("/REPORT", (data) => { + if(data.length < 2) return IOAnswer("/REPORT", false) + if(!data["level"] || !data["desc"]) return IOAnswer("/REPORT", false) + const report = new Report(socketUser.identity.username, data["level"], data["desc"]).send() IOAnswer("/REPORT", true) }) @@ -457,12 +601,16 @@ function init() { function checkUserGuild(socketUser, guildId) { - // Vérifie si l'utilisateur n'est pas banni de la guilde + // Check if the guildId is referenced in the bot guilds + if(!discordBot.getGuilds().has(guildId)) { + wlog.warn("La guilde : " + guildId + " n'est pas référencée dans le bot") + return false + } if(socketUser.isBanned(guildId)) { wlog.warn("L'utilisateur '" + socketUser.identity.username + "' est banni de la guilde : " + guildId) return false } - if(!socketUser.guilds.includes(guildId)) { + if(!socketUser.guilds.find(g => g.id == guildId)) { wlog.warn("L'utilisateur '" + socketUser.identity.username + "' n'est pas membre de la guilde : " + guildId) // Si user admin, override if(!socketUser.isAdmin()) { @@ -470,6 +618,7 @@ function init() { } wlog.log("L'utilisateur '" + socketUser.identity.username + "' est admin donc à le droit d'accéder à la guilde : " + guildId) } + return true } @@ -491,14 +640,20 @@ function init() { } - - 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 handleDisconnect() { + if(socketUser) { + wlog.log("Déconnexion de l'utilisateur : " + socketUser.identity.username + " (" + socketUser.identity.id + ") - Socket : " + socket.id) + allConnectedUsers.splice(allConnectedUsers.indexOf(socketUser.identity), 1) + removeGuildConnectedUser(socketUser.identity) + process.emit("USERS_UPDATE") + if(socketUser.isAdmin()) { + socket.leave("ADMIN") + } + UsersBySocket.delete(socketUser.identity.id) + } + } function sendSession() { const newSession = session.addSession(socket.id) @@ -509,17 +664,16 @@ function init() { function IORequest(RequestName, RequestCallback) { - socket.on(GQname, (value) => { - wlog.log(socketUser.identity.username + " - Socket : " + socket.id + " - POST/" + GQname + " - [RECIEVED]") - GQcallback(value) + socket.on(RequestName, (value) => { + wlog.log(socketUser.identity.username + " - Socket : " + socket.id + " - " + RequestName + " - [RECIEVED]") + RequestCallback(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") + wlog.log(socketUser.identity.username + " - Socket : " + socket.id + " - " + AnswerName + " - [ANSWERED]") + socket.emit(AnswerName, AnswerValue) } }) @@ -531,16 +685,16 @@ function init() { function AdminRequest(GRname, GRvalue) { - io.to("admin").emit("ALWAYS/" + 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.set(guild.id, new Array()) } - guildConnectedUsers.get(guild).push(user) + guildConnectedUsers.get(guild.id).push(user) } } diff --git a/backend/src/server/auth/DiscordAuth.js b/backend/src/server/auth/DiscordAuth.js index 69c5688..6b1d3cc 100644 --- a/backend/src/server/auth/DiscordAuth.js +++ b/backend/src/server/auth/DiscordAuth.js @@ -1,10 +1,12 @@ const { LogType } = require('loguix'); const dlog = new LogType("DiscordAuth"); -const { getPort, getToken, getWebsiteLink, getClientSecret } = require('../../utils/Database/Configuration'); -const discordBot = require("../../discord/Bot") +const { getWebsiteLink, getClientSecret } = require('../../utils/Database/Configuration'); async function getDiscordUser(sessionId, auth_code) { + + return new Promise((resolve, reject) => { + const discordBot = require("../../discord/Bot") const discordBotClient = discordBot.getClient() dlog.step.init("discord_auth_" + sessionId, "Authentification Discord de la session :" + sessionId); @@ -25,6 +27,13 @@ async function getDiscordUser(sessionId, auth_code) { }, body: params, }).then(accessTokenResp => accessTokenResp.json()).then(accessToken => { + if (accessToken.error) { + dlog.step.error("discord_auth_" + sessionId, "Erreur lors de la récupération du token d'accès Discord associé à la session : " + sessionId + " : " + accessToken.error + " : " + accessToken.error_description); + resolve("ACCESS_TOKEN_ERROR"); + return; + } + + dlog.log("Récupération réussi du token d'accès Discord associé à la session : " + sessionId); fetch("https://discord.com/api/users/@me", { @@ -46,22 +55,91 @@ async function getDiscordUser(sessionId, auth_code) { identity: user, guilds: guilds, } - return userData; + resolve(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"; + resolve("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"; + resolve( "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"; + resolve("ACCESS_TOKEN_ERROR"); + }) }) - } -module.exports = {getDiscordUser} \ No newline at end of file +function refreshToken(refresh_token) { + return new Promise((resolve, reject) => { + const discordBot = require("../../discord/Bot") + const params = new URLSearchParams(); + params.append("client_id", discordBot.getClient().user.id); + params.append("client_secret", getClientSecret()); + params.append("grant_type", "refresh_token"); + params.append("refresh_token", refresh_token); + 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 => { + if (accessToken.error) { + dlog.error("Erreur lors de la récupération du token d'accès Discord : " + accessToken.error + " : " + accessToken.error_description); + resolve(null); + return; + } + resolve(accessToken); + }).catch(err => { + dlog.error("Erreur lors de la récupération du token d'accès Discord : " + err); + resolve(null); + }) + }) +} + +function getUserGuilds(accessToken) { + return new Promise((resolve, reject) => { + fetch("https://discord.com/api/users/@me/guilds", { + headers: { + authorization: `${accessToken.token_type} ${accessToken.access_token}`, + }, + }).then(guildsResp => guildsResp.json()).then(guilds => { + if (guilds.error) { + dlog.error("Erreur lors de la récupération des guildes de l'utilisateur Discord : " + guilds.error + " : " + guilds.error_description); + resolve(null); + return; + } + resolve(guilds); + }).catch(err => { + dlog.error( "Erreur lors de la récupération des guildes de l'utilisateur Discord : " + err); + resolve(null); + }) + }) +} + +function getUserIdentity(accessToken) { + return new Promise((resolve, reject) => { + fetch("https://discord.com/api/users/@me", { + headers: { + authorization: `${accessToken.token_type} ${accessToken.access_token}`, + }, + }).then(userResp => userResp.json()).then(user => { + if (user.error) { + dlog.error("Erreur lors de la récupération des informations de l'utilisateur Discord : " + user.error + " : " + user.error_description); + resolve(null); + return; + } + resolve(user); + }).catch(err => { + dlog.error("Erreur lors de la récupération des informations de l'utilisateur Discord : " + err); + resolve(null); + }) + }) +} +module.exports = {getDiscordUser, refreshToken, getUserGuilds, getUserIdentity} \ No newline at end of file diff --git a/backend/src/server/auth/User.js b/backend/src/server/auth/User.js index 87d434c..63cd182 100644 --- a/backend/src/server/auth/User.js +++ b/backend/src/server/auth/User.js @@ -3,11 +3,11 @@ const { __glob } = require('../../utils/GlobalVars'); const { generateToken } = require('../../utils/TokenManager'); const { LogType } = require('loguix'); const clog = new LogType("User"); +const discordAuth = require('./DiscordAuth'); +const e = require('cors'); const UserDB = new Database("Users", __glob.USERFILE, []); -const userList = new Array(); -loadUsers(); - +var userList = new Array(); class User { auth; identity; @@ -23,38 +23,60 @@ class User { } setAdmin() { - if (this.labels.includes("ADMIN")) { - this.labels.splice(this.labels.indexOf("ADMIN"), 1); + const userInUserList = userList.find(user => user.identity.id === this.identity.id); + if (!userInUserList) { + clog.warn(`Utilisateur ${this.identity.username} non trouvé dans la liste des utilisateurs.`); + return null; + } + if (userInUserList.labels.includes("ADMIN")) { + userInUserList.labels.splice(userInUserList.labels.indexOf("ADMIN"), 1); clog.log(`L'utilisateur ${this.identity.username} n'est plus admin.`); } else { - this.labels.push("ADMIN"); + userInUserList.labels.push("ADMIN"); clog.log(`L'utilisateur ${this.identity.username} est maintenant admin.`); } + saveUsers() } setBan(guildId) { + const userInUserList = userList.find(user => user.identity.id === this.identity.id); + if (!userInUserList) { + clog.warn(`Utilisateur ${this.identity.username} non trouvé dans la liste des utilisateurs.`); + return null; + } const banLabel = `BAN_${guildId}`; - if (this.labels.includes(banLabel)) { - this.labels.splice(this.labels.indexOf(banLabel), 1); + if (userInUserList.labels.includes(banLabel)) { + userInUserList.labels.splice(userInUserList.labels.indexOf(banLabel), 1); clog.log(`L'utilisateur ${this.identity.username} n'est plus banni du serveur ${guildId}.`); } else { - this.labels.push(banLabel); + userInUserList.labels.push(banLabel); clog.log(`L'utilisateur ${this.identity.username} est maintenant banni du serveur ${guildId}.`); } + saveUsers() } createToken() { - const token = generateToken(); - this.tokens.push(token); + const token = generateToken(this.identity.id); + const userInUserList = userList.find(user => user.identity.id === this.identity.id); + if (!userInUserList) { + clog.warn(`Utilisateur ${this.identity.username} non trouvé dans la liste des utilisateurs.`); + return null; + } + userInUserList.tokens.push(token); saveUsers(); clog.log(`Token créé pour l'utilisateur ${this.identity.username}.`); return token; } removeToken(token) { - const index = this.tokens.indexOf(token); + const userInUserList = userList.find(user => user.identity.id === this.identity.id); + if (!userInUserList) { + clog.warn(`Utilisateur ${this.identity.username} non trouvé dans la liste des utilisateurs.`); + return null; + } + const index = userInUserList.tokens.indexOf(token); if (index > -1) { - this.tokens.splice(index, 1); + userInUserList.tokens.splice(index, 1); saveUsers(); clog.log(`Token supprimé pour l'utilisateur ${this.identity.username}.`); } else { @@ -62,6 +84,58 @@ class User { } } + clearToken() { + const userInUserList = userList.find(user => user.identity.id === this.identity.id); + if (!userInUserList) { + clog.warn(`Utilisateur ${this.identity.username} non trouvé dans la liste des utilisateurs.`); + return null; + } + userInUserList.tokens = []; + saveUsers(); + clog.log(`Tous les tokens supprimés pour l'utilisateur ${this.identity.username}.`); + return userInUserList.tokens; + } + + clearAuth() { + const userInUserList = userList.find(user => user.identity.id === this.identity.id); + if (!userInUserList) { + clog.warn(`Utilisateur ${this.identity.username} non trouvé dans la liste des utilisateurs.`); + return null; + } + userInUserList.auth = null; + saveUsers(); + clog.log(`Authentification supprimée pour l'utilisateur ${this.identity.username}.`); + } + + destroyAuth() { + const userInUserList = userList.find(user => user.identity.id === this.identity.id); + if (!userInUserList) { + clog.warn(`Utilisateur ${this.identity.username} non trouvé dans la liste des utilisateurs.`); + return null; + } + userInUserList.auth = null; + userInUserList.tokens = []; + saveUsers(); + clog.log(`Authentification et tokens supprimés pour l'utilisateur ${this.identity.username}.`); + + } + + setFullBan() { + const userInUserList = userList.find(user => user.identity.id === this.identity.id); + if (!userInUserList) { + clog.warn(`Utilisateur ${this.identity.username} non trouvé dans la liste des utilisateurs.`); + return null; + } + if (userInUserList.labels.find(label => label == "BAN")) { + userInUserList.labels.splice(userInUserList.labels.indexOf("BAN"), 1); + clog.log(`L'utilisateur ${this.identity.username} n'est plus banni.`); + } else { + userInUserList.labels.push("BAN"); + clog.log(`L'utilisateur ${this.identity.username} est maintenant banni.`); + } + saveUsers() + } + isBanned(guildId) { const banLabel = `BAN_${guildId}`; return this.labels.includes(banLabel); @@ -80,15 +154,142 @@ class User { isOwner(guildId) { const ownerLabel = `OWNER_${guildId}`; + if(this.isAdmin()) return true; return this.labels.includes(ownerLabel); } + -} -// ADD +} -function addUser(auth, identity, guilds) { +//REFRESH USER + +async function refreshAllUserInformation() { + await loadUsers(); + clog.log("Récupération des informations de tous les utilisateurs..."); + for (const user of userList) { + await refreshUserInformation(user.identity.id); + } + saveUsers(); +} + +async function refreshUserInformation(id) { + const user = getUserById(id); + if (!user) { + clog.warn(`Utilisateur ${id} non trouvé.`); + return null; + } + clog.log(`Récupération (Refresh) des informations de l'utilisateur ${user.identity.username} (${user.identity.id})...`); + if (user.auth) { + const refresh_token = user.auth.refresh_token; + const authCredientials = await discordAuth.refreshToken(refresh_token); + if(authCredientials) { + user.auth = authCredientials; + const guilds = await discordAuth.getUserGuilds(authCredientials); + const identity = await discordAuth.getUserIdentity(authCredientials); + if(identity) { + user.identity = identity; + clog.log(`Récupération réussie des informations de l'utilisateur ${user.identity.username} (${user.identity.id})`); + } + else { + clog.warn(`Erreur lors de la récupération des informations de l'utilisateur ${user.identity.username} (${user.identity.id})`); + } + if(guilds) { + user.guilds = guilds; + clog.log(`Récupération réussie des guildes de l'utilisateur ${user.identity.username} (${user.identity.id})`); + } + else { + clog.warn(`Erreur lors de la récupération des guildes de l'utilisateur ${user.identity.username} (${user.identity.id})`); + } + // Update the user in the list + const userInUserList = userList.find(u => u.identity.id === user.identity.id); + if (userInUserList) { + userInUserList.auth = user.auth; + userInUserList.guilds = user.guilds; + } + } else { + clog.warn(`Erreur lors de la récupération du token d'accès pour l'utilisateur ${user.identity.username} (${user.identity.id})`); + // Delete tokens to oblige the user to reauthenticate + // Clear auth + + user.destroyAuth(); + return null; + } + } else { + clog.warn(`Aucune authentification trouvée pour l'utilisateur ${user.identity.username} (${user.identity.id})`); + } + +} + +async function updateGuilds(id) { + const user = getUserById(id); + if (!user) { + clog.warn(`Utilisateur ${id} non trouvé.`); + return null; + } + clog.log(`Mise à jour des guildes de l'utilisateur ${user.identity.username} (${user.identity.id})...`); + if (user.auth) { + const guilds = await discordAuth.getUserGuilds(user.auth); + if(guilds) { + user.guilds = guilds; + clog.log(`Mise à jour réussie des guildes de l'utilisateur ${user.identity.username} (${user.identity.id})`); + } + else { + clog.warn(`Erreur lors de la mise à jour des guildes de l'utilisateur ${user.identity.username} (${user.identity.id})`); + } + // Update the user in the list + const userInUserList = userList.find(u => u.identity.id === user.identity.id); + if (userInUserList) { + userInUserList.auth = user.auth; + userInUserList.guilds = user.guilds; + } + } else { + clog.warn(`Aucune authentification trouvée pour l'utilisateur ${user.identity.username} (${user.identity.id})`); + } + saveUsers(); + return user.guilds; +} + +async function updateIdentity(id) { + const user = getUserById(id); + if (!user) { + clog.warn(`Utilisateur ${id} non trouvé.`); + return null; + } + clog.log(`Mise à jour de l'identité de l'utilisateur ${user.identity.username} (${user.identity.id})...`); + if (user.auth) { + const identity = await discordAuth.getUserIdentity(user.auth); + if(identity) { + user.identity = identity; + clog.log(`Mise à jour réussie de l'identité de l'utilisateur ${user.identity.username} (${user.identity.id})`); + } + else { + clog.warn(`Erreur lors de la mise à jour de l'identité de l'utilisateur ${user.identity.username} (${user.identity.id})`); + } + // Update the user in the list + const userInUserList = userList.find(u => u.identity.id === user.identity.id); + if (userInUserList) { + userInUserList.auth = user.auth; + userInUserList.identity = user.identity; + } + } else { + clog.warn(`Aucune authentification trouvée pour l'utilisateur ${user.identity.username} (${user.identity.id})`); + } + saveUsers(); + return user.identity; +} + +// EDIT USER + +/** + * + * @param {*} auth + * @param {*} identity + * @param {*} guilds + * @returns {User} user + */ +async function addUser(auth, identity, guilds) { // Check if the user already exists const existingUser = userList.find(user => user.identity.id === identity.id); if (existingUser) { @@ -107,7 +308,7 @@ function addUser(auth, identity, guilds) { const newUser = new User(auth, identity, [], [], guilds); userList.push(newUser); - saveUsers(); + await saveUsers(); return newUser; } @@ -233,6 +434,10 @@ function setGuildMod(id, guildId) { function setGuildBan(id, guildId) { const user = getUserById(id); if (user) { + if(user.isAdmin()) { + clog.warn(`L'utilisateur ${user.identity.username} est admin, il ne peut pas être banni.`); + return; + } user.setBan(guildId); saveUsers(); } else { @@ -243,21 +448,28 @@ function setGuildBan(id, guildId) { function setFullBan(id) { const user = getUserById(id); if (user) { - user.labels.push("BAN"); + if(user.isAdmin()) { + clog.warn(`L'utilisateur ${user.identity.username} est admin, il ne peut pas être banni.`); + return; + } + user.setFullBan(); saveUsers(); } else { clog.warn(`Utilisateur ${id} non trouvé.`); } } -function setGuildOwner(id, guildId) { +function setGuildOwner(id, guildId, force) { const user = getUserById(id); if (user) { const ownerLabel = `OWNER_${guildId}`; - if (user.labels.includes(ownerLabel)) { + if (user.labels.includes(ownerLabel) && !force) { user.labels.splice(user.labels.indexOf(ownerLabel), 1); clog.log(`L'utilisateur ${user.identity.username} n'est plus propriétaire du serveur ${guildId}.`); } else { + if(force && user.labels.includes(ownerLabel)) { + return; + } user.labels.push(ownerLabel); clog.log(`L'utilisateur ${user.identity.username} est maintenant propriétaire du serveur ${guildId}.`); } @@ -272,8 +484,8 @@ function setGuildOwner(id, guildId) { function loadUsers() { UserDB.load() - userList.length = 0; - for (const user in UserDB.data) { + userList = new Array(); + for (const user of UserDB.getData()) { userList.push(new User(user.auth, user.identity, user.tokens, user.labels, user.guilds)); } clog.log(`Chargement de ${userList.length} utilisateurs.`); @@ -298,4 +510,23 @@ function saveUsers() { module.exports = {User} -module.exports = {addUser, setGuildOwner , setFullBan, removeUser, getUserByToken , getUserById, getUsers, setAdmin, setGuildMod, setGuildBan, addToken, removeToken, getSimpleUsers, getSimpleUser} +module.exports = { + addUser, + setGuildOwner, + setFullBan, + removeUser, + getUserByToken, + getUserById, + getUsers, + setAdmin, + setGuildMod, + setGuildBan, + addToken, + removeToken, + getSimpleUsers, + getSimpleUser, + refreshUserInformation, + refreshAllUserInformation, + updateGuilds, + updateIdentity +}; diff --git a/backend/src/utils/Database/Database.js b/backend/src/utils/Database/Database.js index 9a6c94c..47f3d59 100644 --- a/backend/src/utils/Database/Database.js +++ b/backend/src/utils/Database/Database.js @@ -83,6 +83,10 @@ class Database { } + getData() { + return this.data + } + } diff --git a/changelog.md b/changelog.md deleted file mode 100644 index 0a138f6..0000000 --- a/changelog.md +++ /dev/null @@ -1,2 +0,0 @@ -- Setup serveur -- \ No newline at end of file