mirror of
https://github.com/MasterAcnolo/Freedom-Loader.git
synced 2026-07-29 18:25:47 +02:00
Using MVC Model + AutoDeteck Package + Fix Download/Info
This commit is contained in:
@@ -1,107 +0,0 @@
|
||||
const { execFile } = require("child_process");
|
||||
const express = require("express");
|
||||
const router = express.Router();
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
|
||||
const { logger } = require("../logger");
|
||||
const { isValidUrl, isSafePath } = require("../helpers/validation");
|
||||
const { buildYtDlpArgs } = require("../helpers/buildArgs");
|
||||
const { notifyDownloadFinished } = require("../helpers/notify");
|
||||
const { userYtDlp } = require("../helpers/path");
|
||||
|
||||
const listeners = [];
|
||||
|
||||
router.post("/", (req, res) => {
|
||||
try {
|
||||
const options = {
|
||||
url: req.body.url,
|
||||
audioOnly: req.body.audioOnly === "1",
|
||||
quality: req.body.quality || "best",
|
||||
outputFolder: req.body.savePath || path.join(process.env.USERPROFILE, "Downloads", "Freedom Loader"),
|
||||
};
|
||||
|
||||
// Vérifications
|
||||
if (!options.url || !isValidUrl(options.url)) return res.status(400).send("❌ URL invalide !");
|
||||
if (!isSafePath(options.outputFolder)) return res.status(400).send("❌ Chemin de sauvegarde non autorisé.");
|
||||
|
||||
// Création dossier si inexistant
|
||||
fs.mkdirSync(options.outputFolder, { recursive: true });
|
||||
|
||||
// Construire les arguments yt-dlp
|
||||
const args = buildYtDlpArgs(options);
|
||||
logger.info(`[yt-dlp args] ${args.join(" ")}`);
|
||||
|
||||
const child = execFile(userYtDlp, args);
|
||||
|
||||
// Gestion erreurs
|
||||
child.on("error", err => {
|
||||
logger.error(`Erreur yt-dlp : ${err.message}`);
|
||||
res.status(500).send(`❌ Erreur yt-dlp : ${err.message}`);
|
||||
});
|
||||
|
||||
// Téléchargement terminé
|
||||
child.on("close", code => {
|
||||
if (code === 0) {
|
||||
notifyDownloadFinished(options.outputFolder);
|
||||
|
||||
// signal SSE fin de playlist
|
||||
listeners.forEach(fn => fn("done"));
|
||||
|
||||
res.send("✅ Téléchargement terminé !");
|
||||
} else {
|
||||
res.status(500).send(`❌ yt-dlp a échoué avec le code : ${code}`);
|
||||
listeners.forEach(fn => fn("done"));
|
||||
}
|
||||
});
|
||||
|
||||
child.stdout.on("data", data => {
|
||||
data.toString().split("\n").forEach(line => line.trim() && logger.info(`[yt-dlp stdout] ${line}`));
|
||||
});
|
||||
|
||||
child.stderr.on("data", data => {
|
||||
data.toString().split("\n").forEach(line => line.trim() && logger.error(`[yt-dlp stderr] ${line}`));
|
||||
});
|
||||
|
||||
|
||||
child.stdout.on("data", data => {
|
||||
const lines = data.toString().split("\n");
|
||||
lines.forEach(line => {
|
||||
// reset progress si nouveau fichier
|
||||
if (line.startsWith("[download] Destination:")) {
|
||||
listeners.forEach(fn => fn("reset")); // tu peux envoyer un signal spécial
|
||||
}
|
||||
|
||||
// envoyer le % classique
|
||||
const match = line.match(/\[download\]\s+(\d+\.\d+)%/);
|
||||
if (match) {
|
||||
const percent = parseFloat(match[1]);
|
||||
listeners.forEach(fn => fn(percent));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
logger.error(`Erreur serveur dans /download : ${err.message}`);
|
||||
res.status(500).send(`Erreur serveur : ${err.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
// SSE endpoint
|
||||
router.get("/progress", (req, res) => {
|
||||
res.set({
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Connection': 'keep-alive',
|
||||
});
|
||||
|
||||
const sendProgress = percent => res.write(`data: ${percent}\n\n`);
|
||||
listeners.push(sendProgress);
|
||||
|
||||
req.on('close', () => {
|
||||
listeners.splice(listeners.indexOf(sendProgress), 1);
|
||||
res.end();
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
40
server/routes/download.route.js
Normal file
40
server/routes/download.route.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const express = require("express");
|
||||
const router = express.Router();
|
||||
const { fetchDownload } = require("../services/download.services");
|
||||
|
||||
const listeners = [];
|
||||
|
||||
router.post("/", async (req, res) => {
|
||||
const options = {
|
||||
url: req.body.url,
|
||||
audioOnly: req.body.audioOnly === "1",
|
||||
quality: req.body.quality || "best",
|
||||
outputFolder: req.body.savePath || null
|
||||
};
|
||||
|
||||
try {
|
||||
await fetchDownload(options, listeners);
|
||||
res.send(`✅ Téléchargement terminé`);
|
||||
} catch (err) {
|
||||
res.status(500).send(`❌ ${err.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
// SSE pour la barre de progression
|
||||
router.get("/progress", (req, res) => {
|
||||
res.set({
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Connection': 'keep-alive',
|
||||
});
|
||||
|
||||
const sendProgress = percent => res.write(`data: ${percent}\n\n`);
|
||||
listeners.push(sendProgress);
|
||||
|
||||
req.on("close", () => {
|
||||
listeners.splice(listeners.indexOf(sendProgress), 1);
|
||||
res.end();
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,101 +0,0 @@
|
||||
const express = require("express");
|
||||
const router = express.Router();
|
||||
const { execFile } = require("child_process");
|
||||
const { logger } = require("../logger");
|
||||
const { userYtDlp } = require("../helpers/path");
|
||||
const getUserBrowser = require("../helpers/getBrowser");
|
||||
const path = require("path");
|
||||
|
||||
function isValidUrl(url) {
|
||||
try {
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
router.post("/", (req, res) => {
|
||||
const { url } = req.body;
|
||||
if (!url || !isValidUrl(url)) return res.status(400).send("❌ URL invalide ou manquante");
|
||||
|
||||
logger.info(`Requête /info reçue pour ${url}`);
|
||||
|
||||
const args = [
|
||||
"--no-js-runtimes",
|
||||
"--js-runtimes",
|
||||
`deno:${path.join(process.resourcesPath, "deno.exe")}`,
|
||||
"--dump-single-json",
|
||||
"--ignore-errors",
|
||||
"--yes-playlist",
|
||||
"--cookies-from-browser",
|
||||
`${getUserBrowser()}`,
|
||||
"--extractor-args",
|
||||
"youtube:player_client=default",
|
||||
"--ignore-no-formats-error",
|
||||
url,
|
||||
];
|
||||
console.log("JS RUNTIME:", `deno:${path.join(process.resourcesPath, "deno.exe")}`);
|
||||
|
||||
execFile(userYtDlp, args, { timeout: 30_000, maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
|
||||
|
||||
if (stderr) {
|
||||
const lower = stderr.toLowerCase();
|
||||
if (lower.includes("sign in to confirm") || lower.includes("failed to decrypt") || lower.includes("could not copy")) {
|
||||
const browser = getUserBrowser();
|
||||
return res.status(400).send(
|
||||
`❌ Impossible d'extraire les cookies depuis ${browser}. Connectez-vous dans ce navigateur et réessayez.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
logger.error(`Erreur yt-dlp: ${error.message}`);
|
||||
if (stderr) logger.debug(`stderr: ${stderr}`);
|
||||
return res.status(500).send("❌ Impossible de récupérer les infos.");
|
||||
}
|
||||
|
||||
if (!stdout) return res.status(500).send("❌ Aucune donnée reçue.");
|
||||
|
||||
try {
|
||||
const data = JSON.parse(stdout);
|
||||
|
||||
if (data._type === "playlist") {
|
||||
const playlistPayload = {
|
||||
type: "playlist",
|
||||
title: data.title || data.id,
|
||||
channel: data.uploader,
|
||||
count: data.entries.length,
|
||||
videos: (data.entries || []).map(v => ({
|
||||
id: v.id,
|
||||
title: v.title,
|
||||
url: v.webpage_url,
|
||||
duration: v.duration,
|
||||
thumbnail: v.thumbnail,
|
||||
uploader: v.uploader
|
||||
}))
|
||||
};
|
||||
logger.info(`Playlist détectée : ${playlistPayload.title} (${playlistPayload.count} vidéos)`);
|
||||
return res.json(playlistPayload);
|
||||
}
|
||||
|
||||
// Vidéo unique
|
||||
if (!data.title) {
|
||||
return res.status(500).send("❌ Impossible de récupérer les infos pour cette vidéo.");
|
||||
|
||||
}
|
||||
|
||||
logger.info(`Vidéo unique récupérée : ${data.title}`);
|
||||
res.json({ type: "video", ...data });
|
||||
|
||||
} catch (e) {
|
||||
// fallback aussi si JSON est illisible
|
||||
logger.error(`Erreur parsing JSON: ${e.message}`);
|
||||
return res.status(500).send("❌ Impossible de récupérer les infos (JSON illisible).");
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
7
server/routes/info.route.js
Normal file
7
server/routes/info.route.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const express = require("express");
|
||||
const router = express.Router();
|
||||
const { infoController } = require("../controller/info.controller");
|
||||
|
||||
router.post("/", infoController);
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user