Compare commits

...

24 Commits

Author SHA1 Message Date
96cd60912b Version 1.2.0 - Ajout des suggestions et de paramètres
All checks were successful
Deployment Pipeline / deploy (push) Successful in 35s
2025-09-07 18:18:52 +02:00
b2aadc7c3c Version 1.1.4 - Correction de Bug
All checks were successful
Deployment Pipeline / deploy (push) Successful in 36s
2025-09-06 15:47:06 +02:00
dcc056455e Version 1.1.3 - Modfication du Proxy
All checks were successful
Deployment Pipeline / deploy (push) Successful in 36s
2025-08-31 16:57:13 +02:00
f777fb821a Version 1.1.2-rc4 - Modif erreur
All checks were successful
Deployment Pipeline / deploy (push) Successful in 37s
2025-08-29 19:44:38 +02:00
ecbda838d3 Merge branch 'main' of https://git.raphix.fr/subsonics/chopin
All checks were successful
Deployment Pipeline / deploy (push) Successful in 35s
2025-08-29 19:41:32 +02:00
5ac195fd46 Version 1.1.2-rc4 - Ajout de agent Cookie 2025-08-29 19:41:08 +02:00
914edbbf13 Version 1.1.2-rc3 - Modification Cookie
All checks were successful
Deployment Pipeline / deploy (push) Successful in 40s
2025-08-29 15:25:22 +02:00
c376e3259c Version 1.1.2-rc2 - Ajout Cookies
All checks were successful
Deployment Pipeline / deploy (push) Successful in 35s
2025-08-29 12:44:25 +02:00
f3b237b74f Version 1.1.2-rc1 - Modif Ytdl
All checks were successful
Deployment Pipeline / deploy (push) Successful in 34s
2025-08-29 12:29:02 +02:00
83e11f3341 Merge branch 'main' of https://git.raphix.fr/subsonics/chopin
All checks were successful
Deployment Pipeline / deploy (push) Successful in 35s
2025-08-29 12:26:32 +02:00
b132041d16 Version 1.1.2 - Modification User-Agent 2025-08-29 12:26:25 +02:00
33da8e8527 Version 1.1.1 - Edit Cookies
All checks were successful
Deployment Pipeline / deploy (push) Successful in 36s
2025-08-29 12:01:13 +02:00
c613d67c60 Version 1.1.1 - Modification du Changelog
All checks were successful
Deployment Pipeline / deploy (push) Successful in 35s
2025-08-29 00:23:21 +02:00
48e5bfad60 Version 1.1.1.rc8 - Changement permissions
All checks were successful
Deployment Pipeline / deploy (push) Successful in 33s
2025-08-29 00:07:11 +02:00
54fd731ab9 Version 1.1.1-rc7.1 - Modif Pipeline
All checks were successful
Deployment Pipeline / deploy (push) Successful in 35s
2025-08-28 23:58:19 +02:00
5b8f591216 Version 1.1.1-rc7 - Modification des valeurs Path
Some checks failed
Deployment Pipeline / deploy (push) Failing after 35s
2025-08-28 23:55:51 +02:00
2fe5d35efe Version 1.1.1-rc6.1 - Modif Erreur Pipeline
Some checks failed
Deployment Pipeline / deploy (push) Failing after 40s
2025-08-28 23:47:34 +02:00
b0f5ccbe5a Version 1.1.1-rc6 - Modif Pipeline
Some checks failed
Deployment Pipeline / deploy (push) Failing after 9s
2025-08-28 23:46:27 +02:00
98e5c41fa5 Version 1.1.1-rc5 - Modif Pipeline Final
Some checks failed
Deployment Pipeline / deploy (push) Failing after 27s
2025-08-28 23:32:40 +02:00
f41eddf1dc Version 1.1.1-rc4.1 - Modif Pipeline
Some checks failed
Deployment Pipeline / deploy (push) Failing after 8s
2025-08-28 23:29:50 +02:00
f12bbe8ad2 Version 1.1.1-rc4 - Modif Pipeline
Some checks failed
Deployment Pipeline / deploy (push) Failing after 8s
2025-08-28 23:28:12 +02:00
0c50874644 Version 1.1.1-rc3 - Modification Pipeline
Some checks failed
Deployment Pipeline / deploy (push) Failing after 7s
2025-08-28 23:27:30 +02:00
59ea576181 Version 1.1.1-rc2 - Premier deploy
Some checks failed
Deployment Pipeline / deploy (push) Failing after 7s
2025-08-28 23:20:57 +02:00
8b2728622c Version 1.1.1 - Premier Deploy 2025-08-28 23:15:27 +02:00
20 changed files with 2058 additions and 1310 deletions

View File

@@ -0,0 +1,86 @@
name: Deployment Pipeline
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup SSH
env:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
run: |
mkdir -p ~/.ssh
echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_rsa
ssh-keyscan git.raphix.fr >> ~/.ssh/known_hosts
- name: Deploy Subsonics as gitlab-ci
run: |
ssh -A -o StrictHostKeyChecking=no raphix@alpha.raphix.fr << 'EOF'
sudo su - gitlab-ci -c '
set -e
# Variables PM2 et npm
export PM2_HOME=/home/gitlab-ci/.pm2
export NPM_CONFIG_CACHE=/home/gitlab-ci/.npm
mkdir -p $PM2_HOME $NPM_CONFIG_CACHE
chown -R gitlab-ci:gitlab-ci $PM2_HOME $NPM_CONFIG_CACHE
echo "[Subsonics-Deploy] - Stage - Déploiement - START"
echo "[Subsonics-Deploy] - Arrêt de Subsonics : Processing"
cd /home/gitlab-ci
pm2 stop "Subsonics - Backend" || true
pm2 delete "Subsonics - Backend" || true
echo "[Subsonics-Deploy] - Arrêt de Subsonics : Success"
# Préparer tempdata
if [ ! -d "/home/gitlab-ci/backend/data" ]; then
mkdir -p /home/gitlab-ci/backend/data
fi
mv /home/gitlab-ci/backend/data/ /home/gitlab-ci/tempdata || true
echo "[Subsonics-Deploy] - Suppression de Subsonics : Processing"
rm -rf ./backend
echo "[Subsonics-Deploy] - Suppression de Subsonics : Success"
echo "[Subsonics-Deploy] - Installation de Subsonics : Processing"
git clone https://git.raphix.fr/subsonics/chopin backend
echo "[Subsonics-Deploy] - Installation de Subsonics : Success"
echo "[Subsonics-Deploy] - Installation des dépendances : Processing"
cd /home/gitlab-ci/backend
# Nettoyage node_modules et tempdata
rm -rf node_modules
if [ -d "/home/gitlab-ci/tempdata" ]; then
mv /home/gitlab-ci/tempdata/ ./data
fi
# Assurer la propriété gitlab-ci
chown -R gitlab-ci:gitlab-ci /home/gitlab-ci/backend
mkdir -p $NPM_CONFIG_CACHE
chown -R gitlab-ci:gitlab-ci $NPM_CONFIG_CACHE
npm install --omit=dev
echo "[Subsonics-Deploy] - Installation des dépendances : Success"
echo "[Subsonics-Deploy] - Démarrage de Subsonics : Processing"
cd /home/gitlab-ci
pm2 start subsonic.config.js
echo "[Subsonics-Deploy] - Démarrage de Subsonics : Success"
echo "[Subsonics-Deploy] - Stage - Déploiement - END"
'
EOF

29
CHANGELOG.html Normal file
View File

@@ -0,0 +1,29 @@
<div class="changelog-version changelog-actual">
<h2>Chopin - Version /*1.2.0*/</h2>
<p class="changelog-date">*_Date de sortie_*: *-07/09/2025-*</p>
<ul>
<li>/#[AJOUT][FRONTEND]#/ Suggestion de recherche, depuis Youtube</li>
<li>/#[FIX][FRONTEND]#/ Optimisation des bouttons, avec une modification du composant</li>
<li>/#[AJOUT][DISCORD]#/ Ajout de la sécurité, permettant de restreindre l'utilisation du Bot au Rôle paramétré</li>
</ul>
</div>
<div class="changelog-version">
<h2>Chopin - Version /*1.1.0*/</h2>
<p class="changelog-date">*_Date de sortie_*: *-06/09/2025-*</p>
<ul>
<li>/#[AJOUT][FRONTEND]#/ Désormais, lorsque l'utilisateur n'est pas connecté, les bouttons de lecture sont désactivés au lieu d'être masqués</li>
<li>/#[AJOUT][FRONTEND] #/ Ajout de la prise en charge de plusieurs fichiers en même temps (Impossibilité sur Discord à cause de restriction) !</li>
<li>/#[FIX][BACKEND]#/ La source Youtube est désormais fonctionnelle</li>
<li>/#[FIX][BACKEND]#/ Le changement de channel est désormais fonctionnel.</li>
<li>/#[FIX][FRONTEND]#/ Résolution d'un problème d'affichage du changelog et de la liste de lecture</li>
<li>/#[FIX][FRONTEND]#/ Le "En ligne" est désormais resynchronisé sur le Bot</li>
</ul>
</div>
<div class="changelog-version">
<h2>Chopin - Version /*1.0.0*/</h2>
<p class="changelog-date">*_Date de sortie_*: *-29/08/2025-*</p>
<ul>
<li>/#[FRONTEND]#/ Sortie de la version 1.0.0 de Chopin, le bot Discord pour SubSonics. Refonte graphique et passage sur Vue JS</li>
<li>/#[BACKEND]#/ Refonte de toute la gestion de la musique et ajout de nouvelles fonctionnalités</li>
</ul>
</div>

2210
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "chopin-backend",
"version": "1.1.1",
"version": "1.2.0",
"description": "Discord Bot for music - Fetching everywhere !",
"main": "src/main.js",
"nodemonConfig": {
@@ -21,12 +21,10 @@
"license": "ISC",
"dependencies": {
"@discordjs/voice": "^0.18.0",
"@distube/ytdl-core": "^4.16.10",
"@distube/ytsr": "2.0.4",
"@distube/ytdl-core": "^4.16.12",
"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",
@@ -42,6 +40,7 @@
"spotify-web-api-node": "^5.0.2",
"uuid": "^11.1.0",
"webmetrik": "^0.1.4",
"yt-search": "^2.13.1",
"ytfps": "^1.2.0"
}
}

View File

@@ -7,6 +7,8 @@ const config = require("../utils/Database/Configuration")
const metric = require("webmetrik")
const { Player } = require("../player/Player")
const {refreshAllUserInformation} = require("../server/auth/User")
const serverSettings = require("./ServerSettings")
const { Embed, EmbedError } = require("./Embed")
const dlog = new LogType("Discord")
const glog = new LogType("GuildUpdater")
@@ -49,6 +51,29 @@ function getGuildMembers(guildId) {
return guild.members.cache.map(member => member.user.id)
}
function getGuildMember(guildId, memberId) {
const guild = client.guilds.cache.get(guildId)
if(!guild) {
dlog.error("Guild not found: " + guildId)
return null
}
return guild.members.cache.get(memberId) || null
}
function getGuildRoles(guildId) {
const guild = client.guilds.cache.get(guildId)
if(!guild) {
dlog.error("Guild not found: " + guildId)
return []
}
return guild.roles.cache.map(role => ({
id: role.id,
name: role.name,
color: role.color,
position: role.position
}))
}
function getChannel(guildId, channelId) {
return client.guilds.cache.get(guildId).channels.cache.get(channelId)
}
@@ -77,22 +102,37 @@ function init() {
operational = true
})
client.on("interactionCreate", (interaction) => {
client.on("interactionCreate", async (interaction) => {
if(!interaction.isCommand()) return;
var numberOfCommands = new metric.Metric("numberOfCommands", "Nombre de commandes éxécutées")
numberOfCommands.setValue(numberOfCommands.getValue() + 1)
var numberOfCommandsServer = new metric.Metric("numberOfCommands_" + interaction.guild.id, "Nombre de commandes éxécutées sur le serveur : " + interaction.guild.name)
numberOfCommandsServer.setValue(numberOfCommandsServer.getValue() + 1)
const command = client.commands.get(interaction.commandName)
const roleProtected = await serverSettings.getSecureRole(interaction.guild.id) || false
var havePermission = true;
if(roleProtected) {
await interaction.member.fetch()
if(!interaction.member.roles.cache.has(roleProtected.id)) {
havePermission = false;
}
}
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)
if(havePermission) {
command.execute(client, interaction)
} else {
const embed = new EmbedError(`L'utilisation du Bot est réservée aux membres ayant le rôle "${roleProtected.name}"`, interaction, true)
embed.setTitle("Accès refusé")
embed.send()
}
} catch(error) {
dlog.error(interaction.member.user.username + "-> /" + interaction.commandName + " : ERREUR RENCONTRE")
@@ -169,7 +209,7 @@ function init() {
}
process.emit("VOCAL_UPDATE")
})
@@ -209,23 +249,15 @@ async function refreshGuilds() {
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'
]
'ViewChannel', // Voir les salons
'SendMessages', // Envoyer des messages texte
'ReadMessageHistory', // Lire lhistorique des messages
'Connect', // Se connecter aux salons vocaux
'Speak' // Parler dans les salons vocaux
]
return requiredPermissions.filter(permission => !guildMember.permissions.has(permission));
}
module.exports = {init, getClient, getGuilds, getMembersVoices, getChannel, getGuildMembers, isReady}
module.exports = {init, getClient, getGuilds, getMembersVoices, getChannel, getGuildMembers, getGuildMember, isReady, getGuildRoles}

View File

@@ -4,6 +4,8 @@ const wlog = new LogType("MediaBase")
const { Database } = require("../utils/Database/Database")
const {__glob} = require("../utils/GlobalVars")
const { AttachmentBuilder } = require("discord.js")
const { Song } = require("../player/Song")
const { getMediaInformationFromUrl } = require("../media/MediaInformation")
const discordBot = require("./Bot")
@@ -54,7 +56,7 @@ discordBot.getClient().on("ready", () => {
// SEND FILE TO DISCORD AND GET THE URL ID
async function postMedia(file) {
async function postMedia(file, userId) {
if(!connected) {
wlog.error("La base de données multimédia n'est pas connectée, impossible d'envoyer le fichier.")
return null
@@ -73,7 +75,8 @@ async function postMedia(file) {
url: url,
name: file.name,
size: file.size,
createdAt: new Date().toISOString()
createdAt: new Date().toISOString(),
userId: userId
})
mediaDB.save()
return url
@@ -83,6 +86,33 @@ async function postMedia(file) {
}
}
async function getAllMedia(userId) {
if(!connected) {
wlog.error("La base de données multimédia n'est pas connectée, impossible de récupérer les fichiers.")
return []
}
const allSongs = mediaDB.data.filter(m => m.userId === userId)
const songs = []
for(const songDB of allSongs) {
const song = new Song()
const information = await getMediaInformationFromUrl(song, songDB.url)
if(!information) {
mediaDB.data = mediaDB.data.filter(m => m.id !== songDB.id)
mediaDB.save()
continue
}
song.type = "attachment"
song.author = songDB.author
song.createdAt = songDB.createdAt
song.author = songDB.userId
song.title = songDB.name
song.id = songDB.id
song.url = songDB.url
songs.push(song)
}
return songs
}
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.")
@@ -103,7 +133,26 @@ async function getMedia(id) {
}
}
function deleteMedia(data, userId) {
if(!connected) {
wlog.error("La base de données multimédia n'est pas connectée, impossible de supprimer le fichier.")
return false
}
const mediaIndex = mediaDB.data.findIndex(m => m.id === data.id && m.userId === userId)
if(mediaIndex === -1) {
wlog.error(`Aucun média trouvé avec l'ID : ${data.id} pour l'utilisateur : ${userId}`)
return false
}
mediaDB.data.splice(mediaIndex, 1)
mediaDB.save()
return true
}
module.exports = {
postMedia,
getMedia,
deleteMedia,
getAllMedia
}

View File

@@ -0,0 +1,38 @@
const { LogType } = require('loguix');
const clog = new LogType('ServerSettings');
const { __glob } = require('../utils/GlobalVars');
const { Database } = require('../utils/Database/Database');
const ServerDB = new Database("server_settings", __glob.SERVER_DB, {});
function getSecureRole(guildId) {
checkKey(guildId);
var role = ServerDB.getData()[guildId].secureRole || null;
if(role) {
if(role.name === "@everyone") {
return null;
}
}
return role;
}
function setSecureRole(guildId, roleId) {
checkKey(guildId);
ServerDB.getData()[guildId].secureRole = roleId;
ServerDB.save();
process.emit("USERS_UPDATE")
return true;
}
async function checkKey(guildId) {
const data = ServerDB.getData();
if (!data[guildId]) {
data[guildId] = { secureRole: null };
ServerDB.save();
}
}
module.exports = {
getSecureRole,
setSecureRole
};

View File

@@ -10,10 +10,9 @@ require("loguix").setup(__glob.LOGS, __glob.PACKAGEINFO)
const config = require("./utils/Database/Configuration")
const metric = require("webmetrik")
metric.setMetricFile(__glob.METRIC_FILE)
metric.publishMetrics("8001", "raphraph")
metric.publishMetrics("8001", "subsonicsMetricsRaph")
// SETUP
// SETUP
setup();

View File

@@ -21,7 +21,7 @@ async function getMediaInformation(instance, media, provider) {
// Obtenir l'auteur (s'il existe)
instance.author = info.streams?.[0]?.tags?.artist ?? instance.author;
return true;
} catch (err) {
clog.error("Impossible de récupérer les informations de la musique : " + media.attachment.name)
clog.error(err)
@@ -46,7 +46,7 @@ async function getMediaInformationFromUrl(instance, url) {
// Obtenir l'auteur (s'il existe)
instance.author = info.streams?.[0]?.tags?.artist ?? "Auteur inconnu";
return true;
} catch (err) {
clog.error("Impossible de récupérer les informations de la musique depuis l'URL : " + url);
console.log(err)

View File

@@ -3,7 +3,7 @@ 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 yts = require("yt-search")
const ytfps = require('ytfps');
async function getQuery(query, multiple) {
@@ -14,15 +14,15 @@ async function getQuery(query, multiple) {
try {
const limit = multiple ? 25 : 1;
const searchResults = await ytsr(query, { limit });
const videos = searchResults.items.filter(item => item.type === 'video');
const searchResults = await yts({ query: query, limit: limit });
const videos = searchResults.videos;
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)));
const songs = await Promise.all(videos.map(video => new Song().processYoutubeVideo(video)));
return multiple ? songs.filter(song => song !== null) : songs[0];
} catch (error) {
clog.error('Erreur lors de la recherche YouTube: ' + error);
@@ -38,9 +38,7 @@ async function getVideo(url) {
}
try {
const searchResults = await ytsr(videoId[1], { limit: 1 });
const video = searchResults.items.find(item => item.type === 'video');
const video = await yts({videoId: videoId[1]});
if (video) {
const songReturn = new Song();
await songReturn.processYoutubeVideo(video);
@@ -72,14 +70,14 @@ async function getPlaylist(url) {
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]);
const playlistInfo = await yts({ listId: playlistId[2] });
if (!playlistInfo) {
clog.error("Impossible de récupérer la playlist YouTube à partir de l'identifiant");
@@ -90,15 +88,16 @@ async function getPlaylist(url) {
playlist.type = "youtube";
playlist.author = playlistInfo.author.name;
playlist.authorId = playlistInfo.author.url;
playlist.authorAvatar = await getYouTubeProfilePicture(playlistInfo.author.url);
playlist.title = playlistInfo.title;
playlist.thumbnail = playlistInfo.thumbnail_url;
playlist.description = playlistInfo.description;
playlist.thumbnail = playlistInfo.thumbnail;
playlist.url = `https://www.youtube.com/playlist?list=${playlistId[2]}`;
playlist.id = playlistId[2];
playlist.id = playlistInfo.listId;
playlist.views = playlistInfo.views;
for (const video of playlistInfo.videos) {
const song = new Song();
await song.processYoutubeVideo(video, true);
await song.processYoutubeVideo(video);
playlist.duration += song.duration;
playlist.songs.push(song);
}
@@ -117,11 +116,9 @@ async function getSecondsFromUrl(url) {
return null;
}
try {
const searchResults = await ytsr(videoId[1], { limit: 1 });
const video = searchResults.items.find(item => item.type === 'video');
console.log(video);
const video = await yts({ videoId: videoId[1] });
if (video) {
return getSecondsDuration(video.duration); // Convert seconds to milliseconds
return video.duration.seconds;
} else {
clog.error("Impossible de récupérer la vidéo YouTube à partir de l'identifiant");
return null;
@@ -132,4 +129,36 @@ async function getSecondsFromUrl(url) {
}
}
async function getYouTubeProfilePicture(channelUrl) {
try {
const res = await fetch(channelUrl, {
headers: {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
}
});
const html = await res.text();
// Match img with yt-spec-avatar-shape__image in class list
const imgRegex = /<img[^>]*(?:class="[^"]*\byt-spec-avatar-shape__image\b[^"]*"[^>]*|[^>]*class="[^"]*\byt-spec-avatar-shape__image\b[^"]*")[^>]*src="([^"]+)"/i;
const imgMatch = html.match(imgRegex);
if (imgMatch && imgMatch[1]) {
return imgMatch[1];
}
// Fallback: look for avatar in embedded JSON
const jsonRegex = /"avatar":\{"thumbnails":\[\{"url":"(.*?)"/;
const match = html.match(jsonRegex);
if (match && match[1]) {
return match[1].replace(/\\u0026/g, "&"); // Decode \u0026 to &
}
console.warn("Photo non trouvée pour :", channelUrl);
return null;
} catch (err) {
console.error("Erreur :", err);
return null;
}
}
module.exports = { getQuery, getVideo, getPlaylist, getSecondsFromUrl };

View File

@@ -60,6 +60,7 @@ class List {
}
this.setCurrent(song)
process.emit("PLAYERS_UPDATE")
//TODO: Check History and continuity
return song
}

View File

@@ -2,19 +2,35 @@ const {createAudioResource, VoiceConnectionStatus, createAudioPlayer, StreamType
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");
const { __glob } = require('../../utils/GlobalVars');
const fs = require('fs');
async function getStream(song) {
// FIXME: Change youtube provider
try {
let stream = ytdl(song.url, {
const headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' +
'AppleWebKit/537.36 (KHTML, like Gecko) ' +
'Chrome/116.0.5845.97 Safari/537.36',
'Accept-Language': 'en-US,en;q=0.9'
};
var cookies = await JSON.parse(await fs.readFileSync(__glob.COOKIES, 'utf-8'));
const proxy = await JSON.parse(await fs.readFileSync(__glob.PROXY, 'utf-8'));
const agent = ytdl.createProxyAgent(proxy, cookies)
let stream = ytdl(song.url, {
quality: 'highestaudio',
highWaterMark: 1 << 30,
liveBuffer: 20000,
dlChunkSize: 0,
bitrate: 128,
requestOptions: {
headers: headers,
},
agent: agent,
});
return stream

View File

@@ -5,6 +5,7 @@ const songCheck = require('./SongCheck')
const ffmpeg = require('fluent-ffmpeg')
const fs = require('fs')
const { PassThrough } = require('stream');
const { Metric } = require('webmetrik')
const plog = new LogType("Player")
const clog = new LogType("Signal")
@@ -21,6 +22,7 @@ class Player {
player;
guildId;
channelId;
channelName;
queue;
currentResource;
loop = false;
@@ -29,7 +31,9 @@ class Player {
clog.error("Impossible de créer un Player, car guildId est null")
return
}
if(AllPlayers.has(guildId)) {
return AllPlayers.get(guildId)
}
this.connection = null
@@ -37,6 +41,7 @@ class Player {
this.guildId = guildId
this.queue = new List(guildId)
AllPlayers.set(guildId, this)
}
async join(channel) {
@@ -59,6 +64,7 @@ class Player {
joinChannel(channel) {
this.channelId = channel.id
this.channelName = channel.name
this.connection = joinVoiceChannel({
channelId: channel.id,
guildId: channel.guild.id,
@@ -77,6 +83,8 @@ class Player {
}
});
this.connected = true
AllPlayers.set(this.guildId, this)
process.emit("PLAYERS_UPDATE")
}
@@ -119,7 +127,7 @@ class Player {
checkConnection() {
if(this.connection === null) {
clog.error(`GUILD : ${this.guildId} - La connection n'est pas définie`)
// clog.error(`GUILD : ${this.guildId} - La connection n'est pas définie`)
return true
}
if(this.player === null) {
@@ -134,7 +142,7 @@ class Player {
const state = {
current: this.queue.current,
next: this.queue.next,
previous: this.queue.previous,
previous: this.queue.getPrevious(),
loop: this.loop,
shuffle: this.queue.shuffle,
paused: playerStatus === AudioPlayerStatus.Paused,
@@ -143,6 +151,7 @@ class Player {
playerState: playerStatus,
connectionState: connectionStatus,
channelId: this.channelId,
channelName: this.channelName,
guildId: this.guildId,
}
return state
@@ -171,12 +180,19 @@ class Player {
}
async play(song) {
if(!songCheck.checkSong(song)) return
if(this.checkConnection()) return
if(this.queue.current != null) {
this.player.stop()
}
var numberOfMusicPlayedPerServer = new Metric("numberOfMusicPlayed_" + this.guildId, "Nombre de musiques jouées sur le serveur : " + this.guildId)
numberOfMusicPlayedPerServer.setValue(numberOfMusicPlayedPerServer.getValue() + 1)
var numberOfSecondsPlayedPerServer = new Metric("numberOfSecondsPlayed_" + this.guildId, "Temps jouée sur le serveur : " + this.guildId)
numberOfSecondsPlayedPerServer.setValue(numberOfSecondsPlayedPerServer.getValue() + song.duration)
this.queue.setCurrent(song)
this.stream = await this.getStream(song)
@@ -240,8 +256,7 @@ class Player {
process.emit("PLAYERS_UPDATE")
return true
}
const { LogType } = require('loguix')
}
@@ -257,6 +272,7 @@ class Player {
this.player = null
this.connection = null
this.channelId = null
this.channelName = null
this.connected = false
Activity.idleActivity()
this.queue.destroy()
@@ -291,6 +307,7 @@ class Player {
}
const passThroughStream = new PassThrough();
duration = Math.floor(duration.time);
ffmpeg(this.stream)
.setStartTime(duration) // Démarrer à la position demandée (en secondes)
.outputOptions('-f', 'mp3') // Specify output format if needed
@@ -398,6 +415,7 @@ function getAllPlayers() {
AllPlayers.forEach((player) => {
players.push(player)
})
return players
}
function isPlayer(guildId) {

View File

@@ -51,31 +51,17 @@ class Song {
}
async processYoutubeVideo(video, playlist) {
if(playlist) {
async processYoutubeVideo(video) {
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.authorId = video.author.url
this.thumbnail = video.thumbnail
this.url = "https://www.youtube.com/watch?v=" + video.videoId
this.type = "youtube"
this.id = video.id
this.id = video.videoId
this.duration = video.milis_length / 1000
this.duration = video.duration.seconds
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
}

View File

@@ -26,11 +26,11 @@ function checkSong(song) {
slog.error("La musique n'a pas d'auteur")
return false
}
if(!song.duration) {
if(song.duration == null) {
slog.error("La musique n'a pas de durée")
return false
}
if(!song.readduration) {
if(song.readduration == null) {
slog.error("La musique n'a pas de durée lisible")
return false
}

View File

@@ -3,6 +3,7 @@ const { getReadableDuration } = require("../utils/TimeConverter");
class Playlist {
title = "Aucun titre";
id;
playlistId;
url;
author = "Auteur inconnu";
authorId;

View File

@@ -35,27 +35,28 @@ function getPlaylistsOfUser(id) {
/**
* @param {string} id
* @param {string} name
* @param {string} playlistId
* @returns {Playlist}
*/
function getPlaylistOfUser(id, name) {
function getPlaylistOfUser(id, playlistId) {
const playlists = getPlaylistsOfUser(id);
const playlist = playlists.find(p => p.title === name);
const playlist = playlists.find(p => p.playlistId === playlistId);
if (!playlist) {
clog.warn(`La playlist ${name} n'existe pas pour l'utilisateur ${id}`);
clog.warn(`La playlist ${playlistId} n'existe pas pour l'utilisateur ${id}`);
return null;
}
return playlist;
}
async function addPlaylist(id, name, url) {
async function addPlaylist(id, name, url, authorName, authorId, authorAvatar) {
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;
let failed = false;
playlist.thumbnail = null
playlist.author = authorName;
playlist.authorAvatar = `https://cdn.discordapp.com/avatars/${authorId}/${authorAvatar}`;
playlist.views = null;
if(url) {
await Finder.search(url, false, "PLAYLIST").then(async (playlistFounded) => {
if(!playlistFounded) {
@@ -69,7 +70,7 @@ async function addPlaylist(id, name, url) {
}
})
}
playlist.playlistId = new String(Date.now());
if(failed) {
clog.error(`Impossible de trouver la playlist ${name} pour l'utilisateur ${id}`);
return null;
@@ -81,65 +82,60 @@ async function addPlaylist(id, name, url) {
return playlist;
}
function removePlaylist(id, name) {
function removePlaylist(id, playlistId) {
const playlists = getPlaylistsOfUser(id);
const index = playlists.findIndex(p => p.title === name);
const index = playlists.findIndex(p => p.playlistId === playlistId);
if (index === -1) {
clog.warn(`La playlist ${name} n'existe pas pour l'utilisateur ${id}`);
clog.warn(`La playlist ${playlistId} 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}`);
clog.log(`Suppression de la playlist ${playlistId} pour l'utilisateur ${id}`);
}
function getPlaylist(id, name) {
function getPlaylist(id, playlistId) {
const playlists = getPlaylistsOfUser(id);
const playlist = playlists.find(p => p.title === name);
const playlist = playlists.find(p => p.playlistId === playlistId);
if (!playlist) {
clog.warn(`La playlist ${name} n'existe pas pour l'utilisateur ${id}`);
clog.warn(`La playlist ${playlistId} n'existe pas pour l'utilisateur ${id}`);
return null;
}
return playlist;
}
function copyPlaylist(fromId, toId, name) {
function copyPlaylist(fromId, toId, playlistId) {
const playlists = getPlaylistsOfUser(fromId);
const playlist = playlists.find(p => p.title === name);
const playlist = playlists.find(p => p.playlistId === playlistId);
if (!playlist) {
clog.warn(`La playlist ${name} n'existe pas pour l'utilisateur ${fromId}`);
clog.warn(`La playlist ${playlistId} 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}`);
if (toPlaylists.find(p => p.title === playlist.title)) {
clog.warn(`La playlist ${playlist.title} 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}`);
clog.log(`Copie de la playlist ${playlist.title} de l'utilisateur ${fromId} vers l'utilisateur ${toId}`);
return false;
}
function renamePlaylist(id, oldName, newName) {
function renamePlaylist(id, playlistId, newName) {
const playlists = getPlaylistsOfUser(id);
const playlist = playlists.find(p => p.title === oldName);
const playlist = playlists.find(p => p.playlistId === playlistId);
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}`);
clog.warn(`La playlist ${playlistId} n'existe pas pour l'utilisateur ${id}`);
return null;
}
playlist.title = newName;
playlistDB.save();
clog.log(`Renommage de la playlist ${oldName} en ${newName} pour l'utilisateur ${id}`);
clog.log(`Renommage de la playlist ${playlistId} en ${newName} pour l'utilisateur ${id}`);
}
function addSong(id, playlistName, song) {
function addSong(id, playlistId, song) {
if(typeof song === "string") {
try {
song = JSON.parse(song)
@@ -154,9 +150,9 @@ function addSong(id, playlistName, song) {
return null;
}
const playlists = getPlaylistsOfUser(id);
const playlist = playlists.find(p => p.title === playlistName);
const playlist = playlists.find(p => p.playlistId === playlistId);
if (!playlist) {
clog.warn(`La playlist ${playlistName} n'existe pas pour l'utilisateur ${id}`);
clog.warn(`La playlist ${playlistId} n'existe pas pour l'utilisateur ${id}`);
return null;
}
// Check the integrity of the song
@@ -165,25 +161,31 @@ function addSong(id, playlistName, song) {
return null;
}
playlist.songs.push(song);
// Recalculate the songs duration and readduration
playlist.duration += song.duration;
playlist.readduration = getReadableDuration(playlist.duration);
playlistDB.save();
clog.log(`Ajout de la chanson ${song.title} à la playlist ${playlistName} pour l'utilisateur ${id}`);
clog.log(`Ajout de la chanson ${song.title} à la playlist ${playlistId} pour l'utilisateur ${id}`);
}
function removeSong(id, playlistName, songId) {
function removeSong(id, playlistId, songId) {
const playlists = getPlaylistsOfUser(id);
const playlist = playlists.find(p => p.title === playlistName);
const playlist = playlists.find(p => p.playlistId === playlistId);
if (!playlist) {
clog.warn(`La playlist ${playlistName} n'existe pas pour l'utilisateur ${id}`);
clog.warn(`La playlist ${playlistId} 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}`);
clog.warn(`La chanson ${songId} n'existe pas dans la playlist ${playlistId} pour l'utilisateur ${id}`);
return null;
}
playlist.duration -= playlist.songs[index].duration;
playlist.songs.splice(index, 1);
playlist.readduration = getReadableDuration(playlist.duration);
playlistDB.save();
clog.log(`Suppression de la chanson ${songId} de la playlist ${playlistName} pour l'utilisateur ${id}`);
clog.log(`Suppression de la chanson ${songId} de la playlist ${playlistId} pour l'utilisateur ${id}`);
}
async function processYoutubeData(userId, data) {
@@ -264,6 +266,55 @@ async function processYoutubeData(userId, data) {
return playlists;
}
async function deleteUserPlaylists(userId) {
// Delete all playlists of the user and the keys
if (!playlistDB.data[userId]) {
clog.warn(`Aucune playlist trouvée pour l'utilisateur ${userId}`);
return;
}
delete playlistDB.data[userId];
playlistDB.save();
}
async function refreshPlaylist(userId, playlistId) {
var playlist = getPlaylistOfUser(userId, playlistId);
if (!playlist) {
clog.warn(`Aucune playlist trouvée pour l'utilisateur ${userId} avec l'ID ${playlistId}`);
return null;
}
let failed = false;
// If playlistHasUrl, refresh the playlist
if (playlist.url) {
await Finder.search(playlist.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.warn(`Échec de la mise à jour de la playlist ${playlistId} pour l'utilisateur ${userId}`);
return null;
}
playlist.playlistId = playlistId;
const playlists = getPlaylistsOfUser(userId);
// Remove the older one
// Push at the same index
const existingIndex = playlists.findIndex(p => p.playlistId === playlistId);
playlists.splice(existingIndex, 1, playlist);
playlistDB.save();
return playlist;
}
module.exports = {
getPlaylistsOfUser,
getPlaylistOfUser,
@@ -274,5 +325,7 @@ module.exports = {
renamePlaylist,
addSong,
removeSong,
processYoutubeData
processYoutubeData,
deleteUserPlaylists,
refreshPlaylist
}

View File

@@ -1,5 +1,6 @@
const {LogType} = require('loguix')
const wlog = new LogType("Server")
const metrics = require("webmetrik")
const fs = require("fs")
const path = require("path")
@@ -18,6 +19,7 @@ const {__glob} = require("../utils/GlobalVars")
const playlists = require("../playlists/PlaylistManager")
const history = require("../playlists/History")
const lyrics = require("../lyrics/Lyrics")
const serverSettings = require("../discord/ServerSettings")
const mediaBase = require("../discord/MediaBase")
const googleApis = require("../playlists/Google/OAuth2")
const youtubeApi = require("../playlists/Google/YoutubeList")
@@ -33,8 +35,6 @@ 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")
@@ -44,16 +44,17 @@ function init() {
cors: {
origin: "*"
},
maxHttpBufferSize: 300 * 1024 * 1024
})
process.on("PLAYERS_UPDATE", () => {
if(io) {
io.sockets.emit("/GUILD/UPDATE")
// 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")
}
@@ -61,7 +62,7 @@ function init() {
}
})
process.on("USERS_UPDATE", () => {
process.on("USERS_UPDATE", async () => {
if(io) {
// Get all players and send them to client subscribed to the guild
for(var guild of discordBot.getGuilds().keys()) {
@@ -75,6 +76,12 @@ function init() {
}
})
process.on("VOCAL_UPDATE", async () => {
if(io) {
io.sockets.emit("/CHANNEL/UPDATE")
}
})
io.on("connection", async (socket) => {
var socketUser;
@@ -86,9 +93,9 @@ function init() {
wlog.log(`Connexion d'un client : ${socket.id}`)
socket.on("disconnect", () => {
socket.on("disconnect", (info) => {
handleDisconnect()
wlog.log("Déconnexion du client : " + socket.id)
wlog.log("Déconnexion du client : " + socket.id + " - Raison : " + info)
})
socket.on("error", (error) => {
@@ -123,11 +130,6 @@ function init() {
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
@@ -220,56 +222,22 @@ function init() {
// 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)
}
}
socketUser = users.getUserById(socketUser.identity.id)
socketUser.identity['isAdmin'] = socketUser.isAdmin()
var guildPresents = getUserGuilds();
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 + "'" )
})
}, true)
wlog.log("[USUAL] - Envoi des informations Discord de '" + socketUser.identity.id + "' à '" + socket.id + "'" )
}, true)
IORequest("/USER/HISTORY", () => {
IOAnswer("/USER/HISTORY", history.getPersonalHistory(socketUser.identity.id))
})
IOAnswer("/USER/HISTORY", history.getPersonalHistory(socketUser.identity.id), true)
}, true)
//CHECKED : 24/04/2025
IORequest("/USER/SIGNOUT", () => {
@@ -278,6 +246,15 @@ function init() {
socket.disconnect()
})
IORequest("/USER/DELETE", async () => {
socketUser.removeToken(token)
await users.deleteAccount(socketUser.identity.id)
await playlists.deleteUserPlaylists(socketUser.identity.id)
await history.clearPersonalHistory(socketUser.identity.id)
await IOAnswer("/USER/DELETE", true)
socket.disconnect()
})
// CHECKED : 24/04/2025
IORequest("/USERS/LIST", () => {
if(!checkUserGuild(socketUser, actualGuildId)) return
@@ -285,6 +262,20 @@ function init() {
IOAnswer("/USERS/LIST", guildConnectedUsers.get(actualGuildId))
})
IORequest("/VERSION", () => {
IOAnswer("/VERSION", __glob.VERSION)
})
IORequest("/CHANGELOG", () => {
const changelogPath = __glob.CHANGELOG_PATH
if(!fs.existsSync(changelogPath)) {
wlog.warn("Aucun changelog trouvé à l'emplacement : " + changelogPath)
return IOAnswer("/CHANGELOG", false)
}
const changelogContent = fs.readFileSync(changelogPath, "utf-8")
IOAnswer("/CHANGELOG", changelogContent, true)
}, true)
// PLAYERS
IORequest("/PLAYER/LYRICS", async () => {
@@ -317,38 +308,81 @@ function init() {
// 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(!checkUserGuild(socketUser, guildId)) return IOAnswer("/GUILD/JOIN", false)
if(socket.rooms.has(guildId)) {
wlog.warn("L'utilisateur '" + socketUser.identity.username + "' est déjà dans la room de la guilde : " + guildId)
wlog.warn("[USUAL] - L'utilisateur '" + socketUser.identity.username + "' est déjà dans la room de la guilde : " + guildId)
IOAnswer("/GUILD/JOIN", true, true)
} 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)
removeGuildConnectedUser(socketUser.identity.id)
}
})
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)
IOAnswer("/GUILD/JOIN", true, true)
process.emit("PLAYERS_UPDATE")
process.emit("USERS_UPDATE")
}
})
}, true)
IORequest("/GUILD/INFO", () => {
if(!checkUserGuild(socketUser, actualGuildId)) return IOAnswer("/GUILD/INFO", false)
const guild = discordBot.getGuilds().get(actualGuildId)
if(!guild) {
wlog.warn("Aucune guilde trouvée pour l'id : " + actualGuildId)
return IOAnswer("/GUILD/INFO", false)
}
const guildData = {
id: guild.id,
name: guild.name,
icon: guild.icon,
owner: guild.owner,
members: new Array(),
serverMember: guild.allMembers.length,
connected: players.getPlayer(actualGuildId) && players.getPlayer(actualGuildId).isConnected(),
labels: socketUser.labels,
isOwner: socketUser.identity.id === guild.owner,
isMod: socketUser.isMod(actualGuildId),
isAdmin: socketUser.isAdmin(),
}
for(var user of guildConnectedUsers.get(actualGuildId) || []) {
const userData = users.getUserById(user.id)
if(userData && userData.identity.id != socketUser.identity.id) {
let infos = {
id: userData.identity.id,
username: userData.identity.username,
global_name: userData.identity.global_name,
avatar: userData.identity.avatar,
isAdmin: userData.isAdmin(),
isOwner: userData.identity.id == guild.owner,
isMod: userData.isMod(actualGuildId),
}
if(guildData.members.find(m => m.id == infos.id)) continue
guildData.members.push(infos)
}
}
IOAnswer("/GUILD/INFO", guildData, true)
}, true)
IORequest("/GUILD/LIST", () => {
const guildData = getUserGuilds()
IOAnswer("/GUILD/LIST", guildData, true)
}, true)
// CHECKED : 03/05/2025
IORequest("/PLAYER/STATE", async () => {
const plaryer = await verifyPlayerAction(actualGuildId)
const player = await verifyPlayerAction(actualGuildId)
if(!player) return IOAnswer("/PLAYER/STATE", false)
IOAnswer("/PLAYER/STATE", await player.getState())
})
IOAnswer("/PLAYER/STATE", await player.getState(), true)
}, true)
// CHECKED : 03/05/2025
@@ -384,7 +418,7 @@ function init() {
// CHECKED : 03/05/2025
IORequest("/PLAYER/CHANNEL/CHANGE", () => {
handlePlayerAction(actualGuildId, (player) => {
const channel = getUserChannel()
const channel = getUserChannel(false, true)
if(!channel) {
IOAnswer("/PLAYER/CHANNEL/CHANGE", false)
return
@@ -409,10 +443,10 @@ function init() {
// CHECKED : 04/05/2025
IORequest("/QUEUE/PLAY", (data) => {
if(!data) return IOAnswer("/QUEUE/PLAY/NOW", false)
if(!data) return IOAnswer("/QUEUE/PLAY", false)
const {index, listType, now} = data
if(!index) return IOAnswer("/QUEUE/PLAY/NOW", false)
if(!listType) return IOAnswer("/QUEUE/PLAY/NOW", false)
if(index == null) return IOAnswer("/QUEUE/PLAY", false)
if(listType == null) return IOAnswer("/QUEUE/PLAY", false)
if(!checkUserGuild(socketUser, actualGuildId)) return
const player = new Player(actualGuildId)
if(!connectToPlayer(actualGuildId, player)) return IOAnswer("/QUEUE/PLAY", false)
@@ -424,7 +458,7 @@ function init() {
const next = player.queue.getNext()
song = next[index]
}
if(!song) return IOAnswer("/QUEUE/PLAY/NOW", false)
if(!song) return IOAnswer("/QUEUE/PLAY", false)
if(listType == "next") player.queue.removeNextByIndex(index)
if(now) {
player.play(song)
@@ -432,12 +466,12 @@ function init() {
player.add(song)
}
history.addToPersonalHistory(socketUser.identity.id, song)
IOAnswer("/QUEUE/PLAY/NOW", true)
IOAnswer("/QUEUE/PLAY", true)
})
// CHECKED : 04/05/2025
IORequest("/QUEUE/NEXT/DELETE", (index) => {
if(!index) return IOAnswer("/QUEUE/NEXT/DELETE", false)
if(index == null) return IOAnswer("/QUEUE/NEXT/DELETE", false)
handlePlayerAction(actualGuildId, (player) => {
const next = player.queue.getNext()
if(!next[index]) return IOAnswer("/QUEUE/NEXT/DELETE", false);
@@ -454,8 +488,8 @@ function init() {
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)
if(index == null) return IOAnswer("/QUEUE/NEXT/MOVE", false)
if(newIndex == null) return IOAnswer("/QUEUE/NEXT/MOVE", false)
handlePlayerAction(actualGuildId, (player) => {
const next = player.queue.getNext()
if(!next[index]) return IOAnswer("/QUEUE/NEXT/MOVE", false);
@@ -478,6 +512,7 @@ function init() {
if(typeof song == "string") {
song = JSON.parse(song)
}
song = new Song(song)
if(!checkUserGuild(socketUser, actualGuildId)) return
const player = new Player(actualGuildId)
if(!connectToPlayer(actualGuildId, player)) return IOAnswer("/SEARCH/PLAY", false)
@@ -523,26 +558,20 @@ function init() {
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)
const url = await mediaBase.postMedia(data, socketUser.identity.id)
if(!url) return IOAnswer("/UPLOAD/FILE", "TOOHIGH")
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)
IORequest("/UPLOAD/FILES", async () => {
const files = await mediaBase.getAllMedia(socketUser.identity.id)
IOAnswer("/UPLOAD/FILES", files)
})
IORequest("/UPLOAD/FILE/DELETE", (data) => {
if(!data) return IOAnswer("/UPLOAD/FILE/DELETE", false)
mediaBase.deleteMedia(data, socketUser.identity.id)
IOAnswer("/UPLOAD/FILE/DELETE", true)
})
// GOOGLE API
@@ -567,24 +596,27 @@ function init() {
// PLAYLISTS
IORequest("/CHANNEL", () => {
// Get the channel of the bot, in actualGuildId if he is connected
const channel = getUserChannel(true)
if(!channel) return IOAnswer("/CHANNEL", false, true)
IOAnswer("/CHANNEL", channel, true)
}, true)
// 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)
const playlist = await playlists.addPlaylist(socketUser.identity.id, name, url, socketUser.identity.username, socketUser.identity.id, socketUser.identity.avatar)
if(!playlist) return IOAnswer("/PLAYLISTS/CREATE", false)
IOAnswer("/PLAYLISTS/CREATE", true)
IOAnswer("/PLAYLISTS/CREATE", playlist)
})
// 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)
playlists.removePlaylist(socketUser.identity.id, data)
IOAnswer("/PLAYLISTS/DELETE", true)
})
@@ -614,34 +646,39 @@ function init() {
// 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)
const {id, newName} = data
if(!id || !newName) return IOAnswer("/PLAYLISTS/RENAME", false)
var playlist = playlists.getPlaylist(socketUser.identity.id, id)
if(!playlist) return IOAnswer("/PLAYLISTS/RENAME", false)
playlists.renamePlaylist(socketUser.identity.id, name, newName)
IOAnswer("/PLAYLISTS/RENAME", true)
playlists.renamePlaylist(socketUser.identity.id, id, newName)
playlist = playlists.getPlaylist(socketUser.identity.id, id)
IOAnswer("/PLAYLISTS/RENAME", playlist)
})
// 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)
const {id, song} = data
if(!id || !song) return IOAnswer("/PLAYLISTS/ADD_SONG", false)
var playlist = playlists.getPlaylistOfUser(socketUser.identity.id, id)
if(!playlist) return IOAnswer("/PLAYLISTS/ADD_SONG", false)
playlists.addSong(socketUser.identity.id, name, song)
IOAnswer("/PLAYLISTS/ADD_SONG", true)
playlists.addSong(socketUser.identity.id, id, song)
playlist = playlists.getPlaylistOfUser(socketUser.identity.id, id)
IOAnswer("/PLAYLISTS/ADD_SONG", playlist)
})
// 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)
const {id, songId} = data
if(!id || !songId) return IOAnswer("/PLAYLISTS/REMOVE_SONG", false)
var playlist = playlists.getPlaylistOfUser(socketUser.identity.id, id)
if(!playlist) return IOAnswer("/PLAYLISTS/REMOVE_SONG", false)
playlists.removeSong(socketUser.identity.id, name, songId)
IOAnswer("/PLAYLISTS/REMOVE_SONG", true)
playlists.removeSong(socketUser.identity.id, id, songId)
playlist = playlists.getPlaylistOfUser(socketUser.identity.id, id)
IOAnswer("/PLAYLISTS/REMOVE_SONG", playlist)
})
// CHECKED : 05/05/2025
@@ -658,6 +695,13 @@ function init() {
IOAnswer("/PLAYLISTS/PLAY", true)
})
IORequest("/PLAYLISTS/REFRESH", async (data) => {
if(!data) return IOAnswer("/PLAYLISTS/REFRESH", false)
const playlist = await playlists.refreshPlaylist(socketUser.identity.id, data)
if(!playlist) return IOAnswer("/PLAYLISTS/REFRESH", false)
IOAnswer("/PLAYLISTS/REFRESH", playlist)
})
// ADMIN
@@ -686,6 +730,7 @@ function init() {
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)
process.emit("USERS_UPDATE")
IOAnswer("/ADMIN/USERS/SWITCH_ADMIN", true)
})
@@ -696,6 +741,7 @@ function init() {
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)
process.emit("USERS_UPDATE")
IOAnswer("/ADMIN/USERS/FULL_BAN", true)
})
@@ -735,45 +781,140 @@ function init() {
// CHECKED : 24/04/2025
IORequest("/OWNER/USERS/SWITCH_MOD", (userId) => {
if(userId || actualGuildId) return IOAnswer("/OWNER/USERS/SWITCH_MOD", false)
IORequest("/OWNER/USERS/SWITCH_MOD", async (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)
const guild = discordBot.getGuilds().get(actualGuildId)
if(!socketUser.identity.id === guild.owner) return IOAnswer("/OWNER/USERS/SWITCH_MOD", false)
if(userId == guild.owner) return IOAnswer("/OWNER/USERS/SWITCH_MOD", false)
await users.setGuildMod(userId, actualGuildId)
await process.emit("USERS_UPDATE")
IOAnswer("/OWNER/USERS/SWITCH_MOD", true)
})
IORequest("/OWNER/ROLES/GET", async () => {
if(!actualGuildId) return IOAnswer("/OWNER/ROLES/GET", false)
const guild = discordBot.getGuilds().get(actualGuildId)
if(!socketUser.identity.id === guild.owner) return IOAnswer("/OWNER/ROLES/GET", false)
const rolesSecure = discordBot.getGuildRoles(actualGuildId)
const actualRole = await serverSettings.getSecureRole(actualGuildId)
// Move the actual role at the start of the array
if(actualRole) {
const index = rolesSecure.findIndex(r => r.id === actualRole.id)
if(index > -1) {
rolesSecure.unshift(rolesSecure.splice(index, 1)[0])
}
} else {
// Find the @everyone role and put it at the start of the array
const index = rolesSecure.findIndex(r => r.name === "@everyone")
if(index > -1) {
rolesSecure.unshift(rolesSecure.splice(index, 1)[0])
}
}
IOAnswer("/OWNER/ROLES/GET", await rolesSecure)
})
IORequest("/OWNER/ROLES/SET", async (data) => {
if(!actualGuildId) return IOAnswer("/OWNER/ROLES/SET", false)
const guild = discordBot.getGuilds().get(actualGuildId)
if(!socketUser.identity.id === guild.owner) return IOAnswer("/OWNER/ROLES/SET", false)
await serverSettings.setSecureRole(actualGuildId, data)
IOAnswer("/OWNER/ROLES/SET", true)
})
// CHECKED : 24/04/2025
IORequest("/MOD/USERS/BAN", (userId) => {
if(userId || actualGuildId) return IOAnswer("/MOD/USERS/BAN", false)
IORequest("/MOD/USERS/BAN", async (userId) => {
if(!userId || !actualGuildId) return IOAnswer("/MOD/USERS/BAN", false)
const guild = discordBot.getGuilds().get(actualGuildId)
if(!guild) return IOAnswer("/MOD/USERS/BAN", false)
if(!socketUser.isMod(actualGuildId) && socketUser.identity.id !== guild.owner) 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)
const user = users.getUserById(userId)
if(user.isMod(actualGuildId) || user.identity.id == guild.owner) return IOAnswer("/MOD/USERS/BAN", false)
await users.setGuildBan(userId, actualGuildId)
// If user is connected, disconnect him
const userSocket = UsersBySocket.get(userId)
if(userSocket) {
const socket = io.sockets.sockets.get(userSocket)
if(socket) {
removeGuildConnectedUser(userId)
if(socket.rooms.has(actualGuildId)) {
socket.leave(actualGuildId)
}
}
}
await process.emit("USERS_UPDATE")
IOAnswer("/MOD/USERS/BAN", true)
})
IORequest("/MOD/USERS/LIST", () => {
if(!actualGuildId) return IOAnswer("/MOD/USERS/LIST", false)
const guild = discordBot.getGuilds().get(actualGuildId)
if(!guild) return IOAnswer("/MOD/USERS/LIST", false)
if(!socketUser.isMod(actualGuildId) && socketUser.identity.id !== guild.owner) return IOAnswer("/MOD/USERS/LIST", false)
if(!checkUserGuild(socketUser, actualGuildId)) return IOAnswer("/MOD/USERS/LIST", false)
const guildUserList = new Array()
for(var user of guild.allMembers) {
const userData = users.getUserById(user)
if(!userData) continue
if(userData.labels.includes("DELETED")) continue
userData.identity['isAdmin'] = userData.isAdmin()
userData.identity['isMod'] = userData.isMod(actualGuildId)
userData.identity['isOwner'] = userData.identity.id == guild.owner
guildUserList.push({
identity: userData.identity,
isBanned: userData.isBanned(actualGuildId),
})
}
IOAnswer("/MOD/USERS/LIST", guildUserList, true)
}, true)
// UTILS
// CHECKED : 24/04/2025
IORequest("/REPORT", (data) => {
if(data.length < 2) return IOAnswer("/REPORT", false)
if(!data) 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)
})
IORequest("/MOD/STATS", () => {
if(!actualGuildId) return IOAnswer("/MOD/USERS/LIST", false)
const guild = discordBot.getGuilds().get(actualGuildId)
if(!guild) return IOAnswer("/MOD/USERS/LIST", false)
if(!socketUser.isMod(actualGuildId) && socketUser.identity.id !== guild.owner) return IOAnswer("/MOD/USERS/LIST", false)
if(!checkUserGuild(socketUser, actualGuildId)) return IOAnswer("/MOD/USERS/LIST", false)
const metrics = JSON.parse(fs.readFileSync(__glob.METRIC_FILE))
const metricsToSend = new Array()
for(var metric of metrics) {
if(metric.name.includes(actualGuildId)) {
metricsToSend.push(metric)
}
}
IOAnswer("/MOD/STATS", metricsToSend, true)
}, true)
// Functions
function getUserChannel() {
function getUserChannel(usual, force) {
const botChannel = getBotChannel()
if(botChannel && !force) {
return botChannel
}
const membersVoices = discordBot.getMembersVoices()
const member = membersVoices.get(socketUser.identity.id)
if(member) {
const channelId = member.channelId
const guildId = member.guildId
if(guildId != actualGuildId) {
if(!usual) wlog.warn("La guilde active : " + actualGuildId + " ne correspond pas à la guilde du channel vocal : " + guildId + " de l'utilisateur '" + socketUser.identity.username + "'")
return null
}
const channel = discordBot.getChannel(guildId, channelId)
if(!channel) {
wlog.warn("Le channel vocal n'existe pas : " + channelId)
@@ -781,11 +922,81 @@ function init() {
}
return channel
} else {
wlog.warn("L'utilisateur '" + socketUser.identity.username + "' n'est pas dans un channel vocal")
if(!usual) wlog.warn("L'utilisateur '" + socketUser.identity.username + "' n'est pas dans un channel vocal")
return null
}
}
function getUserGuilds() {
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
guildData.restricted = false
const secureRole = serverSettings.getSecureRole(guild[0])
if(secureRole && socketUser.identity.id !== discordBot.getGuilds().get(guild[0]).owner) {
const member = discordBot.getGuildMember(guild[0], socketUser.identity.id)
if(!member.roles.cache.has(secureRole.id) && socketUser.identity.id !== guild[1].owner && !socketUser.isAdmin()) {
guildData.restricted = true
} else {
guildData.restricted = false
}
}
guildData.allowed = true
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,
global_name: userData.identity.global_name,
username: userData.identity.username,
avatar: userData.identity.avatar,
isAdmin: userData.isAdmin(),
isOwner: userData.identity.id == guild[1].owner,
isMod: userData.isMod(guild[0]),
}
// If it's already in guildData.members, skip
if(guildData.members.find(m => m.id == infos.id)) continue
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.id)
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)
}
}
return guildPresents
}
function getBotChannel() {
const playersList = players.getAllPlayers()
if(!playersList || playersList.length == 0) return null
const player = playersList.find(p => p.isConnected() && p.guildId == actualGuildId)
if(player) {
const channel = discordBot.getChannel(actualGuildId, player.channelId)
if(channel) {
return channel
}
}
return null
}
/**
* @param {Player} player
*/
@@ -815,6 +1026,15 @@ function init() {
wlog.warn("Aucun guildId n'est actif pour l'utilisateur : " + socketUser.identity.username)
return false
}
// Check role if secure role is set
const secureRole = serverSettings.getSecureRole(guildId)
if(secureRole && socketUser.identity.id !== discordBot.getGuilds().get(guildId).owner) {
const member = discordBot.getGuildMember(guildId, socketUser.identity.id)
if(member.roles.cache.has(secureRole.id) == false) {
wlog.warn("L'utilisateur '" + socketUser.identity.username + "' n'a pas le rôle requis pour accéder à la guilde : " + guildId)
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")
@@ -864,7 +1084,7 @@ function init() {
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)
removeGuildConnectedUser(socketUser.identity.id)
process.emit("USERS_UPDATE")
// Remove every rooms include admin
socket.rooms.forEach((room) => {
@@ -882,16 +1102,19 @@ function init() {
}
function IORequest(RequestName, RequestCallback) {
function IORequest(RequestName, RequestCallback, usual) {
socket.on(RequestName, (value) => {
wlog.log(socketUser.identity.username + " - Socket : " + socket.id + " - " + RequestName + " - [RECIEVED]")
if(!usual) {
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]")
function IOAnswer(AnswerName, AnswerValue, usual) {
if(!usual) {
wlog.log(socketUser.identity.username + " - Socket : " + socket.id + " - " + AnswerName + " - [ANSWERED]")
}
socket.emit(AnswerName, AnswerValue)
}
@@ -907,26 +1130,28 @@ function init() {
if(!guildConnectedUsers.has(guildId)) {
guildConnectedUsers.set(guildId, new Array())
}
const users = guildConnectedUsers.get(guildId)
if(users.includes(user)) {
if(guildConnectedUsers.get(guildId).includes(user)) {
wlog.warn("L'utilisateur '" + user.username + "' est déjà connecté à la guilde : " + guildId)
return
} else {
guildConnectedUsers.get(guildId).push(user)
}
guildConnectedUsers.get(guildId).push(user)
}
function removeGuildConnectedUser(user) {
function removeGuildConnectedUser(userId) {
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)
}
const userIndex = users.findIndex(u => u.id == userId)
if(userIndex != -1) {
users.splice(userIndex, 1)
}
}
}
}

View File

@@ -145,15 +145,8 @@ class User {
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);
return this.labels.includes(modLabel) || this.isAdmin();
}
justUpdated() {
@@ -241,6 +234,10 @@ async function updateIdentity(id) {
clog.warn(`Utilisateur ${id} non trouvé.`);
return null;
}
if(user.labels.includes("DELETED")) {
clog.warn(`L'utilisateur ${user.identity.username} (${user.identity.id}) est marqué comme supprimé, il ne peut pas être mis à jour.`);
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);
@@ -284,6 +281,10 @@ async function addUser(auth, identity) {
const existingUser = userList.find(user => user.identity.id === identity.id);
if (existingUser) {
clog.warn(`L'utilisateur ${identity.username} existe déjà.`);
if(existingUser.labels.includes("DELETED")) {
clog.warn(`L'utilisateur ${identity.username} est marqué comme supprimé, il sera réactivé.`);
existingUser.labels = existingUser.labels.filter(label => label !== "DELETED");
}
// Update the existing user with new information
existingUser.auth = auth;
existingUser.identity = identity;
@@ -391,88 +392,88 @@ function getSimpleUser(id) {
// SET LABELS
function setAdmin(id) {
async function setAdmin(id) {
const user = getUserById(id);
if (user) {
user.setAdmin();
saveUsers();
await user.setAdmin();
await saveUsers();
} else {
clog.warn(`Utilisateur ${id} non trouvé.`);
}
}
function setGuildMod(id, guildId) {
async 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);
await 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);
await user.labels.push(modLabel);
clog.log(`L'utilisateur ${user.identity.username} est maintenant modérateur du serveur ${guildId}.`);
}
saveUsers();
await saveUsers();
} else {
clog.warn(`Utilisateur ${id} non trouvé.`);
}
}
function setGuildBan(id, guildId) {
async 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();
await user.setBan(guildId);
await saveUsers();
} else {
clog.warn(`Utilisateur ${id} non trouvé.`);
}
}
function setFullBan(id) {
async 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();
await user.setFullBan();
await saveUsers();
} else {
clog.warn(`Utilisateur ${id} non trouvé.`);
}
}
function setGuildOwner(id, guildId, force) {
function deleteAccount(id) {
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}.`);
}
user.labels = user.labels.filter(label => label.includes('BAN'));
user.labels.push('DELETED'); // Add a deleted label
user.tokens = []; // Clear tokens
user.auth = null; // Clear authentication
user.identity = { id: user.identity.id, username: user.identity.username }; // Keep only identity information
saveUsers();
clog.log(`Suppression du compte de l'utilisateur ${user.identity.username}.`);
} else {
clog.warn(`Utilisateur ${id} non trouvé.`);
}
}
// USERS DB
function loadUsers() {
UserDB.load()
userList = new Array();
for (const user of UserDB.getData()) {
if(user?.labels?.includes("DELETED")) {
clog.log(`Utilisateur ${user.identity.id} marqué comme supprimé, ignoré.`);
userList.push(new User(null, user.identity, [], user.labels));
continue; // Skip deleted users
}
userList.push(new User(user.auth, user.identity, user.tokens, user.labels));
}
clog.log(`Chargement de ${userList.length} utilisateurs.`);
@@ -506,7 +507,6 @@ function clearNeedUpdateForUsers() {
module.exports = {User}
module.exports = {
addUser,
setGuildOwner,
setFullBan,
removeUser,
getUserByToken,
@@ -522,5 +522,6 @@ module.exports = {
updateCredientials,
refreshAllUserInformation,
updateIdentity,
clearNeedUpdateForUsers
clearNeedUpdateForUsers,
deleteAccount
};

View File

@@ -1,5 +1,6 @@
const path = require("path")
const root = path.resolve(__dirname, '../../')
const version = JSON.parse(require('fs').readFileSync(root + path.sep + "package.json", "utf-8")).version
const __glob = {
PACKAGEINFO: root + path.sep + "package.json",
@@ -7,13 +8,18 @@ const __glob = {
SRC: root + path.sep + "src",
LOGS: root + path.sep + "logs",
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",
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",
SERVER_DB: root + path.sep + "data" + path.sep + "servers.json",
VERSION: version,
CHANGELOG_PATH: root + path.sep + "CHANGELOG.html",
COOKIES: root + path.sep + "data" + path.sep + "cookies.json",
PROXY: root + path.sep + "data" + path.sep + "proxy.json"
}
module.exports = {__glob}