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 });
|
||||
if (info.streams?.[0]?.duration_ts) {
|
||||
instance.duration = info.streams[0].duration;
|
||||
instance.readduration = getReadableDuration(instance.duration)
|
||||
// 1. Vérifier si ./tmp existe, sinon le créer
|
||||
const tmpDir = path.resolve("./tmp");
|
||||
if (!fs.existsSync(tmpDir)) {
|
||||
fs.mkdirSync(tmpDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Vérification pour éviter une erreur si `streams[0]` ou `tags` n'existe pas
|
||||
instance.thumbnail = info.streams?.[0]?.tags?.thumbnail ??
|
||||
"https://radomisol.fr/wp-content/uploads/2016/08/cropped-note-radomisol-musique.png";
|
||||
// 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}`);
|
||||
|
||||
// Obtenir le titre (sinon utiliser le nom du fichier)
|
||||
instance.title = info.streams?.[0]?.tags?.title ?? media.attachment.name;
|
||||
const buffer = Buffer.from(await res.arrayBuffer());
|
||||
tmpFile = path.join(tmpDir, `${Date.now()}-${media.attachment.name}`);
|
||||
fs.writeFileSync(tmpFile, buffer);
|
||||
|
||||
// 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)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
|
||||
instance.thumbnail = info.streams?.[0]?.tags?.thumbnail ??
|
||||
"https://radomisol.fr/wp-content/uploads/2016/08/cropped-note-radomisol-musique.png";
|
||||
instance.title = info.streams?.[0]?.tags?.title ?? media.attachment.name;
|
||||
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);
|
||||
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 {
|
||||
// 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 });
|
||||
|
||||
async function getMediaInformationFromUrl(instance, url) {
|
||||
try {
|
||||
const info = await ffprobe(url, { path: ffprobeStatic.path });
|
||||
if (info.streams?.[0]?.duration_ts) {
|
||||
instance.duration = info.streams[0].duration;
|
||||
instance.readduration = getReadableDuration(instance.duration);
|
||||
}
|
||||
|
||||
// Vérification pour éviter une erreur si `streams[0]` ou `tags` n'existe pas
|
||||
instance.thumbnail = info.streams?.[0]?.tags?.thumbnail ??
|
||||
"https://radomisol.fr/wp-content/uploads/2016/08/cropped-note-radomisol-musique.png";
|
||||
|
||||
// Obtenir le titre (sinon utiliser le nom du fichier)
|
||||
instance.title = info.streams?.[0]?.tags?.title ?? "Titre inconnu";
|
||||
|
||||
// Obtenir l'auteur (s'il existe)
|
||||
instance.author = info.streams?.[0]?.tags?.artist ?? "Auteur inconnu";
|
||||
|
||||
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) {
|
||||
|
||||
// FIXME: Change youtube provider
|
||||
|
||||
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);
|
||||
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}`);
|
||||
|
||||
// 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 })
|
||||
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,
|
||||
});
|
||||
|
||||
// Récupérer les infos vidéo
|
||||
const videoInfo = await youtube.getInfo(song.id);
|
||||
return stream
|
||||
|
||||
|
||||
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);
|
||||
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