diff --git a/.gitignore b/.gitignore index 825f0c2..40dce40 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,23 @@ +# Dependencies /node_modules -/downloads + +# Build Output Folder /dist /out -/ressources/ffmpeg.exe -/ressources/ffprobe.exe -/ressources/deno.exe - -/theme/** - +# Config JSON File. Create it and fill it if you want to have dev only settings config/config.dev.json -logs/*.json -logs/*.log \ No newline at end of file +# IDE +/.idea +/.vscode + +# Linux executable +ffmpeg +ffprobe +deno + +# Windows Executable +ffmpeg.exe +ffprobe.exe +deno.exe \ No newline at end of file diff --git a/README.md b/README.md index 8b9d935..d1cf577 100644 --- a/README.md +++ b/README.md @@ -275,7 +275,7 @@ Freedom-Loader/ │ ├── settingsPanel.css │ ├── topbar.css │ └── videoinfo.css -├── ressources/ # Internal resources (binaries) +├── resources/ # Internal resources (binaries) ├── server/ # Express backend server │ ├── logger.js │ ├── server.js @@ -386,7 +386,7 @@ npm run build This project relies on several external binaries. Some of them are **not included** in the public repository and must be added manually. -You must download the required binaries and place them in the `ressources` folder, using the exact filenames listed below. +You must download the required binaries and place them in the `resources` folder, using the exact filenames listed below. #### Required binaries @@ -407,7 +407,7 @@ You must download the required binaries and place them in the `ressources` folde Final folder structure: ``` -ressources/ +resources/ ├── deno.exe ├── ffmpeg.exe ├── ffprobe.exe diff --git a/app/dependencyCheck.js b/app/dependencyCheck.js index 69501d2..fb5ec91 100644 --- a/app/dependencyCheck.js +++ b/app/dependencyCheck.js @@ -25,15 +25,19 @@ function checkNativeDependencies() { */ const { binaryPaths } = require("../server/helpers/path.helpers"); + const isWindows = process.platform === "win32"; + + const addExe = isWindows ? ".exe" : ""; + /** * List of required external executables used by the application runtime. * Each entry defines a binary name and its resolved absolute path. */ const deps = [ - { name: "yt-dlp.exe", path: binaryPaths.ytDlp }, - { name: "ffmpeg.exe", path: binaryPaths.ffmpeg }, - { name: "ffprobe.exe", path: binaryPaths.ffprobe }, - { name: "deno.exe", path: binaryPaths.deno }, + { name: `yt-dlp${addExe}`, path: binaryPaths.ytDlp }, + { name: `ffmpeg${addExe}`, path: binaryPaths.ffmpeg }, + { name: `ffprobe${addExe}`, path: binaryPaths.ffprobe }, + { name: `deno${addExe}`, path: binaryPaths.deno }, ]; const missing = deps.filter(d => !fs.existsSync(d.path)); @@ -52,7 +56,7 @@ function checkNativeDependencies() { app.whenReady().then(() => { dialog.showErrorBox( "Missing dependencies", - `The following files are missing in the 'ressources' folder:\n${list}\n\nThe application will now exit. Try to reinstall.` + `The following files are missing in the 'resources' folder:\n${list}\n\nThe application will now exit. Try to reinstall.` ); app.quit(); }); diff --git a/app/pathValidator.js b/app/pathValidator.js index 94eb936..e7f3f67 100644 --- a/app/pathValidator.js +++ b/app/pathValidator.js @@ -62,10 +62,19 @@ function validateDownloadPath(userPath) { * Required to prevent path traversal or alias bypass. */ const resolved = fs.realpathSync(path.resolve(userPath)); + + if (!fs.existsSync(resolved)) { + fs.mkdirSync(resolved, { recursive: true }); + logger.info(`Download folder created: ${resolved}`); + } + + const real = fs.realpathSync(resolved); + if (!isSafePath(resolved)) { throw new Error("Path not allowed: system folders are blocked!"); } - return resolved; + + return real; } catch (err) { diff --git a/build/app-icon.png b/build/app-icon.png new file mode 100755 index 0000000..bd79e9c Binary files /dev/null and b/build/app-icon.png differ diff --git a/main.js b/main.js index e4accce..dba0039 100644 --- a/main.js +++ b/main.js @@ -79,7 +79,7 @@ const { updateYtDlp } = require("./app/ytDlpUpdater"); const { createMainWindow, getMainWindow } = require("./app/windowManager"); const { registerIpcHandlers } = require("./app/ipcHandlers"); const { createSplashWindow, closeSplashWindow, setSplashProgress } = require("./app/splashManager"); -const { userThemesPath, initUserThemes } = require("./server/helpers/path.helpers"); +const { userThemesPath, initUserThemes, isWindows } = require("./server/helpers/path.helpers"); /** @@ -116,8 +116,15 @@ app.whenReady().then(async () => { registerIpcHandlers(getMainWindow); setSplashProgress(2); // Loading themes - initUserThemes(); - await initThemes(userThemesPath); + + // TODO: Patch this, i disable this features for the moment + // Themes are currently disabled if we are not on Windows + if(isWindows){ + initUserThemes(); + await initThemes(userThemesPath); + } else { + logger.info(`OS is ${process.platform}, Skipping themes loading`) + } setSplashProgress(3); // Almost ready await createMainWindow(); diff --git a/package.json b/package.json index 0367ffa..96ec7db 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,17 @@ { "name": "freedom-loader", "productName": "Freedom Loader", - "version": "1.5.1", - "author": "MasterAcnolo", + "version": "1.6.0", + "author": "MasterAcnolo ", "description": "Freedom Loader", "main": "main.js", "scripts": { "start": "electron --trace-warnings .", + "postinstall": "chmod +x resources/binaries/linux/* 2>/dev/null || true", "build": "electron-builder", + "build:win": "electron-builder --win", + "build:linux": "electron-builder --linux", + "build:all": "electron-builder -wl", "update": "npm update" }, "dependencies": { @@ -26,7 +30,6 @@ "electron": "^41.2.0", "electron-builder": "^26.8.1", "@electron/devtron": "^2.1.1" - }, "build": { "appId": "com.masteracnolo.freedomloader", @@ -49,54 +52,23 @@ }, "asar": true, "extraResources": [ - { - "from": "theme", - "to": "theme" - }, - { - "from": "build/confirm-icon.png", - "to": "confirm-icon.png" - }, - { - "from": "build/app-icon.ico", - "to": "app-icon.ico" - }, - { - "from": "build/banner.bmp", - "to": "banner.bmp" - }, - { - "from": "build/banner.png", - "to": "banner.png" - }, - { - "from": "build/error.png", - "to": "error.png" - }, - { - "from": "ressources/yt-dlp.exe", - "to": "binaries/yt-dlp.exe" - }, - { - "from": "ressources/ffmpeg.exe", - "to": "binaries/ffmpeg.exe" - }, - { - "from": "ressources/ffprobe.exe", - "to": "binaries/ffprobe.exe" - }, - { - "from": "ressources/deno.exe", - "to": "binaries/deno.exe" - }, - { - "from": "config/config.default.json", - "to": "config/config.default.json" - } + { "from": "theme", "to": "theme" }, + { "from": "build/confirm-icon.png", "to": "confirm-icon.png" }, + { "from": "build/banner.bmp", "to": "banner.bmp" }, + { "from": "build/banner.png", "to": "banner.png" }, + { "from": "build/error.png", "to": "error.png" }, + { "from": "config/config.default.json", "to": "config/config.default.json" } ], "win": { "target": "nsis", - "icon": "build/app-icon.ico" + "icon": "build/app-icon.ico", + "artifactName": "${productName}-Setup-${version}.${ext}", + "extraResources": [ + { "from": "resources/binaries/win-32/yt-dlp.exe", "to": "binaries/yt-dlp.exe" }, + { "from": "resources/binaries/win-32/ffmpeg.exe", "to": "binaries/ffmpeg.exe" }, + { "from": "resources/binaries/win-32/ffprobe.exe", "to": "binaries/ffprobe.exe" }, + { "from": "resources/binaries/win-32/deno.exe", "to": "binaries/deno.exe" } + ] }, "nsis": { "oneClick": false, @@ -108,6 +80,23 @@ "createDesktopShortcut": true, "createStartMenuShortcut": true, "shortcutName": "Freedom Loader" + }, + "linux": { + "target": ["AppImage", "deb","rpm"], + "category": "Utility", + "icon": "build/app-icon.png", + "extraResources": [ + { "from": "resources/binaries/linux/yt-dlp", "to": "binaries/yt-dlp" }, + { "from": "resources/binaries/linux/ffmpeg", "to": "binaries/ffmpeg" }, + { "from": "resources/binaries/linux/ffprobe", "to": "binaries/ffprobe" }, + { "from": "resources/binaries/linux/deno", "to": "binaries/deno" } + ], + "extraFiles": [ + { + "from": "package/com.masteracnolo.freedomloader.metainfo.xml", + "to": "usr/share/metainfo/com.masteracnolo.freedomloader.metainfo.xml" + } + ] } } -} +} \ No newline at end of file diff --git a/preload.js b/preload.js index f280b7b..aee2ac8 100644 --- a/preload.js +++ b/preload.js @@ -9,6 +9,11 @@ const { contextBridge, ipcRenderer } = require("electron"); */ contextBridge.exposeInMainWorld("electronAPI", { + /** + * Return process.platform to renderer + */ + getProcessPlatform: () => process.platform, + /** * Returns the default system download directory. */ diff --git a/public/index.html b/public/index.html index 7e623f0..2d134d6 100644 --- a/public/index.html +++ b/public/index.html @@ -290,6 +290,7 @@ + diff --git a/public/script/customConfiguration.js b/public/script/customConfiguration.js new file mode 100644 index 0000000..84c799a --- /dev/null +++ b/public/script/customConfiguration.js @@ -0,0 +1,30 @@ +async function initPlatformRestrictions() { + const platform = await window.electronAPI.getProcessPlatform(); + + const themeSelect = document.getElementById("themeSelect"); + const refreshBtn = document.getElementById("refresh-themes-btn"); + + /** + * Is the OS windows + * @type {boolean} + */ + const isWindows = platform === "win32"; + + console.log(isWindows); + + // If it's windows, the button is not disabled. + themeSelect.disabled = !isWindows; + refreshBtn.disabled = !isWindows; + + themeSelect.classList.toggle("disabled", !isWindows); + refreshBtn.classList.toggle("disabled", !isWindows); + + if (!isWindows) { + themeSelect.title = "Themes are only available on Windows"; + refreshBtn.title = "Themes are only available on Windows"; + } +} + +document.addEventListener('DOMContentLoaded', function(){ + initPlatformRestrictions(); +}) \ No newline at end of file diff --git a/public/styles/styles.css b/public/styles/styles.css index db0db99..948263a 100644 --- a/public/styles/styles.css +++ b/public/styles/styles.css @@ -16,6 +16,14 @@ @import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap'); +/* Mark element as disabled */ +.disabled { + pointer-events: none; + opacity: 0.5; + filter: grayscale(40%); + cursor: not-allowed; +} + /* Because why not */ #reference{ position: fixed; diff --git a/resources/binaries/linux/yt-dlp b/resources/binaries/linux/yt-dlp new file mode 100755 index 0000000..8d9bafb Binary files /dev/null and b/resources/binaries/linux/yt-dlp differ diff --git a/ressources/yt-dlp.exe b/resources/binaries/win-32/yt-dlp.exe similarity index 100% rename from ressources/yt-dlp.exe rename to resources/binaries/win-32/yt-dlp.exe diff --git a/server/helpers/getBrowser.helpers.js b/server/helpers/getBrowser.helpers.js index 2610a6d..8e8caf7 100644 --- a/server/helpers/getBrowser.helpers.js +++ b/server/helpers/getBrowser.helpers.js @@ -22,7 +22,8 @@ const { logger } = require("../logger"); * @returns {string} The detected browser identifier. */ function getUserBrowser() { - const userProfile = os.homedir(); + //const userProfile = os.homedir(); + const firefoxPath = getFirefoxProfilePath(); /** * Browsers supported by yt-dlp cookie extraction. @@ -32,7 +33,7 @@ function getUserBrowser() { * once compatibility has been verified. */ const browsers = [ - { name: "firefox", path: path.join(userProfile, "AppData", "Roaming", "Mozilla", "Firefox", "Profiles") }, + { name: "firefox", path: firefoxPath }, // { name: "chrome", path: path.join(userProfile, "AppData", "Local", "Google", "Chrome", "User Data", "Default") }, // { name: "brave", path: path.join(userProfile, "AppData", "Local", "BraveSoftware", "Brave-Browser", "User Data", "Default") }, // { name: "edge", path: path.join(userProfile, "AppData", "Local", "Microsoft", "Edge", "User Data", "Default") }, @@ -44,7 +45,7 @@ function getUserBrowser() { // Search for the first available browser profile. for (const browser of browsers) { - if (fs.existsSync(browser.path)) { + if (browser.path && fs.existsSync(browser.path)) { logger.info(`Browser found: ${browser.name}`); return browser.name; } @@ -52,11 +53,50 @@ function getUserBrowser() { // No supported browser found => Notify User logger.warn("No supported browser found on the system"); - notify.notifyFirefoxBrowserMissing(); - + + // If you somehow managed to live without Firefox and need help installing it. - Don't applied to my Linux chad + if (process.platform === "win32") { + notify.notifyFirefoxBrowserMissing(); + } + // Fallback to Firefox and let yt-dlp handle the error gracefully. // This prevents the application from crashing return "firefox"; } +function getFirefoxProfilePath() { + const home = os.homedir(); + + if (process.platform === "win32") { + return path.join( + home, + "AppData", + "Roaming", + "Mozilla", + "Firefox", + "Profiles" + ); + } + + if (process.platform === "linux") { + return path.join( + "usr", + "bin", + "firefox" + ); + } + + if (process.platform === "darwin") { + return path.join( + home, + "Library", + "Application Support", + "Firefox", + "Profiles" + ); + } + + return null; +} + module.exports = getUserBrowser \ No newline at end of file diff --git a/server/helpers/path.helpers.js b/server/helpers/path.helpers.js index 95114d9..890d116 100644 --- a/server/helpers/path.helpers.js +++ b/server/helpers/path.helpers.js @@ -1,18 +1,49 @@ const path = require("path"); const fs = require("fs"); -const os = require("os"); const { app } = require("electron"); const config = require("../../config.js"); const { logger } = require("../logger.js"); -// Centralized resource paths -const resourcesPath = config.devMode - ? path.join(__dirname, "../../ressources") +/** + * Is the OS windows + */ +const isWindows = process.platform === 'win32' + +/** + * On windows the binaries are name.exe on Linux/macOS no + */ +const ext = isWindows ? '.exe' : '' + +/** Centralized resource paths */ +const resourcesPath = config.devMode + ? path.join(__dirname, "../../resources") : process.resourcesPath; -// Default download folder (centralized) -const defaultDownloadFolder = path.join(os.homedir(), "Downloads", "Freedom Loader"); +/** Default download folder (centralized) */ +const defaultDownloadFolder = path.join(app.getPath("downloads"), "Freedom Loader"); + +/** + * Current platform: + * win32 | linux | darwin + */ +const platform = process.platform; + +/** + * Dev source tree uses platform-named subfolders (matches resources/binaries/{win-32,linux,darwin}). + * Packaged builds flatten everything directly under binaries/, since each + * platform build only ships its own binaries — no subfolder needed. + */ +const devPlatformFolder = { + win32: "win-32", + linux: "linux", + darwin: "darwin" +}[platform]; + +const binariesPath = config.devMode + ? path.join(resourcesPath, "binaries", devPlatformFolder) + : path.join(resourcesPath, "binaries"); + /** * Runtime-resolved binary paths. @@ -24,7 +55,10 @@ let ffmpegPath; let denoPath; /** Source binary bundled inside application resources (used for first-time copy) */ -const sourceYtDlp = path.join(resourcesPath, "binaries","yt-dlp.exe"); +const sourceYtDlp = path.join( + binariesPath, + `yt-dlp${ext}` +); /** * Resolve binary locations depending on environment: @@ -32,16 +66,47 @@ const sourceYtDlp = path.join(resourcesPath, "binaries","yt-dlp.exe"); * - prod: extracted userData + packaged resources */ if (config.devMode) { - userYtDlp = path.join(__dirname, "../../ressources/yt-dlp.exe"); - ffmpegPath = path.join(__dirname, "../../ressources/"); // <- has ffmpeg.exe and ffprobe.exe - denoPath = path.join(__dirname, "../../ressources/deno.exe"); -} else { - userYtDlp = path.join(app.getPath("userData"),"yt-dlp.exe"); - ffmpegPath = path.join(resourcesPath, "binaries","ffmpeg.exe"); - denoPath = path.join(resourcesPath, "binaries","deno.exe"); + userYtDlp = path.join( + binariesPath, + `yt-dlp${ext}` + ); - if (!fs.existsSync(userYtDlp)) { + ffmpegPath = path.join( + binariesPath, + `ffmpeg${ext}` + ); + + denoPath = path.join( + binariesPath, + `deno${ext}` + ); +} else { + userYtDlp = path.join( + app.getPath("userData"), + `yt-dlp${ext}` + ); + + ffmpegPath = path.join( + binariesPath, + `ffmpeg${ext}` + ); + + denoPath = path.join( + binariesPath, + `deno${ext}` + ); + + if (!fs.existsSync(userYtDlp) && fs.existsSync(sourceYtDlp)) { fs.copyFileSync(sourceYtDlp, userYtDlp); + + // Linux/macOS executable permission + if (!isWindows) { + try { + fs.chmodSync(userYtDlp, 0o755); + } catch (err) { + logger.warn(`Failed to chmod yt-dlp: ${err.message}`); + } + } } } @@ -60,10 +125,25 @@ const iconPaths = { * Used mainly for verification and diagnostics. */ const binaryPaths = { - ytDlp: path.join(resourcesPath, "binaries", "yt-dlp.exe"), - ffmpeg: path.join(resourcesPath, "binaries", "ffmpeg.exe"), - ffprobe: path.join(resourcesPath, "binaries", "ffprobe.exe"), - deno: path.join(resourcesPath, "binaries", "deno.exe") + ytDlp: path.join( + binariesPath, + `yt-dlp${ext}` + ), + + ffmpeg: path.join( + binariesPath, + `ffmpeg${ext}` + ), + + ffprobe: path.join( + binariesPath, + `ffprobe${ext}` + ), + + deno: path.join( + binariesPath, + `deno${ext}` + ) }; /** @@ -96,25 +176,31 @@ function initUserThemes() { for (const theme of defaultThemes) { const srcPath = path.join(defaultThemesSourcePath, theme); const destPath = path.join(userThemesPath, theme); - if (!fs.existsSync(destPath)) { try { copyDirectory(srcPath, destPath); fs.chmodSync(destPath, 0o777); logger.info(`Copied default theme: ${theme}`); } catch (copyErr) { - logger.warn(`Failed to copy theme ${theme}: ${copyErr.message}. Theme folder will be created, add theme files manually.`); + logger.warn( + `Failed to copy theme ${theme}: ${copyErr.message}. Theme folder will be created, add theme files manually.` + ); + try { fs.mkdirSync(destPath, { recursive: true }); fs.chmodSync(destPath, 0o777); } catch (mkdirErr) { - logger.warn(`Could not create theme directory ${theme}: ${mkdirErr.message}`); + logger.warn( + `Could not create theme directory ${theme}: ${mkdirErr.message}` + ); } } } } } else { - logger.warn(`Default themes source path not found: ${defaultThemesSourcePath}`); + logger.warn( + `Default themes source path not found: ${defaultThemesSourcePath}` + ); } } catch (err) { logger.error(`Failed to initialize user themes: ${err.message}`); @@ -131,14 +217,18 @@ function initUserThemes() { function copyDirectory(src, dest) { try { if (!fs.existsSync(dest)) { - fs.mkdirSync(dest, { recursive: true, mode: 0o777 }); + fs.mkdirSync(dest, { + recursive: true, + mode: 0o777 + }); } - + const files = fs.readdirSync(src); + for (const file of files) { const srcFile = path.join(src, file); const destFile = path.join(dest, file); - + if (fs.statSync(srcFile).isDirectory()) { copyDirectory(srcFile, destFile); } else { @@ -151,8 +241,16 @@ function copyDirectory(src, dest) { } // Runtime validation of critical binaries -if (!userYtDlp){ logger.error("Missing yt-dlp binary")} -if (!ffmpegPath){ logger.error("Missing ffmpeg binary")} -if (!denoPath){ logger.error("Missing deno binary")} +if (!userYtDlp) { + logger.error("Missing yt-dlp binary"); +} -module.exports = { userYtDlp, ffmpegPath, denoPath, iconPaths, binaryPaths, resourcesPath, defaultDownloadFolder, userThemesPath, initUserThemes }; +if (!ffmpegPath) { + logger.error("Missing ffmpeg binary"); +} + +if (!denoPath) { + logger.error("Missing deno binary"); +} + +module.exports = { userYtDlp, ffmpegPath, denoPath, iconPaths, binaryPaths, resourcesPath, defaultDownloadFolder, userThemesPath, initUserThemes, isWindows }; \ No newline at end of file diff --git a/server/logger.js b/server/logger.js index 3ed6724..6ce86dc 100644 --- a/server/logger.js +++ b/server/logger.js @@ -4,9 +4,10 @@ const fs = require("fs"); const path = require("path"); const os = require("os"); const config = require("../config"); +const {isWindows} = require("./helpers/path.helpers"); // Logs folder in Windows -const logDir = path.join(os.homedir(), "AppData", "Local", "FreedomLoader", "logs"); +const logDir = isWindows ? path.join(os.homedir(), "AppData", "Local", "FreedomLoader", "logs") : path.join(process.env.XDG_DATA_HOME || path.join(os.homedir(), ".local", "share"), "FreedomLoader", "logs"); // Create "Logs" folder if needed try {