diff --git a/.gitignore b/.gitignore
index e8f57ff..9dc20d2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -144,4 +144,23 @@ test/
data/
__DEBUG.js
-__TEST.js
\ No newline at end of file
+__TEST.js
+
+# --- YT-DLP / Téléchargements temporaires ---
+# Ignore tous les fichiers contenant "Frag" (votre demande spécifique)
+*Frag*
+
+# Ignore les fichiers partiels classiques (.part, .part-Frag...)
+*.part*
+
+# Ignore les fichiers temporaires de l'ancien format youtube-dl
+*.ytdl
+
+# Ignore les formats temporaires lors du merge (ex: vidéo.f137.webm)
+*.f[0-9]*
+
+# --- SÉCURITÉ (INDISPENSABLE) ---
+# N'oubliez pas d'ignorer votre fichier de cookies pour ne pas le partager !
+cookies.txt
+cookies-*.txt
+tmp
\ No newline at end of file
diff --git a/CHANGELOG.html b/CHANGELOG.html
index ce3d20c..e1d0d82 100644
--- a/CHANGELOG.html
+++ b/CHANGELOG.html
@@ -1,37 +1,52 @@
+
Chopin - Version /*1.4.0*/
+
*_Date de sortie_*: *-06/12/2025-*
+
+ - /#[FIX][BACKEND]#/ Résolution majeure du module Youtube : la lecture de flux est de nouveau pleinement fonctionnelle
+ - /#[FIX][BACKEND]#/ Correction d'un bug de duplication des titres dans l'historique de lecture
+ - /#[FIX][BACKEND]#/ Correctif de sécurité empêchant l'ajout de fichiers personnels dans les playlists partagées
+ - /#[AJOUT][FRONTEND]#/ Ajout de tooltips (infobulles) sur les éléments interactifs pour améliorer l'accessibilité
+ - /#[FIX][FRONTEND]#/ Correction de divers bugs visuels mineurs sur l'interface
+
+
+
+
Chopin - Version /*1.3.0*/
*_Date de sortie_*: *-07/10/2025-*
- - /#[FIX][BACKEND]#/ Fix de l'erreur sur les fichiers médias importés qui ne fonctionnaient pas
- - /#[BACKEND]#/ Migration du serveur vers une nouvelle architecture et Docker (instabilité prévu)
+ - /#[FIX][BACKEND]#/ Correction de l'erreur empêchant la lecture des fichiers médias importés
+ - /#[BACKEND]#/ Migration de l'architecture serveur vers Docker (des instabilités sont à prévoir temporairement)
+
Chopin - Version /*1.2.0*/
*_Date de sortie_*: *-07/09/2025-*
- - /#[AJOUT][FRONTEND]#/ Suggestion de recherche, depuis Youtube
- - /#[FIX][FRONTEND]#/ Optimisation des bouttons, avec une modification du composant
- - /#[AJOUT][DISCORD]#/ Ajout de la sécurité, permettant de restreindre l'utilisation du Bot au Rôle paramétré
+ - /#[AJOUT][FRONTEND]#/ Intégration des suggestions de recherche via YouTube
+ - /#[FIX][FRONTEND]#/ Refonte du composant bouton et optimisation des performances
+ - /#[AJOUT][DISCORD]#/ Implémentation du système de sécurité restreignant l'usage du Bot selon les Rôles configurés
+
Chopin - Version /*1.1.0*/
*_Date de sortie_*: *-06/09/2025-*
- - /#[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
- - /#[AJOUT][FRONTEND] #/ Ajout de la prise en charge de plusieurs fichiers en même temps (Impossibilité sur Discord à cause de restriction) !
- - /#[FIX][BACKEND]#/ La source Youtube est désormais fonctionnelle
- - /#[FIX][BACKEND]#/ Le changement de channel est désormais fonctionnel.
- - /#[FIX][FRONTEND]#/ Résolution d'un problème d'affichage du changelog et de la liste de lecture
- - /#[FIX][FRONTEND]#/ Le "En ligne" est désormais resynchronisé sur le Bot
+ - /#[AJOUT][FRONTEND]#/ Désactivation visuelle (plutôt que masquage) des boutons de lecture pour les utilisateurs non connectés
+ - /#[AJOUT][FRONTEND]#/ Prise en charge de l'upload multi-fichiers (contournement des restrictions Discord)
+ - /#[FIX][BACKEND]#/ Réparation du connecteur de source Youtube
+ - /#[FIX][BACKEND]#/ Le changement de salon vocal est désormais opérationnel
+ - /#[FIX][FRONTEND]#/ Correction des bugs d'affichage sur le changelog et la liste de lecture
+ - /#[FIX][FRONTEND]#/ Resynchronisation correcte du statut "En ligne" du Bot
+
Chopin - Version /*1.0.0*/
*_Date de sortie_*: *-29/08/2025-*
- - /#[FRONTEND]#/ Sortie de la version 1.0.0 de Chopin, le bot Discord pour SubSonics. Refonte graphique et passage sur Vue JS
- - /#[BACKEND]#/ Refonte de toute la gestion de la musique et ajout de nouvelles fonctionnalités
+ - /#[FRONTEND]#/ Lancement officiel de la v1.0.0 de Chopin (Bot pour SubSonics). Refonte graphique complète et migration vers Vue JS
+ - /#[BACKEND]#/ Refonte intégrale du moteur audio et implémentation des nouvelles fonctionnalités
-
+
\ No newline at end of file
diff --git a/TODOS.md b/TODOS.md
index 043c975..42c73fa 100644
--- a/TODOS.md
+++ b/TODOS.md
@@ -1,3 +1,4 @@
# List
TODO: Récupération des recommendations, playlists Youtube et Spotify
+TODO: Faire une interface Admin
\ No newline at end of file
diff --git a/package.json b/package.json
index 2c996cd..05fc2c1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "chopin-backend",
- "version": "1.3.2",
+ "version": "1.4.0",
"description": "Discord Bot for music - Fetching everywhere !",
"main": "src/main.js",
"nodemonConfig": {
diff --git a/src/player/List.js b/src/player/List.js
index 614344c..1bfcc15 100644
--- a/src/player/List.js
+++ b/src/player/List.js
@@ -60,7 +60,6 @@ class List {
}
this.setCurrent(song)
process.emit("PLAYERS_UPDATE")
- //TODO: Check History and continuity
return song
}
diff --git a/src/player/Method/Youtube.js b/src/player/Method/Youtube.js
index ad6a8e8..d889afb 100644
--- a/src/player/Method/Youtube.js
+++ b/src/player/Method/Youtube.js
@@ -2,51 +2,146 @@ const { LogType } = require('loguix');
const clog = new LogType("Youtube-Stream");
const { spawn } = require('child_process');
-async function getStream(song) {
- return new Promise((resolve, reject) => {
- clog.log(`[YT-DLP] Lancement du processus natif pour : ${song.url}`);
+// Variable globale pour stocker le processus actif
+let currentYtProcess = null;
- // On lance yt-dlp directement.
- // ATTENTION : "yt-dlp" doit être reconnu dans ton terminal (installé dans le PATH)
- const yt = spawn('yt-dlp', [
+/**
+ * Tue le processus yt-dlp en cours proprement et attend sa fin réelle.
+ * Cela garantit qu'aucun flux ne se chevauche.
+ */
+function killCurrentProcess() {
+ return new Promise((resolve) => {
+ if (!currentYtProcess || currentYtProcess.exitCode !== null) {
+ currentYtProcess = null;
+ return resolve();
+ }
+
+ const pid = currentYtProcess.pid;
+ clog.log(`[YT-DLP] Nettoyage violent du processus PID: ${pid}`);
+
+ // Détection de l'OS pour utiliser la bonne commande de kill
+ const isWindows = process.platform === 'win32';
+
+ if (isWindows) {
+ // Sur Windows, taskkill /T (Tree) /F (Force) est nécessaire pour tuer les enfants (ffmpeg)
+ try {
+ exec(`taskkill /pid ${pid} /T /F`, (err) => {
+ // Peu importe l'erreur (ex: processus déjà mort), on considère que c'est fini
+ currentYtProcess = null;
+ resolve();
+ });
+ } catch (e) {
+ // Fallback si taskkill échoue
+ try { currentYtProcess.kill('SIGKILL'); } catch (e2) {}
+ currentYtProcess = null;
+ resolve();
+ }
+ } else {
+ // Sur Linux/Mac, on tente de tuer le groupe de processus
+ try {
+ process.kill(-pid, 'SIGKILL');
+ } catch (e) {
+ // Fallback si le group kill échoue
+ try { currentYtProcess.kill('SIGKILL'); } catch (e2) {}
+ }
+ currentYtProcess = null;
+ resolve();
+ }
+ });
+}
+
+/**
+ * @param {Object} song - L'objet contenant l'URL
+ * @param {number} seekTime - Le temps de démarrage en secondes (par défaut 0)
+ */
+async function getStream(song, seekTime = 0) {
+ // ÉTAPE 1 : On s'assure que l'ancien processus est BIEN mort avant de faire quoi que ce soit.
+ await killCurrentProcess();
+
+ return new Promise((resolve, reject) => {
+ clog.log(`[YT-DLP] Lancement pour : ${song.url} (Début: ${seekTime}s)`);
+
+ const ytArgs = [
song.url,
- '-o', '-', // Rediriger le son vers la sortie standard (stdout)
- '-f', 'bestaudio', // Meilleure qualité audio
- '--no-warnings', // Masquer les avertissements
- '--no-check-certificate', // Évite les erreurs SSL courantes
- '--prefer-free-formats' // Préférer Opus/WebM (meilleur pour Discord)
- ]);
+ '-o', '-',
+ '-f', 'bestaudio[ext=webm]/bestaudio[ext=m4a]/bestaudio',
+ // NETWORKS
+ '--force-ipv4', // INDISPENSABLE : Force la connexion via IPv4 (plus stable pour le streaming)
+ '--user-agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', // Masque yt-dlp
+ '--no-warnings',
+ // --- GESTION DE LA RAM & FICHIERS ---
+ '--no-part', // Ne pas créer de fichiers .part
+ '--no-keep-fragments', // Supprimer les fragments immédiatement (s'ils sont créés)
+ '--buffer-size', '16K', // Petit buffer pour forcer le flux continu (évite les pics d'écriture)
+ // --- OPTIONS DIVERS ---
+ '--no-check-certificate',
+ '--prefer-free-formats',
+ '--ignore-config'
+ ];
+
+ // --- GESTION DU TIMECODE (SEEK) ---
+ if (seekTime && seekTime > 0) {
+ // --begin accepte un format "10s", "1m30s" ou juste des secondes.
+ // C'est la méthode la plus fiable pour yt-dlp en streaming.
+ clog.log(`[YT-DLP] Positionnement à ${Math.round(seekTime)} secondes.`);
+ ytArgs.push('--download-sections', `*${Math.round(seekTime)}-inf`);
+ }
+
+ // --- GESTION DES COOKIES ---
+ if (typeof __glob !== 'undefined' && __glob.COOKIES) {
+ // clog.log(`[YT-DLP] Cookies chargés.`);
+ ytArgs.push('--cookies', __glob.COOKIES);
+ ytArgs.push('--no-cache-dir');
+ }
+
+ // Lancement du nouveau processus
+ const yt = spawn('yt-dlp', ytArgs);
+
+ // On met à jour la variable globale tout de suite
+ currentYtProcess = yt;
let errorLogs = "";
- // Capture des erreurs du processus (si yt-dlp râle)
yt.stderr.on('data', (data) => {
- // On ignore les infos de progression [download]
const msg = data.toString();
- if (!msg.includes('[download]')) {
+ console.log(msg);
+ if (!msg.includes('[download]') && !msg.includes('[youtube]')) {
errorLogs += msg;
}
});
- // Gestion des erreurs de lancement (ex: yt-dlp n'est pas installé)
yt.on('error', (err) => {
- clog.error("[YT-DLP] Impossible de lancer la commande. Vérifie que yt-dlp est bien installé sur le PC/Serveur !", err);
+ clog.error("[YT-DLP] Erreur au lancement.", err);
+ // Si c'est ce processus qui est en cours, on le clean
+ if (currentYtProcess === yt) currentYtProcess = null;
reject(err);
});
- // Fin du processus
yt.on('close', (code) => {
- if (code !== 0) {
- clog.warn(`[YT-DLP] Arrêt avec code ${code}. Détails : ${errorLogs}`);
+ // Nettoyage de la référence globale si c'est bien nous
+ if (currentYtProcess === yt) currentYtProcess = null;
+
+ if (code !== 0 && code !== null && code !== 143 && code !== 137) { // 137 = SIGKILL
+ clog.warn(`[YT-DLP] Arrêt code ${code}. Logs : ${errorLogs}`);
}
});
- // Si le flux est créé, on le renvoie immédiatement
if (yt.stdout) {
- clog.log("[YT-DLP] Flux audio capturé avec succès.");
+ // --- SÉCURITÉ ANTI-OVERRIDE SUR LE FLUX ---
+ // Si le stream se ferme (le bot quitte le vocal ou skip), on tue yt-dlp
+ yt.stdout.on('close', () => {
+ if (!yt.killed && currentYtProcess === yt) {
+ yt.kill();
+ }
+ });
+
+ yt.stdout.on('error', () => {
+ if (!yt.killed && currentYtProcess === yt) yt.kill();
+ });
+
resolve(yt.stdout);
} else {
- reject(new Error("Le processus yt-dlp n'a généré aucun flux."));
+ reject(new Error("Aucun flux stdout généré."));
}
});
}
diff --git a/src/player/Player.js b/src/player/Player.js
index 0ddb852..2318f46 100644
--- a/src/player/Player.js
+++ b/src/player/Player.js
@@ -206,13 +206,13 @@ class Player {
plog.log(`GUILD : ${this.guildId} - Lecture de la musique : ${song.title} - Type : ${song.type}`)
}
- async getStream(song) {
+ async getStream(song, duration = 0) {
let stream = null
if(song.type == "attachment") {
stream = await media.getStream(song)
}
if(song.type == 'youtube') {
- stream = await youtube.getStream(song)
+ stream = await youtube.getStream(song, duration)
}
if(song.type == "soundcloud") {
stream = await soundcloud.getStream(song)
@@ -284,18 +284,17 @@ class Player {
async setDuration(duration) {
- //FIXME: SET DURATION FONCTIONNE TRES LENTEMENT
if (this.checkConnection()) return;
if (this.queue.current == null) return;
if (this.currentResource == null) return;
const maxDuration = this.queue.current.duration;
- if (duration > maxDuration) {
+ if (duration.time > maxDuration) {
plog.error(`GUILD : ${this.guildId} - La durée demandée dépasse la durée maximale de la musique.`);
return;
}
- this.stream = await this.getStream(this.queue.current);
+ this.stream = await this.getStream(this.queue.current, duration.time);
if (this.stream === null) {
plog.error(`GUILD : ${this.guildId} - Impossible de lire la musique : ${this.queue.current.title} avec le type : ${this.queue.current.type}`);
return;
@@ -306,24 +305,13 @@ class Player {
if(typeof this.stream === "string") {
this.stream = fs.createReadStream(this.stream)
}
-
- 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
- .on('error', (err) => {
- plog.error(`GUILD : ${this.guildId} - Une erreur est survenue avec ffmpeg : ${err.message}`);
- })
- .pipe(passThroughStream, { end: true });
-
- this.stream = passThroughStream;
-
this.playStream(this.stream); // Jouer le nouveau flux
- this.currentResource.playbackDuration = duration * 1000; // Mettre à jour la durée de lecture du resource
+ this.currentResource.playbackDuration = duration.time * 1000; // Mettre à jour la durée de lecture du resource
+ console.log(this.currentResource.playbackDuration)
+ console.log(this.getDuration())
- plog.log(`GUILD : ${this.guildId} - Lecture déplacée à ${duration}s.`);
+ plog.log(`GUILD : ${this.guildId} - Lecture déplacée à ${duration.time}s.`);
}
diff --git a/src/playlists/History.js b/src/playlists/History.js
index d003be4..1cf041e 100644
--- a/src/playlists/History.js
+++ b/src/playlists/History.js
@@ -28,7 +28,14 @@ function getPersonalHistory(userId) {
*/
function addToPersonalHistory(userId, entry) {
hlog.log(`Ajout d'une entrée à l'historique personnel de l'utilisateur : ${userId}`);
+
+ // Check if there is already the same entry (by ID) and remove it to avoid duplicates
+
const history = getPersonalHistory(userId);
+ const existingIndex = history.findIndex(e => e.id === entry.id);
+ if (existingIndex !== -1) {
+ history.splice(existingIndex, 1);
+ }
// Limit to 25 entries
if (history.length >= 25) {
history.shift();
diff --git a/src/utils/GlobalVars.js b/src/utils/GlobalVars.js
index c04a589..9d3f48d 100644
--- a/src/utils/GlobalVars.js
+++ b/src/utils/GlobalVars.js
@@ -18,7 +18,7 @@ const __glob = {
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",
+ COOKIES: root + path.sep + "data" + path.sep + "cookies.txt",
PROXY: root + path.sep + "data" + path.sep + "proxy.json"
}