Compare commits
14 Commits
frontend-0
...
e313d4228c
Author | SHA1 | Date | |
---|---|---|---|
e313d4228c | |||
98cdae97c0 | |||
a59d7a66db | |||
e686edb0e6 | |||
3aa4201dd2 | |||
812d5c72fa | |||
12c4e2740a | |||
407d9d6b9a | |||
2a934d14ae | |||
c8c8fd71be | |||
a060d00599 | |||
f99fc24aa9 | |||
5e722e9dbc | |||
6f3847138b |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -142,3 +142,6 @@ docs/_book
|
|||||||
test/
|
test/
|
||||||
|
|
||||||
data/
|
data/
|
||||||
|
|
||||||
|
__DEBUG.js
|
||||||
|
__TEST.js
|
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
> Cette version est une refonte complète et intégrale de [Subsonics - Web](https://git.raphix.fr/subsonics/web)
|
> 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 vidéos depuis Youtube, Spotify et SoundClound
|
||||||
> - Lecture de fichiers locaux *(Uniquement sur le site)*
|
> - Lecture de fichiers locaux *(Uniquement sur le site)*
|
||||||
@@ -14,7 +14,6 @@
|
|||||||
> - Une interface refaite pour toutes les platformes.
|
> - Une interface refaite pour toutes les platformes.
|
||||||
> - Récupération de vos recommendations & Playlists Spotify / Youtube
|
> - 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
|
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)
|
[CHANGELOG](https://git.raphix.fr/subsonics/chopin/src/branch/main/changelog.md)
|
3
TODOS.md
Normal file
3
TODOS.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# List
|
||||||
|
|
||||||
|
TODO: Récupération des recommendations, playlists Youtube et Spotify
|
1717
backend/package-lock.json
generated
1717
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,30 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "chopin-backend",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"description": "Discord Bot for music - Fetching everywhere !",
|
|
||||||
"main": "src/main.js",
|
|
||||||
"nodemonConfig": {
|
|
||||||
"ext": "js, html",
|
|
||||||
"ignore": [
|
|
||||||
"*.json",
|
|
||||||
"*.html"
|
|
||||||
],
|
|
||||||
"delay": "2000000"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"start": "nodemon src/main.js"
|
|
||||||
},
|
|
||||||
"keywords": [],
|
|
||||||
"author": "Raphix",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"cors": "^2.8.5",
|
|
||||||
"discord.js": "^14.18.0",
|
|
||||||
"express": "^4.21.2",
|
|
||||||
"loguix": "^1.4.2",
|
|
||||||
"nodemon": "^3.1.9",
|
|
||||||
"socket.io": "^4.8.1",
|
|
||||||
"uuid": "^11.1.0",
|
|
||||||
"webmetrik": "^0.1.4"
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,78 +0,0 @@
|
|||||||
const { Client, GatewayIntentBits, Collection, ActivityType, REST, Routes } = require("discord.js")
|
|
||||||
const fs = require("node:fs")
|
|
||||||
const path = require("path")
|
|
||||||
const { __glob } = require("../utils/GlobalVars")
|
|
||||||
const { LogType } = require("loguix")
|
|
||||||
const config = require("../utils/Database/Configuration")
|
|
||||||
const metric = require("webmetrik")
|
|
||||||
|
|
||||||
const dlog = new LogType("Discord")
|
|
||||||
|
|
||||||
const client = new Client({
|
|
||||||
intents:[GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildMembers],
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
//Getter for the client
|
|
||||||
function getClient() {
|
|
||||||
return client
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function init() {
|
|
||||||
|
|
||||||
client.once('ready', () => {
|
|
||||||
dlog.log("Connexion au Bot Discord réussi ! Connecté en tant que : " + client.user.tag)
|
|
||||||
|
|
||||||
const Activity = require("./Activity")
|
|
||||||
Activity.idleActivity()
|
|
||||||
|
|
||||||
const CommandUpdater = require("./CommandUpdater")
|
|
||||||
CommandUpdater.init()
|
|
||||||
|
|
||||||
const commandManager = client.application.commands;
|
|
||||||
|
|
||||||
if (!commandManager) {
|
|
||||||
dlog.error('Command manager not available.');
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
commandManager.set([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
dlog.step.end("d_init")
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("interactionCreate", (interaction) => {
|
|
||||||
|
|
||||||
if(!interaction.isCommand()) return;
|
|
||||||
|
|
||||||
var numberOfCommands = new metric.Metric("numberOfCommands", "Nombre de commandes éxécutées")
|
|
||||||
numberOfCommands.setValue(numberOfCommands.getValue() + 1)
|
|
||||||
|
|
||||||
const command = client.commands.get(interaction.commandName)
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
// Create a metric to count the number of commands executed by each user
|
|
||||||
const userCommand = new metric.Metric("userCommand_" + interaction.member.user.username, "Nombre de commandes éxécutées par l'utilisateur : " + interaction.member.user.username)
|
|
||||||
userCommand.setValue(userCommand.getValue() + 1)
|
|
||||||
dlog.log(interaction.member.user.username + "-> /" + interaction.commandName)
|
|
||||||
command.execute(client, interaction)
|
|
||||||
} catch(error) {
|
|
||||||
|
|
||||||
dlog.error(interaction.member.user.username + "-> /" + interaction.commandName + " : ERREUR RENCONTRE")
|
|
||||||
dlog.error(error)
|
|
||||||
interaction.reply({content:"Erreur lors de l'éxécution de la commande !", ephemeral: true})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
client.login(config.getToken())
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {init, getClient}
|
|
||||||
|
|
||||||
|
|
@@ -1,14 +0,0 @@
|
|||||||
const { Command } = require('../Command');
|
|
||||||
const { Embed } = require('../Embed');
|
|
||||||
|
|
||||||
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.send(interaction)
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
module.exports = {command}
|
|
@@ -1,33 +0,0 @@
|
|||||||
const {Database} = require("./Database")
|
|
||||||
const {__glob} = require("../GlobalVars")
|
|
||||||
const {LogType} = require("loguix")
|
|
||||||
const path = require("path")
|
|
||||||
|
|
||||||
const clog = new LogType("Configuration")
|
|
||||||
|
|
||||||
const config = new Database("config", __glob.DATA + path.sep + "config.json", {
|
|
||||||
token: "",
|
|
||||||
report: {
|
|
||||||
channel : "",
|
|
||||||
contact : ""
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function getToken() {
|
|
||||||
return config.data.token
|
|
||||||
}
|
|
||||||
|
|
||||||
function getReportChannel() {
|
|
||||||
return config.data.report.channel
|
|
||||||
}
|
|
||||||
|
|
||||||
function getReportContact() {
|
|
||||||
return config.data.report.contact
|
|
||||||
}
|
|
||||||
|
|
||||||
if(getToken() == "") {
|
|
||||||
clog.error("Impossible de démarrer sans token valide")
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {getToken, getReportChannel, getReportContact}
|
|
17
changelog.md
17
changelog.md
@@ -1,17 +0,0 @@
|
|||||||
# **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
|
|
6944
package-lock.json
generated
Normal file
6944
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
47
package.json
Normal file
47
package.json
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"name": "chopin-backend",
|
||||||
|
"version": "1.1.1",
|
||||||
|
"description": "Discord Bot for music - Fetching everywhere !",
|
||||||
|
"main": "src/main.js",
|
||||||
|
"nodemonConfig": {
|
||||||
|
"ext": "js, html",
|
||||||
|
"ignore": [
|
||||||
|
"*.json",
|
||||||
|
"*.html"
|
||||||
|
],
|
||||||
|
"delay": "2000000"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"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.10",
|
||||||
|
"@distube/ytsr": "2.0.4",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"discord-player": "^7.1.0",
|
||||||
|
"discord.js": "^14.18.0",
|
||||||
|
"express": "^4.21.2",
|
||||||
|
"ffmpeg-static": "^5.2.0",
|
||||||
|
"ffprobe": "^1.1.2",
|
||||||
|
"ffprobe-static": "^3.1.0",
|
||||||
|
"fluent-ffmpeg": "^2.1.3",
|
||||||
|
"googleapis": "^149.0.0",
|
||||||
|
"libsodium-wrappers": "^0.7.15",
|
||||||
|
"loguix": "^1.4.2",
|
||||||
|
"mime-types": "^3.0.1",
|
||||||
|
"nodemon": "^3.1.10",
|
||||||
|
"pm2": "^5.4.3",
|
||||||
|
"socket.io": "^4.8.1",
|
||||||
|
"soundcloud.ts": "^0.6.3",
|
||||||
|
"spotify-web-api-node": "^5.0.2",
|
||||||
|
"uuid": "^11.1.0",
|
||||||
|
"webmetrik": "^0.1.4",
|
||||||
|
"ytfps": "^1.2.0"
|
||||||
|
}
|
||||||
|
}
|
@@ -13,10 +13,7 @@ function setMusicActivity(songName, artistName, imageUrl) {
|
|||||||
const client = bot.getClient()
|
const client = bot.getClient()
|
||||||
client.user.setActivity(`${songName} - ${artistName}`,{
|
client.user.setActivity(`${songName} - ${artistName}`,{
|
||||||
type: ActivityType.Listening,
|
type: ActivityType.Listening,
|
||||||
/*assets: {
|
url: imageUrl
|
||||||
largeImage: imageUrl,
|
|
||||||
largeText: songName
|
|
||||||
}*/
|
|
||||||
});
|
});
|
||||||
dlog.log(`Activité mise à jour : ${songName} - ${artistName}`)
|
dlog.log(`Activité mise à jour : ${songName} - ${artistName}`)
|
||||||
}
|
}
|
231
src/discord/Bot.js
Normal file
231
src/discord/Bot.js
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
const { Client, GatewayIntentBits, Collection, ActivityType, REST, Routes } = require("discord.js")
|
||||||
|
const fs = require("node:fs")
|
||||||
|
const path = require("path")
|
||||||
|
const { __glob } = require("../utils/GlobalVars")
|
||||||
|
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")
|
||||||
|
const glog = new LogType("GuildUpdater")
|
||||||
|
dlog.log("Initialisation du Bot Discord")
|
||||||
|
|
||||||
|
const membersVoices = new Map()
|
||||||
|
const timers = new Map()
|
||||||
|
const guilds = new Map()
|
||||||
|
|
||||||
|
var operational = false
|
||||||
|
|
||||||
|
const client = new Client({
|
||||||
|
intents:[GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildMembers],
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
//Getter for the client
|
||||||
|
function getClient() {
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGuilds() {
|
||||||
|
return guilds
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMembersVoices() {
|
||||||
|
return membersVoices
|
||||||
|
}
|
||||||
|
|
||||||
|
function isReady() {
|
||||||
|
return operational
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGuildMembers(guildId) {
|
||||||
|
const guild = client.guilds.cache.get(guildId)
|
||||||
|
if(!guild) {
|
||||||
|
dlog.error("Guild not found: " + guildId)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return guild.members.cache.map(member => member.user.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getChannel(guildId, channelId) {
|
||||||
|
return client.guilds.cache.get(guildId).channels.cache.get(channelId)
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
|
||||||
|
client.once('ready', async () => {
|
||||||
|
dlog.log("Connexion au Bot Discord réussi ! Connecté en tant que : " + client.user.tag)
|
||||||
|
await refreshGuilds()
|
||||||
|
await refreshAllUserInformation()
|
||||||
|
|
||||||
|
const Activity = require("./Activity")
|
||||||
|
Activity.idleActivity()
|
||||||
|
|
||||||
|
const CommandUpdater = require("./CommandUpdater")
|
||||||
|
CommandUpdater.init()
|
||||||
|
|
||||||
|
const commandManager = client.application.commands
|
||||||
|
if (!commandManager) {
|
||||||
|
dlog.error('Command manager not available.')
|
||||||
|
} else {
|
||||||
|
commandManager.set([])
|
||||||
|
}
|
||||||
|
|
||||||
|
dlog.step.end("d_init")
|
||||||
|
operational = true
|
||||||
|
})
|
||||||
|
|
||||||
|
client.on("interactionCreate", (interaction) => {
|
||||||
|
|
||||||
|
if(!interaction.isCommand()) return;
|
||||||
|
|
||||||
|
var numberOfCommands = new metric.Metric("numberOfCommands", "Nombre de commandes éxécutées")
|
||||||
|
numberOfCommands.setValue(numberOfCommands.getValue() + 1)
|
||||||
|
|
||||||
|
const command = client.commands.get(interaction.commandName)
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Create a metric to count the number of commands executed by each user
|
||||||
|
const userCommand = new metric.Metric("userCommand_" + interaction.member.user.username, "Nombre de commandes éxécutées par l'utilisateur : " + interaction.member.user.username)
|
||||||
|
userCommand.setValue(userCommand.getValue() + 1)
|
||||||
|
dlog.log(interaction.member.user.username + "-> /" + interaction.commandName)
|
||||||
|
command.execute(client, interaction)
|
||||||
|
} catch(error) {
|
||||||
|
|
||||||
|
dlog.error(interaction.member.user.username + "-> /" + interaction.commandName + " : ERREUR RENCONTRE")
|
||||||
|
dlog.error(error)
|
||||||
|
interaction.reply({content:"Erreur lors de l'éxécution de la commande !", ephemeral: true})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
client.on("guildMemberAdd", async (member) => {
|
||||||
|
dlog.log("Nouveau membre dans la guilde : " + member.guild.name + " (" + member.guild.id + ") - Membre : " + member.user.username + " (" + member.user.id + ")")
|
||||||
|
await refreshGuilds()
|
||||||
|
process.emit("USERS_UPDATE")
|
||||||
|
})
|
||||||
|
|
||||||
|
client.on("guildMemberRemove", async (member) => {
|
||||||
|
dlog.log("Membre quitté la guilde : " + member.guild.name + " (" + member.guild.id + ") - Membre : " + member.user.username + " (" + member.user.id + ")")
|
||||||
|
await refreshGuilds()
|
||||||
|
membersVoices.delete(member.user.id)
|
||||||
|
process.emit("USERS_UPDATE")
|
||||||
|
})
|
||||||
|
|
||||||
|
// If a new guild is added, we will add it to the guilds map
|
||||||
|
client.on("guildCreate", async (guild) => {
|
||||||
|
|
||||||
|
await refreshGuilds()
|
||||||
|
glog.log("Guilde ajoutée : " + guild.name + " (" + guild.id + ")")
|
||||||
|
process.emit("USERS_UPDATE")
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
client.on("guildDelete", (guild) => {
|
||||||
|
dlog.log("Guilde supprimée : " + guild.name)
|
||||||
|
guilds.delete(guild.id)
|
||||||
|
glog.log("Guilde supprimée : " + guild.name + " (" + guild.id + ")")
|
||||||
|
process.emit("USERS_UPDATE")
|
||||||
|
})
|
||||||
|
|
||||||
|
client.on('guildUpdate', async () => {
|
||||||
|
await refreshGuilds()
|
||||||
|
process.emit("USERS_UPDATE")
|
||||||
|
})
|
||||||
|
|
||||||
|
client.on("voiceStateUpdate", (oldMember, newMember) => {
|
||||||
|
membersVoices.set(newMember.id, {
|
||||||
|
guildId: newMember.guild.id,
|
||||||
|
channelId: newMember.channelId,
|
||||||
|
})
|
||||||
|
|
||||||
|
const player = new Player(newMember.guild.id)
|
||||||
|
|
||||||
|
if(player.connection && player.channelId) {
|
||||||
|
client.channels.fetch(player.channelId).then(channel => {
|
||||||
|
|
||||||
|
if(channel.members.size <= 1) {
|
||||||
|
|
||||||
|
// If the player is alone in the channel, we will destroy it in 10 minutes
|
||||||
|
// 10 minutes = 600000 ms
|
||||||
|
// 10 second = 10000 ms
|
||||||
|
timers.set(newMember.guild.id, setTimeout(() => {
|
||||||
|
const getPlayer = new Player(newMember.guild.id)
|
||||||
|
if(getPlayer.connection && player.channelId) {
|
||||||
|
getPlayer.leave()
|
||||||
|
dlog.log("[Automatic Task] Guild Id :" + newMember.guild.id + " - Player supprimé : " + channel.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
}, 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)
|
||||||
|
clearTimeout(timers.get(newMember.guild.id))
|
||||||
|
timers.delete(newMember.guild.id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
client.login(config.getToken())
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshGuilds() {
|
||||||
|
glog.step.init("d_refresh_guilds", "Rafraichissement des guildes")
|
||||||
|
await client.guilds.fetch()
|
||||||
|
for(const guild of client.guilds.cache.values()) {
|
||||||
|
await guild.members.fetch()
|
||||||
|
var allMembersOfGuild = guild.members.cache.map(member => member.user.id)
|
||||||
|
const missingPermissions = checkRequiredPermission(guild.members.me)
|
||||||
|
if(missingPermissions.length > 0) {
|
||||||
|
dlog.error("Le bot n'a pas les permissions nécessaires pour rejoindre la guilde : " + guild.name)
|
||||||
|
guild.leave()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guilds.set(guild.id, {
|
||||||
|
id: guild.id,
|
||||||
|
name: guild.name,
|
||||||
|
allMembers: allMembersOfGuild,
|
||||||
|
icon: guild.iconURL(),
|
||||||
|
banner: guild.bannerURL(),
|
||||||
|
description: guild.description,
|
||||||
|
features: guild.features,
|
||||||
|
owner: guild.ownerId,
|
||||||
|
joinedAt: guild.joinedAt,
|
||||||
|
createdAt: guild.createdAt,
|
||||||
|
})
|
||||||
|
glog.log("Guilde rafraichie : " + guild.name + " (" + guild.id + ")")
|
||||||
|
}
|
||||||
|
glog.step.end("d_refresh_guilds")
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkRequiredPermission(guildMember) {
|
||||||
|
const requiredPermissions = [
|
||||||
|
'CreateInstantInvite', 'AddReactions',
|
||||||
|
'Stream', 'ViewChannel',
|
||||||
|
'SendMessages', 'SendTTSMessages',
|
||||||
|
'EmbedLinks', 'AttachFiles',
|
||||||
|
'ReadMessageHistory', 'UseExternalEmojis',
|
||||||
|
'Connect', 'Speak',
|
||||||
|
'UseVAD', 'ChangeNickname',
|
||||||
|
'UseApplicationCommands', 'RequestToSpeak',
|
||||||
|
'CreatePublicThreads', 'CreatePrivateThreads',
|
||||||
|
'UseExternalStickers', 'SendMessagesInThreads',
|
||||||
|
'UseEmbeddedActivities', 'UseSoundboard',
|
||||||
|
'UseExternalSounds', 'SendVoiceMessages',
|
||||||
|
'SendPolls', 'UseExternalApps'
|
||||||
|
]
|
||||||
|
return requiredPermissions.filter(permission => !guildMember.permissions.has(permission));
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {init, getClient, getGuilds, getMembersVoices, getChannel, getGuildMembers, isReady}
|
||||||
|
|
||||||
|
|
26
src/discord/Button.js
Normal file
26
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 };
|
@@ -53,10 +53,18 @@ class Command {
|
|||||||
})
|
})
|
||||||
SlashCommand.addStringOption(option => option.setName(SelOption.name).setDescription(SelOption.description).setRequired(SelOption.required).addChoices(choices))
|
SlashCommand.addStringOption(option => option.setName(SelOption.name).setDescription(SelOption.description).setRequired(SelOption.required).addChoices(choices))
|
||||||
}
|
}
|
||||||
|
if(SelOption.type === "FILE") {
|
||||||
|
SlashCommand.addAttachmentOption(option => option.setName(SelOption.name).setDescription(SelOption.description).setRequired(SelOption.required))
|
||||||
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {SlashCommandBuilder}
|
||||||
|
* @param {Client} client
|
||||||
|
* @param {Interaction} interaction
|
||||||
|
*/
|
||||||
this.data = {data: SlashCommand, async execute(client, interaction) {callback(client, interaction)}}
|
this.data = {data: SlashCommand, async execute(client, interaction) {callback(client, interaction)}}
|
||||||
|
|
||||||
}
|
}
|
@@ -50,10 +50,6 @@ client.commands = new Collection()
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
dlog.step.end("d_commands_refresh")
|
dlog.step.end("d_commands_refresh")
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// And of course, make sure you catch and log any errors!
|
// And of course, make sure you catch and log any errors!
|
@@ -10,8 +10,8 @@ const command = new Command("about", "Affiche des informations sur le bot", (cli
|
|||||||
const minutes = Math.floor((uptime % 3600) / 60);
|
const minutes = Math.floor((uptime % 3600) / 60);
|
||||||
const seconds = Math.floor(uptime % 60);
|
const seconds = Math.floor(uptime % 60);
|
||||||
|
|
||||||
const embed = new Embed()
|
const embed = new Embed(interaction)
|
||||||
embed.setColor(0xb0f542)
|
embed.setColor(237, 12, 91)
|
||||||
embed.setThumbnail("https://cdn.discordapp.com/avatars/" + client.user.id + "/" + client.user.avatar + ".png")
|
embed.setThumbnail("https://cdn.discordapp.com/avatars/" + client.user.id + "/" + client.user.avatar + ".png")
|
||||||
embed.setTitle('Subsonics - Chopin')
|
embed.setTitle('Subsonics - Chopin')
|
||||||
embed.addField('Informations',"")
|
embed.addField('Informations',"")
|
||||||
@@ -20,15 +20,18 @@ const command = new Command("about", "Affiche des informations sur le bot", (cli
|
|||||||
embed.addField("Ping", `${client.ws.ping} ms `, true)
|
embed.addField("Ping", `${client.ws.ping} ms `, true)
|
||||||
embed.addField("Réalisé par", "Raphix - 2025", true)
|
embed.addField("Réalisé par", "Raphix - 2025", true)
|
||||||
embed.addColumn()
|
embed.addColumn()
|
||||||
embed.addField('Versions',"")
|
embed.addField('Versions :',"")
|
||||||
embed.addField('Node.js', process.version,true)
|
embed.addField('Node.js', process.version,true)
|
||||||
embed.addField('Discord.js', packageJson.dependencies["discord.js"].replace("^", ""),true)
|
embed.addField('Discord.js', packageJson.dependencies["discord.js"].replace("^", ""),true)
|
||||||
embed.addColumn()
|
embed.addColumn()
|
||||||
embed.addField('Webmetrik', packageJson.dependencies["webmetrik"].replace("^", ""),true)
|
embed.addField('Webmetrik', packageJson.dependencies["webmetrik"].replace("^", ""),true)
|
||||||
embed.addField('Loguix', packageJson.dependencies["loguix"].replace("^", ""),true)
|
embed.addField('Loguix', packageJson.dependencies["loguix"].replace("^", ""),true)
|
||||||
embed.addColumn()
|
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)
|
embed.send()
|
||||||
|
|
||||||
|
|
||||||
})
|
})
|
@@ -3,7 +3,7 @@ const { Embed } = require('../Embed');
|
|||||||
|
|
||||||
const command = new Command("help", "Affiche la liste des commandes", (client, interaction) => {
|
const command = new Command("help", "Affiche la liste des commandes", (client, interaction) => {
|
||||||
|
|
||||||
const embed = new Embed()
|
const embed = new Embed(interaction)
|
||||||
embed.setColor(0x03ff2d)
|
embed.setColor(0x03ff2d)
|
||||||
embed.setTitle('Comment assister au concert ?')
|
embed.setTitle('Comment assister au concert ?')
|
||||||
embed.setDescription("**Eh ! Tu as eu ton ticket ? Tant mieux ! Voici la liste des commandes à utiliser dans le salon prévu à cet effet !**")
|
embed.setDescription("**Eh ! Tu as eu ton ticket ? Tant mieux ! Voici la liste des commandes à utiliser dans le salon prévu à cet effet !**")
|
||||||
@@ -18,7 +18,7 @@ const command = new Command("help", "Affiche la liste des commandes", (client, i
|
|||||||
option.choices.forEach(choice => {
|
option.choices.forEach(choice => {
|
||||||
choices.push(choice.name)
|
choices.push(choice.name)
|
||||||
})
|
})
|
||||||
CommandName += " " + choices.join(" | ")
|
CommandName += " <" + choices.join(" | ") +">"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -28,7 +28,7 @@ const command = new Command("help", "Affiche la liste des commandes", (client, i
|
|||||||
})
|
})
|
||||||
embed.addField("La queue et la gestion du redémarrage se fait par le site https://subsonics.raphix.fr/", ":star:" )
|
embed.addField("La queue et la gestion du redémarrage se fait par le site https://subsonics.raphix.fr/", ":star:" )
|
||||||
embed.setThumbnail("https://static.wikia.nocookie.net/codelyoko/images/9/95/Subdigitals.jpg/revision/latest/scale-to-width-down/180?cb=20120105180510&path-prefix=fr");
|
embed.setThumbnail("https://static.wikia.nocookie.net/codelyoko/images/9/95/Subdigitals.jpg/revision/latest/scale-to-width-down/180?cb=20120105180510&path-prefix=fr");
|
||||||
embed.send(interaction)
|
embed.send()
|
||||||
})
|
})
|
||||||
|
|
||||||
module.exports = {command}
|
module.exports = {command}
|
20
src/discord/Commands/Invite.js
Normal file
20
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(interaction)
|
||||||
|
embed.setColor(0xFF007F)
|
||||||
|
embed.setTitle('**Inviter le bot sur d\'autres serveurs**')
|
||||||
|
embed.setDescription('Vous pouvez m\'inviter sur d\'autres serveurs en cliquant sur le bouton ci-dessous.')
|
||||||
|
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()
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = {command}
|
34
src/discord/Commands/Leave.js
Normal file
34
src/discord/Commands/Leave.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
const {Command} = require("../Command")
|
||||||
|
const {Embed, EmbedError} = require("../Embed")
|
||||||
|
const {Player, AllPlayers} = require("../../player/Player")
|
||||||
|
|
||||||
|
const command = new Command("leave", "Quitter le salon vocal", (client, interaction) => {
|
||||||
|
|
||||||
|
if(!interaction.member.voice.channel) return new EmbedError("Vous devez rejoindre un salon vocal pour arrêter le bot !", interaction)
|
||||||
|
const channel = interaction.member.voice.channel
|
||||||
|
var embed = new Embed(interaction)
|
||||||
|
|
||||||
|
if(AllPlayers.has(channel.guildId)) {
|
||||||
|
const player = AllPlayers.get(channel.guildId)
|
||||||
|
if(!player?.connected) {
|
||||||
|
return embed.returnError("Le bot n'est pas connecté à ce salon vocal")
|
||||||
|
}
|
||||||
|
player.leave()
|
||||||
|
|
||||||
|
|
||||||
|
embed.setColor(200, 20, 20)
|
||||||
|
embed.setTitle('**Déconnexion**')
|
||||||
|
embed.setDescription('Déconnexion du salon vocal')
|
||||||
|
embed.setThumbnail("https://www.iconsdb.com/icons/download/white/phone-51-64.png")
|
||||||
|
|
||||||
|
embed.send()
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
embed.returnError("Le bot n'est pas connecté à ce salon vocal")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = {command}
|
55
src/discord/Commands/Media.js
Normal file
55
src/discord/Commands/Media.js
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
const {Command} = require('../Command');
|
||||||
|
const {Embed, EmbedError} = require('../Embed');
|
||||||
|
const { Player } = require('../../player/Player');
|
||||||
|
const { Song } = require('../../player/Song');
|
||||||
|
const history = require('../../playlists/History');
|
||||||
|
|
||||||
|
const command = new Command("media", "Lire un média MP3/WAV dans un salon vocal", async (client, interaction) => {
|
||||||
|
|
||||||
|
if(!interaction.member.voice.channel) return new EmbedError("Vous devez rejoindre un salon vocal pour jouer un média !", interaction, true)
|
||||||
|
|
||||||
|
const media = interaction.options.get("media")
|
||||||
|
const now = interaction.options.getBoolean("now") || false
|
||||||
|
|
||||||
|
if(media.attachment.contentType != "audio/mpeg" && media.attachment.contentType != "audio/wav") return new EmbedError("Le média doit être un fichier audio MP3 ou WAV !", interaction)
|
||||||
|
|
||||||
|
|
||||||
|
const embed = new Embed(interaction)
|
||||||
|
embed.setColor(0x15e6ed)
|
||||||
|
const channel = interaction.member.voice.channel
|
||||||
|
const song = new Song()
|
||||||
|
await song.processMedia(media, interaction.user.username)
|
||||||
|
|
||||||
|
const player = new Player(channel.guildId)
|
||||||
|
player.join(channel)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if(now) {
|
||||||
|
player.play(song)
|
||||||
|
embed.setTitle('**Lecture immédiate**')
|
||||||
|
|
||||||
|
} else {
|
||||||
|
player.add(song)
|
||||||
|
embed.setTitle('**Ajout à liste de lecture**')
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
history.addToPersonalHistory(interaction.user.id, song)
|
||||||
|
|
||||||
|
embed.setDescription('**Titre : **' + song.title)
|
||||||
|
embed.addField('**Durée : **', song.readduration)
|
||||||
|
embed.addField("**Artiste : **",song.author)
|
||||||
|
embed.addField('**Demandé par **' + interaction.member.user.username, "")
|
||||||
|
embed.setThumbnail(song.thumbnail)
|
||||||
|
|
||||||
|
|
||||||
|
embed.send()
|
||||||
|
|
||||||
|
|
||||||
|
}, [{type: "FILE", name: "media", description: "Fichier audio à lire", required: true},
|
||||||
|
{type:"BOOLEAN", name: "now", description: "Lire le média maintenant", required: false}]
|
||||||
|
)
|
||||||
|
|
||||||
|
module.exports = {command}
|
44
src/discord/Commands/Pause.js
Normal file
44
src/discord/Commands/Pause.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
const {Command} = require("../Command")
|
||||||
|
const {Embed, EmbedError} = require("../Embed")
|
||||||
|
const { Player } = require("../../player/Player")
|
||||||
|
|
||||||
|
const command = new Command("pause", "Mettre en pause / Reprendre la musique en cours", (client, interaction) => {
|
||||||
|
|
||||||
|
|
||||||
|
if(!interaction.member.voice.channel) return new EmbedError("Vous devez rejoindre un salon vocal pour mettre en pause la musique !", interaction)
|
||||||
|
|
||||||
|
const channel = interaction.member.voice.channel
|
||||||
|
const player = new Player(channel.guildId)
|
||||||
|
const result = player.pause()
|
||||||
|
|
||||||
|
|
||||||
|
var embed = new Embed(interaction)
|
||||||
|
embed.setColor(0x03ff2d)
|
||||||
|
|
||||||
|
result.then((pause) => {
|
||||||
|
|
||||||
|
if(pause == "no_music") {
|
||||||
|
embed.returnError("Il n'y a pas de musique en cours de lecture")
|
||||||
|
|
||||||
|
} else if(pause) {
|
||||||
|
embed.setTitle('Musique en pause')
|
||||||
|
embed.setDescription("La musique a été mise en pause")
|
||||||
|
embed.setThumbnail("https://www.iconsdb.com/icons/download/white/pause-64.png")
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
embed.setTitle('Musique reprise')
|
||||||
|
embed.setDescription("La musique a été reprise")
|
||||||
|
embed.setThumbnail("https://www.iconsdb.com/icons/download/white/play-64.png")
|
||||||
|
}
|
||||||
|
|
||||||
|
embed.send()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Réponse en embed
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = {command}
|
93
src/discord/Commands/Play.js
Normal file
93
src/discord/Commands/Play.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
const { Command } = require("../Command");
|
||||||
|
const { Embed, EmbedError } = require("../Embed");
|
||||||
|
const { Player } = require("../../player/Player");
|
||||||
|
const Finder = require("../../player/Finder");
|
||||||
|
const { Playlist } = require("../../playlists/Playlist");
|
||||||
|
const spotify = require("../../media/SpotifyInformation");
|
||||||
|
const history = require("../../playlists/History");
|
||||||
|
|
||||||
|
const command = new Command("play", "Jouer une musique à partir d'un lien dans un salon vocal", async (client, interaction) => {
|
||||||
|
|
||||||
|
if(!interaction.member.voice.channel) return new EmbedError("Vous devez rejoindre un salon vocal pour jouer une musique !", interaction)
|
||||||
|
|
||||||
|
const url = interaction.options.get("url")
|
||||||
|
const channel = interaction.member.voice.channel
|
||||||
|
const now = interaction.options.getBoolean("now") || false
|
||||||
|
const embed = new Embed(interaction)
|
||||||
|
await Finder.search(url.value).then(async (song) => {
|
||||||
|
if(!song) return embed.returnError("Impossible de trouver la musique à partir du lien donné ou des mots clés donnés")
|
||||||
|
|
||||||
|
const player = new Player(channel.guildId)
|
||||||
|
player.join(channel)
|
||||||
|
|
||||||
|
|
||||||
|
embed.setColor(0x15e6ed)
|
||||||
|
|
||||||
|
// Check if song is playlist
|
||||||
|
if(song instanceof Playlist) {
|
||||||
|
|
||||||
|
embed.setDescription('**Playlist : **' + song.songs.length + ' musiques')
|
||||||
|
embed.addField('**Titre : **' + song.title, "")
|
||||||
|
embed.addField('**Demandé par : **', interaction.member.user.username,)
|
||||||
|
embed.addField('**Auteur : **', song.author)
|
||||||
|
embed.addField('**Provient de : **', song.type.replace(/^\w/, (c) => c.toUpperCase()))
|
||||||
|
if(!song.type == "spotify") {
|
||||||
|
embed.addField('**Durée : **', song.readduration)
|
||||||
|
}
|
||||||
|
embed.addField('**Lien : **', song.url)
|
||||||
|
embed.addField(":warning: La récupération des musiques peut prendre du temps", "Veuillez patienter ... et éviter de lancer d'autres commandes")
|
||||||
|
|
||||||
|
|
||||||
|
embed.setThumbnail(song.thumbnail)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
embed.setDescription('**Titre : **' + song.title)
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if(now) {
|
||||||
|
embed.setTitle("Lecture immédiate")
|
||||||
|
} else {
|
||||||
|
embed.setTitle("Ajoutée à la file d'attente")
|
||||||
|
}
|
||||||
|
|
||||||
|
embed.send()
|
||||||
|
|
||||||
|
if(song instanceof Playlist) {
|
||||||
|
if(song.type == "spotify") {
|
||||||
|
song = await spotify.getTracks(song)
|
||||||
|
}
|
||||||
|
player.readPlaylist(song, now)
|
||||||
|
} else {
|
||||||
|
|
||||||
|
|
||||||
|
if(now) {
|
||||||
|
|
||||||
|
player.play(song)
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
player.add(song)
|
||||||
|
}
|
||||||
|
|
||||||
|
history.addToPersonalHistory(interaction.user.id, song)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
}, [{type: "STRING", name: "url", description: "Recherche / Lien audio (Youtube / Soundclound / Spotify)", required: true},
|
||||||
|
{type:"BOOLEAN", name: "now", description: "Lire le média maintenant", required: false}]
|
||||||
|
)
|
||||||
|
|
||||||
|
module.exports = {command}
|
51
src/discord/Commands/Previous.js
Normal file
51
src/discord/Commands/Previous.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
const {Command} = require("../Command")
|
||||||
|
const {Embed, EmbedError} = require("../Embed")
|
||||||
|
const { Player, AllPlayers } = require("../../player/Player")
|
||||||
|
|
||||||
|
const command = new Command("previous", "Passe à la musique précédente", (client, interaction) => {
|
||||||
|
|
||||||
|
if(!interaction.member.voice.channel) return new EmbedError("Vous devez rejoindre un salon vocal pour passer à la musique suivante !", interaction)
|
||||||
|
|
||||||
|
const channel = interaction.member.voice.channel
|
||||||
|
var embed = new Embed(interaction)
|
||||||
|
|
||||||
|
if(AllPlayers.has(channel.guildId)) {
|
||||||
|
|
||||||
|
const player = new Player(channel.guildId)
|
||||||
|
const result = player.previous()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
embed.setColor(0x15e6ed)
|
||||||
|
|
||||||
|
result.then((song) => {
|
||||||
|
|
||||||
|
if(song == "no_music") {
|
||||||
|
embed.returnError("Il n'y a pas de musique précédemment jouée")
|
||||||
|
|
||||||
|
} else if(song) {
|
||||||
|
|
||||||
|
// Result is a song
|
||||||
|
|
||||||
|
|
||||||
|
embed.setTitle('**Musique précédente !**')
|
||||||
|
embed.setDescription('**Titre : **' + song.title)
|
||||||
|
embed.addField('**Durée : **'+ song.readduration, "")
|
||||||
|
embed.addField("**Artiste : **" + song.author, "")
|
||||||
|
embed.setThumbnail(song.thumbnail)
|
||||||
|
|
||||||
|
embed.send()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return embed.returnError("Le bot n'est pas connecté", interaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = {command}
|
43
src/discord/Commands/Queue.js
Normal file
43
src/discord/Commands/Queue.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
const {Command} = require("../Command")
|
||||||
|
const {Embed, EmbedError} = require("../Embed")
|
||||||
|
const { Player, AllPlayers } = require("../../player/Player")
|
||||||
|
|
||||||
|
const command = new Command("liste", "Affiche la file d'attente", (client, interaction) => {
|
||||||
|
|
||||||
|
const channel = interaction.member.voice.channel
|
||||||
|
var embed = new Embed(interaction)
|
||||||
|
|
||||||
|
|
||||||
|
if(AllPlayers.has(channel.guildId)) {
|
||||||
|
|
||||||
|
const player = new Player(channel.guildId)
|
||||||
|
const queue = player.queue.getNext()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if(queue.length == 0) {
|
||||||
|
embed.returnError("Il n'y a pas de musique en file d'attente")
|
||||||
|
|
||||||
|
} else if(queue.length > 0) {
|
||||||
|
|
||||||
|
// Result is a song
|
||||||
|
embed.setColor(0x15e6ed)
|
||||||
|
embed.setThumbnail("https://www.iconsdb.com/icons/download/white/list-2-64.png")
|
||||||
|
embed.setTitle('**File d\'attente :**')
|
||||||
|
embed.setDescription('**' + queue.length + ' musiques**')
|
||||||
|
queue.forEach((song, index) => {
|
||||||
|
// max 24 fields
|
||||||
|
if(index > 10) return
|
||||||
|
embed.addField(`**${index+1} - ${song.title}**`, `**Durée : **${song.readduration}\n**Artiste : **${song.author}`)
|
||||||
|
})
|
||||||
|
embed.send()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
embed.returnError("Le bot n'est pas connecté")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = {command}
|
@@ -5,7 +5,7 @@ const { Report } = require('../ReportSender');
|
|||||||
const command = new Command("report", "Signaler un problème avec le bot", (client, interaction) => {
|
const command = new Command("report", "Signaler un problème avec le bot", (client, interaction) => {
|
||||||
const report = new Report(interaction.user.username, interaction.options.getString("type"), interaction.options.getString("description"))
|
const report = new Report(interaction.user.username, interaction.options.getString("type"), interaction.options.getString("description"))
|
||||||
const result = report.send()
|
const result = report.send()
|
||||||
const embed = new Embed()
|
const embed = new Embed(interaction)
|
||||||
|
|
||||||
|
|
||||||
result.then((res) => {
|
result.then((res) => {
|
||||||
@@ -20,7 +20,7 @@ const command = new Command("report", "Signaler un problème avec le bot", (clie
|
|||||||
embed.setDescription("Votre rapport a bien été envoyé !")
|
embed.setDescription("Votre rapport a bien été envoyé !")
|
||||||
|
|
||||||
}
|
}
|
||||||
embed.send(interaction)
|
embed.send()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
27
src/discord/Commands/Restart.js
Normal file
27
src/discord/Commands/Restart.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
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(interaction)
|
||||||
|
embed.setColor(150, 20, 20)
|
||||||
|
embed.setTitle('Redémarrage')
|
||||||
|
embed.setDescription("Veuillez patientez, le bot va redémarrer dans un instant ! :arrows_counterclockwise:")
|
||||||
|
embed.addField('Raison', reason)
|
||||||
|
embed.send()
|
||||||
|
},
|
||||||
|
[{type: "STRING", name: "reason", description: "Raison du redémarrage", required: true}]
|
||||||
|
)
|
||||||
|
|
||||||
|
module.exports = {command}
|
48
src/discord/Commands/Skip.js
Normal file
48
src/discord/Commands/Skip.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
const {Command} = require("../Command")
|
||||||
|
const {Embed, EmbedError} = require("../Embed")
|
||||||
|
const { Player, AllPlayers } = require("../../player/Player")
|
||||||
|
|
||||||
|
const command = new Command("skip", "Passe à la musique suivante", (client, interaction) => {
|
||||||
|
|
||||||
|
if(!interaction.member.voice.channel) return new EmbedError("Vous devez rejoindre un salon vocal pour passer à la musique suivante !", interaction)
|
||||||
|
|
||||||
|
const channel = interaction.member.voice.channel
|
||||||
|
var embed = new Embed(interaction)
|
||||||
|
|
||||||
|
if(AllPlayers.has(channel.guildId)) {
|
||||||
|
|
||||||
|
const player = new Player(channel.guildId)
|
||||||
|
const result = player.skip()
|
||||||
|
|
||||||
|
embed.setColor(0x15e6ed)
|
||||||
|
result.then((song) => {
|
||||||
|
|
||||||
|
if(song == "no_music") {
|
||||||
|
embed.returnError("Il n'y a pas de musique en file d'attente", interaction)
|
||||||
|
|
||||||
|
} else if(song) {
|
||||||
|
|
||||||
|
// Result is a song
|
||||||
|
embed.setColor(0x15e6ed)
|
||||||
|
|
||||||
|
embed.setTitle('**Musique suivante !**')
|
||||||
|
embed.setDescription('**Titre : **' + song.title)
|
||||||
|
embed.addField('**Durée : **'+ song.readduration, "")
|
||||||
|
embed.addField("**Artiste : **" + song.author, "")
|
||||||
|
embed.setThumbnail(song.thumbnail)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
embed.send()
|
||||||
|
})
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return embed.returnError("Le bot n'est pas connecté", interaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = {command}
|
34
src/discord/Commands/State.js
Normal file
34
src/discord/Commands/State.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
const {Command} = require("../Command")
|
||||||
|
const {Embed, EmbedError} = require("../Embed")
|
||||||
|
const {Player} = require("../../player/Player")
|
||||||
|
|
||||||
|
const command = new Command("state", "Affiche la musique en cours", (client, interaction) => {
|
||||||
|
|
||||||
|
const channel = interaction.member.voice.channel
|
||||||
|
const player = new Player(channel.guildId)
|
||||||
|
const song = player.queue.getCurrent()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if(!song) {
|
||||||
|
var embed = new EmbedError("Il n'y a pas de musique en cours de lecture", interaction)
|
||||||
|
|
||||||
|
} else if(song) {
|
||||||
|
var embed = new Embed(interaction)
|
||||||
|
// Result is a song
|
||||||
|
embed.setColor(0x15e6ed)
|
||||||
|
|
||||||
|
embed.setTitle('**Musique en cours :**')
|
||||||
|
embed.setDescription('**Titre : **' + song.title)
|
||||||
|
embed.addField('**Durée : **', song.readduration)
|
||||||
|
embed.addField("**Artiste : **", song.author)
|
||||||
|
embed.setThumbnail(song.thumbnail)
|
||||||
|
|
||||||
|
embed.send()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = {command}
|
20
src/discord/Commands/Web.js
Normal file
20
src/discord/Commands/Web.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
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(interaction)
|
||||||
|
embed.setColor(0xffffff)
|
||||||
|
embed.setTitle('Subsonics - Chopin')
|
||||||
|
embed.addBotPicture(client)
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = {command}
|
@@ -1,10 +1,20 @@
|
|||||||
const { EmbedBuilder } = require("discord.js");
|
const { EmbedBuilder, ActionRowBuilder } = require("discord.js");
|
||||||
|
|
||||||
class Embed {
|
class Embed {
|
||||||
fields;
|
fields;
|
||||||
constructor() {
|
buttons;
|
||||||
|
constructor (interaction, ephemeral) {
|
||||||
this.embed = new EmbedBuilder().setTimestamp()
|
this.embed = new EmbedBuilder().setTimestamp()
|
||||||
this.fields = []
|
this.fields = []
|
||||||
|
this.buttons = []
|
||||||
|
this.isSended = false
|
||||||
|
if(interaction) {
|
||||||
|
interaction.deferReply({ ephemeral: ephemeral }).then(() => {
|
||||||
|
this.isSended = true
|
||||||
|
})
|
||||||
|
this.interaction = interaction
|
||||||
|
this.ephemeral = ephemeral
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setTitle(title) {
|
setTitle(title) {
|
||||||
@@ -75,15 +85,48 @@ class Embed {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addButton(button) {
|
||||||
|
this.buttons.push(button)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
//Add Fields to an object
|
//Add Fields to an object
|
||||||
this.embed.addFields(this.fields)
|
this.embed.addFields(this.fields)
|
||||||
|
if(this.buttons.length > 0) {
|
||||||
|
this.actionRow = new ActionRowBuilder()
|
||||||
|
.addComponents(this.buttons);
|
||||||
|
}
|
||||||
return this.embed
|
return this.embed
|
||||||
}
|
}
|
||||||
|
|
||||||
send(interaction) {
|
async send() {
|
||||||
interaction.reply({embeds: [this.build()]})
|
// Add a secutiry check to avoid sending an embed if the interaction is not defined and retry one again
|
||||||
|
while(!this.isSended) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 50));
|
||||||
|
}
|
||||||
|
if(this.ephemeral === undefined) this.ephemeral = false;
|
||||||
|
this.interaction.editReply({ embeds: [this.build()], components: this.buttons.length > 0 ? [this.actionRow] : [] })
|
||||||
|
}
|
||||||
|
|
||||||
|
async returnError(message) {
|
||||||
|
this.setColor(150, 20, 20)
|
||||||
|
this.setTitle('Erreur')
|
||||||
|
this.setThumbnail("https://upload.wikimedia.org/wikipedia/commons/thumb/9/97/Dialog-error-round.svg/2048px-Dialog-error-round.svg.png")
|
||||||
|
this.setDescription(message)
|
||||||
|
await this.send()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {Embed}
|
class EmbedError extends Embed {
|
||||||
|
constructor(message, interaction, ephemeral) {
|
||||||
|
super(interaction, ephemeral)
|
||||||
|
this.setColor(150, 20, 20)
|
||||||
|
this.setTitle('Erreur')
|
||||||
|
this.setThumbnail("https://upload.wikimedia.org/wikipedia/commons/thumb/9/97/Dialog-error-round.svg/2048px-Dialog-error-round.svg.png")
|
||||||
|
this.setDescription(message)
|
||||||
|
this.send()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {Embed, EmbedError}
|
109
src/discord/MediaBase.js
Normal file
109
src/discord/MediaBase.js
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
const {LogType} = require("loguix")
|
||||||
|
const config = require("../utils/Database/Configuration")
|
||||||
|
const wlog = new LogType("MediaBase")
|
||||||
|
const { Database } = require("../utils/Database/Database")
|
||||||
|
const {__glob} = require("../utils/GlobalVars")
|
||||||
|
const { AttachmentBuilder } = require("discord.js")
|
||||||
|
const discordBot = require("./Bot")
|
||||||
|
|
||||||
|
|
||||||
|
var connected = false
|
||||||
|
var mediaDB = new Database("media", __glob.MEDIA_DB, [])
|
||||||
|
|
||||||
|
wlog.step.init("init_db", "Initialisation de la base de données multimédia")
|
||||||
|
|
||||||
|
if(!config.getMediaGuildId() || !config.getMediaChannelId()) {
|
||||||
|
wlog.warn("La configuration de la base de données multimédia n'est pas définie, vérifiez le fichier de configuration.")
|
||||||
|
wlog.step.error("init_db","Impossible d'initialiser la base de données multimédia, vérifiez le fichier de configuration.")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var channel = null
|
||||||
|
|
||||||
|
discordBot.getClient().on("ready", () => {
|
||||||
|
try {
|
||||||
|
channel = discordBot.getChannel(config.getMediaGuildId(), config.getMediaChannelId())
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!channel) {
|
||||||
|
wlog.warn("Le canal multimédia n'existe pas, vérifiez le fichier de configuration.")
|
||||||
|
wlog.step.error("init_db","Impossible d'initialiser la base de données multimédia, vérifiez le fichier de configuration.")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
const dateTime = new Date()
|
||||||
|
const date = dateTime.toLocaleDateString('fr-FR', { timeZone: 'Europe/Paris' })
|
||||||
|
const time = dateTime.toLocaleTimeString('fr-FR', { timeZone: 'Europe/Paris' })
|
||||||
|
const message = `[LOGS] La base de données multimédia a été initialisée le ${date} à ${time}`
|
||||||
|
channel.send(message)
|
||||||
|
wlog.log("La base de données multimédia a été initialisée avec succès.")
|
||||||
|
wlog.step.end("init_db")
|
||||||
|
connected = true
|
||||||
|
} catch (e) {
|
||||||
|
wlog.error("Impossible d'envoyer un message au canal multimédia, vérifiez le fichier de configuration.")
|
||||||
|
wlog.step.error("init_db","Impossible d'envoyer un message au canal multimédia, vérifiez le fichier de configuration.")
|
||||||
|
connected = false
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// SEND FILE TO DISCORD AND GET THE URL ID
|
||||||
|
|
||||||
|
async function postMedia(file) {
|
||||||
|
if(!connected) {
|
||||||
|
wlog.error("La base de données multimédia n'est pas connectée, impossible d'envoyer le fichier.")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const attachment = new AttachmentBuilder(file.file)
|
||||||
|
attachment.setName(file.name) // Set the name of the file
|
||||||
|
attachment.setDescription("Fichier envoyé par Subsonics Chopin - Raphix")
|
||||||
|
const message = await channel.send({ files: [attachment] })
|
||||||
|
const url = message.attachments.first().url
|
||||||
|
wlog.log(`Fichier envoyé avec succès : ${url}`)
|
||||||
|
// add the file to the database
|
||||||
|
mediaDB.data.push({
|
||||||
|
id: message.id,
|
||||||
|
url: url,
|
||||||
|
name: file.name,
|
||||||
|
size: file.size,
|
||||||
|
createdAt: new Date().toISOString()
|
||||||
|
})
|
||||||
|
mediaDB.save()
|
||||||
|
return url
|
||||||
|
} catch (error) {
|
||||||
|
wlog.error(`Erreur lors de l'envoi du fichier : ${error.message}`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getMedia(id) {
|
||||||
|
if(!connected) {
|
||||||
|
wlog.error("La base de données multimédia n'est pas connectée, impossible de récupérer le fichier.")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const media = mediaDB.data.find(m => m.id === id)
|
||||||
|
if(!media) {
|
||||||
|
wlog.error(`Aucun média trouvé avec l'ID : ${id}`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return media.url
|
||||||
|
} catch (error) {
|
||||||
|
wlog.error(`Erreur lors de la récupération du média : ${error.message}`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
postMedia,
|
||||||
|
getMedia,
|
||||||
|
}
|
@@ -46,6 +46,7 @@ class Report {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {Report}
|
module.exports = {Report}
|
58
src/lyrics/Lyrics.js
Normal file
58
src/lyrics/Lyrics.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
const { LogType } = require("loguix");
|
||||||
|
const plog = new LogType('Lyrics');
|
||||||
|
const urls = require('./urls.json');
|
||||||
|
|
||||||
|
// Make sure Url exists and get lyrics for the first item only
|
||||||
|
async function getLyrics(name) {
|
||||||
|
let result = null;
|
||||||
|
try {
|
||||||
|
const searchResponse = await fetch(`${urls.urlSearch}${encodeURIComponent(name)}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const searchData = await searchResponse.json();
|
||||||
|
|
||||||
|
// Check if data exists and has at least one item
|
||||||
|
if (searchData && searchData.data && searchData.data.length > 0) {
|
||||||
|
const firstItem = searchData.data[0];
|
||||||
|
const artist = firstItem.artist && firstItem.artist.name ? firstItem.artist.name : null;
|
||||||
|
const title = firstItem.title || null;
|
||||||
|
|
||||||
|
if (artist && title) {
|
||||||
|
try {
|
||||||
|
const lyricsResponse = await fetch(`${urls.urlGet}${encodeURIComponent(artist)}/${encodeURIComponent(title)}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const lyricsData = await lyricsResponse.json();
|
||||||
|
console.log(lyricsData);
|
||||||
|
if (lyricsData && lyricsData && lyricsData.lyrics) {
|
||||||
|
result = lyricsData.lyrics;
|
||||||
|
} else {
|
||||||
|
plog.error('Invalid response structure:', lyricsData);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
plog.error('Error fetching lyrics data:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
plog.error('Artist or title missing in search result');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
plog.error('No search results found');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
plog.error('Error fetching search data:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { getLyrics };
|
4
src/lyrics/urls.json
Normal file
4
src/lyrics/urls.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"urlSearch": "http://api.deezer.com/search?limit=5&q=",
|
||||||
|
"urlGet": "https://api.lyrics.ovh/v1/"
|
||||||
|
}
|
@@ -5,7 +5,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
const { LogType } = require('loguix');
|
|
||||||
const { __glob } = require("./utils/GlobalVars")
|
const { __glob } = require("./utils/GlobalVars")
|
||||||
require("loguix").setup(__glob.LOGS, __glob.PACKAGEINFO)
|
require("loguix").setup(__glob.LOGS, __glob.PACKAGEINFO)
|
||||||
const config = require("./utils/Database/Configuration")
|
const config = require("./utils/Database/Configuration")
|
||||||
@@ -18,7 +17,9 @@ metric.publishMetrics("8001", "raphraph")
|
|||||||
|
|
||||||
setup();
|
setup();
|
||||||
|
|
||||||
function setup() {
|
async function setup() {
|
||||||
const DiscordBot = require("./discord/Bot")
|
const DiscordBot = require("./discord/Bot")
|
||||||
DiscordBot.init()
|
await DiscordBot.init()
|
||||||
|
const Server = require("./server/Server")
|
||||||
|
await Server.init()
|
||||||
}
|
}
|
58
src/media/MediaInformation.js
Normal file
58
src/media/MediaInformation.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
const ffprobe = require('ffprobe');
|
||||||
|
const ffprobeStatic = require('ffprobe-static');
|
||||||
|
const { getReadableDuration } = require('../utils/TimeConverter');
|
||||||
|
const clog = require("loguix").getInstance("Song")
|
||||||
|
|
||||||
|
|
||||||
|
async function getMediaInformation(instance, media, provider) {
|
||||||
|
try {
|
||||||
|
const info = await ffprobe(media.attachment.url, { path: ffprobeStatic.path });
|
||||||
|
if (info.streams?.[0]?.duration_ts) {
|
||||||
|
instance.duration = info.streams[0].duration;
|
||||||
|
instance.readduration = getReadableDuration(instance.duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérification pour éviter une erreur si `streams[0]` ou `tags` n'existe pas
|
||||||
|
instance.thumbnail = info.streams?.[0]?.tags?.thumbnail ??
|
||||||
|
"https://radomisol.fr/wp-content/uploads/2016/08/cropped-note-radomisol-musique.png";
|
||||||
|
|
||||||
|
// Obtenir le titre (sinon utiliser le nom du fichier)
|
||||||
|
instance.title = info.streams?.[0]?.tags?.title ?? media.attachment.name;
|
||||||
|
|
||||||
|
// Obtenir l'auteur (s'il existe)
|
||||||
|
instance.author = info.streams?.[0]?.tags?.artist ?? instance.author;
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
clog.error("Impossible de récupérer les informations de la musique : " + media.attachment.name)
|
||||||
|
clog.error(err)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getMediaInformationFromUrl(instance, url) {
|
||||||
|
try {
|
||||||
|
const info = await ffprobe(url, { path: ffprobeStatic.path });
|
||||||
|
if (info.streams?.[0]?.duration_ts) {
|
||||||
|
instance.duration = info.streams[0].duration;
|
||||||
|
instance.readduration = getReadableDuration(instance.duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérification pour éviter une erreur si `streams[0]` ou `tags` n'existe pas
|
||||||
|
instance.thumbnail = info.streams?.[0]?.tags?.thumbnail ??
|
||||||
|
"https://radomisol.fr/wp-content/uploads/2016/08/cropped-note-radomisol-musique.png";
|
||||||
|
|
||||||
|
// Obtenir le titre (sinon utiliser le nom du fichier)
|
||||||
|
instance.title = info.streams?.[0]?.tags?.title ?? "Titre inconnu";
|
||||||
|
|
||||||
|
// Obtenir l'auteur (s'il existe)
|
||||||
|
instance.author = info.streams?.[0]?.tags?.artist ?? "Auteur inconnu";
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
clog.error("Impossible de récupérer les informations de la musique depuis l'URL : " + url);
|
||||||
|
console.log(err)
|
||||||
|
clog.error(err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {getMediaInformation, getMediaInformationFromUrl};
|
85
src/media/SoundcloudInformation.js
Normal file
85
src/media/SoundcloudInformation.js
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
const {LogType} = require('loguix');
|
||||||
|
const clog = new LogType("SoundcloudInformation");
|
||||||
|
const {Song} = require('../player/Song');
|
||||||
|
const {Playlist} = require('../playlists/Playlist');
|
||||||
|
const {Soundcloud} = require('soundcloud.ts')
|
||||||
|
const {getReadableDuration} = require('../utils/TimeConverter');
|
||||||
|
|
||||||
|
const soundcloud = new Soundcloud();
|
||||||
|
|
||||||
|
async function getTrack(url) {
|
||||||
|
try {
|
||||||
|
const info = await soundcloud.tracks.get(url)
|
||||||
|
|
||||||
|
if(!info) {
|
||||||
|
clog.error("Impossible de récupérer les informations de la piste Soundcloud à partir de l'URL");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const song = new Song();
|
||||||
|
song.title = info.title;
|
||||||
|
song.author = info.user.username;
|
||||||
|
song.url = info.permalink_url;
|
||||||
|
song.thumbnail = info.artwork_url;
|
||||||
|
song.id = info.id;
|
||||||
|
song.duration = info.duration / 1000;
|
||||||
|
song.readduration = getReadableDuration(info.duration / 1000);
|
||||||
|
song.type = "soundcloud";
|
||||||
|
|
||||||
|
return song;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
clog.error('Erreur lors de la recherche Soundcloud (Track): ' + error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getPlaylist(url) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
const info = await soundcloud.playlists.get(url)
|
||||||
|
|
||||||
|
if(!info) {
|
||||||
|
clog.error("Impossible de récupérer les informations de la playlist Soundcloud à partir de l'URL");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const playlist = new Playlist();
|
||||||
|
|
||||||
|
playlist.title = info.title;
|
||||||
|
playlist.author = info.user.username;
|
||||||
|
playlist.url = info.permalink_url;
|
||||||
|
playlist.thumbnail = info.artwork_url;
|
||||||
|
playlist.id = info.id;
|
||||||
|
playlist.duration = 0;
|
||||||
|
playlist.songs = [];
|
||||||
|
playlist.type = "soundcloud";
|
||||||
|
|
||||||
|
for(const track of info.tracks) {
|
||||||
|
const song = new Song();
|
||||||
|
song.title = track.title;
|
||||||
|
song.author = track.user.username;
|
||||||
|
song.url = track.permalink_url;
|
||||||
|
song.thumbnail = track.artwork_url;
|
||||||
|
song.id = track.id;
|
||||||
|
song.duration = track.duration / 1000;
|
||||||
|
song.readduration = getReadableDuration(track.duration / 1000);
|
||||||
|
song.type = "soundcloud";
|
||||||
|
|
||||||
|
playlist.duration += track.duration / 1000;
|
||||||
|
playlist.songs.push(song);
|
||||||
|
}
|
||||||
|
|
||||||
|
playlist.readduration = getReadableDuration(playlist.duration);
|
||||||
|
|
||||||
|
return playlist;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
clog.error('Erreur lors de la recherche Soundcloud (Playlist): ' + error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {getTrack, getPlaylist}
|
179
src/media/SpotifyInformation.js
Normal file
179
src/media/SpotifyInformation.js
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
const {LogType} = require('loguix');
|
||||||
|
const clog = new LogType("SpotifyInformation");
|
||||||
|
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('../playlists/Playlist');
|
||||||
|
const {Song} = require('../player/Song');
|
||||||
|
const youtube = require("../media/YoutubeInformation");
|
||||||
|
const {getReadableDuration} = require('../utils/TimeConverter');
|
||||||
|
|
||||||
|
const spotifyApi = new SpotifyWebApi({
|
||||||
|
clientId: SPOTIFY_CLIENT_ID,
|
||||||
|
clientSecret: SPOTIFY_CLIENT_SECRET,
|
||||||
|
});
|
||||||
|
|
||||||
|
async function getSong(url) {
|
||||||
|
try {
|
||||||
|
const data = await spotifyApi.clientCredentialsGrant();
|
||||||
|
spotifyApi.setAccessToken(data.body['access_token']);
|
||||||
|
|
||||||
|
const parts = url.split('/');
|
||||||
|
const trackId = parts[parts.length - 1];
|
||||||
|
|
||||||
|
if(!trackId) {
|
||||||
|
clog.error("Impossible de récupérer l'identifiant de la piste Spotify à partir de l'URL");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const trackInfo = await spotifyApi.getTrack(trackId);
|
||||||
|
|
||||||
|
const trackName = trackInfo.body.name;
|
||||||
|
const artistName = trackInfo.body.artists[0].name;
|
||||||
|
|
||||||
|
return `${trackName} - ${artistName}`;
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
clog.error("Impossible de récupérer les informations de la piste Spotify à partir de l'URL");
|
||||||
|
clog.error(error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAlbum(url) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
|
||||||
|
const creditdata = await spotifyApi.clientCredentialsGrant();
|
||||||
|
spotifyApi.setAccessToken(creditdata.body['access_token']);
|
||||||
|
|
||||||
|
const parts = url.split('/');
|
||||||
|
const albumId = parts[parts.indexOf('album') + 1].split('?')[0];
|
||||||
|
|
||||||
|
const data = await spotifyApi.getAlbum(albumId);
|
||||||
|
const info = data.body;
|
||||||
|
|
||||||
|
if(!info) {
|
||||||
|
clog.error("Impossible de récupérer les informations de l'album Spotify à partir de l'URL");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
clog.log("Informations de l'album récupérées : " + info.name);
|
||||||
|
|
||||||
|
const playlist = new Playlist()
|
||||||
|
playlist.title = info.name;
|
||||||
|
playlist.author = info.artists[0].name;
|
||||||
|
playlist.authorId = info.artists[0].id;
|
||||||
|
playlist.thumbnail = info.images[0].url;
|
||||||
|
playlist.url = info.external_urls.spotify;
|
||||||
|
playlist.id = albumId;
|
||||||
|
playlist.type = "spotify";
|
||||||
|
playlist.songs = info.tracks.items;
|
||||||
|
|
||||||
|
return playlist;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
clog.error("Impossible de récupérer les informations de l'album Spotify à partir de l'URL");
|
||||||
|
clog.error(error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getPlaylist(url) {
|
||||||
|
// Get the playlist and return a Playlist Object
|
||||||
|
|
||||||
|
try {
|
||||||
|
const creditdata = await spotifyApi.clientCredentialsGrant();
|
||||||
|
spotifyApi.setAccessToken(creditdata.body['access_token']);
|
||||||
|
|
||||||
|
const parts = url.split('/');
|
||||||
|
const playlistId = parts[parts.indexOf('playlist') + 1].split('?')[0];
|
||||||
|
|
||||||
|
const data = await spotifyApi.getPlaylist(playlistId)
|
||||||
|
|
||||||
|
const info = data.body;
|
||||||
|
|
||||||
|
if(!info) {
|
||||||
|
clog.error("Impossible de récupérer les informations de la playlist Spotify à partir de l'URL");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
clog.log("Informations de la playlist récupérées : " + info.name);
|
||||||
|
|
||||||
|
const playlist = new Playlist()
|
||||||
|
playlist.title = info.name;
|
||||||
|
playlist.author = info.owner.display_name;
|
||||||
|
playlist.authorId = info.owner.id;
|
||||||
|
playlist.thumbnail = info.images[0].url;
|
||||||
|
playlist.url = info.external_urls.spotify;
|
||||||
|
playlist.id = playlistId;
|
||||||
|
playlist.type = "spotify";
|
||||||
|
|
||||||
|
for(const track of info.tracks.items) {
|
||||||
|
playlist.songs.push(track.track);
|
||||||
|
}
|
||||||
|
|
||||||
|
return playlist;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
clog.error("Impossible de récupérer les informations de l'album Spotify à partir de l'URL");
|
||||||
|
clog.error(error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getTracks(playlist) {
|
||||||
|
|
||||||
|
const tracks = playlist.songs
|
||||||
|
playlistSongs = [];
|
||||||
|
for(const track of tracks) {
|
||||||
|
|
||||||
|
var trackName = track.name;
|
||||||
|
var artistName = track.artists[0].name;
|
||||||
|
var queryForYoutube = `${trackName} - ${artistName}`;
|
||||||
|
|
||||||
|
var urlYoutubeFounded = await youtube.getQuery(queryForYoutube).then(function(songFind) {
|
||||||
|
if(!songFind) return null;
|
||||||
|
return songFind.url;
|
||||||
|
});
|
||||||
|
|
||||||
|
clog.log("URL de la vidéo YouTube trouvée : " + urlYoutubeFounded);
|
||||||
|
|
||||||
|
if(!urlYoutubeFounded) {
|
||||||
|
clog.error("Impossible de récupérer l'URL de la vidéo YouTube à partir de la requête " + queryForYoutube);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
const song = new Song();
|
||||||
|
|
||||||
|
song.title = track.name;
|
||||||
|
song.author = track.artists[0].name;
|
||||||
|
song.url = urlYoutubeFounded;
|
||||||
|
song.thumbnail = playlist.thumbnail;
|
||||||
|
song.id = track.id;
|
||||||
|
song.duration = track.duration_ms / 1000;
|
||||||
|
song.readduration = getReadableDuration(track.duration_ms / 1000);
|
||||||
|
song.type = "youtube";
|
||||||
|
|
||||||
|
playlist.duration += track.duration_ms / 1000;
|
||||||
|
playlistSongs.push(song);
|
||||||
|
}
|
||||||
|
|
||||||
|
// When finish do this
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
playlist.readduration = getReadableDuration(playlist.duration);
|
||||||
|
playlist.songs = playlistSongs;
|
||||||
|
|
||||||
|
|
||||||
|
return playlist;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {getSong, getAlbum, getPlaylist, getTracks}
|
135
src/media/YoutubeInformation.js
Normal file
135
src/media/YoutubeInformation.js
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
const { LogType } = require('loguix');
|
||||||
|
const clog = new LogType("YoutubeInformation");
|
||||||
|
const { Song } = require('../player/Song');
|
||||||
|
const { Playlist } = require('../playlists/Playlist');
|
||||||
|
const { getReadableDuration, getSecondsDuration } = require('../utils/TimeConverter');
|
||||||
|
const ytsr = require('@distube/ytsr');
|
||||||
|
const ytfps = require('ytfps');
|
||||||
|
|
||||||
|
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 limit = multiple ? 25 : 1;
|
||||||
|
const searchResults = await ytsr(query, { limit });
|
||||||
|
const videos = searchResults.items.filter(item => item.type === '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 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getVideo(url) {
|
||||||
|
const videoId = url.match(/(?:youtu\.be\/|youtube\.com\/|music\.youtube\.com\/)(?:watch\?v=)?([a-zA-Z0-9_-]{11})/);
|
||||||
|
if (videoId === null) {
|
||||||
|
clog.error("Impossible de récupérer l'identifiant de la vidéo YouTube à partir de l'URL");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const searchResults = await ytsr(videoId[1], { limit: 1 });
|
||||||
|
const video = searchResults.items.find(item => item.type === 'video');
|
||||||
|
|
||||||
|
if (video) {
|
||||||
|
const songReturn = new Song();
|
||||||
|
await songReturn.processYoutubeVideo(video);
|
||||||
|
return songReturn;
|
||||||
|
} else {
|
||||||
|
clog.error("Impossible de récupérer la vidéo YouTube à partir de l'identifiant");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
clog.error('Erreur lors de la recherche de la vidéo YouTube:' + error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getPlaylist(url) {
|
||||||
|
if (url === null || typeof url !== 'string') {
|
||||||
|
clog.error("Impossible de rechercher une playlist YouTube, car la requête est nulle");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// 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[2]);
|
||||||
|
|
||||||
|
if (!playlistInfo) {
|
||||||
|
clog.error("Impossible de récupérer la playlist YouTube à partir de l'identifiant");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const playlist = new Playlist();
|
||||||
|
playlist.type = "youtube";
|
||||||
|
playlist.author = playlistInfo.author.name;
|
||||||
|
playlist.authorId = playlistInfo.author.url;
|
||||||
|
playlist.title = playlistInfo.title;
|
||||||
|
playlist.thumbnail = playlistInfo.thumbnail_url;
|
||||||
|
playlist.description = playlistInfo.description;
|
||||||
|
playlist.url = `https://www.youtube.com/playlist?list=${playlistId[2]}`;
|
||||||
|
playlist.id = playlistId[2];
|
||||||
|
|
||||||
|
for (const video of playlistInfo.videos) {
|
||||||
|
const song = new Song();
|
||||||
|
await song.processYoutubeVideo(video, true);
|
||||||
|
playlist.duration += song.duration;
|
||||||
|
playlist.songs.push(song);
|
||||||
|
}
|
||||||
|
playlist.readduration = getReadableDuration(playlist.duration);
|
||||||
|
return playlist;
|
||||||
|
} catch (error) {
|
||||||
|
clog.error('Erreur lors de la recherche YouTube: ' + error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSecondsFromUrl(url) {
|
||||||
|
const videoId = url.match(/(?:youtu\.be\/|youtube\.com\/|music\.youtube\.com\/)(?:watch\?v=)?([a-zA-Z0-9_-]{11})/);
|
||||||
|
if (videoId === null) {
|
||||||
|
clog.error("Impossible de récupérer l'identifiant de la vidéo YouTube à partir de l'URL");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const searchResults = await ytsr(videoId[1], { limit: 1 });
|
||||||
|
const video = searchResults.items.find(item => item.type === 'video');
|
||||||
|
console.log(video);
|
||||||
|
if (video) {
|
||||||
|
return getSecondsDuration(video.duration); // Convert seconds to milliseconds
|
||||||
|
} else {
|
||||||
|
clog.error("Impossible de récupérer la vidéo YouTube à partir de l'identifiant");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
clog.error('Erreur lors de la recherche de la vidéo YouTube:' + error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { getQuery, getVideo, getPlaylist, getSecondsFromUrl };
|
82
src/player/Finder.js
Normal file
82
src/player/Finder.js
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
const Resolver = require('../utils/Resolver');
|
||||||
|
const { QueryType } = require('../utils/QueryType');
|
||||||
|
const { Links } = require('../utils/Links');
|
||||||
|
const youtube = require("../media/YoutubeInformation")
|
||||||
|
const spotify = require("../media/SpotifyInformation")
|
||||||
|
const soundcloud = require("../media/SoundcloudInformation")
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
}
|
||||||
|
if(type == QueryType.YOUTUBE_VIDEO) {
|
||||||
|
|
||||||
|
return await youtube.getVideo(query)
|
||||||
|
}
|
||||||
|
if(type == QueryType.YOUTUBE_PLAYLIST) {
|
||||||
|
|
||||||
|
return await youtube.getPlaylist(query)
|
||||||
|
}
|
||||||
|
if(type == QueryType.SPOTIFY_SONG) {
|
||||||
|
return await youtube.getQuery(await spotify.getSong(query))
|
||||||
|
|
||||||
|
}
|
||||||
|
if(type == QueryType.SPOTIFY_ALBUM) {
|
||||||
|
return await spotify.getAlbum(query)
|
||||||
|
}
|
||||||
|
if(type == QueryType.SPOTIFY_PLAYLIST) {
|
||||||
|
return await spotify.getPlaylist(query)
|
||||||
|
|
||||||
|
}
|
||||||
|
if(type == QueryType.SOUNDCLOUD_TRACK) {
|
||||||
|
return await soundcloud.getTrack(query)
|
||||||
|
|
||||||
|
}
|
||||||
|
if(type == QueryType.SOUNDCLOUD_PLAYLIST) {
|
||||||
|
return await soundcloud.getPlaylist(query)
|
||||||
|
|
||||||
|
}
|
||||||
|
//MORELATER: Add more providers
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {search}
|
239
src/player/List.js
Normal file
239
src/player/List.js
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
const { Database } = require('../utils/Database/Database')
|
||||||
|
const { __glob } = require('../utils/GlobalVars')
|
||||||
|
const PreviousDB = new Database("previous", __glob.PREVIOUSFILE, {})
|
||||||
|
const {LogType} = require("loguix")
|
||||||
|
const clog = new LogType("List")
|
||||||
|
const { Song } = require('./Song')
|
||||||
|
|
||||||
|
const AllLists = new Map() // Map<guildId, List>
|
||||||
|
|
||||||
|
class List {
|
||||||
|
next;
|
||||||
|
current;
|
||||||
|
shuffle;
|
||||||
|
guildId;
|
||||||
|
constructor(guildId) {
|
||||||
|
if(guildId === null) {
|
||||||
|
clog.error("Impossible de créer une liste, car guildId est null")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if(AllLists.has(guildId)) {
|
||||||
|
return AllLists.get(guildId)
|
||||||
|
}
|
||||||
|
// Add PreviousDB.data[this.guildId]
|
||||||
|
if(PreviousDB.data[guildId] === undefined) {
|
||||||
|
PreviousDB.data[guildId] = new Array()
|
||||||
|
savePrevious()
|
||||||
|
}
|
||||||
|
AllLists.set(guildId, this)
|
||||||
|
this.next = new Array();
|
||||||
|
this.current = null;
|
||||||
|
this.shuffle = false;
|
||||||
|
this.guildId = guildId;
|
||||||
|
}
|
||||||
|
getNext() {
|
||||||
|
return this.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
getNextSong() {
|
||||||
|
if(this.next.length > 0) {
|
||||||
|
return this.next[0];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
nextSong() {
|
||||||
|
if(this.current != null) {
|
||||||
|
this.addPreviousSong(this.current)
|
||||||
|
}
|
||||||
|
var song = null;
|
||||||
|
if(!this.shuffle) {
|
||||||
|
song = this.next[0]
|
||||||
|
this.next.splice(0, 1)
|
||||||
|
} else {
|
||||||
|
const randomIndex = Math.floor(Math.random() * this.next.length);
|
||||||
|
song = this.next[randomIndex]
|
||||||
|
this.next.splice(randomIndex, 1)
|
||||||
|
|
||||||
|
}
|
||||||
|
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() {
|
||||||
|
const previousList = new Array()
|
||||||
|
|
||||||
|
for(const song of PreviousDB.data[this.guildId]) {
|
||||||
|
previousList.push(new Song(song))
|
||||||
|
}
|
||||||
|
return previousList;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
getPreviousSong() {
|
||||||
|
if(PreviousDB.data[this.guildId].length > 0) {
|
||||||
|
return new Song(PreviousDB.data[this.guildId][0])
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
previousSong() {
|
||||||
|
if(this.current != null) {
|
||||||
|
this.firstNext(this.current)
|
||||||
|
}
|
||||||
|
if(PreviousDB.data[this.guildId].length > 0) {
|
||||||
|
const song = PreviousDB.data[this.guildId][0]
|
||||||
|
// Remove the song from the previous list
|
||||||
|
PreviousDB.data[this.guildId].splice(0, 1)
|
||||||
|
savePrevious()
|
||||||
|
return new Song(song)
|
||||||
|
} 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() {
|
||||||
|
return this.current;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrent(value) {
|
||||||
|
this.current = value;
|
||||||
|
process.emit("PLAYERS_UPDATE")
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.clearNext();
|
||||||
|
this.current = null
|
||||||
|
this.shuffle = false;
|
||||||
|
AllLists.delete(this.guildId)
|
||||||
|
process.emit("PLAYERS_UPDATE")
|
||||||
|
}
|
||||||
|
|
||||||
|
setShuffle() {
|
||||||
|
this.shuffle = !this.shuffle;
|
||||||
|
}
|
||||||
|
|
||||||
|
isShuffle() {
|
||||||
|
return this.shuffle;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Play the song with the index in the queue and delete it from the queue
|
||||||
|
playByIndex(index, typelist) {
|
||||||
|
|
||||||
|
var index = data[0]
|
||||||
|
var list = data[1]
|
||||||
|
|
||||||
|
if(typelist == ListType.NEXT) {
|
||||||
|
|
||||||
|
const song = this.next[index]
|
||||||
|
this.next.splice(index, 1)
|
||||||
|
|
||||||
|
return song
|
||||||
|
|
||||||
|
} else if(typelist == ListType.PREVIOUS) {
|
||||||
|
|
||||||
|
const song = PreviousDB.data[this.guildId][index]
|
||||||
|
return song
|
||||||
|
|
||||||
|
}
|
||||||
|
process.emit("PLAYERS_UPDATE")
|
||||||
|
}
|
||||||
|
|
||||||
|
addNextPlaylist(playlist, firstAlreadyPlayed) {
|
||||||
|
if(firstAlreadyPlayed) {
|
||||||
|
playlist.songs.shift()
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const song of playlist.songs) {
|
||||||
|
this.addNextSong(song)
|
||||||
|
}
|
||||||
|
process.emit("PLAYERS_UPDATE")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
moveNext(fromIndex, toIndex) {
|
||||||
|
// Check if fromIndex and toIndex are valid
|
||||||
|
if(fromIndex < 0 || fromIndex >= this.next.length + 1 || toIndex < 0 || toIndex >= this.next.length + 1) {
|
||||||
|
clog.error("Impossible de déplacer la musique, car l'index est invalide, GuildId : " + this.guildId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if(fromIndex == toIndex) return;
|
||||||
|
const song = this.next[fromIndex]
|
||||||
|
this.next.splice(fromIndex, 1)
|
||||||
|
this.next.splice(toIndex, 0, song)
|
||||||
|
process.emit("PLAYERS_UPDATE")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const ListType = {
|
||||||
|
NEXT: "0",
|
||||||
|
PREVIOUS: "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
function savePrevious() {
|
||||||
|
|
||||||
|
for(const guildId in PreviousDB.data) {
|
||||||
|
if(PreviousDB.data[guildId].length > 50) {
|
||||||
|
PreviousDB.data[guildId].splice(50, PreviousDB.data[guildId].length - 50)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PreviousDB.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { List, ListType }
|
21
src/player/Method/Media.js
Normal file
21
src/player/Method/Media.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
const {createAudioResource, VoiceConnectionStatus, createAudioPlayer, StreamType} = require('@discordjs/voice');
|
||||||
|
const {LogType} = require('loguix')
|
||||||
|
const clog = new LogType("Media")
|
||||||
|
const plog = require("loguix").getInstance("Player")
|
||||||
|
const ffmpeg = require('fluent-ffmpeg')
|
||||||
|
|
||||||
|
async function getStream(song) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
return song.url;
|
||||||
|
|
||||||
|
|
||||||
|
} catch(e) {
|
||||||
|
clog.error("Erreur lors de la lecture de la musique : " + song.title)
|
||||||
|
clog.error(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {getStream}
|
25
src/player/Method/Soundcloud.js
Normal file
25
src/player/Method/Soundcloud.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
const {createAudioResource, VoiceConnectionStatus, createAudioPlayer, StreamType} = require('@discordjs/voice');
|
||||||
|
const {LogType} = require('loguix')
|
||||||
|
const clog = new LogType("Soundcloud-Stream")
|
||||||
|
const {Soundcloud} = require('soundcloud.ts')
|
||||||
|
const ffmpeg = require('fluent-ffmpeg')
|
||||||
|
|
||||||
|
const soundcloud = new Soundcloud();
|
||||||
|
|
||||||
|
async function getStream(song) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
var stream = await soundcloud.util.streamTrack(song.url)
|
||||||
|
return stream
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} catch(e) {
|
||||||
|
clog.error("Erreur lors de la récupération du stream : " + song.title)
|
||||||
|
clog.error(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {getStream}
|
30
src/player/Method/Youtube.js
Normal file
30
src/player/Method/Youtube.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
const {createAudioResource, VoiceConnectionStatus, createAudioPlayer, StreamType} = require('@discordjs/voice');
|
||||||
|
const {LogType} = require('loguix')
|
||||||
|
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 getStream(song) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
let stream = ytdl(song.url, {
|
||||||
|
quality: 'highestaudio',
|
||||||
|
highWaterMark: 1 << 30,
|
||||||
|
liveBuffer: 20000,
|
||||||
|
dlChunkSize: 0,
|
||||||
|
bitrate: 128,
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return stream
|
||||||
|
|
||||||
|
} catch(e) {
|
||||||
|
clog.error("Erreur lors de la récupération du stream : " + song.title)
|
||||||
|
clog.error(e)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {getStream}
|
417
src/player/Player.js
Normal file
417
src/player/Player.js
Normal file
@@ -0,0 +1,417 @@
|
|||||||
|
const { joinVoiceChannel, getVoiceConnection, VoiceConnectionStatus, createAudioPlayer, AudioPlayerStatus, StreamType, createAudioResource } = require('@discordjs/voice');
|
||||||
|
const {List} = require('./List')
|
||||||
|
const {LogType} = require("loguix");
|
||||||
|
const songCheck = require('./SongCheck')
|
||||||
|
const ffmpeg = require('fluent-ffmpeg')
|
||||||
|
const fs = require('fs')
|
||||||
|
const { PassThrough } = require('stream');
|
||||||
|
|
||||||
|
const plog = new LogType("Player")
|
||||||
|
const clog = new LogType("Signal")
|
||||||
|
|
||||||
|
const media = require('./Method/Media');
|
||||||
|
const youtube = require('./Method/Youtube');
|
||||||
|
const soundcloud = require('./Method/Soundcloud');
|
||||||
|
|
||||||
|
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")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if(AllPlayers.has(guildId)) {
|
||||||
|
return AllPlayers.get(guildId)
|
||||||
|
}
|
||||||
|
this.connection = null
|
||||||
|
this.player = null
|
||||||
|
this.guildId = guildId
|
||||||
|
this.queue = new List(guildId)
|
||||||
|
AllPlayers.set(guildId, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
async join(channel) {
|
||||||
|
|
||||||
|
if(getVoiceConnection(channel.guild.id)) {
|
||||||
|
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,
|
||||||
|
adapterCreator: channel.guild.voiceAdapterCreator,
|
||||||
|
selfDeaf: false,
|
||||||
|
selfMute: false
|
||||||
|
});
|
||||||
|
|
||||||
|
this.connection.on('stateChange', (oldState, newState) => {
|
||||||
|
clog.log(`GUILD : ${this.guildId} - [STATE] OLD : "${oldState.status}" NEW : "${newState.status}"`);
|
||||||
|
|
||||||
|
// Si la connection est fermée, on détruit le player
|
||||||
|
|
||||||
|
if(newState.status === VoiceConnectionStatus.Disconnected) {
|
||||||
|
this.leave()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.connected = true
|
||||||
|
process.emit("PLAYERS_UPDATE")
|
||||||
|
}
|
||||||
|
|
||||||
|
generatePlayerEvents() {
|
||||||
|
|
||||||
|
const Activity = require('../discord/Activity');
|
||||||
|
|
||||||
|
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, () => {
|
||||||
|
if(this.checkConnection()) return
|
||||||
|
// 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, () => {
|
||||||
|
if(this.checkConnection()) return
|
||||||
|
plog.log(`GUILD : ${this.guildId} - Le player est en train de jouer le contenu suivant : ${this.queue.current.title}`);
|
||||||
|
Activity.setMusicActivity(this.queue.current.title, this.queue.current.author, this.queue.current.thumbnail)
|
||||||
|
process.emit("PLAYERS_UPDATE")
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
checkConnection() {
|
||||||
|
if(this.connection === null) {
|
||||||
|
clog.error(`GUILD : ${this.guildId} - La connection n'est pas définie`)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if(this.player === null) {
|
||||||
|
plog.error(`GUILD : ${this.guildId} - Le player n'est pas défini`)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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: playerStatus === AudioPlayerStatus.Paused,
|
||||||
|
playing: playerStatus === AudioPlayerStatus.Playing,
|
||||||
|
duration: this.getDuration(),
|
||||||
|
playerState: playerStatus,
|
||||||
|
connectionState: connectionStatus,
|
||||||
|
channelId: this.channelId,
|
||||||
|
guildId: this.guildId,
|
||||||
|
}
|
||||||
|
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(!songCheck.checkSong(song)) return
|
||||||
|
if(this.checkConnection()) return
|
||||||
|
if(this.queue.current != null) {
|
||||||
|
this.player.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.queue.setCurrent(song)
|
||||||
|
this.stream = await this.getStream(song)
|
||||||
|
|
||||||
|
if(this.stream === null) {
|
||||||
|
plog.error(`GUILD : ${this.guildId} - Impossible de lire la musique : ${song.title} avec le type : ${song.type}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
if(this.player?.state?.status == AudioPlayerStatus.Idle && this.queue.current === null && this.queue.next.length === 0) {
|
||||||
|
this.play(song)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.queue.addNextSong(song)
|
||||||
|
plog.log(`GUILD : ${this.guildId} - La musique a été ajoutée à la liste de lecture : ${song.title}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
async readPlaylist(playlist, now) {
|
||||||
|
if(this.player?.state?.status == AudioPlayerStatus.Idle && this.queue.current === null && this.queue.next.length === 0) {
|
||||||
|
this.play(playlist.songs[0])
|
||||||
|
this.queue.addNextPlaylist(playlist, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if(now) this.play(playlist.songs[0])
|
||||||
|
this.queue.addNextPlaylist(playlist, now)
|
||||||
|
|
||||||
|
plog.log(`GUILD : ${this.guildId} - La playlist a été ajoutée à la liste de lecture : ${playlist.title}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
async pause() {
|
||||||
|
if(this.checkConnection()) return "no_music"
|
||||||
|
if(this.player.state.status == AudioPlayerStatus.Paused) {
|
||||||
|
this.player.unpause()
|
||||||
|
plog.log(`GUILD : ${this.guildId} - La musique a été reprise`)
|
||||||
|
process.emit("PLAYERS_UPDATE")
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
this.player.pause()
|
||||||
|
plog.log(`GUILD : ${this.guildId} - La musique a été mise en pause`)
|
||||||
|
process.emit("PLAYERS_UPDATE")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const { LogType } = require('loguix')
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async leave() {
|
||||||
|
const Activity = require('../discord/Activity');
|
||||||
|
if(this.checkConnection()) return
|
||||||
|
if(this.queue.current != null) {
|
||||||
|
this.queue.addPreviousSong(this.queue.current)
|
||||||
|
}
|
||||||
|
// Détruit la connection et le player et l'enlève de la liste des
|
||||||
|
this.connection.destroy()
|
||||||
|
this.player.stop()
|
||||||
|
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) {
|
||||||
|
//FIXME: SET DURATION FONCTIONNE TRES LENTEMENT
|
||||||
|
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.`);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
return this.currentResource.playbackDuration / 1000
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentResource(value) {
|
||||||
|
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"
|
||||||
|
if(this.queue.next.length === 0) {
|
||||||
|
return "no_music"
|
||||||
|
}
|
||||||
|
const songSkip = this.queue.nextSong()
|
||||||
|
this.play(songSkip)
|
||||||
|
process.emit("PLAYERS_UPDATE")
|
||||||
|
return songSkip
|
||||||
|
}
|
||||||
|
|
||||||
|
async previous() {
|
||||||
|
|
||||||
|
if(this.checkConnection()) return "no_music"
|
||||||
|
if(this.queue.getPrevious().length === 0) {
|
||||||
|
return "no_music"
|
||||||
|
}
|
||||||
|
|
||||||
|
const songPrevious = this.queue.previousSong()
|
||||||
|
this.play(songPrevious)
|
||||||
|
process.emit("PLAYERS_UPDATE")
|
||||||
|
return songPrevious
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @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}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
You can access created connections elsewhere in your code without having to track the connections yourself. It is best practice to not track the voice connections yourself as you may forget to clean them up once they are destroyed, leading to memory leaks.
|
||||||
|
|
||||||
|
const connection = getVoiceConnection(myVoiceChannel.guild.id);
|
||||||
|
|
||||||
|
*/
|
84
src/player/Song.js
Normal file
84
src/player/Song.js
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
const {LogType} = require('loguix')
|
||||||
|
|
||||||
|
const clog = new LogType("Song")
|
||||||
|
const MediaInformation = require('../media/MediaInformation')
|
||||||
|
const { getReadableDuration, getSecondsDuration } = require('../utils/TimeConverter');
|
||||||
|
|
||||||
|
class Song {
|
||||||
|
title = "Aucun titre";
|
||||||
|
id = "Aucun fichier";
|
||||||
|
author = "Auteur inconnu"
|
||||||
|
authorId;
|
||||||
|
url;
|
||||||
|
thumbnail = "https://radomisol.fr/wp-content/uploads/2016/08/cropped-note-radomisol-musique.png" ;
|
||||||
|
duration;
|
||||||
|
readduration;
|
||||||
|
form = "SONG";
|
||||||
|
type;
|
||||||
|
userAddedId;
|
||||||
|
|
||||||
|
constructor(properties) {
|
||||||
|
if(properties) {
|
||||||
|
this.type = properties.type ?? this.type
|
||||||
|
this.title = properties.title ?? this.title
|
||||||
|
this.id = properties.id ?? this.id
|
||||||
|
this.author = properties.author ?? this.author
|
||||||
|
this.url = properties.url ?? this.url
|
||||||
|
this.thumbnail = properties.thumbnail ?? this.thumbnail
|
||||||
|
this.duration = properties.duration ?? this.duration
|
||||||
|
this.readduration = properties.readduration ?? this.readduration
|
||||||
|
this.type = properties.type ?? this.type
|
||||||
|
this.authorId = properties.authorId ?? this.authorId
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async processMedia(media, provider) {
|
||||||
|
if(provider) this.author = provider;
|
||||||
|
if(provider) this.authorId = provider;
|
||||||
|
// Check if media is a file or a link
|
||||||
|
if(media.attachment) {
|
||||||
|
this.url = media.attachment.url
|
||||||
|
this.id = media.attachment.name
|
||||||
|
this.type = "attachment"
|
||||||
|
|
||||||
|
// In face, duration is null, get the metadata of the file to get the duration
|
||||||
|
await MediaInformation.getMediaInformation(this, media)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
clog.error("Impossible de traiter le média")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async processYoutubeVideo(video, playlist) {
|
||||||
|
if(playlist) {
|
||||||
|
this.title = video.title
|
||||||
|
this.author = video.author.name
|
||||||
|
this.authorId = video.author.channel_url
|
||||||
|
this.thumbnail = video.thumbnail_url
|
||||||
|
this.url = video.url
|
||||||
|
this.type = "youtube"
|
||||||
|
this.id = video.id
|
||||||
|
|
||||||
|
this.duration = video.milis_length / 1000
|
||||||
|
this.readduration = getReadableDuration(this.duration)
|
||||||
|
} else {
|
||||||
|
this.title = video.name
|
||||||
|
this.author = video.author.name
|
||||||
|
this.authorId = video.author.url
|
||||||
|
this.thumbnail = video.thumbnail
|
||||||
|
this.url = video.url
|
||||||
|
this.type = "youtube"
|
||||||
|
this.id = video.id
|
||||||
|
|
||||||
|
this.duration = getSecondsDuration(video.duration)
|
||||||
|
this.readduration = getReadableDuration(this.duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {Song}
|
44
src/player/SongCheck.js
Normal file
44
src/player/SongCheck.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
const {LogType} = require("loguix")
|
||||||
|
const {Song} = require("./Song")
|
||||||
|
const slog = new LogType("SongCheck")
|
||||||
|
|
||||||
|
function checkSong(song) {
|
||||||
|
if(!(song instanceof Song)) {
|
||||||
|
slog.error("La musique n'est pas une instance de la classe Song")
|
||||||
|
// Check if the song is valid and if it has all the required properties
|
||||||
|
if(song.title && song.id && song.author && song.url && song.duration && song.readduration && song.type) {
|
||||||
|
slog.log("Acceptation de la musique : " + song.title)
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
slog.error("La musique n'est pas valide")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!song.url) {
|
||||||
|
slog.error("La musique n'a pas d'url")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if(!song.title) {
|
||||||
|
slog.error("La musique n'a pas de titre")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if(!song.author) {
|
||||||
|
slog.error("La musique n'a pas d'auteur")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if(!song.duration) {
|
||||||
|
slog.error("La musique n'a pas de durée")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if(!song.readduration) {
|
||||||
|
slog.error("La musique n'a pas de durée lisible")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if(!song.type) {
|
||||||
|
slog.error("La musique n'a pas de type")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {checkSong}
|
87
src/playlists/Google/OAuth2.js
Normal file
87
src/playlists/Google/OAuth2.js
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
const { LogType } = require('loguix');
|
||||||
|
const alog = new LogType("GoogleOAuth2");
|
||||||
|
const { google } = require('googleapis');
|
||||||
|
const config = require("../../utils/Database/Configuration");
|
||||||
|
const Users = require('../../server/auth/User');
|
||||||
|
|
||||||
|
const clientId = config.getYoutubeApiClientId();
|
||||||
|
const clientSecret = config.getYoutubeApiClientSecret();
|
||||||
|
const redirectUri = config.getWebsiteLink() + "/oauth2callback";
|
||||||
|
|
||||||
|
|
||||||
|
const oAuth2Map = new Map();
|
||||||
|
|
||||||
|
function createAuthUrl(userId) {
|
||||||
|
if(!checkCredientials()) return null;
|
||||||
|
var oAuth2Client;
|
||||||
|
const user = Users.getUserById(userId);
|
||||||
|
if (!user) {
|
||||||
|
alog.error(`User with ID ${userId} not found.`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!clientId || !clientSecret) {
|
||||||
|
alog.error("YouTube API client ID or secret is not set in the configuration.");
|
||||||
|
} else {
|
||||||
|
oAuth2Client = new google.auth.OAuth2(
|
||||||
|
clientId,
|
||||||
|
clientSecret,
|
||||||
|
redirectUri
|
||||||
|
);
|
||||||
|
|
||||||
|
alog.log("Google OAuth2 client initialized successfully.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!oAuth2Client) {
|
||||||
|
alog.error("OAuth2 client is not initialized. Please check your configuration.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
oAuth2Map.set(userId, oAuth2Client);
|
||||||
|
alog.log(`OAuth2 client created for user ${userId}.`);
|
||||||
|
return oAuth2Client.generateAuthUrl({
|
||||||
|
access_type: 'offline',
|
||||||
|
scope: SCOPES,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAuthorization(userId, code) {
|
||||||
|
if(!checkCredientials()) return null;
|
||||||
|
try {
|
||||||
|
const user = Users.getUserById(userId);
|
||||||
|
if (!user) {
|
||||||
|
alog.error(`User with ID ${userId} not found.`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
oAuth2Client = oAuth2Map.get(userId);
|
||||||
|
if (!oAuth2Client) {
|
||||||
|
alog.error(`OAuth2 client for user ${userId} not found. Please create an OAuth2 client first.`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { tokens } = await oAuth2Client.getToken(code);
|
||||||
|
oAuth2Client.setCredentials(tokens);
|
||||||
|
alog.log(`OAuth2 client credentials set for user ${userId}.`);
|
||||||
|
return oAuth2Client;
|
||||||
|
} catch (error) {
|
||||||
|
alog.error(`Error during OAuth2 authorization for user ${userId}:`, error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkCredientials() {
|
||||||
|
if (!clientId || !clientSecret) {
|
||||||
|
alog.error("YouTube API client ID or secret is not set in the configuration.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
createAuthUrl,
|
||||||
|
getAuthorization,
|
||||||
|
getOAuth2Client: (userId) => oAuth2Map.get(userId),
|
||||||
|
oAuth2Map
|
||||||
|
};
|
||||||
|
|
||||||
|
const SCOPES = ['https://www.googleapis.com/auth/youtube.readonly'];
|
||||||
|
|
62
src/playlists/Google/YoutubeList.js
Normal file
62
src/playlists/Google/YoutubeList.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
const { google } = require('googleapis');
|
||||||
|
const { LogType } = require('loguix');
|
||||||
|
const alog = new LogType("YoutubeAPI");
|
||||||
|
|
||||||
|
const OAuth2 = require('./OAuth2');
|
||||||
|
const Users = require('../../server/auth/User');
|
||||||
|
|
||||||
|
async function getYoutubePlaylists(userId) {
|
||||||
|
const user = Users.getUserById(userId);
|
||||||
|
if (!user) {
|
||||||
|
alog.error(`User with ID ${userId} not found.`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const oAuth2Client = OAuth2.getOAuth2Client(userId);
|
||||||
|
if (!oAuth2Client) {
|
||||||
|
alog.error(`OAuth2 client for user ${userId} not found. Please authenticate first.`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const youtube = google.youtube({ version: 'v3', auth: oAuth2Client });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await youtube.playlists.list({
|
||||||
|
part: 'snippet,contentDetails',
|
||||||
|
mine: true,
|
||||||
|
maxResults: 50
|
||||||
|
});
|
||||||
|
alog.log(`Retrieved playlists for user ${userId}.`);
|
||||||
|
return response.data.items;
|
||||||
|
} catch (error) {
|
||||||
|
alog.error(`Error retrieving playlists for user ${userId}:`, error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getYoutubePlaylistSongs(playlistId, userId) {
|
||||||
|
const user = Users.getUserById(userId);
|
||||||
|
if (!user) {
|
||||||
|
alog.error(`User with ID ${userId} not found.`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const oAuth2Client = OAuth2.getOAuth2Client(userId);
|
||||||
|
if (!oAuth2Client) {
|
||||||
|
alog.error(`OAuth2 client for user ${userId} not found. Please authenticate first.`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const youtube = google.youtube({ version: 'v3', auth: oAuth2Client });
|
||||||
|
|
||||||
|
return youtube.playlistItems.list({
|
||||||
|
part: 'snippet',
|
||||||
|
playlistId: playlistId,
|
||||||
|
maxResults: 50
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getYoutubePlaylists,
|
||||||
|
getYoutubePlaylistSongs
|
||||||
|
};
|
54
src/playlists/History.js
Normal file
54
src/playlists/History.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
const {LogType} = require("loguix")
|
||||||
|
const hlog = new LogType("PersonalHistory")
|
||||||
|
const {__glob} = require("../utils/GlobalVars")
|
||||||
|
const { Database } = require("../utils/Database/Database")
|
||||||
|
const historyDb = new Database("history", __glob.HISTORY_DB, {})
|
||||||
|
historyDb.load()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} userId
|
||||||
|
* @returns {Array<Object>}
|
||||||
|
* @description Renvoie l'historique personnel de l'utilisateur
|
||||||
|
*/
|
||||||
|
function getPersonalHistory(userId) {
|
||||||
|
if (historyDb.data[userId]) {
|
||||||
|
return historyDb.data[userId];
|
||||||
|
} else {
|
||||||
|
hlog.log(`Création d'une clé pour l'utilisateur : ${userId}`);
|
||||||
|
historyDb.data[userId] = [];
|
||||||
|
historyDb.save();
|
||||||
|
return historyDb.data[userId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} userId
|
||||||
|
* @param {Object} entry
|
||||||
|
* @description Ajoute une entrée à l'historique personnel de l'utilisateur
|
||||||
|
*/
|
||||||
|
function addToPersonalHistory(userId, entry) {
|
||||||
|
hlog.log(`Ajout d'une entrée à l'historique personnel de l'utilisateur : ${userId}`);
|
||||||
|
const history = getPersonalHistory(userId);
|
||||||
|
// Limit to 25 entries
|
||||||
|
if (history.length >= 25) {
|
||||||
|
history.shift();
|
||||||
|
}
|
||||||
|
history.push(entry)
|
||||||
|
historyDb.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} userId
|
||||||
|
* @description Vide l'historique personnel de l'utilisateur
|
||||||
|
*/
|
||||||
|
function clearPersonalHistory(userId) {
|
||||||
|
hlog.log(`Vidage de l'historique personnel de l'utilisateur : ${userId}`);
|
||||||
|
historyDb.data[userId] = [];
|
||||||
|
historyDb.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getPersonalHistory,
|
||||||
|
addToPersonalHistory,
|
||||||
|
clearPersonalHistory
|
||||||
|
};
|
36
src/playlists/Playlist.js
Normal file
36
src/playlists/Playlist.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
const { getReadableDuration } = require("../utils/TimeConverter");
|
||||||
|
|
||||||
|
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;
|
||||||
|
form = "PLAYLIST";
|
||||||
|
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 || new Array();
|
||||||
|
this.thumbnail = thumbnail;
|
||||||
|
// Make the some of durations of the songs
|
||||||
|
if(this.songs.length > 0) {
|
||||||
|
this.duration = this.songs.reduce((acc, song) => acc + song.duration, 0);
|
||||||
|
this.readduration = getReadableDuration(this.duration);
|
||||||
|
}
|
||||||
|
this.description = description;
|
||||||
|
if(!this.url) {
|
||||||
|
this.type = "playlist";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {Playlist};
|
||||||
|
|
278
src/playlists/PlaylistManager.js
Normal file
278
src/playlists/PlaylistManager.js
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
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 { getYoutubePlaylistSongs } = require('./Google/YoutubeList');
|
||||||
|
const { auth } = require('googleapis/build/src/apis/abusiveexperiencereport');
|
||||||
|
const { getReadableDuration } = require('../utils/TimeConverter');
|
||||||
|
const { getSecondsFromUrl } = require('../media/YoutubeInformation');
|
||||||
|
|
||||||
|
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.title === 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.title === name)) {
|
||||||
|
clog.warn(`La playlist ${name} existe déjà pour l'utilisateur ${id}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var failed;
|
||||||
|
if(url) {
|
||||||
|
await Finder.search(url, false, "PLAYLIST").then(async (playlistFounded) => {
|
||||||
|
if(!playlistFounded) {
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
|
if(playlistFounded instanceof Playlist) {
|
||||||
|
playlist = playlistFounded;
|
||||||
|
}
|
||||||
|
if(playlist.type === "spotify") {
|
||||||
|
playlist.songs = await spotify.getTracks(playlist);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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}`);
|
||||||
|
return playlist;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removePlaylist(id, name) {
|
||||||
|
const playlists = getPlaylistsOfUser(id);
|
||||||
|
const index = playlists.findIndex(p => p.title === 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.title === 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.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 false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renamePlaylist(id, oldName, newName) {
|
||||||
|
const playlists = getPlaylistsOfUser(id);
|
||||||
|
const playlist = playlists.find(p => p.title === oldName);
|
||||||
|
if (!playlist) {
|
||||||
|
clog.warn(`La playlist ${oldName} n'existe pas pour l'utilisateur ${id}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 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.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}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeSong(id, playlistName, songId) {
|
||||||
|
const playlists = getPlaylistsOfUser(id);
|
||||||
|
const playlist = playlists.find(p => p.title === 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}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processYoutubeData(userId, data) {
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
clog.warn(`Aucune donnée YouTube trouvée pour l'utilisateur ${userId}`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const playlists = [];
|
||||||
|
for (const item of data) {
|
||||||
|
if (item.snippet && item.contentDetails) {
|
||||||
|
const playlist = new Playlist();
|
||||||
|
playlist.id = item.id;
|
||||||
|
playlist.title = item.snippet.title;
|
||||||
|
playlist.url = `https://www.youtube.com/playlist?list=${item.id}`;
|
||||||
|
playlist.description = item.snippet.description || "Aucune description disponible";
|
||||||
|
playlist.author = item.snippet.channelTitle;
|
||||||
|
playlist.thumbnail = item.snippet.thumbnails.default.url;
|
||||||
|
playlist.authorId = `https://www.youtube.com/channel/${item.snippet.channelId}`;
|
||||||
|
playlist.songs = []; // You can fetch songs later if needed
|
||||||
|
await getYoutubePlaylistSongs(item.id, userId).then(songsData => {
|
||||||
|
if (songsData && songsData.data && songsData.data.items) {
|
||||||
|
playlist.songs = songsData.data.items.map(song => ({
|
||||||
|
id: song.snippet.resourceId.videoId,
|
||||||
|
title: song.snippet.title,
|
||||||
|
author: song.snippet.videoOwnerChannelTitle,
|
||||||
|
authorId: `https://www.youtube.com/channel/${song.snippet.videoOwnerChannelId}`,
|
||||||
|
url: `https://www.youtube.com/watch?v=${song.snippet.resourceId.videoId}`,
|
||||||
|
thumbnail: song.snippet?.thumbnails?.default?.url || "https://radomisol.fr/wp-content/uploads/2016/08/cropped-note-radomisol-musique.png",
|
||||||
|
}));
|
||||||
|
// Add readduration for every items in songs
|
||||||
|
|
||||||
|
} else {
|
||||||
|
clog.warn(`Aucune chanson trouvée pour la playlist ${item.id}`);
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
clog.error(`Erreur lors de la récupération des chansons pour la playlist ${item.id}:`, err);
|
||||||
|
});
|
||||||
|
for (const song of playlist.songs) {
|
||||||
|
// If authorId is not defined, delete the song
|
||||||
|
if (song.authorId == "https://www.youtube.com/channel/undefined") {
|
||||||
|
clog.warn(`L'auteur de la chanson ${song.title} (${song.id}) n'est pas défini. Suppression de la chanson.`);
|
||||||
|
playlist.songs.splice(playlist.songs.indexOf(song), 1);
|
||||||
|
continue; // Skip this song
|
||||||
|
}
|
||||||
|
song.duration = await getSecondsFromUrl(song.url);
|
||||||
|
if (song.duration === null) {
|
||||||
|
clog.warn(`Impossible de récupérer la durée de la chanson ${song.title} (${song.id})`);
|
||||||
|
song.duration = 0; // Set to 0 if duration cannot be fetched
|
||||||
|
} else {
|
||||||
|
song.readduration = getReadableDuration(song.duration);
|
||||||
|
playlist.duration += song.duration; // Initialize duration if not set
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
playlist.readduration = getReadableDuration(playlist.duration);
|
||||||
|
playlist.type = "youtube";
|
||||||
|
playlists.push(playlist);
|
||||||
|
} else {
|
||||||
|
clog.warn(`Données YouTube manquantes pour l'élément ${item.id}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
clog.log(`Traitement des données YouTube pour l'utilisateur ${userId} terminé. Nombre de playlists trouvées : ${playlists.length}`);
|
||||||
|
// Save the playlists to the user's playlist collection
|
||||||
|
const userPlaylists = getPlaylistsOfUser(userId);
|
||||||
|
// Remove existing playlists with the same IDs to avoid duplicates
|
||||||
|
for (const playlist of playlists) {
|
||||||
|
const existingIndex = userPlaylists.findIndex(p => p.id === playlist.id);
|
||||||
|
if (existingIndex !== -1) {
|
||||||
|
userPlaylists.splice(existingIndex, 1); // Remove existing playlist with the same ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
userPlaylists.push(...playlists);
|
||||||
|
playlistDB.save();
|
||||||
|
clog.log(`Playlists ajoutées pour l'utilisateur ${userId}. Nombre total de playlists : ${userPlaylists.length}`);
|
||||||
|
return playlists;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getPlaylistsOfUser,
|
||||||
|
getPlaylistOfUser,
|
||||||
|
addPlaylist,
|
||||||
|
removePlaylist,
|
||||||
|
getPlaylist,
|
||||||
|
copyPlaylist,
|
||||||
|
renamePlaylist,
|
||||||
|
addSong,
|
||||||
|
removeSong,
|
||||||
|
processYoutubeData
|
||||||
|
}
|
940
src/server/Server.js
Normal file
940
src/server/Server.js
Normal file
@@ -0,0 +1,940 @@
|
|||||||
|
const {LogType} = require('loguix')
|
||||||
|
const wlog = new LogType("Server")
|
||||||
|
|
||||||
|
const fs = require("fs")
|
||||||
|
const path = require("path")
|
||||||
|
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 {__glob} = require("../utils/GlobalVars")
|
||||||
|
const playlists = require("../playlists/PlaylistManager")
|
||||||
|
const history = require("../playlists/History")
|
||||||
|
const lyrics = require("../lyrics/Lyrics")
|
||||||
|
const mediaBase = require("../discord/MediaBase")
|
||||||
|
const googleApis = require("../playlists/Google/OAuth2")
|
||||||
|
const youtubeApi = require("../playlists/Google/YoutubeList")
|
||||||
|
|
||||||
|
const configuration = require("../utils/Database/Configuration")
|
||||||
|
const { List } = require('../player/List')
|
||||||
|
const { restart } = require('../utils/Maintenance')
|
||||||
|
const { isAudioFile } = require('../utils/AudioBufferCheck')
|
||||||
|
const { Song } = require('../player/Song')
|
||||||
|
const { getMediaInformationFromUrl } = require('../media/MediaInformation')
|
||||||
|
|
||||||
|
const allConnectedUsers = new Array()
|
||||||
|
const guildConnectedUsers = new Map()
|
||||||
|
const UsersBySocket = new Map()
|
||||||
|
|
||||||
|
//TODO: Refactor this file to implement the fact that server can be joined and leaved and all the events are now handled, so guildId is not required for every event
|
||||||
|
|
||||||
|
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().keys()) {
|
||||||
|
const player = players.getPlayer(guild)
|
||||||
|
if(player) {
|
||||||
|
if(!player.isConnected()) continue;
|
||||||
|
io.to(player.guildId).emit("/PLAYER/UPDATE", player.getState())
|
||||||
|
wlog.log("Envoi de l'état du player de la guilde : " + player.guildId + " à tous les utilisateurs connectés")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
process.on("USERS_UPDATE", () => {
|
||||||
|
if(io) {
|
||||||
|
// Get all players and send them to client subscribed to the guild
|
||||||
|
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.sockets.emit("/USER/READY")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
io.on("connection", async (socket) => {
|
||||||
|
var socketUser;
|
||||||
|
|
||||||
|
// Make sure Discord Bot is loaded and make an interruption until it is loaded
|
||||||
|
while(!await discordBot.isReady()) {
|
||||||
|
wlog.warn("Attente de traitement : "+ socket.id + " : Le bot Discord n'est pas encore chargé, attente de 0.5 seconde... (Avoid Rate Limit)")
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 500))
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
sendSession()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var token = socket.handshake.auth.token
|
||||||
|
var sessionId = socket.handshake.auth.sessionId
|
||||||
|
var auth_code = socket.handshake.auth.auth_code
|
||||||
|
var inLogin = false
|
||||||
|
|
||||||
|
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 == "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", discordUser)
|
||||||
|
socket.disconnect()
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
const loggedUser = await users.addUser(discordUser.auth, discordUser.identity)
|
||||||
|
for(var guild of discordUser.guilds) {
|
||||||
|
if(guild.owner) {
|
||||||
|
users.setGuildOwner(loggedUser.identity.id, guild.id, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const newToken = await loggedUser.createToken()
|
||||||
|
socket.emit("NEW_TOKEN", newToken)
|
||||||
|
token = newToken
|
||||||
|
inLogin = true
|
||||||
|
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)
|
||||||
|
socket.emit("AUTH_ERROR", "Code manquant invalide")
|
||||||
|
socket.disconnect()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!token) {
|
||||||
|
wlog.warn("Token manquant pour le client :" + socket.id)
|
||||||
|
socket.emit("AUTH_ERROR", "Token invalide")
|
||||||
|
sendSession()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
socketUser = users.getUserByToken(token)
|
||||||
|
|
||||||
|
if(!socketUser) {
|
||||||
|
wlog.warn("Token invalide pour le client :" + socket.id)
|
||||||
|
socket.emit("AUTH_ERROR", "Token invalide")
|
||||||
|
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", "L'authentification Discord de l'utilisateur n'est pas valide")
|
||||||
|
socket.disconnect()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!inLogin) {
|
||||||
|
if(socketUser.needUpdate()) {
|
||||||
|
if (!(await users.updateIdentity(socketUser.identity.id))) {
|
||||||
|
wlog.error("Erreur lors de la mise à jour des informations de l'utilisateur : " + socketUser.identity.id);
|
||||||
|
socket.emit("AUTH_ERROR", "Mise à jour des informations de l'utilisateur impossible");
|
||||||
|
wlog.log("Déconnexion de l'utilisateur : " + socketUser.identity.username + " (" + socketUser.identity.id + ") - Socket : " + socket.id)
|
||||||
|
socket.disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
socketUser.justUpdated()
|
||||||
|
} else {
|
||||||
|
wlog.log("Pas de mise à jour des informations de l'utilisateur : " + socketUser.identity.id + " car l'utilisateur vient de se connecter")
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
wlog.log("L'utilisateur '" + socketUser.identity.username + "' s'est connecté via la session : " + sessionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
socketUser = users.getUserByToken(token)
|
||||||
|
|
||||||
|
if(socketUser) {
|
||||||
|
var actualGuildId = null
|
||||||
|
if(allConnectedUsers.includes(socketUser.identity)) {
|
||||||
|
wlog.warn("L'utilisateur '" + socketUser.identity.username + "' est déjà connecté sur un autre appareil")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
allConnectedUsers.push(socketUser.identity)
|
||||||
|
UsersBySocket.set(socketUser.identity.id, socket.id)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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("AUTH_ERROR", "Vous êtes banni du serveur")
|
||||||
|
socket.disconnect()
|
||||||
|
}
|
||||||
|
if(socketUser.isAdmin()) {
|
||||||
|
socket.join("ADMIN")
|
||||||
|
wlog.log("Utilisateur admin identifié : " + socketUser.identity.username + " (" + socketUser.identity.id + ")")
|
||||||
|
}
|
||||||
|
|
||||||
|
process.emit("USERS_UPDATE")
|
||||||
|
// USERS
|
||||||
|
|
||||||
|
// CHECKED : 24/04/2025
|
||||||
|
IORequest("/USER/INFO", () => {
|
||||||
|
var guildPresents = new Array();
|
||||||
|
var guildsOfBot = discordBot.getGuilds()
|
||||||
|
for(var guild of guildsOfBot) {
|
||||||
|
if(guild[1].allMembers.includes(socketUser.identity.id)) {
|
||||||
|
const guildData = guild[1]
|
||||||
|
guildData['members'] = new Array()
|
||||||
|
guildData.serverMember = guild[1].allMembers.length
|
||||||
|
for(var user of guildConnectedUsers.get(guild[0]) || []) {
|
||||||
|
const userData = users.getUserById(user.id)
|
||||||
|
if(userData && userData.identity.id != socketUser.identity.id) {
|
||||||
|
let infos = {
|
||||||
|
id: userData.identity.id,
|
||||||
|
username: userData.identity.username,
|
||||||
|
avatar: userData.identity.avatar,
|
||||||
|
isAdmin: userData.isAdmin(),
|
||||||
|
isOwner: userData.isOwner(guild[0]),
|
||||||
|
isMod: userData.isMod(guild[0]),
|
||||||
|
}
|
||||||
|
guildData.members.push(infos)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send if the bot is connected to the guild
|
||||||
|
if(players.getPlayer(guild[0]) && players.getPlayer(guild[0]).isConnected()) {
|
||||||
|
guildData.connected = true
|
||||||
|
} else {
|
||||||
|
guildData.connected = false
|
||||||
|
}
|
||||||
|
// Leave the room if the user is not in the guild
|
||||||
|
if(socket.rooms.has(guild[0]) && !checkUserGuild(socketUser, guild[0])) {
|
||||||
|
socket.leave(guild[0])
|
||||||
|
removeGuildConnectedUser(socketUser.identity)
|
||||||
|
wlog.warn("L'utilisateur '" + socketUser.identity.username + "' quitte la room de la guilde : " + guild[0] + " car il n'est pas dans la guilde) /!\\")
|
||||||
|
}
|
||||||
|
guildPresents.push(guildData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IOAnswer("/USER/INFO", {
|
||||||
|
identity: socketUser.identity,
|
||||||
|
guilds: guildPresents,
|
||||||
|
labels: socketUser.labels,
|
||||||
|
history: history.getPersonalHistory(socketUser.identity.id),
|
||||||
|
})
|
||||||
|
wlog.log("Envoi des informations Discord de '" + socketUser.identity.id + "' à '" + socket.id + "'" )
|
||||||
|
})
|
||||||
|
|
||||||
|
IORequest("/USER/HISTORY", () => {
|
||||||
|
IOAnswer("/USER/HISTORY", history.getPersonalHistory(socketUser.identity.id))
|
||||||
|
})
|
||||||
|
|
||||||
|
//CHECKED : 24/04/2025
|
||||||
|
IORequest("/USER/SIGNOUT", () => {
|
||||||
|
socketUser.removeToken(token)
|
||||||
|
IOAnswer("/USER/SIGNOUT", true)
|
||||||
|
socket.disconnect()
|
||||||
|
})
|
||||||
|
|
||||||
|
// CHECKED : 24/04/2025
|
||||||
|
IORequest("/USERS/LIST", () => {
|
||||||
|
if(!checkUserGuild(socketUser, actualGuildId)) return
|
||||||
|
if(!guildConnectedUsers.has(actualGuildId)) return IOAnswer("/USERS/LIST", false)
|
||||||
|
IOAnswer("/USERS/LIST", guildConnectedUsers.get(actualGuildId))
|
||||||
|
})
|
||||||
|
|
||||||
|
// PLAYERS
|
||||||
|
|
||||||
|
IORequest("/PLAYER/LYRICS", async () => {
|
||||||
|
if(!checkUserGuild(socketUser, actualGuildId)) return
|
||||||
|
const player = await verifyPlayerAction(actualGuildId)
|
||||||
|
if(!player) return IOAnswer("/PLAYER/LYRICS", false)
|
||||||
|
if(!player.queue?.current) {
|
||||||
|
wlog.warn("Le player de la guilde : " + actualGuildId + " n'a pas de musique en cours")
|
||||||
|
IOAnswer("/PLAYER/LYRICS", false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const song = player.queue.current
|
||||||
|
const lyricsData = await lyrics.getLyrics(song.title + " " + song.author)
|
||||||
|
if(!lyricsData) {
|
||||||
|
wlog.warn("Aucune lyrics trouvée pour la musique : " + song.title + " de l'artiste : " + song.author)
|
||||||
|
IOAnswer("/PLAYER/LYRICS", false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
IOAnswer("/PLAYER/LYRICS", lyricsData)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//CHECKED : 03/05/2025
|
||||||
|
IORequest("/PLAYER/PREVIOUS/LIST", () => {
|
||||||
|
if(!checkUserGuild(socketUser, actualGuildId)) return
|
||||||
|
const list = new List(actualGuildId)
|
||||||
|
IOAnswer("/PLAYER/PREVIOUS/LIST", list.getPrevious())
|
||||||
|
})
|
||||||
|
|
||||||
|
// ChECKED : 03/05/2025
|
||||||
|
IORequest("/GUILD/JOIN", async (guildId) => {
|
||||||
|
if(!checkUserGuild(socketUser, guildId)) return IOAnswer("/GUILD/JOIN", "No guild found or not in the guild")
|
||||||
|
if(socket.rooms.has(guildId)) {
|
||||||
|
wlog.warn("L'utilisateur '" + socketUser.identity.username + "' est déjà dans la room de la guilde : " + guildId)
|
||||||
|
} else {
|
||||||
|
// Make him to leave all the other rooms except the ADMIN room if he is admin
|
||||||
|
await socket.rooms.forEach((room) => {
|
||||||
|
if(room != "ADMIN" && room != guildId && room != socket.id) {
|
||||||
|
socket.leave(room)
|
||||||
|
wlog.log("L'utilisateur '" + socketUser.identity.username + "' quitte la room de la guilde: " + room)
|
||||||
|
removeGuildConnectedUser(socketUser.identity)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
socket.join(guildId)
|
||||||
|
wlog.log("L'utilisateur '" + socketUser.identity.username + "' rejoint la room de la guilde : " + guildId)
|
||||||
|
addGuildConnectedUser(socketUser.identity, guildId)
|
||||||
|
actualGuildId = guildId
|
||||||
|
IOAnswer("/GUILD/JOIN", true)
|
||||||
|
process.emit("PLAYERS_UPDATE")
|
||||||
|
process.emit("USERS_UPDATE")
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// CHECKED : 03/05/2025
|
||||||
|
IORequest("/PLAYER/STATE", async () => {
|
||||||
|
const plaryer = await verifyPlayerAction(actualGuildId)
|
||||||
|
if(!player) return IOAnswer("/PLAYER/STATE", false)
|
||||||
|
IOAnswer("/PLAYER/STATE", await player.getState())
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// CHECKED : 03/05/2025
|
||||||
|
IORequest("/PLAYER/PAUSE", () => {
|
||||||
|
handlePlayerAction(actualGuildId, (player) => player.pause(), "/PLAYER/PAUSE");
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// CHECKED : 03/05/2025
|
||||||
|
IORequest("/PLAYER/BACKWARD", () => {
|
||||||
|
handlePlayerAction(actualGuildId, (player) => player.previous(), "/PLAYER/BACKWARD");
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// CHECKED : 03/05/2025
|
||||||
|
IORequest("/PLAYER/FORWARD", () => {
|
||||||
|
handlePlayerAction(actualGuildId, (player) => player.skip(), "/PLAYER/FORWARD");
|
||||||
|
});
|
||||||
|
|
||||||
|
// CHECKED : 03/05/2025
|
||||||
|
IORequest("/PLAYER/LOOP", () => {
|
||||||
|
handlePlayerAction(actualGuildId, (player) => player.setLoop(), "/PLAYER/LOOP");
|
||||||
|
});
|
||||||
|
|
||||||
|
// CHECKED : 03/05/2025
|
||||||
|
IORequest("/PLAYER/SHUFFLE", () => {
|
||||||
|
handlePlayerAction(actualGuildId, (player) => player.setShuffle(), "/PLAYER/SHUFFLE");
|
||||||
|
});
|
||||||
|
// CHECKED : 03/05/2025
|
||||||
|
IORequest("/PLAYER/DISCONNECT", () => {
|
||||||
|
handlePlayerAction(actualGuildId, (player) => player.leave(), "/PLAYER/DISCONNECT");
|
||||||
|
});
|
||||||
|
// CHECKED : 03/05/2025
|
||||||
|
IORequest("/PLAYER/CHANNEL/CHANGE", () => {
|
||||||
|
handlePlayerAction(actualGuildId, (player) => {
|
||||||
|
const channel = getUserChannel()
|
||||||
|
if(!channel) {
|
||||||
|
IOAnswer("/PLAYER/CHANNEL/CHANGE", false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
player.changeChannel(channel)
|
||||||
|
}, "/PLAYER/CHANNEL/CHANGE");
|
||||||
|
});
|
||||||
|
|
||||||
|
// CHECKED : 03/05/2025
|
||||||
|
IORequest("/PLAYER/SEEK", (time) => {
|
||||||
|
if(!time) return IOAnswer("/PLAYER/SEEK", false)
|
||||||
|
handlePlayerAction(actualGuildId, (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");
|
||||||
|
});
|
||||||
|
|
||||||
|
// CHECKED : 04/05/2025
|
||||||
|
IORequest("/QUEUE/PLAY", (data) => {
|
||||||
|
if(!data) return IOAnswer("/QUEUE/PLAY/NOW", false)
|
||||||
|
const {index, listType, now} = data
|
||||||
|
if(!index) return IOAnswer("/QUEUE/PLAY/NOW", false)
|
||||||
|
if(!listType) return IOAnswer("/QUEUE/PLAY/NOW", false)
|
||||||
|
if(!checkUserGuild(socketUser, actualGuildId)) return
|
||||||
|
const player = new Player(actualGuildId)
|
||||||
|
if(!connectToPlayer(actualGuildId, player)) return IOAnswer("/QUEUE/PLAY", false)
|
||||||
|
var song;
|
||||||
|
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/NOW", false)
|
||||||
|
if(listType == "next") player.queue.removeNextByIndex(index)
|
||||||
|
if(now) {
|
||||||
|
player.play(song)
|
||||||
|
} else {
|
||||||
|
player.add(song)
|
||||||
|
}
|
||||||
|
history.addToPersonalHistory(socketUser.identity.id, song)
|
||||||
|
IOAnswer("/QUEUE/PLAY/NOW", true)
|
||||||
|
})
|
||||||
|
|
||||||
|
// CHECKED : 04/05/2025
|
||||||
|
IORequest("/QUEUE/NEXT/DELETE", (index) => {
|
||||||
|
if(!index) return IOAnswer("/QUEUE/NEXT/DELETE", false)
|
||||||
|
handlePlayerAction(actualGuildId, (player) => {
|
||||||
|
const next = player.queue.getNext()
|
||||||
|
if(!next[index]) return IOAnswer("/QUEUE/NEXT/DELETE", false);
|
||||||
|
player.queue.removeNextByIndex(index)
|
||||||
|
}, "/QUEUE/NEXT/DELETE")
|
||||||
|
})
|
||||||
|
|
||||||
|
// CHECKED : 04/05/2025
|
||||||
|
IORequest("/QUEUE/NEXT/DELETEALL", () => {
|
||||||
|
handlePlayerAction(actualGuildId, (player) => player.queue.clearNext(), "/QUEUE/NEXT/DELETEALL")
|
||||||
|
})
|
||||||
|
|
||||||
|
// CHECKED : 04/05/2025
|
||||||
|
IORequest("/QUEUE/NEXT/MOVE", (data) => {
|
||||||
|
if(!data) return IOAnswer("/QUEUE/NEXT/MOVE", false)
|
||||||
|
const {index, newIndex} = data
|
||||||
|
if(!index) return IOAnswer("/QUEUE/NEXT/MOVE", false)
|
||||||
|
if(!newIndex) return IOAnswer("/QUEUE/NEXT/MOVE", false)
|
||||||
|
handlePlayerAction(actualGuildId, (player) => {
|
||||||
|
const next = player.queue.getNext()
|
||||||
|
if(!next[index]) return IOAnswer("/QUEUE/NEXT/MOVE", false);
|
||||||
|
player.queue.moveNext(index, newIndex)
|
||||||
|
}, "/QUEUE/NEXT/MOVE")
|
||||||
|
})
|
||||||
|
|
||||||
|
// SEARCH
|
||||||
|
|
||||||
|
// CHECKED : 24/04/2025
|
||||||
|
IORequest("/SEARCH", async (query) => {
|
||||||
|
IOAnswer("/SEARCH", await Finder.search(query, true))
|
||||||
|
})
|
||||||
|
|
||||||
|
// CHECKED : 03/05/2025
|
||||||
|
IORequest("/SEARCH/PLAY", async (data) => {
|
||||||
|
if(!data) return IOAnswer("/SEARCH/PLAY", false)
|
||||||
|
var {song, now} = data
|
||||||
|
if(!song) return IOAnswer("/SEARCH/PLAY", false)
|
||||||
|
if(typeof song == "string") {
|
||||||
|
song = JSON.parse(song)
|
||||||
|
}
|
||||||
|
if(!checkUserGuild(socketUser, actualGuildId)) return
|
||||||
|
const player = new Player(actualGuildId)
|
||||||
|
if(!connectToPlayer(actualGuildId, player)) return IOAnswer("/SEARCH/PLAY", false)
|
||||||
|
if(now) {
|
||||||
|
player.play(song)
|
||||||
|
} else {
|
||||||
|
player.add(song)
|
||||||
|
}
|
||||||
|
history.addToPersonalHistory(socketUser.identity.id, song)
|
||||||
|
IOAnswer("/SEARCH/PLAY", true)
|
||||||
|
})
|
||||||
|
|
||||||
|
// CHECKED : 05/05/2025
|
||||||
|
IORequest("/SEARCH/PLAYLIST", async (data) => {
|
||||||
|
if(!data) return IOAnswer("/SEARCH/PLAYLIST", false)
|
||||||
|
const {url, now} = data
|
||||||
|
if(!url) return IOAnswer("/SEARCH/PLAYLIST", false)
|
||||||
|
const playlist = await Finder.search(url, true, "PLAYLIST")
|
||||||
|
if(!playlist) return IOAnswer("/SEARCH/PLAYLIST", false)
|
||||||
|
const player = new Player(actualGuildId)
|
||||||
|
if(!connectToPlayer(actualGuildId, player)) return IOAnswer("/SEARCH/PLAYLIST", false)
|
||||||
|
player.readPlaylist(playlist, now)
|
||||||
|
IOAnswer("/SEARCH/PLAYLIST", true)
|
||||||
|
})
|
||||||
|
|
||||||
|
IORequest("/SEARCH/LYRICS", async (name) => {
|
||||||
|
if(!name) return IOAnswer("/SEARCH/LYRICS", false)
|
||||||
|
const lyricsData = await lyrics.getLyrics(name)
|
||||||
|
if(!lyricsData) return IOAnswer("/SEARCH/LYRICS", false)
|
||||||
|
IOAnswer("/SEARCH/LYRICS", lyricsData)
|
||||||
|
})
|
||||||
|
|
||||||
|
// UPLOAD
|
||||||
|
|
||||||
|
// CHECKED : 29/05/2025
|
||||||
|
|
||||||
|
IORequest("/UPLOAD/FILE", async (data) => {
|
||||||
|
if(!data) return IOAnswer("/UPLOAD/FILE", false)
|
||||||
|
if(!data.name) return IOAnswer("/UPLOAD/FILE", false)
|
||||||
|
const file = data.file
|
||||||
|
// Check wav or mp3
|
||||||
|
if(isAudioFile(file) == false) {
|
||||||
|
wlog.warn("Le fichier envoyé n'est pas un fichier audio valide (MP3/WAV)")
|
||||||
|
return IOAnswer("/UPLOAD/FILE", false)
|
||||||
|
}
|
||||||
|
const url = await mediaBase.postMedia(data)
|
||||||
|
if(!url) return IOAnswer("/UPLOAD/FILE", false)
|
||||||
|
IOAnswer("/UPLOAD/FILE", {"url": url, "name": data.name})
|
||||||
|
})
|
||||||
|
|
||||||
|
// CHECKED : 29/05/2025
|
||||||
|
IORequest("/UPLOAD/FILE/GET_SONG", async (data) => {
|
||||||
|
if(!data) return IOAnswer("/UPLOAD/FILE/GET_SONG", false)
|
||||||
|
const {name, url} = data
|
||||||
|
if(!url) return IOAnswer("/UPLOAD/FILE/GET_SONG", false)
|
||||||
|
if(!name) return IOAnswer("/UPLOAD/FILE/GET_SONG", false)
|
||||||
|
const song = new Song()
|
||||||
|
if(!song) return IOAnswer("/UPLOAD/FILE/GET_SONG", false)
|
||||||
|
await getMediaInformationFromUrl(song, url)
|
||||||
|
song.type = "attachment"
|
||||||
|
song.author = socketUser.identity.username
|
||||||
|
song.authorId = socketUser.identity.id
|
||||||
|
song.title = name
|
||||||
|
song.url = url
|
||||||
|
IOAnswer("/UPLOAD/FILE/GET_SONG", song)
|
||||||
|
})
|
||||||
|
|
||||||
|
// GOOGLE API
|
||||||
|
|
||||||
|
IORequest("/GOOGLE/AUTH", () => {
|
||||||
|
IOAnswer("/GOOGLE/AUTH", googleApis.createAuthUrl(socketUser.identity.id))
|
||||||
|
})
|
||||||
|
|
||||||
|
IORequest("/GOOGLE/YOUTUBE/ADD_PLAYLIST", async (code) => {
|
||||||
|
if(!code) {
|
||||||
|
IOAnswer("/GOOGLE/YOUTUBE/ADD_PLAYLIST", false)
|
||||||
|
}
|
||||||
|
const token = await googleApis.getAuthorization(socketUser.identity.id, code)
|
||||||
|
if(!token) {
|
||||||
|
IOAnswer("/GOOGLE/YOUTUBE/ADD_PLAYLIST", false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
playlists.processYoutubeData(socketUser.identity.id, await youtubeApi.getYoutubePlaylists(socketUser.identity.id))
|
||||||
|
IOAnswer("/GOOGLE/YOUTUBE/ADD_PLAYLIST", true)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// PLAYLISTS
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 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 = await playlists.addPlaylist(socketUser.identity.id, name, url)
|
||||||
|
if(!playlist) return IOAnswer("/PLAYLISTS/CREATE", false)
|
||||||
|
IOAnswer("/PLAYLISTS/CREATE", true)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
playlists.renamePlaylist(socketUser.identity.id, name, newName)
|
||||||
|
IOAnswer("/PLAYLISTS/RENAME", true)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
})
|
||||||
|
|
||||||
|
// CHECKED : 05/05/2025
|
||||||
|
IORequest("/PLAYLISTS/PLAY", async (data) => {
|
||||||
|
if(!data) return IOAnswer("/PLAYLISTS/PLAY", false)
|
||||||
|
const {name, now} = data
|
||||||
|
if(!name) return IOAnswer("/PLAYLISTS/PLAY", false)
|
||||||
|
if(!checkUserGuild(socketUser, actualGuildId)) return IOAnswer("/PLAYLISTS/PLAY", false)
|
||||||
|
const playlist = playlists.getPlaylistOfUser(socketUser.identity.id, name)
|
||||||
|
if(!playlist) return IOAnswer("/PLAYLISTS/PLAY", false)
|
||||||
|
const player = new Player(actualGuildId)
|
||||||
|
if(!await connectToPlayer(actualGuildId, player)) return IOAnswer("/PLAYLISTS/PLAY", false)
|
||||||
|
player.readPlaylist(playlist, now)
|
||||||
|
IOAnswer("/PLAYLISTS/PLAY", true)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
logs_data.push({"name":log, "value": fs.readFileSync(__glob.LOGS + path.sep + log).toString()})
|
||||||
|
}
|
||||||
|
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/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("AUTH_ERROR", "Votre compte a été supprimé")
|
||||||
|
socket.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IOAnswer("/ADMIN/USERS/DELETE", true)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 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 in allPlayers) {
|
||||||
|
await states.push(await player.getState())
|
||||||
|
}
|
||||||
|
IOAnswer("/ADMIN/PLAYER/GETALLSTATE", states)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// CHECKED : 24/04/2025
|
||||||
|
IORequest("/OWNER/USERS/SWITCH_MOD", (userId) => {
|
||||||
|
if(userId || actualGuildId) return IOAnswer("/OWNER/USERS/SWITCH_MOD", false)
|
||||||
|
if(socketUser.identity.id == userId) return IOAnswer("/OWNER/USERS/SWITCH_MOD", false)
|
||||||
|
if(!socketUser.isOwner(actualGuildId)) return IOAnswer("/OWNER/USERS/SWITCH_MOD", false)
|
||||||
|
users.setGuildMod(userId, actualGuildId)
|
||||||
|
IOAnswer("/OWNER/USERS/SWITCH_MOD", true)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// CHECKED : 24/04/2025
|
||||||
|
IORequest("/MOD/USERS/BAN", (userId) => {
|
||||||
|
if(userId || actualGuildId) return IOAnswer("/MOD/USERS/BAN", false)
|
||||||
|
if(socketUser.identity.id == userId) return IOAnswer("/MOD/USERS/BAN", false)
|
||||||
|
if(!socketUser.isMod(actualGuildId)) return IOAnswer("/MOD/USERS/BAN", false)
|
||||||
|
users.setGuildBan(userId, actualGuildId)
|
||||||
|
IOAnswer("/MOD/USERS/BAN", true)
|
||||||
|
})
|
||||||
|
|
||||||
|
// UTILS
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Functions
|
||||||
|
|
||||||
|
function getUserChannel() {
|
||||||
|
const membersVoices = discordBot.getMembersVoices()
|
||||||
|
const member = membersVoices.get(socketUser.identity.id)
|
||||||
|
if(member) {
|
||||||
|
const channelId = member.channelId
|
||||||
|
const guildId = member.guildId
|
||||||
|
const channel = discordBot.getChannel(guildId, channelId)
|
||||||
|
if(!channel) {
|
||||||
|
wlog.warn("Le channel vocal n'existe pas : " + channelId)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verifyPlayerAction(guildId) {
|
||||||
|
if (!checkUserGuild(socketUser, guildId)) return null;
|
||||||
|
const player = players.getPlayer(guildId);
|
||||||
|
if (player) {
|
||||||
|
return player;
|
||||||
|
} else {
|
||||||
|
wlog.warn(`Le player de la guilde : ${guildId} n'existe pas`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function checkUserGuild(socketUser, guildId) {
|
||||||
|
if(!guildId) {
|
||||||
|
wlog.warn("Aucun guildId n'est actif pour l'utilisateur : " + socketUser.identity.username)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
const allGuilds = discordBot.getGuilds()
|
||||||
|
|
||||||
|
if(!allGuilds.get(guildId).allMembers.includes(socketUser.identity.id)) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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")
|
||||||
|
// Remove every rooms include admin
|
||||||
|
socket.rooms.forEach((room) => {
|
||||||
|
socket.leave(room)
|
||||||
|
})
|
||||||
|
UsersBySocket.delete(socketUser.identity.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(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 + " - " + AnswerName + " - [ANSWERED]")
|
||||||
|
socket.emit(AnswerName, AnswerValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
httpServer.listen(configuration.getPort(), () => {
|
||||||
|
wlog.log(`Le serveur écoute sur le port ${configuration.getPort()}`)
|
||||||
|
wlog.step.end("server_init")
|
||||||
|
})
|
||||||
|
|
||||||
|
function addGuildConnectedUser(user, guildId) {
|
||||||
|
// Check if the user is already connected to the guild
|
||||||
|
if(!guildConnectedUsers.has(guildId)) {
|
||||||
|
guildConnectedUsers.set(guildId, new Array())
|
||||||
|
}
|
||||||
|
const users = guildConnectedUsers.get(guildId)
|
||||||
|
if(users.includes(user)) {
|
||||||
|
wlog.warn("L'utilisateur '" + user.username + "' est déjà connecté à la guilde : " + guildId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guildConnectedUsers.get(guildId).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}
|
126
src/server/auth/DiscordAuth.js
Normal file
126
src/server/auth/DiscordAuth.js
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
const { LogType } = require('loguix');
|
||||||
|
const dlog = new LogType("DiscordAuth");
|
||||||
|
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);
|
||||||
|
|
||||||
|
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() + "/redirect");
|
||||||
|
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.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", {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
resolve("ACCESS_TOKEN_ERROR");
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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, getUserIdentity}
|
33
src/server/auth/Session.js
Normal file
33
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};
|
526
src/server/auth/User.js
Normal file
526
src/server/auth/User.js
Normal file
@@ -0,0 +1,526 @@
|
|||||||
|
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 discordAuth = require('./DiscordAuth');
|
||||||
|
const e = require('cors');
|
||||||
|
|
||||||
|
const UserDB = new Database("Users", __glob.USERFILE, []);
|
||||||
|
var userList = new Array();
|
||||||
|
class User {
|
||||||
|
auth;
|
||||||
|
identity;
|
||||||
|
tokens;
|
||||||
|
labels;
|
||||||
|
constructor(auth, identity, tokens, labels) {
|
||||||
|
this.auth = auth;
|
||||||
|
this.identity = identity;
|
||||||
|
this.tokens = tokens;
|
||||||
|
this.labels = labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
setAdmin() {
|
||||||
|
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 {
|
||||||
|
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 (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 {
|
||||||
|
userInUserList.labels.push(banLabel);
|
||||||
|
clog.log(`L'utilisateur ${this.identity.username} est maintenant banni du serveur ${guildId}.`);
|
||||||
|
}
|
||||||
|
saveUsers()
|
||||||
|
}
|
||||||
|
|
||||||
|
createToken() {
|
||||||
|
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 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) {
|
||||||
|
userInUserList.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}.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
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}`;
|
||||||
|
if(this.isAdmin()) return true;
|
||||||
|
return this.labels.includes(ownerLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
justUpdated() {
|
||||||
|
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 false;
|
||||||
|
}
|
||||||
|
userInUserList.labels = userInUserList.labels.filter(label => !label.startsWith("UPDATED["));
|
||||||
|
userInUserList.labels.push("UPDATED[" + new Date().toISOString() + "]");
|
||||||
|
saveUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
needUpdate() {
|
||||||
|
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 false;
|
||||||
|
}
|
||||||
|
const lastUpdate = userInUserList.labels.find(label => label.startsWith("UPDATED["));
|
||||||
|
if (lastUpdate) {
|
||||||
|
const date = new Date(lastUpdate.replace("UPDATED[", "").replace("]", ""));
|
||||||
|
const now = new Date();
|
||||||
|
const diff = now - date;
|
||||||
|
// Check for 30 seconds
|
||||||
|
clog.log(`Dernière mise à jour de l'utilisateur ${this.identity.username} : ${date.toISOString()} (${diff} ms) - Besoin de mise à jour : ${diff > 30000}`);
|
||||||
|
// If the difference is greater than 30 seconds, we need to update
|
||||||
|
return diff > 30000; // 30 seconds
|
||||||
|
}
|
||||||
|
clog.log(`Aucune mise à jour n'a été effectuée pour l'utilisateur ${this.identity.username}.`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//REFRESH USER
|
||||||
|
|
||||||
|
async function refreshAllUserInformation() {
|
||||||
|
await loadUsers();
|
||||||
|
clog.log("Récupération des informations de tous les utilisateurs...");
|
||||||
|
for (const user of userList) {
|
||||||
|
await updateCredientials(user.identity.id);
|
||||||
|
}
|
||||||
|
saveUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateCredientials(id) {
|
||||||
|
const user = getUserById(id);
|
||||||
|
if (!user) {
|
||||||
|
clog.warn(`Utilisateur ${id} non trouvé.`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
clog.log(`Mise à jour des informations d'authentification Discord de l'utilisateur ${user.identity.username} (${user.identity.id})...`);
|
||||||
|
if (user.auth) {
|
||||||
|
// Check if the token is expired
|
||||||
|
const auth = await discordAuth.refreshToken(user.auth.refresh_token);
|
||||||
|
if (auth) {
|
||||||
|
// Check Rate limit by checking if auth.message exists
|
||||||
|
if(typeof auth.message !== "undefined") {
|
||||||
|
clog.warn(`Erreur lors de la mise à jour des informations d'authentification de l'utilisateur ${user.identity.username} (${user.identity.id}) : ${auth.message}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
user.auth = auth;
|
||||||
|
clog.log(`Mise à jour réussie des informations d'authentification de l'utilisateur ${user.identity.username} (${user.identity.id})`);
|
||||||
|
} else {
|
||||||
|
clog.warn(`Erreur lors de la mise à jour des informations d'authentification 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
clog.warn(`Aucune authentification trouvée pour l'utilisateur ${user.identity.username} (${user.identity.id})`);
|
||||||
|
}
|
||||||
|
saveUsers();
|
||||||
|
return user.auth;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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) {
|
||||||
|
// Check Rate limit by checking if identity.message exists
|
||||||
|
if(typeof identity.message !== "undefined") {
|
||||||
|
clog.warn(`Erreur lors de la mise à jour de l'identité de l'utilisateur ${user.identity.username} (${user.identity.id}) : ${identity.message}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
user.identity = identity;
|
||||||
|
clog.log(`Mise à jour réussie de l'identité de l'utilisateur ${user.identity.username} (${user.identity.id})`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
clog.warn(`Erreur lors de la mise à jour de l'identité de l'utilisateur ${user.identity.username} (${user.identity.id})`);
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
// Update the user in the list
|
||||||
|
const userInUserList = userList.find(u => u.identity.id === user.identity.id);
|
||||||
|
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})`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
saveUsers();
|
||||||
|
return user.identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// EDIT USER
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} auth
|
||||||
|
* @param {*} identity
|
||||||
|
* @returns {User} user
|
||||||
|
*/
|
||||||
|
async function addUser(auth, identity) {
|
||||||
|
// 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.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, [], []);
|
||||||
|
|
||||||
|
userList.push(newUser);
|
||||||
|
await 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,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSimpleUser(id) {
|
||||||
|
const user = getUserById(id);
|
||||||
|
if(user) {
|
||||||
|
return {
|
||||||
|
identity: user.identity,
|
||||||
|
labels: user.labels,
|
||||||
|
};
|
||||||
|
} 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) {
|
||||||
|
if(user.isAdmin()) {
|
||||||
|
clog.warn(`L'utilisateur ${user.identity.username} est admin, il ne peut pas être banni.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
user.setBan(guildId);
|
||||||
|
saveUsers();
|
||||||
|
} else {
|
||||||
|
clog.warn(`Utilisateur ${id} non trouvé.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setFullBan(id) {
|
||||||
|
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.setFullBan();
|
||||||
|
saveUsers();
|
||||||
|
} else {
|
||||||
|
clog.warn(`Utilisateur ${id} non trouvé.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setGuildOwner(id, guildId, force) {
|
||||||
|
const user = getUserById(id);
|
||||||
|
if (user) {
|
||||||
|
const ownerLabel = `OWNER_${guildId}`;
|
||||||
|
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}.`);
|
||||||
|
}
|
||||||
|
saveUsers();
|
||||||
|
} else {
|
||||||
|
clog.warn(`Utilisateur ${id} non trouvé.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// USERS DB
|
||||||
|
|
||||||
|
function loadUsers() {
|
||||||
|
UserDB.load()
|
||||||
|
userList = new Array();
|
||||||
|
for (const user of UserDB.getData()) {
|
||||||
|
userList.push(new User(user.auth, user.identity, user.tokens, user.labels));
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
UserDB.save()
|
||||||
|
clog.log(`Sauvegarde de ${userList.length} utilisateurs.`);
|
||||||
|
|
||||||
|
return loadUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearNeedUpdateForUsers() {
|
||||||
|
userList.forEach(user => {
|
||||||
|
user.labels = user.labels.filter(label => !label.startsWith("UPDATED["));
|
||||||
|
});
|
||||||
|
saveUsers();
|
||||||
|
clog.log("Nettoyage des mises à jour nécessaires pour tous les utilisateurs.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {User}
|
||||||
|
module.exports = {
|
||||||
|
addUser,
|
||||||
|
setGuildOwner,
|
||||||
|
setFullBan,
|
||||||
|
removeUser,
|
||||||
|
getUserByToken,
|
||||||
|
getUserById,
|
||||||
|
getUsers,
|
||||||
|
setAdmin,
|
||||||
|
setGuildMod,
|
||||||
|
setGuildBan,
|
||||||
|
addToken,
|
||||||
|
removeToken,
|
||||||
|
getSimpleUsers,
|
||||||
|
getSimpleUser,
|
||||||
|
updateCredientials,
|
||||||
|
refreshAllUserInformation,
|
||||||
|
updateIdentity,
|
||||||
|
clearNeedUpdateForUsers
|
||||||
|
};
|
31
src/utils/AudioBufferCheck.js
Normal file
31
src/utils/AudioBufferCheck.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
function isAudioFile(buffer) {
|
||||||
|
// Ensure the buffer is long enough to contain the magic number
|
||||||
|
if (!Buffer.isBuffer(buffer)) {
|
||||||
|
throw new TypeError('Expected a Buffer');
|
||||||
|
}
|
||||||
|
if (buffer.length < 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the first few bytes to a hex string
|
||||||
|
const signature = buffer.subarray(0, 4).toString('hex').toUpperCase();
|
||||||
|
|
||||||
|
// Audio file signatures
|
||||||
|
const audioSignatures = [
|
||||||
|
'494433', // ID3 tag for MP3
|
||||||
|
'FFFB', // Another possible MP3 signature
|
||||||
|
'FFF3', // Another possible MP3 signature
|
||||||
|
'FFF2', // Another possible MP3 signature
|
||||||
|
'52494646', // RIFF header for WAV
|
||||||
|
'4F676753', // OGG container
|
||||||
|
'66747970', // MP4 container
|
||||||
|
// Add more audio file signatures as needed
|
||||||
|
];
|
||||||
|
|
||||||
|
// Check if the signature matches any known audio file type
|
||||||
|
return audioSignatures.some(magicNumber => signature.startsWith(magicNumber));
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
isAudioFile
|
||||||
|
};
|
102
src/utils/Database/Configuration.js
Normal file
102
src/utils/Database/Configuration.js
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
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 : ""
|
||||||
|
},
|
||||||
|
api: {
|
||||||
|
youtube: {
|
||||||
|
clientId : "" ,
|
||||||
|
clientSecret: ""
|
||||||
|
},
|
||||||
|
spotify: {
|
||||||
|
clientId: "",
|
||||||
|
clientSecret: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
website: "",
|
||||||
|
server_port: 5000,
|
||||||
|
media: {
|
||||||
|
guildId: "",
|
||||||
|
channelId: "",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function getToken() {
|
||||||
|
return config.data.token
|
||||||
|
}
|
||||||
|
|
||||||
|
function getReportChannel() {
|
||||||
|
return config.data.report.channel
|
||||||
|
}
|
||||||
|
|
||||||
|
function getReportContact() {
|
||||||
|
return config.data.report.contact
|
||||||
|
}
|
||||||
|
|
||||||
|
function getYoutubeApiClientId() {
|
||||||
|
return config.data.api.youtube.clientId
|
||||||
|
}
|
||||||
|
|
||||||
|
function getYoutubeApiClientSecret() {
|
||||||
|
return config.data.api.youtube.clientSecret
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSpotifyClientId() {
|
||||||
|
return config.data.api.spotify.clientId
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMediaGuildId() {
|
||||||
|
return config.data.media.guildId
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMediaChannelId() {
|
||||||
|
return config.data.media.channelId
|
||||||
|
}
|
||||||
|
|
||||||
|
if(getToken() == "") {
|
||||||
|
clog.error("Impossible de démarrer sans token valide")
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getToken,
|
||||||
|
getClientSecret,
|
||||||
|
getReportChannel,
|
||||||
|
getReportContact,
|
||||||
|
getYoutubeApiClientId,
|
||||||
|
getYoutubeApiClientSecret,
|
||||||
|
getSpotifyClientId,
|
||||||
|
getSpotifyClientSecret,
|
||||||
|
getWebsiteLink,
|
||||||
|
getPort,
|
||||||
|
getMediaGuildId,
|
||||||
|
getMediaChannelId
|
||||||
|
|
||||||
|
}
|
@@ -62,6 +62,9 @@ class Database {
|
|||||||
clog.error(e)
|
clog.error(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Assure that the database is up to date and reloaded
|
||||||
|
this.update()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
@@ -80,6 +83,10 @@ class Database {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getData() {
|
||||||
|
return this.data
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@@ -8,7 +8,12 @@ const __glob = {
|
|||||||
LOGS: root + path.sep + "logs",
|
LOGS: root + path.sep + "logs",
|
||||||
DATA: root + path.sep + "data",
|
DATA: root + path.sep + "data",
|
||||||
COMMANDS: root + path.sep + "src" + path.sep + "discord" + path.sep + "commands",
|
COMMANDS: root + path.sep + "src" + path.sep + "discord" + path.sep + "commands",
|
||||||
METRIC_FILE: root + path.sep + "data" + path.sep + "metrics.json"
|
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",
|
||||||
|
HISTORY_DB: root + path.sep + "data" + path.sep + "history.json",
|
||||||
|
MEDIA_DB: root + path.sep + "data" + path.sep + "media.json",
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {__glob}
|
module.exports = {__glob}
|
79
src/utils/Links.js
Normal file
79
src/utils/Links.js
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
const YoutubeLinks = [
|
||||||
|
"youtube.com",
|
||||||
|
"youtu.be",
|
||||||
|
"music.youtube.com",
|
||||||
|
"gaming.youtube.com",
|
||||||
|
"www.youtube.com",
|
||||||
|
"m.youtube.com"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
var youtubePlaylistRegex = new RegExp(/^https?:\/\/(www.)?youtube.com\/playlist\?list=((PL|FL|UU|LL|RD|OL)[a-zA-Z0-9-_]{16,41})$/)
|
||||||
|
var youtubeVideoURLRegex = new RegExp(/^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:watch\?v=))([\w-]{11})(\S+)?$/)
|
||||||
|
var youtubeVideoIdRegex = new RegExp(/^[a-zA-Z0-9-_]{11}$/)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const SpotifyLinks = [
|
||||||
|
"open.spotify.com",
|
||||||
|
"embed.spotify.com"
|
||||||
|
]
|
||||||
|
|
||||||
|
var spotifySongRegex = new RegExp(/^https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(intl-([a-z]|[A-Z])+\/)?(?:track\/|\?uri=spotify:track:)((\w|-){22})(\?si=.+)?$/)
|
||||||
|
var spotifyPlaylistRegex = new RegExp(/^https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(intl-([a-z]|[A-Z])+\/)?(?:playlist\/|\?uri=spotify:playlist:)((\w|-){22})(\?si=.+)?$/)
|
||||||
|
var spotifyAlbumRegex = new RegExp(/^https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(intl-([a-z]|[A-Z])+\/)?(?:album\/|\?uri=spotify:album:)((\w|-){22})(\?si=.+)?$/)
|
||||||
|
|
||||||
|
|
||||||
|
const SoundcloudLinks = [
|
||||||
|
"soundcloud.com"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
var soundcloudTrackRegex = new RegExp(/^https?:\/\/(m.|www.)?soundcloud.com\/(\w|-)+\/(\w|-)+(.+)?$/)
|
||||||
|
var soundcloudPlaylistRegex = new RegExp(/^https?:\/\/(m.|www.)?soundcloud.com\/(\w|-)+\/sets\/(\w|-)+(.+)?$/)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} Links
|
||||||
|
* @property {Object} regex
|
||||||
|
* @property {Object} regex.youtube
|
||||||
|
* @property {RegExp} regex.youtube.playlist
|
||||||
|
* @property {RegExp} regex.youtube.videoURL
|
||||||
|
* @property {RegExp} regex.youtube.videoId
|
||||||
|
* @property {Object} regex.spotify
|
||||||
|
* @property {RegExp} regex.spotify.song
|
||||||
|
* @property {RegExp} regex.spotify.playlist
|
||||||
|
* @property {RegExp} regex.spotify.album
|
||||||
|
* @property {Object} regex.soundcloud
|
||||||
|
* @property {RegExp} regex.soundcloud.track
|
||||||
|
* @property {RegExp} regex.soundcloud.playlist
|
||||||
|
* @property {Object} types
|
||||||
|
* @property {Array<String>} types.youtube
|
||||||
|
* @property {Array<String>} types.spotify
|
||||||
|
* @property {Array<String>} types.soundcloud
|
||||||
|
*/
|
||||||
|
const Links = {
|
||||||
|
regex: {
|
||||||
|
youtube: {
|
||||||
|
playlist: youtubePlaylistRegex,
|
||||||
|
videoURL: youtubeVideoURLRegex,
|
||||||
|
videoId: youtubeVideoIdRegex
|
||||||
|
},
|
||||||
|
spotify: {
|
||||||
|
song: spotifySongRegex,
|
||||||
|
playlist: spotifyPlaylistRegex,
|
||||||
|
album: spotifyAlbumRegex
|
||||||
|
},
|
||||||
|
soundcloud: {
|
||||||
|
track: soundcloudTrackRegex,
|
||||||
|
playlist: soundcloudPlaylistRegex
|
||||||
|
}
|
||||||
|
},
|
||||||
|
types: {
|
||||||
|
youtube: YoutubeLinks,
|
||||||
|
spotify: SpotifyLinks,
|
||||||
|
soundcloud: SoundcloudLinks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {Links}
|
11
src/utils/Maintenance.js
Normal file
11
src/utils/Maintenance.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
const pm2 = require("pm2")
|
||||||
|
const { LogType } = require("loguix")
|
||||||
|
const clog = new LogType("Maintenance")
|
||||||
|
|
||||||
|
function restart(reason) {
|
||||||
|
clog.warn("Redémarrage du serveur Subsonics")
|
||||||
|
clog.warn(`Reason: ${reason}`)
|
||||||
|
pm2.restart("Subsonics")
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {restart}
|
16
src/utils/QueryType.js
Normal file
16
src/utils/QueryType.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Enum for query types
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
const QueryType = {
|
||||||
|
SPOTIFY_PLAYLIST: 'spotify_playlist',
|
||||||
|
SPOTIFY_ALBUM: 'spotify_album',
|
||||||
|
SPOTIFY_SONG: 'spotify_song',
|
||||||
|
YOUTUBE_PLAYLIST: 'youtube_playlist',
|
||||||
|
YOUTUBE_VIDEO: 'youtube_video',
|
||||||
|
SOUNDCLOUD_TRACK: 'soundcloud_track',
|
||||||
|
SOUNDCLOUD_PLAYLIST: 'soundcloud_playlist',
|
||||||
|
YOUTUBE_SEARCH: 'youtube_search',
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { QueryType };
|
32
src/utils/Resolver.js
Normal file
32
src/utils/Resolver.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
const {Links} = require('./Links')
|
||||||
|
const {QueryType} = require('./QueryType')
|
||||||
|
|
||||||
|
function getQueryType(url) {
|
||||||
|
// Check if it's string
|
||||||
|
|
||||||
|
if(typeof url !== "string") return "NOT_STRING"
|
||||||
|
// Check if it's a Youtube link
|
||||||
|
|
||||||
|
if(Links.regex.youtube.playlist.test(url)) return QueryType.YOUTUBE_PLAYLIST
|
||||||
|
if(Links.regex.youtube.videoURL.test(url)) return QueryType.YOUTUBE_VIDEO
|
||||||
|
|
||||||
|
// Check if it's a Spotify link
|
||||||
|
|
||||||
|
if(Links.regex.spotify.playlist.test(url)) return QueryType.SPOTIFY_PLAYLIST
|
||||||
|
if(Links.regex.spotify.album.test(url)) return QueryType.SPOTIFY_ALBUM
|
||||||
|
if(Links.regex.spotify.song.test(url)) return QueryType.SPOTIFY_SONG
|
||||||
|
|
||||||
|
// Check if it's a Soundcloud link
|
||||||
|
|
||||||
|
if(Links.regex.soundcloud.playlist.test(url)) return QueryType.SOUNDCLOUD_PLAYLIST
|
||||||
|
if(Links.regex.soundcloud.track.test(url)) return QueryType.SOUNDCLOUD_TRACK
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return QueryType.YOUTUBE_SEARCH
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {getQueryType}
|
46
src/utils/TimeConverter.js
Normal file
46
src/utils/TimeConverter.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
function getReadableDuration(duration) {
|
||||||
|
var max = ""
|
||||||
|
duration *= 1000;
|
||||||
|
|
||||||
|
const maxhours = Math.floor(duration / 3600000);
|
||||||
|
|
||||||
|
var maxmin = Math.trunc(duration / 60000) - (Math.floor(duration / 60000 / 60) * 60);
|
||||||
|
var maxsec = Math.floor(duration / 1000) - (Math.floor(duration / 1000 / 60) * 60);
|
||||||
|
|
||||||
|
|
||||||
|
if (maxsec < 10) {
|
||||||
|
maxsec = `0${maxsec}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if(maxhours != 0) {
|
||||||
|
|
||||||
|
if (maxmin < 10) {
|
||||||
|
maxmin = `0${maxmin}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
max = maxhours + "h" + maxmin + "m" + maxsec
|
||||||
|
} else {
|
||||||
|
max = maxmin + "m" + maxsec + "s"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return max
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSecondsDuration(duration) {
|
||||||
|
// Duration is in format hh:mm:ss and can be just m:ss or mm:ss
|
||||||
|
var durationArray = duration.split(":");
|
||||||
|
var seconds = 0;
|
||||||
|
if(durationArray.length == 3) {
|
||||||
|
seconds = parseInt(durationArray[0]) * 3600 + parseInt(durationArray[1]) * 60 + parseInt(durationArray[2]);
|
||||||
|
} else if(durationArray.length == 2) {
|
||||||
|
seconds = parseInt(durationArray[0]) * 60 + parseInt(durationArray[1]);
|
||||||
|
} else {
|
||||||
|
seconds = parseInt(durationArray[0]);
|
||||||
|
}
|
||||||
|
return seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {getReadableDuration, getSecondsDuration}
|
18
src/utils/TokenManager.js
Normal file
18
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}
|
Reference in New Issue
Block a user