mirror of
https://github.com/MasterAcnolo/Freedom-Loader.git
synced 2026-09-28 12:31:14 +02:00
Compare commits
6 Commits
553e383c94
...
testing
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0bdd887e3 | ||
|
|
609ac2eadb | ||
|
|
c484d322bf | ||
|
|
b321a1d164 | ||
|
|
89e6fcf9bc | ||
|
|
ba2f317551 |
18
README.md
18
README.md
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
### **A clean, open-source multimedia downloader for Windows and Linux**
|
### **A clean, open-source multimedia downloader for Windows and Linux**
|
||||||
|
|
||||||
[](https://github.com/MasterAcnolo/Freedom-Loader/releases)
|
[](https://github.com/MasterAcnolo/Freedom-Loader/releases)
|
||||||
[](https://www.gnu.org/licenses/gpl-3.0)
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
||||||
[](https://masteracnolo.github.io/Freedom-Loader-Site/)
|
[](https://masteracnolo.github.io/Freedom-Loader-Site/)
|
||||||
[](https://masteracnolo.github.io/Freedom-Loader-Workshop/)
|
[](https://masteracnolo.github.io/Freedom-Loader-Workshop/)
|
||||||
@@ -88,8 +88,7 @@ The primary goal is to make media downloading accessible to users who want offli
|
|||||||
|
|
||||||
[](https://copr.fedorainfracloud.org/coprs/masteracnolo/freedom-loader/)
|
[](https://copr.fedorainfracloud.org/coprs/masteracnolo/freedom-loader/)
|
||||||
[](https://github.com/MasterAcnolo/Freedom-Loader/releases)
|
[](https://github.com/MasterAcnolo/Freedom-Loader/releases)
|
||||||
|
[](https://snapcraft.io/freedom-loader)
|
||||||
[//]: # ([](https://snapcraft.io/freedom-loader))
|
|
||||||
[](https://github.com/MasterAcnolo/Freedom-Loader/releases)
|
[](https://github.com/MasterAcnolo/Freedom-Loader/releases)
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
@@ -122,14 +121,11 @@ sudo dnf install freedom-loader
|
|||||||
sudo apt install ./freedom-loader_1.x.x_amd64.deb
|
sudo apt install ./freedom-loader_1.x.x_amd64.deb
|
||||||
```
|
```
|
||||||
|
|
||||||
[//]: # (#### Snap (any distro with snapd))
|
#### Snap (any distro with snapd)
|
||||||
|
|
||||||
[//]: # ()
|
```bash
|
||||||
[//]: # (```bash)
|
sudo snap install freedom-loader
|
||||||
|
```
|
||||||
[//]: # (sudo snap install freedom-loader)
|
|
||||||
|
|
||||||
[//]: # (```)
|
|
||||||
|
|
||||||
#### RPM (Fedora/RHEL/openSUSE, without Copr)
|
#### RPM (Fedora/RHEL/openSUSE, without Copr)
|
||||||
|
|
||||||
@@ -543,7 +539,7 @@ You are free to use, modify, and redistribute this software under the terms of t
|
|||||||
|
|
||||||
- [yt-dlp](https://github.com/yt-dlp/yt-dlp) for the robust download engine
|
- [yt-dlp](https://github.com/yt-dlp/yt-dlp) for the robust download engine
|
||||||
- [FFmpeg](https://ffmpeg.org/) for media processing capabilities
|
- [FFmpeg](https://ffmpeg.org/) for media processing capabilities
|
||||||
- [Deno](https://deno.com/) for helping me bypassing challenges
|
- [Deno](https://deno.com/) for helping me to bypass challenges
|
||||||
- [Electron](https://www.electronjs.org/) for the desktop application framework
|
- [Electron](https://www.electronjs.org/) for the desktop application framework
|
||||||
- The open-source community for continuous support and contributions
|
- The open-source community for continuous support and contributions
|
||||||
- [@SpicyFire21](https://github.com/SpicyFire21) to be the spiciest one
|
- [@SpicyFire21](https://github.com/SpicyFire21) to be the spiciest one
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ When using Freedom Loader, I recommend:
|
|||||||
- **Official Sources**: Download only from official sources:
|
- **Official Sources**: Download only from official sources:
|
||||||
- [GitHub Releases](https://github.com/MasterAcnolo/Freedom-Loader/releases)
|
- [GitHub Releases](https://github.com/MasterAcnolo/Freedom-Loader/releases)
|
||||||
- [Fedora COPR](https://copr.fedorainfracloud.org/coprs/masteracnolo/freedom-loader/)
|
- [Fedora COPR](https://copr.fedorainfracloud.org/coprs/masteracnolo/freedom-loader/)
|
||||||
<!-- - [Snap Store](https://snapcraft.io/freedom-loader) -->
|
- [Snap Store](https://snapcraft.io/freedom-loader)
|
||||||
- **Verify Downloads**: Check that installers are from one of the above sources
|
- **Verify Downloads**: Check that installers are from one of the above sources
|
||||||
- **Antivirus**: Keep your antivirus software up to date
|
- **Antivirus**: Keep your antivirus software up to date
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const path = require("path");
|
|||||||
* Indicates whether the application is running in development mode.
|
* Indicates whether the application is running in development mode.
|
||||||
* Determined by Electron's packaging state.
|
* Determined by Electron's packaging state.
|
||||||
*/
|
*/
|
||||||
const devMode = !app.isPackaged;
|
const devMode = process.env.NODE_ENV === "test" || !app?.isPackaged;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves the configuration file path depending on runtime environment.
|
* Resolves the configuration file path depending on runtime environment.
|
||||||
|
|||||||
21
jest.config.js
Normal file
21
jest.config.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
module.exports = {
|
||||||
|
testEnvironment: "node",
|
||||||
|
testMatch: ["**/test/unit/**/*.test.js"],
|
||||||
|
collectCoverageFrom: [
|
||||||
|
"server/**/*.js",
|
||||||
|
"app/**/*.js",
|
||||||
|
"!app/autoUpdater.js",
|
||||||
|
],
|
||||||
|
|
||||||
|
modulePathIgnorePatterns: [
|
||||||
|
"<rootDir>/dist",
|
||||||
|
"<rootDir>/release",
|
||||||
|
"<rootDir>/build",
|
||||||
|
],
|
||||||
|
|
||||||
|
watchPathIgnorePatterns: [
|
||||||
|
"<rootDir>/dist",
|
||||||
|
"<rootDir>/release",
|
||||||
|
"<rootDir>/build",
|
||||||
|
],
|
||||||
|
};
|
||||||
6092
package-lock.json
generated
6092
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
21
package.json
21
package.json
@@ -16,12 +16,16 @@
|
|||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "electron --trace-warnings .",
|
"start": "electron --trace-warnings .",
|
||||||
"postinstall": "chmod +x resources/binaries/linux/* 2>/dev/null || true",
|
|
||||||
"build": "electron-builder",
|
"build": "electron-builder",
|
||||||
"build:win": "electron-builder --win",
|
"build:win": "electron-builder --win",
|
||||||
"build:linux": "electron-builder --linux",
|
"build:linux": "electron-builder --linux",
|
||||||
"build:rpm": "bash scripts/create-copr-package.sh",
|
"build:rpm": "bash scripts/create-copr-package.sh",
|
||||||
"build:all": "electron-builder -wl",
|
"build:all": "electron-builder -wl",
|
||||||
|
|
||||||
|
"test": "jest",
|
||||||
|
"test:unit": "jest tests/unit",
|
||||||
|
|
||||||
"update": "npm update"
|
"update": "npm update"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -38,8 +42,12 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@electron/devtron": "^2.1.1",
|
"@electron/devtron": "^2.1.1",
|
||||||
|
"@jest/globals": "^30.4.1",
|
||||||
|
"@playwright/test": "^1.62.1",
|
||||||
"electron": "^41.2.0",
|
"electron": "^41.2.0",
|
||||||
"electron-builder": "^25.1.8"
|
"electron-builder": "^26.15.3",
|
||||||
|
"jest": "^30.4.2",
|
||||||
|
"playwright": "^1.62.1"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "com.masteracnolo.freedomloader",
|
"appId": "com.masteracnolo.freedomloader",
|
||||||
@@ -164,14 +172,7 @@
|
|||||||
"confinement": "strict",
|
"confinement": "strict",
|
||||||
"plugs": [
|
"plugs": [
|
||||||
"default",
|
"default",
|
||||||
"home",
|
"removable-media"
|
||||||
"removable-media",
|
|
||||||
"network",
|
|
||||||
"audio-playback",
|
|
||||||
"desktop",
|
|
||||||
"desktop-legacy",
|
|
||||||
"x11",
|
|
||||||
"wayland"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
182
test/unit/buildArgs.test.js
Normal file
182
test/unit/buildArgs.test.js
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
jest.mock("../../server/helpers/getBrowser.helpers.js", () =>
|
||||||
|
jest.fn(() => "firefox")
|
||||||
|
);
|
||||||
|
|
||||||
|
jest.mock("../../server/helpers/path.helpers.js", () => ({
|
||||||
|
ffmpegPath: "/mock/ffmpeg",
|
||||||
|
denoPath: "/mock/deno",
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("../../config.js", () => ({
|
||||||
|
configFeatures: {
|
||||||
|
autoUpdate: false,
|
||||||
|
discordRPC: false,
|
||||||
|
customTopBar: false,
|
||||||
|
autoCheckInfo: false,
|
||||||
|
addThumbnail: true,
|
||||||
|
addMetadata: true,
|
||||||
|
verboseLogs: false,
|
||||||
|
autoDownloadPlaylist: false,
|
||||||
|
customCodec: "h264",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("../../server/logger.js", () => ({
|
||||||
|
logger: {
|
||||||
|
info: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { buildYtDlpArgs } = require("../../server/helpers/buildArgs.helpers");
|
||||||
|
const { configFeatures } = require("../../config");
|
||||||
|
const { logger } = require("../../server/logger");
|
||||||
|
|
||||||
|
describe("buildYtDlpArgs", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("builds default video arguments", () => {
|
||||||
|
const args = buildYtDlpArgs({
|
||||||
|
url: "https://youtube.com/watch?v=123",
|
||||||
|
audioOnly: false,
|
||||||
|
quality: "best",
|
||||||
|
outputFolder: "/downloads",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(args).toContain("--cookies-from-browser");
|
||||||
|
expect(args).toContain("firefox");
|
||||||
|
|
||||||
|
expect(args).toContain("--ffmpeg-location");
|
||||||
|
expect(args).toContain("/mock/ffmpeg");
|
||||||
|
|
||||||
|
expect(args).toContain("--js-runtimes");
|
||||||
|
expect(args).toContain("deno:/mock/deno");
|
||||||
|
|
||||||
|
expect(args).toContain("--merge-output");
|
||||||
|
expect(args).toContain("mp4");
|
||||||
|
|
||||||
|
expect(args).toContain("-f");
|
||||||
|
expect(args).toContain("bestvideo+bestaudio/best/mp4");
|
||||||
|
|
||||||
|
expect(args).toContain(
|
||||||
|
path.join("/downloads", "%(title)s.%(ext)s")
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(args.at(-1)).toBe("https://youtube.com/watch?v=123");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("builds audio-only arguments", () => {
|
||||||
|
const args = buildYtDlpArgs({
|
||||||
|
url: "https://youtube.com/watch?v=123",
|
||||||
|
audioOnly: true,
|
||||||
|
quality: "best",
|
||||||
|
outputFolder: "/downloads",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(args).toContain("--extract-audio");
|
||||||
|
expect(args).toContain("--audio-format");
|
||||||
|
expect(args).toContain("mp3");
|
||||||
|
expect(args).toContain("bestaudio");
|
||||||
|
|
||||||
|
expect(args).not.toContain("--merge-output");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("uses requested quality", () => {
|
||||||
|
const args = buildYtDlpArgs({
|
||||||
|
url: "https://youtube.com/watch?v=123",
|
||||||
|
audioOnly: false,
|
||||||
|
quality: "720",
|
||||||
|
outputFolder: "/downloads",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(args).toContain(
|
||||||
|
"bestvideo[height<=720]+bestaudio/best[height<=720]/mp4"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("falls back to best for unknown quality", () => {
|
||||||
|
const args = buildYtDlpArgs({
|
||||||
|
url: "https://youtube.com/watch?v=123",
|
||||||
|
audioOnly: false,
|
||||||
|
quality: "unknown",
|
||||||
|
outputFolder: "/downloads",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(args).toContain("best");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("uses --no-playlist by default", () => {
|
||||||
|
const args = buildYtDlpArgs({
|
||||||
|
url: "https://youtube.com/watch?v=123",
|
||||||
|
audioOnly: false,
|
||||||
|
quality: "best",
|
||||||
|
outputFolder: "/downloads",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(args).toContain("--no-playlist");
|
||||||
|
expect(args).not.toContain("--yes-playlist");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("uses --yes-playlist when enabled", () => {
|
||||||
|
configFeatures.autoDownloadPlaylist = true;
|
||||||
|
|
||||||
|
const args = buildYtDlpArgs({
|
||||||
|
url: "https://youtube.com/watch?v=123",
|
||||||
|
audioOnly: false,
|
||||||
|
quality: "best",
|
||||||
|
outputFolder: "/downloads",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(args).toContain("--yes-playlist");
|
||||||
|
expect(args).not.toContain("--no-playlist");
|
||||||
|
|
||||||
|
configFeatures.autoDownloadPlaylist = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
test("embeds thumbnail and metadata when enabled", () => {
|
||||||
|
const args = buildYtDlpArgs({
|
||||||
|
url: "https://youtube.com/watch?v=123",
|
||||||
|
audioOnly: false,
|
||||||
|
quality: "best",
|
||||||
|
outputFolder: "/downloads",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(args).toContain("--embed-thumbnail");
|
||||||
|
expect(args).toContain("--add-metadata");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("logs codec validation", () => {
|
||||||
|
buildYtDlpArgs({
|
||||||
|
url: "https://youtube.com/watch?v=123",
|
||||||
|
audioOnly: false,
|
||||||
|
quality: "best",
|
||||||
|
outputFolder: "/downloads",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(logger.info).toHaveBeenCalledWith("Codec Valid: h264");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("falls back to h264 when codec is invalid", () => {
|
||||||
|
configFeatures.customCodec = "invalid";
|
||||||
|
|
||||||
|
const args = buildYtDlpArgs({
|
||||||
|
url: "https://youtube.com/watch?v=123",
|
||||||
|
audioOnly: false,
|
||||||
|
quality: "best",
|
||||||
|
outputFolder: "/downloads",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(logger.error).toHaveBeenCalledWith(
|
||||||
|
"Codec not valid: invalid. Using default codec"
|
||||||
|
);
|
||||||
|
|
||||||
|
const sortIndex = args.indexOf("-S");
|
||||||
|
expect(args[sortIndex + 1]).toContain("vcodec:h264");
|
||||||
|
|
||||||
|
configFeatures.customCodec = "h264";
|
||||||
|
});
|
||||||
|
});
|
||||||
133
test/unit/notify.helpers.test.js
Normal file
133
test/unit/notify.helpers.test.js
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
const mockShow = jest.fn();
|
||||||
|
const mockOn = jest.fn();
|
||||||
|
const mockOpenPath = jest.fn();
|
||||||
|
const mockOpenExternal = jest.fn();
|
||||||
|
|
||||||
|
jest.mock("electron", () => ({
|
||||||
|
Notification: jest.fn().mockImplementation(() => ({
|
||||||
|
show: mockShow,
|
||||||
|
on: mockOn,
|
||||||
|
})),
|
||||||
|
shell: {
|
||||||
|
openPath: mockOpenPath,
|
||||||
|
openExternal: mockOpenExternal,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("../../server/helpers/path.helpers", () => ({
|
||||||
|
iconPaths: {
|
||||||
|
confirm: "/icons/confirm.png",
|
||||||
|
error: "/icons/error.png",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("../../server/logger", () => ({
|
||||||
|
logger: {
|
||||||
|
info: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { Notification, shell } = require("electron");
|
||||||
|
|
||||||
|
const {
|
||||||
|
notifyDownloadFinished,
|
||||||
|
notifyCookiesBrowserError,
|
||||||
|
notifyFirefoxBrowserMissing,
|
||||||
|
} = require("../../server/helpers/notify.helpers");
|
||||||
|
|
||||||
|
describe("notify.helpers", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("notifyDownloadFinished", () => {
|
||||||
|
test("does nothing when notifications are disabled", () => {
|
||||||
|
notifyDownloadFinished("/downloads", false);
|
||||||
|
|
||||||
|
expect(Notification).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("does nothing when folder is missing", () => {
|
||||||
|
notifyDownloadFinished(undefined, true);
|
||||||
|
|
||||||
|
expect(Notification).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("creates and shows notification", () => {
|
||||||
|
notifyDownloadFinished("/downloads");
|
||||||
|
|
||||||
|
expect(Notification).toHaveBeenCalledWith({
|
||||||
|
title: "Freedom Loader",
|
||||||
|
body: "Your download is complete, click here to open it.",
|
||||||
|
icon: "/icons/confirm.png",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockOn).toHaveBeenCalledWith(
|
||||||
|
"click",
|
||||||
|
expect.any(Function)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockShow).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("opens folder when notification is clicked", () => {
|
||||||
|
notifyDownloadFinished("/downloads");
|
||||||
|
|
||||||
|
const callback = mockOn.mock.calls[0][1];
|
||||||
|
callback();
|
||||||
|
|
||||||
|
expect(shell.openPath).toHaveBeenCalledWith("/downloads");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("notifyCookiesBrowserError", () => {
|
||||||
|
test("creates notification", () => {
|
||||||
|
notifyCookiesBrowserError();
|
||||||
|
|
||||||
|
expect(Notification).toHaveBeenCalledWith({
|
||||||
|
title: "Cookies Error",
|
||||||
|
body: "Unable to retrieve cookies. Please log in to your browser and click here to view the tutorial.",
|
||||||
|
icon: "/icons/error.png",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockShow).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("opens tutorial when clicked", () => {
|
||||||
|
notifyCookiesBrowserError();
|
||||||
|
|
||||||
|
const callback = mockOn.mock.calls[0][1];
|
||||||
|
callback();
|
||||||
|
|
||||||
|
expect(shell.openExternal).toHaveBeenCalledWith(
|
||||||
|
"https://youtube.com/shorts/cN9f4s1Mf88?si=519QCVd_-fzJqRf1"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("notifyFirefoxBrowserMissing", () => {
|
||||||
|
test("creates notification", () => {
|
||||||
|
notifyFirefoxBrowserMissing();
|
||||||
|
|
||||||
|
expect(Notification).toHaveBeenCalledWith({
|
||||||
|
title: "Firefox Missing",
|
||||||
|
body: "Firefox was not found on your system. Click here to follow the installation guide",
|
||||||
|
icon: "/icons/error.png",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockShow).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("opens installation guide when clicked", () => {
|
||||||
|
notifyFirefoxBrowserMissing();
|
||||||
|
|
||||||
|
const callback = mockOn.mock.calls[0][1];
|
||||||
|
callback();
|
||||||
|
|
||||||
|
expect(shell.openExternal).toHaveBeenCalledWith(
|
||||||
|
"https://youtube.com/shorts/cN9f4s1Mf88?si=519QCVd_-fzJqRf1"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user