mirror of
https://github.com/MasterAcnolo/Freedom-Loader.git
synced 2026-09-27 20:11:15 +02:00
feat: centralize isPlaylistUrl function as a helper that could be upgraded in the future
This commit is contained in:
@@ -2,6 +2,7 @@ const { fetchInfo } = require("../services/info.services");
|
|||||||
const { parseVideo, parsePlaylist } = require("../helpers/parseInfo.helpers");
|
const { parseVideo, parsePlaylist } = require("../helpers/parseInfo.helpers");
|
||||||
const { logger } = require("../logger");
|
const { logger } = require("../logger");
|
||||||
const { isValidUrl } = require("../helpers/validation.helpers");
|
const { isValidUrl } = require("../helpers/validation.helpers");
|
||||||
|
const {isUrlPlaylist} = require("../helpers/playlist.helpers");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles metadata retrieval for a video or playlist.
|
* Handles metadata retrieval for a video or playlist.
|
||||||
@@ -36,7 +37,7 @@ async function infoController(req, res) {
|
|||||||
logger.info(`Info request received. ENCODED: ${encodedUrl}`);
|
logger.info(`Info request received. ENCODED: ${encodedUrl}`);
|
||||||
|
|
||||||
// Lightweight heuristic to detect playlist URLs. It works for Youtube.
|
// Lightweight heuristic to detect playlist URLs. It works for Youtube.
|
||||||
const isPlaylistUrl = url.includes("&list") || url.includes("?list");
|
const isPlaylistUrl = isUrlPlaylist(url);
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
isPlaylistUrl
|
isPlaylistUrl
|
||||||
|
|||||||
12
server/helpers/playlist.helpers.js
Normal file
12
server/helpers/playlist.helpers.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Determines whether a URL targets a playlist.
|
||||||
|
*
|
||||||
|
* The detection is based on the presence of the YouTube
|
||||||
|
* playlist query parameter (`list`).
|
||||||
|
*
|
||||||
|
* @param {string} url - URL to inspect.
|
||||||
|
* @returns {boolean} True if the URL appears to be a playlist.
|
||||||
|
*/
|
||||||
|
export function isUrlPlaylist(url) {
|
||||||
|
return url.includes("?list=") || url.includes("&list=") || url.includes("@");
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ const notify = require("../helpers/notify.helpers");
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
const { isSafePath } = require("../helpers/validation.helpers");
|
const { isSafePath } = require("../helpers/validation.helpers");
|
||||||
const {reloadFeatures} = require("../../config");
|
const {reloadFeatures} = require("../../config");
|
||||||
|
const {isUrlPlaylist} = require("../helpers/playlist.helpers");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reference to the currently running yt-dlp process.
|
* Reference to the currently running yt-dlp process.
|
||||||
@@ -18,19 +19,6 @@ const {reloadFeatures} = require("../../config");
|
|||||||
*/
|
*/
|
||||||
let currentDownloadProcess = null;
|
let currentDownloadProcess = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether a URL targets a playlist.
|
|
||||||
*
|
|
||||||
* The detection is based on the presence of the YouTube
|
|
||||||
* playlist query parameter (`list`).
|
|
||||||
*
|
|
||||||
* @param {string} url - URL to inspect.
|
|
||||||
* @returns {boolean} True if the URL appears to be a playlist.
|
|
||||||
*/
|
|
||||||
function isPlaylistUrl(url) {
|
|
||||||
return url.includes("?list=") || url.includes("&list=");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a dedicated download folder for a playlist.
|
* Creates a dedicated download folder for a playlist.
|
||||||
*
|
*
|
||||||
@@ -49,23 +37,23 @@ function isPlaylistUrl(url) {
|
|||||||
*/
|
*/
|
||||||
function createPlaylistFolder(basePath, playlistTitle) {
|
function createPlaylistFolder(basePath, playlistTitle) {
|
||||||
const sanitizedTitle = playlistTitle
|
const sanitizedTitle = playlistTitle
|
||||||
.replace(/[<>:"|?*]/g, '')
|
.replace(/[<>:"|?*]/g, '')
|
||||||
.replace(/\s+/g, ' ')
|
.replace(/\s+/g, ' ')
|
||||||
.trim()
|
.trim()
|
||||||
.substring(0, 200);
|
.substring(0, 200);
|
||||||
|
|
||||||
const playlistPath = path.join(basePath, sanitizedTitle);
|
const playlistPath = path.join(basePath, sanitizedTitle);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!fs.existsSync(playlistPath)) {
|
if (!fs.existsSync(playlistPath)) {
|
||||||
fs.mkdirSync(playlistPath, { recursive: true });
|
fs.mkdirSync(playlistPath, { recursive: true });
|
||||||
logger.info(`Playlist folder created: ${playlistPath}`);
|
logger.info(`Playlist folder created: ${playlistPath}`);
|
||||||
return playlistPath;
|
return playlistPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
let counter = 1;
|
let counter = 1;
|
||||||
let newPath;
|
let newPath;
|
||||||
|
|
||||||
while (counter <= 1000) {
|
while (counter <= 1000) {
|
||||||
newPath = path.join(basePath, `${sanitizedTitle} ${counter}`);
|
newPath = path.join(basePath, `${sanitizedTitle} ${counter}`);
|
||||||
if (!fs.existsSync(newPath)) {
|
if (!fs.existsSync(newPath)) {
|
||||||
@@ -75,17 +63,17 @@ function createPlaylistFolder(basePath, playlistTitle) {
|
|||||||
}
|
}
|
||||||
counter++;
|
counter++;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.error(`Could not find available playlist folder after 1000 attempts`);
|
logger.error(`Could not find available playlist folder after 1000 attempts`);
|
||||||
throw new Error("Unable to create playlist folder");
|
throw new Error("Unable to create playlist folder");
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.warn(`Failed to create playlist folder with title "${sanitizedTitle}": ${err.message}`);
|
logger.warn(`Failed to create playlist folder with title "${sanitizedTitle}": ${err.message}`);
|
||||||
|
|
||||||
// Fallback: create folder "Untitled Playlist X"
|
// Fallback: create folder "Untitled Playlist X"
|
||||||
let counter = 1;
|
let counter = 1;
|
||||||
let fallbackPath;
|
let fallbackPath;
|
||||||
|
|
||||||
while (counter <= 1000) {
|
while (counter <= 1000) {
|
||||||
fallbackPath = path.join(basePath, `Untitled Playlist ${counter}`);
|
fallbackPath = path.join(basePath, `Untitled Playlist ${counter}`);
|
||||||
if (!fs.existsSync(fallbackPath)) {
|
if (!fs.existsSync(fallbackPath)) {
|
||||||
@@ -100,7 +88,7 @@ function createPlaylistFolder(basePath, playlistTitle) {
|
|||||||
}
|
}
|
||||||
counter++;
|
counter++;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.error(`Could not create playlist folder after 1000 attempts`);
|
logger.error(`Could not create playlist folder after 1000 attempts`);
|
||||||
throw new Error("Unable to create playlist folder");
|
throw new Error("Unable to create playlist folder");
|
||||||
}
|
}
|
||||||
@@ -139,17 +127,17 @@ function fetchDownload(options, progressListeners, speedListeners, stageListener
|
|||||||
const userConfig = reloadFeatures();
|
const userConfig = reloadFeatures();
|
||||||
|
|
||||||
logger.info(`CONFIG createPlaylistFolders: ${userConfig.createPlaylistFolders}`);
|
logger.info(`CONFIG createPlaylistFolders: ${userConfig.createPlaylistFolders}`);
|
||||||
|
|
||||||
let outputFolder = options.outputFolder || defaultDownloadFolder;
|
let outputFolder = options.outputFolder || defaultDownloadFolder;
|
||||||
|
|
||||||
// Normalize path and validate it's safe (within Users folder)
|
// Normalize path and validate it's safe (within Users folder)
|
||||||
let safeOutputFolder = path.resolve(outputFolder);
|
let safeOutputFolder = path.resolve(outputFolder);
|
||||||
|
|
||||||
if (!isSafePath(safeOutputFolder)) {
|
if (!isSafePath(safeOutputFolder)) {
|
||||||
logger.warn(`Path not allowed, using default instead: ${safeOutputFolder}`);
|
logger.warn(`Path not allowed, using default instead: ${safeOutputFolder}`);
|
||||||
safeOutputFolder = path.resolve(defaultDownloadFolder);
|
safeOutputFolder = path.resolve(defaultDownloadFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create download folder if it doesn't exist
|
// Create download folder if it doesn't exist
|
||||||
try {
|
try {
|
||||||
fs.mkdirSync(safeOutputFolder, { recursive: true });
|
fs.mkdirSync(safeOutputFolder, { recursive: true });
|
||||||
@@ -160,7 +148,7 @@ function fetchDownload(options, progressListeners, speedListeners, stageListener
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Détecte si c'est une playlist et crée un dossier approprié
|
// Détecte si c'est une playlist et crée un dossier approprié
|
||||||
const isPlaylist = options.playlistTitle || isPlaylistUrl(options.url);
|
const isPlaylist = options.playlistTitle || isUrlPlaylist(options.url);
|
||||||
|
|
||||||
if (isPlaylist && userConfig.createPlaylistFolders) {
|
if (isPlaylist && userConfig.createPlaylistFolders) {
|
||||||
try {
|
try {
|
||||||
@@ -196,13 +184,13 @@ function fetchDownload(options, progressListeners, speedListeners, stageListener
|
|||||||
else reject(new Error(`YT-DLP failed with code : ${code}`));
|
else reject(new Error(`YT-DLP failed with code : ${code}`));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
child.stdout.on("data", data => {
|
child.stdout.on("data", data => {
|
||||||
data.toString().split("\n").forEach(line => {
|
data.toString().split("\n").forEach(line => {
|
||||||
if (!line.trim()) return;
|
if (!line.trim()) return;
|
||||||
logger.info(`[yt-dlp] ${line}`);
|
logger.info(`[yt-dlp] ${line}`);
|
||||||
|
|
||||||
// Progress Bar
|
// Progress Bar
|
||||||
if (line.startsWith("[download] Destination:")) {
|
if (line.startsWith("[download] Destination:")) {
|
||||||
progressListeners.forEach(fn => fn("reset"));
|
progressListeners.forEach(fn => fn("reset"));
|
||||||
stageListeners.forEach(fn => fn("Downloading..."));
|
stageListeners.forEach(fn => fn("Downloading..."));
|
||||||
@@ -253,7 +241,7 @@ function fetchDownload(options, progressListeners, speedListeners, stageListener
|
|||||||
data.toString().split("\n").forEach(line => line.trim() && logger.error(`[yt-dlp stderr] ${line}`));
|
data.toString().split("\n").forEach(line => line.trim() && logger.error(`[yt-dlp stderr] ${line}`));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,7 +258,7 @@ function fetchDownload(options, progressListeners, speedListeners, stageListener
|
|||||||
function cancelDownload() {
|
function cancelDownload() {
|
||||||
if (currentDownloadProcess) {
|
if (currentDownloadProcess) {
|
||||||
logger.info("Cancelling download and killing all child processes...");
|
logger.info("Cancelling download and killing all child processes...");
|
||||||
|
|
||||||
// Force kill the process and all its children with SIGKILL
|
// Force kill the process and all its children with SIGKILL
|
||||||
try {
|
try {
|
||||||
// Try to kill with SIGKILL on Windows (process group) or Unix
|
// Try to kill with SIGKILL on Windows (process group) or Unix
|
||||||
@@ -286,7 +274,7 @@ function cancelDownload() {
|
|||||||
// Fallback to regular kill
|
// Fallback to regular kill
|
||||||
currentDownloadProcess.kill('SIGKILL');
|
currentDownloadProcess.kill('SIGKILL');
|
||||||
}
|
}
|
||||||
|
|
||||||
currentDownloadProcess = null;
|
currentDownloadProcess = null;
|
||||||
logger.info("Download cancelled successfully");
|
logger.info("Download cancelled successfully");
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
Reference in New Issue
Block a user