Version 2.0.0 - update of everything

This commit is contained in:
raphix committed 2026-10-07 14:04:26 +02:00
1 parent 164a1da009
commit 6017e03eac
85 files changed
+13065 -2107

No files matched your search

+2072
View File
File diff suppressed because it is too large. Load diff
+518 -432
View File
File diff suppressed because it is too large. Load diff
+109 -97
View File
@@ -1,11 +1,35 @@
const { LogType } = require('loguix');
const { LogType } = require('loguix');
const dlog = new LogType("DiscordAuth");
const { getWebsiteLink, getClientSecret } = require('../../utils/Database/Configuration');
// Délai maximal d'une requête vers l'API Discord
const REQUEST_TIMEOUT = 10 * 1000;
/**
* Appelle l'API Discord et renvoie le JSON, en levant une erreur si le statut HTTP n'est pas 2xx
* (429 / 401 renvoient un JSON {message, retry_after} qui ne doit jamais être pris pour un utilisateur)
*/
async function discordFetch(url, options = {}) {
const resp = await fetch(url, { ...options, signal: AbortSignal.timeout(REQUEST_TIMEOUT) });
let body = null;
try {
body = await resp.json();
} catch (e) {
body = null;
}
if (!resp.ok) {
const detail = body?.error_description || body?.error || body?.message || resp.statusText;
const error = new Error(`HTTP ${resp.status} : ${detail}`);
error.status = resp.status;
throw error;
}
return body;
}
/**
* @returns {Promise<{auth, identity}|"ACCESS_TOKEN_ERROR"|"USER_INFO_ERROR">}
*/
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);
@@ -18,109 +42,97 @@ async function getDiscordUser(sessionId, auth_code) {
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", {
let accessToken;
try {
accessToken = await discordFetch("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);
})
})
}
});
if (!accessToken?.access_token || accessToken.error) {
throw new Error((accessToken?.error || "Réponse invalide") + " : " + (accessToken?.error_description || ""));
}
} 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.message);
return "ACCESS_TOKEN_ERROR";
}
dlog.log("Récupération réussi du token d'accès Discord associé à la session : " + sessionId);
function getUserIdentity(accessToken) {
return new Promise((resolve, reject) => {
fetch("https://discord.com/api/users/@me", {
// Le scope demandé par le site est "identify" : l'appel /users/@me/guilds était toujours refusé et inutilisé
try {
const user = await discordFetch("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);
})
})
});
if (!user?.id) throw new Error("Identité Discord sans identifiant");
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 + ")");
dlog.step.end("discord_auth_" + sessionId)
return {
auth: accessToken,
identity: user,
};
} 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.message);
return "USER_INFO_ERROR";
}
}
module.exports = {getDiscordUser, refreshToken, getUserIdentity}
/**
* @returns {Promise<Object|null>} nouveau token d'accès, null en cas d'échec
*/
async function refreshToken(refresh_token) {
if (!refresh_token) return null;
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);
try {
const accessToken = await discordFetch("https://discord.com/api/oauth2/token", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: params,
});
if (!accessToken?.access_token || accessToken.error) {
dlog.error("Erreur lors de la récupération du token d'accès Discord : " + accessToken?.error + " : " + accessToken?.error_description);
return null;
}
return accessToken;
} catch (err) {
dlog.error("Erreur lors de la récupération du token d'accès Discord : " + err.message);
return null;
}
}
/**
* @returns {Promise<Object|null>} identité Discord (/users/@me), null en cas d'échec
*/
async function getUserIdentity(accessToken) {
if (!accessToken?.access_token) return null;
try {
const user = await discordFetch("https://discord.com/api/users/@me", {
headers: {
authorization: `${accessToken.token_type} ${accessToken.access_token}`,
},
});
if (!user?.id) {
dlog.error("Réponse de Discord sans identifiant utilisateur");
return null;
}
return user;
} catch (err) {
dlog.error("Erreur lors de la récupération des informations de l'utilisateur Discord : " + err.message);
return null;
}
}
module.exports = {getDiscordUser, refreshToken, getUserIdentity}
+47 -10
View File
@@ -1,33 +1,70 @@
const { LogType } = require('loguix');
const { generateSessionId } = require('../../utils/TokenManager');
const { generateSessionId } = require('../../utils/TokenManager');
const clog = new LogType("Session");
const sessions = new Array();
// Une session OAuth n'est utile que le temps de la redirection Discord : on l'expire après 10 minutes
const SESSION_TTL = 10 * 60 * 1000;
const CLEAN_INTERVAL = 60 * 1000;
// sessionId -> date de création (ms)
const sessions = new Map();
/**
* Supprime les sessions expirées (OAuth jamais terminé)
*/
function purgeExpired() {
const now = Date.now();
let removed = 0;
for (const [sessionId, createdAt] of sessions) {
if (now - createdAt > SESSION_TTL) {
sessions.delete(sessionId);
removed++;
}
}
if (removed > 0) clog.log(`${removed} session(s) expirée(s) supprimée(s).`);
return removed;
}
setInterval(purgeExpired, CLEAN_INTERVAL).unref();
function checkSession(sessionId) {
return sessions.includes(sessionId);
if (typeof sessionId !== "string") return false;
const createdAt = sessions.get(sessionId);
if (createdAt === undefined) return false;
if (Date.now() - createdAt > SESSION_TTL) {
sessions.delete(sessionId);
clog.warn(`Session ${sessionId} expirée.`);
return false;
}
return true;
}
function addSession() {
purgeExpired();
const sessionId = generateSessionId();
if (checkSession(sessionId)) {
clog.warn(`Session ${sessionId} non trouvée dans la liste des sessions.`);
if (sessions.has(sessionId)) {
clog.warn(`La session ${sessionId} existe déjà, génération d'un nouvel identifiant.`);
return addSession(); // Recursively generate a new session ID if it already exists
}
sessions.push(sessionId);
sessions.set(sessionId, Date.now());
clog.log(`Nouvelle session ${sessionId} ajoutée.`);
return sessionId;
}
function removeSession(sessionId) {
const index = sessions.indexOf(sessionId);
if (index > -1) {
sessions.splice(index, 1);
if (sessions.delete(sessionId)) {
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};
/**
* Nombre de sessions OAuth en attente (vue d'ensemble de l'administration)
*/
function getSessionCount() {
purgeExpired();
return sessions.size;
}
module.exports = {checkSession, addSession, removeSession, getSessionCount};
+214 -68
View File
@@ -4,53 +4,86 @@ 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();
/**
* Ajoute ou retire un label. value === undefined : bascule (ancien comportement).
* @returns {boolean} présence du label après l'opération
*/
function applyLabel(labels, label, value) {
const has = labels.includes(label);
const wanted = value === undefined ? !has : !!value;
if (wanted && !has) labels.push(label);
if (!wanted && has) {
// Retire toutes les occurrences (doublons éventuels dans users.json)
for (let i = labels.length - 1; i >= 0; i--) {
if (labels[i] === label) labels.splice(i, 1);
}
}
return wanted;
}
class User {
auth;
identity;
tokens;
labels;
constructor(auth, identity, tokens, labels) {
banInfo;
constructor(auth, identity, tokens, labels, banInfo) {
this.auth = auth;
this.identity = identity;
this.tokens = tokens;
this.labels = labels;
this.tokens = tokens || [];
this.labels = labels || [];
// Raison et date du bannissement global : {reason, at} ou null
this.banInfo = banInfo || null;
}
setAdmin() {
/**
* @param {boolean} [value] true = admin, false = retiré, undefined = bascule
* @returns {boolean|null} nouvel état
*/
setAdmin(value) {
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");
const wasAdmin = userInUserList.labels.includes("ADMIN");
const isAdmin = applyLabel(userInUserList.labels, "ADMIN", value);
if (wasAdmin === isAdmin) return isAdmin;
if (isAdmin) {
clog.log(`L'utilisateur ${this.identity.username} est maintenant admin.`);
} else {
clog.log(`L'utilisateur ${this.identity.username} n'est plus admin.`);
}
saveUsers()
return isAdmin;
}
setBan(guildId) {
/**
* @param {string} guildId
* @param {boolean} [value] undefined = bascule
* @returns {boolean|null} nouvel état
*/
setBan(guildId, value) {
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);
const wasBanned = userInUserList.labels.includes(banLabel);
const banned = applyLabel(userInUserList.labels, banLabel, value);
if (wasBanned === banned) return banned;
if (banned) {
clog.log(`L'utilisateur ${this.identity.username} est maintenant banni du serveur ${guildId}.`);
} else {
clog.log(`L'utilisateur ${this.identity.username} n'est plus banni du serveur ${guildId}.`);
}
saveUsers()
return banned;
}
createToken() {
@@ -118,20 +151,36 @@ class User {
}
setFullBan() {
/**
* Bannissement global
* @param {boolean} [value] undefined = bascule
* @param {string} [reason]
* @returns {boolean|null} nouvel état
*/
setFullBan(value, reason) {
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.`);
const wasBanned = userInUserList.labels.includes("BAN");
const banned = applyLabel(userInUserList.labels, "BAN", value);
if (banned) {
if (!wasBanned || reason) {
userInUserList.banInfo = { reason: reason ? String(reason).slice(0, 500) : null, at: new Date().toISOString() };
}
} else {
userInUserList.labels.push("BAN");
clog.log(`L'utilisateur ${this.identity.username} est maintenant banni.`);
userInUserList.banInfo = null;
}
if (wasBanned !== banned) {
if (banned) {
clog.log(`L'utilisateur ${this.identity.username} est maintenant banni.`);
} else {
clog.log(`L'utilisateur ${this.identity.username} n'est plus banni.`);
}
}
saveUsers()
return banned;
}
isBanned(guildId) {
@@ -148,6 +197,9 @@ class User {
const modLabel = `MOD_${guildId}`;
return this.labels.includes(modLabel) || this.isAdmin();
}
isDeleted() {
return this.labels.includes("DELETED");
}
justUpdated() {
const userInUserList = userList.find(user => user.identity.id === this.identity.id);
@@ -187,13 +239,19 @@ class 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);
// Une seule sauvegarde à la fin plutôt qu'une réécriture complète du fichier par utilisateur
for (const user of [...userList]) {
if (!user.auth) continue;
await updateCredientials(user.identity.id, false);
}
saveUsers();
}
async function updateCredientials(id) {
/**
* @param {string} id
* @param {boolean} [save=true] false pour regrouper les sauvegardes (rafraîchissement global)
*/
async function updateCredientials(id, save = true) {
const user = getUserById(id);
if (!user) {
clog.warn(`Utilisateur ${id} non trouvé.`);
@@ -223,7 +281,7 @@ async function updateCredientials(id) {
else {
clog.warn(`Aucune authentification trouvée pour l'utilisateur ${user.identity.username} (${user.identity.id})`);
}
saveUsers();
if (save) saveUsers();
return user.auth;
}
@@ -277,6 +335,11 @@ async function updateIdentity(id) {
* @returns {User} user
*/
async function addUser(auth, identity) {
// Une réponse d'erreur de Discord (429, 401...) n'a pas d'id : on ne crée jamais d'utilisateur fantôme
if (!identity || typeof identity.id !== "string" || identity.id === "") {
clog.error("Ajout d'un utilisateur refusé : identité Discord invalide");
return null;
}
// Check if the user already exists
const existingUser = userList.find(user => user.identity.id === identity.id);
if (existingUser) {
@@ -320,12 +383,12 @@ function removeToken(token) {
if (index > -1) {
user.tokens.splice(index, 1);
saveUsers();
clog.log(`Token ${token} supprimé pour l'utilisateur ${user.identity.username}.`);
clog.log(`Token supprimé pour l'utilisateur ${user.identity.username}.`);
} else {
clog.warn(`Token ${token} non trouvé pour l'utilisateur ${user.identity.username}.`);
clog.warn(`Token non trouvé pour l'utilisateur ${user.identity.username}.`);
}
} else {
clog.warn(`Utilisateur avec le token "${token}" non trouvé.`);
clog.warn(`Utilisateur introuvable pour le token fourni.`);
}
return user;
}
@@ -336,7 +399,7 @@ function addToken(id) {
const token = generateToken();
user.tokens.push(token);
saveUsers();
clog.log(`Token "${token}" ajouté pour l'utilisateur ${user.identity.username}.`);
clog.log(`Token ajouté pour l'utilisateur ${user.identity.username}.`);
return token;
} else {
clog.warn(`Utilisateur ${id} non trouvé.`);
@@ -392,74 +455,151 @@ function getSimpleUser(id) {
// SET LABELS
async function setAdmin(id) {
/**
* Toutes les fonctions de labels acceptent une valeur explicite (idempotent) ;
* sans valeur, elles basculent l'état comme avant (compatibilité).
* @returns {Promise<boolean|null>} nouvel état, null si l'utilisateur est introuvable ou protégé
*/
async function setAdmin(id, value) {
const user = getUserById(id);
if (user) {
await user.setAdmin();
await saveUsers();
return user.setAdmin(value);
} else {
clog.warn(`Utilisateur ${id} non trouvé.`);
return null;
}
}
async function setGuildMod(id, guildId) {
async function setGuildMod(id, guildId, value) {
const user = getUserById(id);
if (user) {
const modLabel = `MOD_${guildId}`;
if (user.labels.includes(modLabel)) {
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 {
await user.labels.push(modLabel);
clog.log(`L'utilisateur ${user.identity.username} est maintenant modérateur du serveur ${guildId}.`);
const wasMod = user.labels.includes(modLabel);
const isMod = applyLabel(user.labels, modLabel, value);
if (wasMod !== isMod) {
if (isMod) {
clog.log(`L'utilisateur ${user.identity.username} est maintenant modérateur du serveur ${guildId}.`);
} else {
clog.log(`L'utilisateur ${user.identity.username} n'est plus modérateur du serveur ${guildId}.`);
}
saveUsers();
}
await saveUsers();
return isMod;
} else {
clog.warn(`Utilisateur ${id} non trouvé.`);
return null;
}
}
async function setGuildBan(id, guildId) {
async function setGuildBan(id, guildId, value) {
const user = getUserById(id);
if (user) {
if(user.isAdmin()) {
if(user.isAdmin() && (value === undefined ? !user.isBanned(guildId) : value)) {
clog.warn(`L'utilisateur ${user.identity.username} est admin, il ne peut pas être banni.`);
return;
return null;
}
await user.setBan(guildId);
await saveUsers();
return user.setBan(guildId, value);
} else {
clog.warn(`Utilisateur ${id} non trouvé.`);
return null;
}
}
async function setFullBan(id) {
async function setFullBan(id, value, reason) {
const user = getUserById(id);
if (user) {
if(user.isAdmin()) {
if(user.isAdmin() && (value === undefined ? !user.isFullBanned() : value)) {
clog.warn(`L'utilisateur ${user.identity.username} est admin, il ne peut pas être banni.`);
return;
return null;
}
await user.setFullBan();
await saveUsers();
return user.setFullBan(value, reason);
} else {
clog.warn(`Utilisateur ${id} non trouvé.`);
return null;
}
}
function deleteAccount(id) {
/**
* Révoque toutes les sessions web (tokens) d'un utilisateur
* @returns {number} nombre de tokens révoqués
*/
function revokeTokens(id) {
const user = getUserById(id);
if (user) {
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 {
if (!user) {
clog.warn(`Utilisateur ${id} non trouvé.`);
return 0;
}
const count = user.tokens?.length || 0;
if (count > 0) user.clearToken();
return count;
}
/**
* Suppression douce : garde l'entrée et les labels BAN (un banni ne retrouve pas un compte vierge),
* ajoute DELETED et efface tokens, authentification et identité détaillée.
* @param {string} id
* @param {{purge?: boolean}} [options] purge : supprime aussi playlists, historique et médias
* @returns {Promise<{playlists: number, history: number, media: number}|null>} éléments purgés, null si introuvable
*/
async function deleteAccount(id, { purge = false } = {}) {
const user = getUserById(id);
if (!user) {
clog.warn(`Utilisateur ${id} non trouvé.`);
return null;
}
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}.`);
// Jetons Google (identifiants, pas des données) : toujours supprimés, révocation chez Google sans attendre
try {
// Chargement différé : OAuth2.js dépend de ce module
require('../../playlists/Google/OAuth2').unlink(id, { revoke: true }).catch((e) => {
clog.error(`Suppression du compte Google de ${id} impossible : ${e?.message || e}`);
});
} catch (e) {
clog.error(`Suppression du compte Google de ${id} impossible : ${e?.message || e}`);
}
const purged = { playlists: 0, history: 0, media: 0 };
if (!purge) return purged;
// Chargement différé : évite les dépendances circulaires au démarrage
const { getDatabase } = require('../../utils/Database/Database');
try {
const playlistDB = getDatabase("Playlists");
purged.playlists = playlistDB?.data?.[id]?.length || 0;
await require('../../playlists/PlaylistManager').deleteUserPlaylists(id);
} catch (e) {
clog.error(`Purge des playlists de ${id} impossible : ${e.message}`);
}
try {
const historyDB = getDatabase("history");
purged.history = historyDB?.data?.[id]?.length || 0;
if (historyDB?.data?.[id]) {
delete historyDB.data[id];
historyDB.save();
}
} catch (e) {
clog.error(`Purge de l'historique de ${id} impossible : ${e.message}`);
}
try {
const mediaDB = getDatabase("media");
if (Array.isArray(mediaDB?.data)) {
const before = mediaDB.data.length;
mediaDB.data = mediaDB.data.filter(m => m.userId !== id);
purged.media = before - mediaDB.data.length;
if (purged.media > 0) mediaDB.save();
}
} catch (e) {
clog.error(`Purge des médias de ${id} impossible : ${e.message}`);
}
clog.log(`Purge des données de l'utilisateur ${id} : ${purged.playlists} playlist(s), ${purged.history} entrée(s) d'historique, ${purged.media} média(s).`);
return purged;
}
// USERS DB
@@ -468,12 +608,15 @@ function loadUsers() {
UserDB.load()
userList = new Array();
for (const user of UserDB.getData()) {
if(!user?.identity?.id) {
clog.warn("Entrée utilisateur sans identifiant ignorée dans users.json.");
continue;
}
if(user?.labels?.includes("DELETED")) {
clog.log(`Utilisateur ${user.identity.id} marqué comme supprimé, ignoré.`);
userList.push(new User(null, user.identity, [], user.labels));
userList.push(new User(null, user.identity, [], user.labels, user.banInfo));
continue; // Skip deleted users
}
userList.push(new User(user.auth, user.identity, user.tokens, user.labels));
userList.push(new User(user.auth, user.identity, user.tokens, user.labels, user.banInfo));
}
clog.log(`Chargement de ${userList.length} utilisateurs.`);
return userList;
@@ -481,12 +624,14 @@ function loadUsers() {
function saveUsers() {
UserDB.data = userList.map(user => {
return {
const data = {
auth: user.auth,
identity: user.identity,
tokens: user.tokens,
labels: user.labels,
};
if (user.banInfo) data.banInfo = user.banInfo;
return data;
});
UserDB.save()
clog.log(`Sauvegarde de ${userList.length} utilisateurs.`);
@@ -503,9 +648,9 @@ function clearNeedUpdateForUsers() {
}
module.exports = {User}
module.exports = {
addUser,
User,
addUser,
setFullBan,
removeUser,
getUserByToken,
@@ -522,5 +667,6 @@ module.exports = {
refreshAllUserInformation,
updateIdentity,
clearNeedUpdateForUsers,
deleteAccount
deleteAccount,
revokeTokens
};