Compare commits
	
		
			7 Commits
		
	
	
		
			0f0f263c98
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| a0cfe9ef2b | |||
| 
						
						
							
						
						5d10c258c8
	
				 | 
					
					
						|||
| 
						
						
							
						
						ccb73807c0
	
				 | 
					
					
						|||
| 
						
						
							
						
						adfa2b1710
	
				 | 
					
					
						|||
| 
						
						
							
						
						dc072f36bd
	
				 | 
					
					
						|||
| 
						
						
							
						
						1a25e26829
	
				 | 
					
					
						|||
| 
						
						
							
						
						4340d876a3
	
				 | 
					
					
						
@@ -1,86 +0,0 @@
 | 
			
		||||
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
 | 
			
		||||
@@ -1,4 +1,12 @@
 | 
			
		||||
<div class="changelog-version changelog-actual">
 | 
			
		||||
    <h2>Chopin - Version /*1.3.0*/</h2>
 | 
			
		||||
    <p class="changelog-date">*_Date de sortie_*: *-07/10/2025-*</p>
 | 
			
		||||
    <ul>
 | 
			
		||||
        <li>/#[FIX][BACKEND]#/ Fix de l'erreur sur les fichiers médias importés qui ne fonctionnaient pas</li>
 | 
			
		||||
        <li>/#[BACKEND]#/ Migration du serveur vers une nouvelle architecture et Docker (instabilité prévu)</li>
 | 
			
		||||
    </ul>
 | 
			
		||||
</div>  
 | 
			
		||||
<div class="changelog-version">
 | 
			
		||||
    <h2>Chopin - Version /*1.2.0*/</h2>
 | 
			
		||||
    <p class="changelog-date">*_Date de sortie_*: *-07/09/2025-*</p>
 | 
			
		||||
    <ul>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								Dockerfile
									
									
									
									
									
								
							@@ -1,19 +1,14 @@
 | 
			
		||||
FROM node:lts-alpine
 | 
			
		||||
 | 
			
		||||
# Crée un dossier de travail
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
 | 
			
		||||
# Copie package.json et package-lock.json
 | 
			
		||||
COPY package*.json ./
 | 
			
		||||
# Installer bash et autres outils utiles
 | 
			
		||||
RUN apk add --no-cache bash
 | 
			
		||||
 | 
			
		||||
# Installe les dépendances
 | 
			
		||||
COPY package*.json ./
 | 
			
		||||
RUN npm install
 | 
			
		||||
 | 
			
		||||
# Copie le code source (mais pas le dossier data, qui sera monté en volume)
 | 
			
		||||
COPY . .
 | 
			
		||||
 | 
			
		||||
# Expose le port backend
 | 
			
		||||
EXPOSE 4000
 | 
			
		||||
 | 
			
		||||
# Commande de démarrage
 | 
			
		||||
CMD ["node", "src/utils/main.js"]
 | 
			
		||||
CMD ["npm", "run", "start"]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "chopin-backend",
 | 
			
		||||
  "version": "1.3.0",
 | 
			
		||||
  "version": "1.3.1",
 | 
			
		||||
  "description": "Discord Bot for music - Fetching everywhere !",
 | 
			
		||||
  "main": "src/main.js",
 | 
			
		||||
  "nodemonConfig": {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,56 +2,105 @@ const ffprobe = require('ffprobe');
 | 
			
		||||
const ffprobeStatic = require('ffprobe-static');
 | 
			
		||||
const { getReadableDuration } = require('../utils/TimeConverter');
 | 
			
		||||
const clog = require("loguix").getInstance("Song")
 | 
			
		||||
 | 
			
		||||
const path = require('path');
 | 
			
		||||
const fs = require('fs');
 | 
			
		||||
 | 
			
		||||
async function getMediaInformation(instance, media, provider) {
 | 
			
		||||
    let tmpFile = null;
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        const info = await ffprobe(media.attachment.url, { path: ffprobeStatic.path });
 | 
			
		||||
        // 1. Vérifier si ./tmp existe, sinon le créer
 | 
			
		||||
        const tmpDir = path.resolve("./tmp");
 | 
			
		||||
        if (!fs.existsSync(tmpDir)) {
 | 
			
		||||
            fs.mkdirSync(tmpDir, { recursive: true });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 2. Télécharger le fichier depuis l'URL
 | 
			
		||||
        const url = media.attachment.url;
 | 
			
		||||
        const res = await fetch(url);
 | 
			
		||||
        if (!res.ok) throw new Error(`Erreur HTTP ${res.status}`);
 | 
			
		||||
 | 
			
		||||
        const buffer = Buffer.from(await res.arrayBuffer());
 | 
			
		||||
        tmpFile = path.join(tmpDir, `${Date.now()}-${media.attachment.name}`);
 | 
			
		||||
        fs.writeFileSync(tmpFile, buffer);
 | 
			
		||||
 | 
			
		||||
        // 3. Lancer ffprobe sur le fichier local
 | 
			
		||||
        const info = await ffprobe(tmpFile, { path: ffprobeStatic.path });
 | 
			
		||||
 | 
			
		||||
        // 4. Récupérer les informations
 | 
			
		||||
        if (info.streams?.[0]?.duration_ts) {
 | 
			
		||||
            instance.duration = info.streams[0].duration;
 | 
			
		||||
            instance.readduration = getReadableDuration(instance.duration)
 | 
			
		||||
        } 
 | 
			
		||||
        
 | 
			
		||||
        // Vérification pour éviter une erreur si `streams[0]` ou `tags` n'existe pas
 | 
			
		||||
        instance.thumbnail = info.streams?.[0]?.tags?.thumbnail ?? 
 | 
			
		||||
            instance.readduration = getReadableDuration(instance.duration);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        instance.thumbnail = info.streams?.[0]?.tags?.thumbnail ??
 | 
			
		||||
            "https://radomisol.fr/wp-content/uploads/2016/08/cropped-note-radomisol-musique.png";
 | 
			
		||||
        
 | 
			
		||||
        // Obtenir le titre (sinon utiliser le nom du fichier)
 | 
			
		||||
        instance.title = info.streams?.[0]?.tags?.title ?? media.attachment.name;
 | 
			
		||||
        
 | 
			
		||||
        // Obtenir l'auteur (s'il existe)
 | 
			
		||||
        instance.author = info.streams?.[0]?.tags?.artist ?? instance.author;
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
        clog.error("Impossible de récupérer les informations de la musique : " + media.attachment.name)
 | 
			
		||||
        clog.error(err)
 | 
			
		||||
        clog.error("Impossible de récupérer les informations de la musique : " + media.attachment.name);
 | 
			
		||||
        console.error(err);
 | 
			
		||||
        return null;
 | 
			
		||||
    } finally {
 | 
			
		||||
        // 5. Nettoyage : supprimer le fichier temporaire
 | 
			
		||||
        if (tmpFile && fs.existsSync(tmpFile)) {
 | 
			
		||||
            try {
 | 
			
		||||
                fs.unlinkSync(tmpFile);
 | 
			
		||||
            } catch (e) {
 | 
			
		||||
                console.error("Erreur lors du unlink:", e);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function getMediaInformationFromUrl(instance, url) {
 | 
			
		||||
    let tmpFile = null;
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        const info = await ffprobe(url, { path: ffprobeStatic.path });
 | 
			
		||||
        // 1. Vérifier si ./tmp existe, sinon le créer
 | 
			
		||||
        const tmpDir = path.resolve("./tmp");
 | 
			
		||||
        if (!fs.existsSync(tmpDir)) {
 | 
			
		||||
            fs.mkdirSync(tmpDir, { recursive: true });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 2. Télécharger le fichier en mémoire et l’écrire en sync
 | 
			
		||||
        const res = await fetch(url);
 | 
			
		||||
        if (!res.ok) throw new Error(`Erreur HTTP ${res.status}`);
 | 
			
		||||
 | 
			
		||||
        const buffer = Buffer.from(await res.arrayBuffer());
 | 
			
		||||
        tmpFile = path.join(tmpDir, `${Date.now()}.mp3`);
 | 
			
		||||
        fs.writeFileSync(tmpFile, buffer);
 | 
			
		||||
 | 
			
		||||
        // 3. Lancer ffprobe sur le fichier local
 | 
			
		||||
        const info = await ffprobe(tmpFile, { path: ffprobeStatic.path });
 | 
			
		||||
 | 
			
		||||
        if (info.streams?.[0]?.duration_ts) {
 | 
			
		||||
            instance.duration = info.streams[0].duration;
 | 
			
		||||
            instance.readduration = getReadableDuration(instance.duration);
 | 
			
		||||
        } 
 | 
			
		||||
        
 | 
			
		||||
        // Vérification pour éviter une erreur si `streams[0]` ou `tags` n'existe pas
 | 
			
		||||
        instance.thumbnail = info.streams?.[0]?.tags?.thumbnail ?? 
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        instance.thumbnail = info.streams?.[0]?.tags?.thumbnail ??
 | 
			
		||||
            "https://radomisol.fr/wp-content/uploads/2016/08/cropped-note-radomisol-musique.png";
 | 
			
		||||
        
 | 
			
		||||
        // Obtenir le titre (sinon utiliser le nom du fichier)
 | 
			
		||||
 | 
			
		||||
        instance.title = info.streams?.[0]?.tags?.title ?? "Titre inconnu";
 | 
			
		||||
        
 | 
			
		||||
        // Obtenir l'auteur (s'il existe)
 | 
			
		||||
        instance.author = info.streams?.[0]?.tags?.artist ?? "Auteur inconnu";
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
        clog.error("Impossible de récupérer les informations de la musique depuis l'URL : " + url);
 | 
			
		||||
        console.log(err)
 | 
			
		||||
        clog.error(err);
 | 
			
		||||
        console.error(err);
 | 
			
		||||
        return null;
 | 
			
		||||
    } finally {
 | 
			
		||||
        // 4. Nettoyage : supprimer le fichier temporaire
 | 
			
		||||
        if (tmpFile && fs.existsSync(tmpFile)) {
 | 
			
		||||
            try {
 | 
			
		||||
                fs.unlinkSync(tmpFile);
 | 
			
		||||
            } catch (e) {
 | 
			
		||||
                console.error("Erreur lors du unlink:", e);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,10 +6,8 @@ const ffmpeg = require('fluent-ffmpeg')
 | 
			
		||||
 | 
			
		||||
async function getStream(song) {
 | 
			
		||||
       try {
 | 
			
		||||
          
 | 
			
		||||
        return song.url;
 | 
			
		||||
           
 | 
			
		||||
 | 
			
		||||
           const stream = await fetch(song.url).then(res => res.body);
 | 
			
		||||
           return stream;
 | 
			
		||||
       } catch(e) {
 | 
			
		||||
            clog.error("Erreur lors de la lecture de la musique : " + song.title)
 | 
			
		||||
            clog.error(e)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,91 +1,46 @@
 | 
			
		||||
const { LogType } = require('loguix');
 | 
			
		||||
const clog = new LogType("Youtube-Stream");
 | 
			
		||||
const {createAudioResource, VoiceConnectionStatus, createAudioPlayer, StreamType} = require('@discordjs/voice');
 | 
			
		||||
const {LogType} = require('loguix')
 | 
			
		||||
const clog = new LogType("Youtube-Stream")
 | 
			
		||||
const ytdl = require('@distube/ytdl-core')
 | 
			
		||||
const { __glob } = require('../../utils/GlobalVars');
 | 
			
		||||
const { Innertube, UniversalCache, ClientType } = require('youtubei.js');
 | 
			
		||||
const fs = require('fs');
 | 
			
		||||
const { ProxyAgent } = require('undici');
 | 
			
		||||
 | 
			
		||||
async function getStream(song) {
 | 
			
		||||
    try {
 | 
			
		||||
        // Lire et formater les cookies comme chaîne
 | 
			
		||||
        const cookiesArr = JSON.parse(fs.readFileSync(__glob.COOKIES, 'utf-8'));
 | 
			
		||||
        const cookieStr = cookiesArr.map(cookie => `${cookie.name}=${cookie.value}`).join('; ');
 | 
			
		||||
 | 
			
		||||
        // Lire et préparer le proxy
 | 
			
		||||
        const proxy = JSON.parse(fs.readFileSync(__glob.PROXY, 'utf-8'));
 | 
			
		||||
        const proxyAgent = new ProxyAgent(proxy.uri);
 | 
			
		||||
    //  FIXME: Change youtube provider
 | 
			
		||||
    
 | 
			
		||||
       try {
 | 
			
		||||
 | 
			
		||||
        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'
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        console.log(`Tentative de récupération pour: ${song.title || song.url}`);
 | 
			
		||||
         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,
 | 
			
		||||
          });
 | 
			
		||||
 | 
			
		||||
        // Création de l'instance Innertube avec les bons paramètres
 | 
			
		||||
        const youtube = await Innertube.create({
 | 
			
		||||
            cookie: cookieStr,
 | 
			
		||||
            player_id: '0004de42',
 | 
			
		||||
            user_agent: `Mozilla/5.0 (Macintosh; Intel Mac OS X 15_0) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15`,
 | 
			
		||||
            client_type: ClientType.WEB,
 | 
			
		||||
            retrieve_player: true,
 | 
			
		||||
            device_category: 'desktop',
 | 
			
		||||
            enable_session_cache: true,
 | 
			
		||||
            generate_session_locally: true,
 | 
			
		||||
            // fetch: (url, options) => fetch(url, { ...options, agent: proxyAgent })
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Récupérer les infos vidéo
 | 
			
		||||
        const videoInfo = await youtube.getInfo(song.id);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        if (!videoInfo) {
 | 
			
		||||
            throw new Error('Impossible de récupérer les informations de la vidéo');
 | 
			
		||||
        }
 | 
			
		||||
        console.log('Informations vidéo récupérées:', videoInfo.basic_info?.title);
 | 
			
		||||
 | 
			
		||||
        // Vérifier la disponibilité de la lecture
 | 
			
		||||
        if (videoInfo.playability_status?.status !== 'OK') {
 | 
			
		||||
            console.log('Statut de lecture:', videoInfo.playability_status?.status);
 | 
			
		||||
            console.log('Raison:', videoInfo.playability_status?.reason);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Recherche des formats audio adaptatifs
 | 
			
		||||
        let audioFormats = videoInfo.streaming_data?.adaptive_formats?.filter(
 | 
			
		||||
            format => format.mime_type?.includes('audio')
 | 
			
		||||
        ) || [];
 | 
			
		||||
 | 
			
		||||
        if (audioFormats.length === 0) {
 | 
			
		||||
            // Si pas de formats adaptatifs, chercher dans les formats classiques
 | 
			
		||||
            const basicFormats = videoInfo.streaming_data?.formats?.filter(
 | 
			
		||||
                format => format.mime_type?.includes('audio')
 | 
			
		||||
            ) || [];
 | 
			
		||||
            if (basicFormats.length === 0) {
 | 
			
		||||
                throw new Error('Aucun format audio trouvé');
 | 
			
		||||
            }
 | 
			
		||||
            audioFormats.push(...basicFormats);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Sélection du format audio de meilleure qualité
 | 
			
		||||
        const bestAudio = audioFormats.reduce((prev, current) =>
 | 
			
		||||
            (prev.bitrate || 0) > (current.bitrate || 0) ? prev : current
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        console.log('Format sélectionné:', bestAudio.mime_type, bestAudio.bitrate);
 | 
			
		||||
 | 
			
		||||
        // Télécharger le stream audio
 | 
			
		||||
        const stream = await videoInfo.download(bestAudio.itag, {
 | 
			
		||||
            // fetch via ton proxy si nécessaire
 | 
			
		||||
            headers: {
 | 
			
		||||
                'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 15_0) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15',
 | 
			
		||||
                'Cookie': cookieStr
 | 
			
		||||
            }
 | 
			
		||||
          
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        console.log(stream)
 | 
			
		||||
 | 
			
		||||
        return stream; // c’est un ReadableStream prêt à être pipé
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
        clog.error("Erreur lors de la récupération du stream : " + (song.title || song.url));
 | 
			
		||||
        clog.error('Détails:', e.message);
 | 
			
		||||
        return stream
 | 
			
		||||
    
 | 
			
		||||
    } catch(e) {
 | 
			
		||||
        clog.error("Erreur lors de la récupération du stream : " + song.title)
 | 
			
		||||
        clog.error(e)
 | 
			
		||||
               
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = { getStream };
 | 
			
		||||
 | 
			
		||||
module.exports = {getStream}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user