1
1
mirror of https://github.com/neosubhamoy/neodlp.git synced 2026-03-22 18:15:50 +05:30

28 Commits

37 changed files with 1573 additions and 1677 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 KiB

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

BIN
.github/images/flathub/downloader.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

BIN
.github/images/flathub/settings.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 86 KiB

View File

@@ -1,10 +1,6 @@
### ✨ Changelog ### ✨ Changelog
- Added delay/sleep configuration settings (delay is now also configurable on search) - Added support for linux Flatpak (some features are unavailable on Flatpak build due to it's strict sandboxing, check 'Settings > Info > Health Check' section for more info)
- Added support for YouTube PO Token generation (based on [bgutil-ytdlp-pot-provider-rs](https://github.com/jim60105/bgutil-ytdlp-pot-provider-rs))
- Implemented custom app titlebar on windows and linux
- Further improved and persisted app logs (stored in [platform specific log directory](https://v2.tauri.app/plugin/logging/#persisting-logs))
- Fixed webview window creation is failing on wayland with nvidia gpu
- Other minor fixes and improvements - Other minor fixes and improvements
### 📝 Notes ### 📝 Notes
@@ -13,7 +9,7 @@
> Users are always adviced to complete/cancel all paused downloads before updating to a newer version, otherwise paused downloads may not resume properly and re-start from the begining. > Users are always adviced to complete/cancel all paused downloads before updating to a newer version, otherwise paused downloads may not resume properly and re-start from the begining.
> [!WARNING] > [!WARNING]
> Linux users make sure `yt-dlp` and `deno` is not installed in your distro (otherwise you will get package installation conflict). Don't worry, You can still use yt-dlp cli as before (the only difference is that now it will be installed and auto-updated by neo-dlp, which You can also disable from neo-dlp Settings if you don't want to auto-update yt-dlp) > Linux users make sure `yt-dlp`, `aria2c` and `deno` is not installed in your distro (otherwise you will get package installation conflict). Don't worry, You can still use yt-dlp cli as before (the only difference is that now it will be installed and auto-updated by neodlp, which You can also disable from neodlp Settings if you don't want to auto-update yt-dlp) (ignore this if you are installing AppImage/Flatpak)
> This is an Un-Signed Build (Windows doesn't trust this Certificate so, it may flag this as malicious software, in that case, disable Windows SmartScreen and Defender, install it, and then re-enable them) > This is an Un-Signed Build (Windows doesn't trust this Certificate so, it may flag this as malicious software, in that case, disable Windows SmartScreen and Defender, install it, and then re-enable them)
@@ -23,11 +19,11 @@
| yt-dlp (updateable) | ffmpeg | ffprobe | aria2c | deno | bgutil-pot-rs | | yt-dlp (updateable) | ffmpeg | ffprobe | aria2c | deno | bgutil-pot-rs |
| :---- | :---- | :---- | :---- | :---- | :---- | | :---- | :---- | :---- | :---- | :---- | :---- |
| v2026.02.17.233631 (nightly) | v7.1.1 | v7.1.1 | v1.37.0 | v2.6.10 | v0.7.1-1.2.2 | | v2026.03.03.162408 (nightly) | v7.1.1 | v7.1.1 | v1.37.0 | v2.7.4 | v0.7.2-1.3.0 |
> ‼️ Linux builds (deb, rpm) does not ships with `ffmpeg` and `ffprobe` (though it will be auto installed as a dependency by your package manager, if you are on fedora make sure to [enable rpmfusion free+nonfree repos](https://docs.fedoraproject.org/en-US/quick-docs/rpmfusion-setup/#_enabling_the_rpm_fusion_repositories_using_command_line_utilities) before installing the rpm package) > ‼️ Linux builds (deb, rpm) does not ships with `ffmpeg` and `ffprobe` (though it will be auto installed as a dependency by your package manager, if you are on fedora make sure to [enable rpmfusion free+nonfree repos](https://docs.fedoraproject.org/en-US/quick-docs/rpmfusion-setup/#_enabling_the_rpm_fusion_repositories_using_command_line_utilities) before installing the rpm package)
> ‼️ MacOS builds (dmg, app) does not ships with `aria2c`, If you want to use [aria2](https://formulae.brew.sh/formula/aria2) install it via [homebrew](https://brew.sh) > ‼️ MacOS builds (dmg, app) does not ships with `aria2c`, If you want to use [aria2](https://formulae.brew.sh/formula/aria2) install it via [homebrew](https://brew.sh) (though it will be auto installed as a dependency if you install neodlp via homebrew)
### ⬇️ Download Section ### ⬇️ Download Section
@@ -40,7 +36,7 @@
> 🪟 Windows x86_64 binary also works on ARM64 (Windows on ARM) devices with emulation (Not planning to release native Windows ARM64 build anytime soon as, x86_64 one works fine on ARM64 without noticeable performance impact) > 🪟 Windows x86_64 binary also works on ARM64 (Windows on ARM) devices with emulation (Not planning to release native Windows ARM64 build anytime soon as, x86_64 one works fine on ARM64 without noticeable performance impact)
> 🚫 Linux AppImage builds are experimental and does not support neodlp's browser intergration features due to it's sandboxed nature. Also, don't run the AppImage with portable (.home, .config) folders, it will break things (it is highly recommended to use native [deb, rpm, AUR] builds if possible for the full experiance, otherwise AppImages are good for trying out NeoDLP without installing) > 🚫 Linux AppImage builds are experimental and does not support neodlp's browser intergration features due to it's limitations. Also, don't run the AppImage with portable (.home, .config) folders, it will break things (it is highly recommended to use native [deb, rpm, AUR] builds if possible for the full experiance, otherwise AppImages are good for trying out NeoDLP without installing)
> ⚠️ MacOS ARM64 binary downloads are experimental and may not open on Apple Silicon Macs if downloaded from browser (You will get 'Damaged File' error) it's because the binaries are not signed (signing MacOS binaries requires 99$/year Apple Developer Account subscription, which I can't afford RN!) and Apple Silicon Macs don't allow unsigned apps (downloaded from browser) to be installed on the system. If you want to use NeoDLP on your Apple Silicon Macs, There are few ways you can bypass these restrictions: > ⚠️ MacOS ARM64 binary downloads are experimental and may not open on Apple Silicon Macs if downloaded from browser (You will get 'Damaged File' error) it's because the binaries are not signed (signing MacOS binaries requires 99$/year Apple Developer Account subscription, which I can't afford RN!) and Apple Silicon Macs don't allow unsigned apps (downloaded from browser) to be installed on the system. If you want to use NeoDLP on your Apple Silicon Macs, There are few ways you can bypass these restrictions:
> 1. Using our automated [Curl-Bash Installer](https://neodlp.neosubhamoy.com/download) (Recommended) > 1. Using our automated [Curl-Bash Installer](https://neodlp.neosubhamoy.com/download) (Recommended)

View File

@@ -13,6 +13,7 @@ Cross-platform Video/Audio Downloader Desktop App based on YT-DLP with Modern UI
> **🥰 Liked this project? Please consider giving it a Star (🌟) on github to show us your appreciation and help the algorythm recommend this project to even more awesome people like you!** > **🥰 Liked this project? Please consider giving it a Star (🌟) on github to show us your appreciation and help the algorythm recommend this project to even more awesome people like you!**
[![winget version](https://img.shields.io/winget/v/neosubhamoy.neodlp?color=lime-green&style=flat-square)](https://github.com/microsoft/winget-pkgs/tree/master/manifests/n/neosubhamoy/neodlp) [![winget version](https://img.shields.io/winget/v/neosubhamoy.neodlp?color=lime-green&style=flat-square)](https://github.com/microsoft/winget-pkgs/tree/master/manifests/n/neosubhamoy/neodlp)
[![flathub version](https://img.shields.io/flathub/v/com.neosubhamoy.neodlp?color=lime-green&style=flat-square)](https://flathub.org/en/apps/com.neosubhamoy.neodlp)
[![aur version](https://img.shields.io/aur/version/neodlp?color=lime-green&style=flat-square)](https://aur.archlinux.org/packages/neodlp) [![aur version](https://img.shields.io/aur/version/neodlp?color=lime-green&style=flat-square)](https://aur.archlinux.org/packages/neodlp)
@@ -51,8 +52,8 @@ After installing the extension you can do the following directly from the browse
## 💻 Supported Platforms ## 💻 Supported Platforms
- Windows (10 / 11) - Windows (10 / 11)
- Linux (Debian / Fedora / RHEL / SUSE / Arch Linux base) - Linux (Mostly all modern distros)
- MacOS (>11) - MacOS (>=11)
## 🤝 External Dependencies ## 🤝 External Dependencies
@@ -85,8 +86,9 @@ After installing the extension you can do the following directly from the browse
| Windows x86_64 / ARM64 | WinGet | `winget install neosubhamoy.neodlp` | | Windows x86_64 / ARM64 | WinGet | `winget install neosubhamoy.neodlp` |
| MacOS x86_64 / ARM64 | Homebrew | `brew install neosubhamoy/tap/neodlp` | | MacOS x86_64 / ARM64 | Homebrew | `brew install neosubhamoy/tap/neodlp` |
| MacOS x86_64 / ARM64 | Curl-Bash Installer | `curl -sSL https://neodlp.neosubhamoy.com/macos_installer.sh \| bash` | | MacOS x86_64 / ARM64 | Curl-Bash Installer | `curl -sSL https://neodlp.neosubhamoy.com/macos_installer.sh \| bash` |
| Linux x86_64 / ARM64 | Curl-Bash Installer | `curl -sSL https://neodlp.neosubhamoy.com/linux_installer.sh \| bash` | | Linux x86_64 / ARM64 (Flatpak) | Flathub | `flatpak install flathub com.neosubhamoy.neodlp` |
| Arch Linux x86_64 / ARM64 | AUR | `yay -S neodlp` or `paru -S neodlp` | | Linux x86_64 / ARM64 (Native) | Curl-Bash Installer | `curl -sSL https://neodlp.neosubhamoy.com/linux_installer.sh \| bash` |
| Arch Linux x86_64 / ARM64 (Native) | AUR | `yay -S neodlp` or `paru -S neodlp` |
## 🧪 Package Testing Status ## 🧪 Package Testing Status

View File

@@ -2,7 +2,7 @@
Type=Application Type=Application
Name=NeoDLP Name=NeoDLP
Comment=Modern video/audio downloader based on yt-dlp with browser integration. Comment=Modern feature-rich video/audio downloader based on yt-dlp.
Icon=com.neosubhamoy.neodlp Icon=com.neosubhamoy.neodlp
Exec=neodlp Exec=neodlp
Terminal=false Terminal=false

View File

@@ -2,30 +2,45 @@
<component type="desktop-application"> <component type="desktop-application">
<id>com.neosubhamoy.neodlp</id> <id>com.neosubhamoy.neodlp</id>
<name>NeoDLP</name> <name>NeoDLP</name>
<summary>Modern video/audio downloader based on yt-dlp with browser integration</summary> <summary>Modern feature-rich video/audio downloader based on yt-dlp</summary>
<developer_name>Subhamoy Biswas</developer_name> <developer id="com.neosubhamoy">
<name>Subhamoy Biswas</name>
</developer>
<metadata_license>CC0-1.0</metadata_license> <metadata_license>CC0-1.0</metadata_license>
<project_license>MIT</project_license> <project_license>MIT</project_license>
<url type="homepage">https://neodlp.neosubhamoy.com</url> <url type="homepage">https://neodlp.neosubhamoy.com</url>
<url type="vcs-browser">https://github.com/neosubhamoy/neodlp</url>
<url type="bugtracker">https://github.com/neosubhamoy/neodlp/issues</url> <url type="bugtracker">https://github.com/neosubhamoy/neodlp/issues</url>
<description> <description>
<p> <p>
NeoDLP is a cross-platform desktop application designed for downloading videos and audio from various online sources based on yt-dlp. NeoDLP is a cross-platform desktop application designed for downloading videos and audio from various online sources based on yt-dlp.
It offers modern user interface, lots of customization options and seamless browser integration. It offers modern user interface, lots of features and customization options.
</p> </p>
</description> </description>
<launchable type="desktop-id">com.neosubhamoy.neodlp.desktop</launchable> <launchable type="desktop-id">com.neosubhamoy.neodlp.desktop</launchable>
<screenshots> <screenshots>
<screenshot type="default"> <screenshot type="default">
<image>https://raw.githubusercontent.com/neosubhamoy/neodlp/main/.github/images/downloader.png</image> <image>https://raw.githubusercontent.com/neosubhamoy/neodlp/main/.github/images/flathub/downloader.png</image>
<caption>NeoDLP Downloader</caption> <caption>Downloader page of NeoDLP</caption>
</screenshot>
<screenshot>
<image>https://raw.githubusercontent.com/neosubhamoy/neodlp/main/.github/images/flathub/completed-downloads.png</image>
<caption>Completed downloads page of NeoDLP</caption>
</screenshot>
<screenshot>
<image>https://raw.githubusercontent.com/neosubhamoy/neodlp/main/.github/images/flathub/ongoing-downloads.png</image>
<caption>Ongoing downloads page of NeoDLP</caption>
</screenshot>
<screenshot>
<image>https://raw.githubusercontent.com/neosubhamoy/neodlp/main/.github/images/flathub/settings.png</image>
<caption>Settings page of NeoDLP</caption>
</screenshot> </screenshot>
</screenshots> </screenshots>
<content_rating type="oars-1.1" /> <content_rating type="oars-1.1" />
<releases> <releases>
<release version="0.4.2" date="2026-02-23"> <release version="0.4.3" date="2026-03-07">
<url type="details">https://github.com/neosubhamoy/neodlp/releases/tag/v0.4.2</url> <url type="details">https://github.com/neosubhamoy/neodlp/releases/tag/v0.4.3</url>
</release> </release>
</releases> </releases>
</component> </component>

1737
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "neodlp", "name": "neodlp",
"private": true, "private": true,
"version": "0.4.1", "version": "0.4.3",
"description": "Cross-platform Video/Audio Downloader Desktop App based on YT-DLP with Modern UI and Browser Integration", "description": "Cross-platform Video/Audio Downloader Desktop App based on YT-DLP with Modern UI and Browser Integration",
"type": "module", "type": "module",
"scripts": { "scripts": {
@@ -21,11 +21,11 @@
}, },
"dependencies": { "dependencies": {
"@hookform/resolvers": "^5.2.2", "@hookform/resolvers": "^5.2.2",
"@tanstack/devtools-vite": "^0.5.1", "@tanstack/devtools-vite": "^0.6.0",
"@tanstack/react-devtools": "^0.9.6", "@tanstack/react-devtools": "^0.10.0",
"@tanstack/react-pacer": "^0.20.0", "@tanstack/react-pacer": "^0.20.0",
"@tanstack/react-pacer-devtools": "^0.5.2", "@tanstack/react-pacer-devtools": "^0.5.5",
"@tanstack/react-query": "^5.90.21", "@tanstack/react-query": "^5.91.2",
"@tanstack/react-query-devtools": "^5.91.3", "@tanstack/react-query-devtools": "^5.91.3",
"@tauri-apps/api": "^2.10.1", "@tauri-apps/api": "^2.10.1",
"@tauri-apps/plugin-clipboard-manager": "^2.3.2", "@tauri-apps/plugin-clipboard-manager": "^2.3.2",
@@ -41,32 +41,30 @@
"@tauri-apps/plugin-updater": "^2.10.0", "@tauri-apps/plugin-updater": "^2.10.0",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"lucide-react": "^0.574.0", "lucide-react": "^0.577.0",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"radix-ui": "^1.4.3", "radix-ui": "^1.4.3",
"react": "^19.2.4", "react": "^19.2.4",
"react-dom": "^19.2.4", "react-dom": "^19.2.4",
"react-hook-form": "^7.71.1", "react-hook-form": "^7.71.2",
"react-resizable-panels": "^4.6.4", "react-resizable-panels": "^4.7.3",
"react-router-dom": "^7.13.0", "react-router-dom": "^7.13.1",
"sonner": "^2.0.7", "sonner": "^2.0.7",
"tailwind-merge": "^3.4.1", "tailwind-merge": "^3.5.0",
"ulid": "^3.0.2", "ulid": "^3.0.2",
"zod": "^4.3.6", "zod": "^4.3.6",
"zustand": "^5.0.11" "zustand": "^5.0.12"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4.2.0", "@tailwindcss/vite": "^4.2.2",
"@tailwindcss/vite": "^4.2.0", "@tauri-apps/cli": "^2.10.1",
"@tauri-apps/cli": "^2.10.0", "@types/node": "^25.5.0",
"@types/node": "^25.2.3",
"@types/react": "^19.2.14", "@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.4", "@vitejs/plugin-react": "^6.0.1",
"postcss": "^8.5.6", "tailwindcss": "^4.2.2",
"tailwindcss": "^4.2.0",
"tw-animate-css": "^1.4.0", "tw-animate-css": "^1.4.0",
"typescript": "~5.9.3", "typescript": "~5.9.3",
"vite": "^7.3.1" "vite": "^8.0.1"
} }
} }

View File

@@ -1,5 +0,0 @@
export default {
plugins: {
"@tailwindcss/postcss": {},
},
}

View File

@@ -16,11 +16,11 @@ const targetPlatform = process.argv[2];
const targetBin = process.argv[3]; const targetBin = process.argv[3];
const versions = { const versions = {
'yt-dlp': 'latest', 'yt-dlp': '2026.03.17.232108',
'ffmpeg-ffprobe': 'latest', 'ffmpeg-ffprobe': 'latest',
'deno': 'latest', 'deno': '2.7.7',
'aria2c': '1.37.0', 'aria2c': '1.37.0',
'neodlp-pot': 'latest' 'neodlp-pot': '0.8.1'
}; };
const binaries = { const binaries = {
@@ -239,7 +239,7 @@ const binaries = {
{ {
name: 'deno-x86_64-pc-windows-msvc', name: 'deno-x86_64-pc-windows-msvc',
platform: 'win32', platform: 'win32',
url: `https://github.com/denoland/deno/releases${versions['deno'] === 'latest' ? '/latest' : ''}/download${versions['deno'] !== 'latest' ? '/'+versions['deno'] : ''}/deno-x86_64-pc-windows-msvc.zip`, url: `https://github.com/denoland/deno/releases${versions['deno'] === 'latest' ? '/latest' : ''}/download${versions['deno'] !== 'latest' ? '/v'+versions['deno'] : ''}/deno-x86_64-pc-windows-msvc.zip`,
src: path.join(downloadDir, 'deno-x86_64-pc-windows-msvc.zip'), src: path.join(downloadDir, 'deno-x86_64-pc-windows-msvc.zip'),
dest: null, dest: null,
archive: { archive: {
@@ -259,7 +259,7 @@ const binaries = {
{ {
name: 'deno-x86_64-unknown-linux-gnu', name: 'deno-x86_64-unknown-linux-gnu',
platform: 'linux', platform: 'linux',
url: `https://github.com/denoland/deno/releases${versions['deno'] === 'latest' ? '/latest' : ''}/download${versions['deno'] !== 'latest' ? '/'+versions['deno'] : ''}/deno-x86_64-unknown-linux-gnu.zip`, url: `https://github.com/denoland/deno/releases${versions['deno'] === 'latest' ? '/latest' : ''}/download${versions['deno'] !== 'latest' ? '/v'+versions['deno'] : ''}/deno-x86_64-unknown-linux-gnu.zip`,
src: path.join(downloadDir, 'deno-x86_64-unknown-linux-gnu.zip'), src: path.join(downloadDir, 'deno-x86_64-unknown-linux-gnu.zip'),
dest: null, dest: null,
archive: { archive: {
@@ -279,7 +279,7 @@ const binaries = {
{ {
name: 'deno-aarch64-unknown-linux-gnu', name: 'deno-aarch64-unknown-linux-gnu',
platform: 'linux', platform: 'linux',
url: `https://github.com/denoland/deno/releases${versions['deno'] === 'latest' ? '/latest' : ''}/download${versions['deno'] !== 'latest' ? '/'+versions['deno'] : ''}/deno-aarch64-unknown-linux-gnu.zip`, url: `https://github.com/denoland/deno/releases${versions['deno'] === 'latest' ? '/latest' : ''}/download${versions['deno'] !== 'latest' ? '/v'+versions['deno'] : ''}/deno-aarch64-unknown-linux-gnu.zip`,
src: path.join(downloadDir, 'deno-aarch64-unknown-linux-gnu.zip'), src: path.join(downloadDir, 'deno-aarch64-unknown-linux-gnu.zip'),
dest: null, dest: null,
archive: { archive: {
@@ -299,7 +299,7 @@ const binaries = {
{ {
name: 'deno-x86_64-apple-darwin', name: 'deno-x86_64-apple-darwin',
platform: 'darwin', platform: 'darwin',
url: `https://github.com/denoland/deno/releases${versions['deno'] === 'latest' ? '/latest' : ''}/download${versions['deno'] !== 'latest' ? '/'+versions['deno'] : ''}/deno-x86_64-apple-darwin.zip`, url: `https://github.com/denoland/deno/releases${versions['deno'] === 'latest' ? '/latest' : ''}/download${versions['deno'] !== 'latest' ? '/v'+versions['deno'] : ''}/deno-x86_64-apple-darwin.zip`,
src: path.join(downloadDir, 'deno-x86_64-apple-darwin.zip'), src: path.join(downloadDir, 'deno-x86_64-apple-darwin.zip'),
dest: null, dest: null,
archive: { archive: {
@@ -319,7 +319,7 @@ const binaries = {
{ {
name: 'deno-aarch64-apple-darwin', name: 'deno-aarch64-apple-darwin',
platform: 'darwin', platform: 'darwin',
url: `https://github.com/denoland/deno/releases${versions['deno'] === 'latest' ? '/latest' : ''}/download${versions['deno'] !== 'latest' ? '/'+versions['deno'] : ''}/deno-aarch64-apple-darwin.zip`, url: `https://github.com/denoland/deno/releases${versions['deno'] === 'latest' ? '/latest' : ''}/download${versions['deno'] !== 'latest' ? '/v'+versions['deno'] : ''}/deno-aarch64-apple-darwin.zip`,
src: path.join(downloadDir, 'deno-aarch64-apple-darwin.zip'), src: path.join(downloadDir, 'deno-aarch64-apple-darwin.zip'),
dest: null, dest: null,
archive: { archive: {
@@ -403,7 +403,7 @@ const binaries = {
{ {
name: 'neodlp-pot-x86_64-pc-windows-msvc', name: 'neodlp-pot-x86_64-pc-windows-msvc',
platform: 'win32', platform: 'win32',
url: `https://github.com/jim60105/bgutil-ytdlp-pot-provider-rs/releases${versions['neodlp-pot'] === 'latest' ? '/latest' : ''}/download${versions['neodlp-pot'] !== 'latest' ? '/'+versions['neodlp-pot'] : ''}/bgutil-pot-windows-x86_64.exe`, url: `https://github.com/jim60105/bgutil-ytdlp-pot-provider-rs/releases${versions['neodlp-pot'] === 'latest' ? '/latest' : ''}/download${versions['neodlp-pot'] !== 'latest' ? '/v'+versions['neodlp-pot'] : ''}/bgutil-pot-windows-x86_64.exe`,
src: path.join(downloadDir, 'bgutil-pot-windows-x86_64.exe'), src: path.join(downloadDir, 'bgutil-pot-windows-x86_64.exe'),
dest: [ dest: [
path.join(binDir, 'neodlp-pot-x86_64-pc-windows-msvc.exe') path.join(binDir, 'neodlp-pot-x86_64-pc-windows-msvc.exe')
@@ -416,7 +416,7 @@ const binaries = {
{ {
name: 'neodlp-pot-x86_64-unknown-linux-gnu', name: 'neodlp-pot-x86_64-unknown-linux-gnu',
platform: 'linux', platform: 'linux',
url: `https://github.com/jim60105/bgutil-ytdlp-pot-provider-rs/releases${versions['neodlp-pot'] === 'latest' ? '/latest' : ''}/download${versions['neodlp-pot'] !== 'latest' ? '/'+versions['neodlp-pot'] : ''}/bgutil-pot-linux-x86_64`, url: `https://github.com/jim60105/bgutil-ytdlp-pot-provider-rs/releases${versions['neodlp-pot'] === 'latest' ? '/latest' : ''}/download${versions['neodlp-pot'] !== 'latest' ? '/v'+versions['neodlp-pot'] : ''}/bgutil-pot-linux-x86_64`,
src: path.join(downloadDir, 'bgutil-pot-linux-x86_64'), src: path.join(downloadDir, 'bgutil-pot-linux-x86_64'),
dest: [ dest: [
path.join(binDir, 'neodlp-pot-x86_64-unknown-linux-gnu') path.join(binDir, 'neodlp-pot-x86_64-unknown-linux-gnu')
@@ -429,7 +429,7 @@ const binaries = {
{ {
name: 'neodlp-pot-aarch64-unknown-linux-gnu', name: 'neodlp-pot-aarch64-unknown-linux-gnu',
platform: 'linux', platform: 'linux',
url: `https://github.com/jim60105/bgutil-ytdlp-pot-provider-rs/releases${versions['neodlp-pot'] === 'latest' ? '/latest' : ''}/download${versions['neodlp-pot'] !== 'latest' ? '/'+versions['neodlp-pot'] : ''}/bgutil-pot-linux-aarch64`, url: `https://github.com/jim60105/bgutil-ytdlp-pot-provider-rs/releases${versions['neodlp-pot'] === 'latest' ? '/latest' : ''}/download${versions['neodlp-pot'] !== 'latest' ? '/v'+versions['neodlp-pot'] : ''}/bgutil-pot-linux-aarch64`,
src: path.join(downloadDir, 'bgutil-pot-linux-aarch64'), src: path.join(downloadDir, 'bgutil-pot-linux-aarch64'),
dest: [ dest: [
path.join(binDir, 'neodlp-pot-aarch64-unknown-linux-gnu') path.join(binDir, 'neodlp-pot-aarch64-unknown-linux-gnu')
@@ -442,7 +442,7 @@ const binaries = {
{ {
name: 'neodlp-pot-x86_64-apple-darwin', name: 'neodlp-pot-x86_64-apple-darwin',
platform: 'darwin', platform: 'darwin',
url: `https://github.com/jim60105/bgutil-ytdlp-pot-provider-rs/releases${versions['neodlp-pot'] === 'latest' ? '/latest' : ''}/download${versions['neodlp-pot'] !== 'latest' ? '/'+versions['neodlp-pot'] : ''}/bgutil-pot-macos-x86_64`, url: `https://github.com/jim60105/bgutil-ytdlp-pot-provider-rs/releases${versions['neodlp-pot'] === 'latest' ? '/latest' : ''}/download${versions['neodlp-pot'] !== 'latest' ? '/v'+versions['neodlp-pot'] : ''}/bgutil-pot-macos-x86_64`,
src: path.join(downloadDir, 'bgutil-pot-macos-x86_64'), src: path.join(downloadDir, 'bgutil-pot-macos-x86_64'),
dest: [ dest: [
path.join(binDir, 'neodlp-pot-x86_64-apple-darwin') path.join(binDir, 'neodlp-pot-x86_64-apple-darwin')
@@ -455,7 +455,7 @@ const binaries = {
{ {
name: 'neodlp-pot-aarch64-apple-darwin', name: 'neodlp-pot-aarch64-apple-darwin',
platform: 'darwin', platform: 'darwin',
url: `https://github.com/jim60105/bgutil-ytdlp-pot-provider-rs/releases${versions['neodlp-pot'] === 'latest' ? '/latest' : ''}/download${versions['neodlp-pot'] !== 'latest' ? '/'+versions['neodlp-pot'] : ''}/bgutil-pot-macos-aarch64`, url: `https://github.com/jim60105/bgutil-ytdlp-pot-provider-rs/releases${versions['neodlp-pot'] === 'latest' ? '/latest' : ''}/download${versions['neodlp-pot'] !== 'latest' ? '/v'+versions['neodlp-pot'] : ''}/bgutil-pot-macos-aarch64`,
src: path.join(downloadDir, 'bgutil-pot-macos-aarch64'), src: path.join(downloadDir, 'bgutil-pot-macos-aarch64'),
dest: [ dest: [
path.join(binDir, 'neodlp-pot-aarch64-apple-darwin') path.join(binDir, 'neodlp-pot-aarch64-apple-darwin')

890
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "neodlp" name = "neodlp"
version = "0.4.1" version = "0.4.3"
description = "Cross-platform Video/Audio Downloader Desktop App based on YT-DLP with Modern UI and Browser Integration" description = "Cross-platform Video/Audio Downloader Desktop App based on YT-DLP with Modern UI and Browser Integration"
authors = ["neosubhamoy <hey@neosubhamoy.com>"] authors = ["neosubhamoy <hey@neosubhamoy.com>"]
edition = "2021" edition = "2021"

View File

@@ -45,22 +45,6 @@
"path": "**" "path": "**"
} }
] ]
},
{
"identifier": "opener:allow-reveal-item-in-dir",
"allow": [
{
"path": "**"
}
]
},
{
"identifier": "opener:allow-open-url",
"allow": [
{
"url": "https:*"
}
]
} }
] ]
} }

View File

@@ -40,6 +40,11 @@
"args": true, "args": true,
"sidecar": true "sidecar": true
}, },
{
"name": "yt-dlp",
"cmd": "yt-dlp",
"args": true
},
{ {
"name": "ffmpeg", "name": "ffmpeg",
"cmd": "ffmpeg", "cmd": "ffmpeg",
@@ -66,9 +71,9 @@
"args": true "args": true
}, },
{ {
"name": "sh", "name": "sh",
"cmd": "sh", "cmd": "sh",
"args": true "args": true
} }
] ]
}, },
@@ -105,6 +110,11 @@
"args": true, "args": true,
"sidecar": true "sidecar": true
}, },
{
"name": "yt-dlp",
"cmd": "yt-dlp",
"args": true
},
{ {
"name": "ffmpeg", "name": "ffmpeg",
"cmd": "ffmpeg", "cmd": "ffmpeg",
@@ -119,6 +129,21 @@
"name": "deno", "name": "deno",
"cmd": "deno", "cmd": "deno",
"args": true "args": true
},
{
"name": "pkexec",
"cmd": "pkexec",
"args": true
},
{
"name": "powershell",
"cmd": "powershell",
"args": true
},
{
"name": "sh",
"cmd": "sh",
"args": true
} }
] ]
} }

View File

@@ -1,6 +1,6 @@
from __future__ import annotations from __future__ import annotations
__version__ = '1.2.2' __version__ = '0.8.1'
import abc import abc
import json import json
@@ -52,28 +52,32 @@ class BgUtilPTPBase(PoTokenProvider, abc.ABC):
def _get_attestation(self, webpage: str | None): def _get_attestation(self, webpage: str | None):
if not webpage: if not webpage:
return None return None
raw_challenge_data = self.ie._search_regex( raw_cd = (
r'''(?sx)window\.ytAtR\s*=\s*(?P<raw_cd>(?P<q>['"]) traverse_obj(
(?: self.ie._search_regex(
\\.| r'''(?sx)window\s*\.\s*ytAtN\s*\(\s*
(?!(?P=q)). (?P<js>\{.+?}\s*)
)* \s*\)\s*;''',
(?P=q))\s*;''', webpage,
webpage, 'ytAtN challenge',
'raw challenge data', default=None),
default=None, ({js_to_json}, {json.loads}, 'R'))
group='raw_cd', or traverse_obj(
) self.ie._search_regex(
att_txt = traverse_obj( r'''(?sx)window\.ytAtR\s*=\s*(?P<raw_cd>(?P<q>['"])
raw_challenge_data, (?:
({js_to_json}, {json.loads}, {json.loads}, 'bgChallenge') \\.|
) (?!(?P=q)).
if not att_txt: )*
self.logger.warning( (?P=q))\s*;''',
'Failed to extract initial attestation from the webpage' webpage,
) 'ytAtR challenge',
return None default=None),
return att_txt ({js_to_json}, {json.loads})))
if att_txt := traverse_obj(raw_cd, ({json.loads}, 'bgChallenge')):
return att_txt
self.logger.warning('Failed to extract initial attestation from the webpage')
return None
__all__ = ['__version__'] __all__ = ['__version__']

View File

@@ -31,6 +31,7 @@ use tokio::{
time::sleep, time::sleep,
}; };
use tokio_tungstenite::accept_async; use tokio_tungstenite::accept_async;
use log::{info, error};
struct ImageCache(StdMutex<HashMap<String, String>>); struct ImageCache(StdMutex<HashMap<String, String>>);
@@ -357,21 +358,48 @@ async fn open_file_with_app(
) -> Result<(), String> { ) -> Result<(), String> {
if let Some(name) = &app_name { if let Some(name) = &app_name {
if name == "explorer" { if name == "explorer" {
println!("Revealing file: {} in explorer", file_path); info!("Revealing file: {} in explorer", file_path);
return app_handle return app_handle
.opener() .opener()
.reveal_item_in_dir(file_path) .reveal_item_in_dir(file_path)
.map_err(|e| e.to_string()); .map_err(|e| {
error!("Failed to reveal file in explorer: {}", e);
e.to_string()
});
} }
println!("Opening file: {} with app: {}", file_path, name); info!("Opening file: {} with app: {}", file_path, name);
} else { } else {
println!("Opening file: {} with default app", file_path); info!("Opening file: {} with default app", file_path);
} }
app_handle app_handle
.opener() .opener()
.open_path(file_path, app_name) .open_path(file_path, app_name)
.map_err(|e| e.to_string()) .map_err(|e| {
error!("Failed to open file: {}", e);
e.to_string()
})
}
#[tauri::command]
async fn open_link_with_app(
app_handle: tauri::AppHandle,
url: String,
app_name: Option<String>,
) -> Result<(), String> {
if let Some(name) = &app_name {
info!("Opening link: {} with app: {}", url, name);
} else {
info!("Opening link: {} with default app", url);
}
app_handle
.opener()
.open_url(url, app_name)
.map_err(|e| {
error!("Failed to open link: {}", e);
e.to_string()
})
} }
#[tauri::command] #[tauri::command]
@@ -619,6 +647,7 @@ pub async fn run() {
kill_all_process, kill_all_process,
fetch_image, fetch_image,
open_file_with_app, open_file_with_app,
open_link_with_app,
list_ongoing_downloads, list_ongoing_downloads,
pause_ongoing_downloads, pause_ongoing_downloads,
send_to_extension, send_to_extension,

View File

@@ -2,7 +2,7 @@
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "NeoDLP", "productName": "NeoDLP",
"mainBinaryName": "neodlp", "mainBinaryName": "neodlp",
"version": "0.4.1", "version": "0.4.3",
"identifier": "com.neosubhamoy.neodlp", "identifier": "com.neosubhamoy.neodlp",
"build": { "build": {
"beforeDevCommand": "npm run dev", "beforeDevCommand": "npm run dev",

View File

@@ -1,8 +1,8 @@
{ {
"identifier": "com.neosubhamoy.neodlp", "identifier": "com.neosubhamoy.neodlp",
"build": { "build": {
"beforeDevCommand": "cargo build --manifest-path=./src-tauri/msghost/Cargo.toml && npm run dev", "beforeDevCommand": "npm run dev",
"beforeBuildCommand": "cargo build --release --manifest-path=./src-tauri/msghost/Cargo.toml && npm run build", "beforeBuildCommand": "npm run build",
"devUrl": "http://localhost:1420", "devUrl": "http://localhost:1420",
"frontendDist": "../dist" "frontendDist": "../dist"
}, },
@@ -37,12 +37,7 @@
"icons/icon.ico" "icons/icon.ico"
], ],
"externalBin": [ "externalBin": [
"binaries/yt-dlp", "binaries/deno"
"binaries/ffmpeg",
"binaries/ffprobe",
"binaries/aria2c",
"binaries/deno",
"binaries/neodlp-pot"
], ],
"resources": { "resources": {
"resources/plugins/yt-dlp-plugins/": "yt-dlp-plugins/" "resources/plugins/yt-dlp-plugins/": "yt-dlp-plugins/"

View File

@@ -4,7 +4,7 @@ import { AppContext } from "@/providers/appContextProvider";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { arch, exeExtension } from "@tauri-apps/plugin-os"; import { arch, exeExtension } from "@tauri-apps/plugin-os";
import { downloadDir, join, resourceDir, tempDir } from "@tauri-apps/api/path"; import { downloadDir, join, resourceDir, tempDir } from "@tauri-apps/api/path";
import { useBasePathsStore, useCurrentVideoMetadataStore, useDownloaderPageStatesStore, useDownloadStatesStore, useKvPairsStatesStore, useSettingsPageStatesStore } from "@/services/store"; import { useBasePathsStore, useCurrentVideoMetadataStore, useDownloaderPageStatesStore, useDownloadStatesStore, useEnvironmentStore, useKvPairsStatesStore, useSettingsPageStatesStore } from "@/services/store";
import { isObjEmpty} from "@/utils"; import { isObjEmpty} from "@/utils";
import { Command } from "@tauri-apps/plugin-shell"; import { Command } from "@tauri-apps/plugin-shell";
import { useUpdateDownloadStatus } from "@/services/mutations"; import { useUpdateDownloadStatus } from "@/services/mutations";
@@ -40,6 +40,10 @@ export default function App({ children }: { children: React.ReactNode }) {
const setDownloadStates = useDownloadStatesStore((state) => state.setDownloadStates); const setDownloadStates = useDownloadStatesStore((state) => state.setDownloadStates);
const setPath = useBasePathsStore((state) => state.setPath); const setPath = useBasePathsStore((state) => state.setPath);
const setIsFlatpak = useEnvironmentStore((state) => state.setIsFlatpak);
const setIsAppimage = useEnvironmentStore((state) => state.setIsAppimage);
const setAppDirPath = useEnvironmentStore((state) => state.setAppDirPath);
const setIsUsingDefaultSettings = useSettingsPageStatesStore((state) => state.setIsUsingDefaultSettings); const setIsUsingDefaultSettings = useSettingsPageStatesStore((state) => state.setIsUsingDefaultSettings);
const setSettingsKey = useSettingsPageStatesStore((state) => state.setSettingsKey); const setSettingsKey = useSettingsPageStatesStore((state) => state.setSettingsKey);
const appVersion = useSettingsPageStatesStore(state => state.appVersion); const appVersion = useSettingsPageStatesStore(state => state.appVersion);
@@ -122,6 +126,26 @@ export default function App({ children }: { children: React.ReactNode }) {
}; };
}, [stopPotServer]); }, [stopPotServer]);
// Detect sandbox environments
useEffect(() => {
const detectEnvironment = async () => {
try {
const isFlatpak = await invoke<boolean>('is_flatpak');
const appimagePath = await invoke<string | null>('get_appimage_path');
console.log('Environment detection results:', { isFlatpak, appimagePath });
if (isFlatpak) setIsFlatpak(true);
if (appimagePath) {
setIsAppimage(true);
setAppDirPath(appimagePath);
}
} catch (e) {
console.error('Failed to detect environment:', e);
}
}
detectEnvironment();
}, [setIsFlatpak, setIsAppimage, setAppDirPath]);
// Listen for websocket messages // Listen for websocket messages
useEffect(() => { useEffect(() => {
const unlisten = listen<WebSocketMessage>('websocket-message', (event) => { const unlisten = listen<WebSocketMessage>('websocket-message', (event) => {
@@ -233,7 +257,10 @@ export default function App({ children }: { children: React.ReactNode }) {
const fetchYtDlpVersion = async () => { const fetchYtDlpVersion = async () => {
setIsFetchingYtDlpVersion(true); setIsFetchingYtDlpVersion(true);
try { try {
const command = Command.sidecar('binaries/yt-dlp', ['--version']); const isFlatpak = await invoke<boolean>('is_flatpak');
const command = isFlatpak
? Command.create('sh', ['-c', `yt-dlp --version`])
: Command.sidecar('binaries/yt-dlp', ['--version']);
const output = await command.execute(); const output = await command.execute();
if (output.code === 0) { if (output.code === 0) {
const version = output.stdout.trim(); const version = output.stdout.trim();

View File

@@ -4,7 +4,7 @@ import { AspectRatio } from "@/components/ui/aspect-ratio";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { toast } from "sonner"; import { toast } from "sonner";
import { useCurrentVideoMetadataStore, useDownloadActionStatesStore, useLibraryPageStatesStore } from "@/services/store"; import { useCurrentVideoMetadataStore, useDownloadActionStatesStore, useEnvironmentStore, useLibraryPageStatesStore } from "@/services/store";
import { formatBitrate, formatCodec, formatDurationString, formatFileSize, paginate } from "@/utils"; import { formatBitrate, formatCodec, formatDurationString, formatFileSize, paginate } from "@/utils";
import { ArrowUpRightIcon, AudioLines, CircleArrowDown, Clock, File, FileAudio2, FileQuestion, FileVideo2, FolderInput, ListVideo, Music, Play, Search, Trash2, Video } from "lucide-react"; import { ArrowUpRightIcon, AudioLines, CircleArrowDown, Clock, File, FileAudio2, FileQuestion, FileVideo2, FolderInput, ListVideo, Music, Play, Search, Trash2, Video } from "lucide-react";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
@@ -33,6 +33,8 @@ export function CompletedDownload({ state }: CompletedDownloadProps) {
const downloadActions = useDownloadActionStatesStore(state => state.downloadActions); const downloadActions = useDownloadActionStatesStore(state => state.downloadActions);
const setIsDeleteFileChecked = useDownloadActionStatesStore(state => state.setIsDeleteFileChecked); const setIsDeleteFileChecked = useDownloadActionStatesStore(state => state.setIsDeleteFileChecked);
const isFlatpak = useEnvironmentStore(state => state.isFlatpak);
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const downloadStateDeleter = useDeleteDownloadState(); const downloadStateDeleter = useDeleteDownloadState();
const navigate = useNavigate(); const navigate = useNavigate();
@@ -276,10 +278,12 @@ export function CompletedDownload({ state }: CompletedDownloadProps) {
<Play className="w-4 h-4" /> <Play className="w-4 h-4" />
Open Open
</Button> </Button>
<Button size="sm" variant="outline" onClick={() => openFile(state.filepath, 'explorer')}> {!isFlatpak && (
<FolderInput className="w-4 h-4" /> <Button size="sm" variant="outline" onClick={() => openFile(state.filepath, 'explorer')}>
Reveal <FolderInput className="w-4 h-4" />
</Button> Reveal
</Button>
)}
<Button size="sm" variant="outline" onClick={() => handleSearch(state.url, state.playlist_id ? true : false)}> <Button size="sm" variant="outline" onClick={() => handleSearch(state.url, state.playlist_id ? true : false)}>
<Search className="w-4 h-4" /> <Search className="w-4 h-4" />
Search Search

View File

@@ -1,13 +1,13 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { Card } from "@/components/ui/card"; import { Card } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { useBasePathsStore, useDownloaderPageStatesStore, useDownloadStatesStore, useSettingsPageStatesStore } from "@/services/store"; import { useBasePathsStore, useDownloaderPageStatesStore, useDownloadStatesStore, useEnvironmentStore, useSettingsPageStatesStore } from "@/services/store";
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { toast } from "sonner"; import { toast } from "sonner";
import { BadgeCheck, BellRing, BrushCleaning, Bug, Cookie, ExternalLink, FilePen, FileVideo, Folder, FolderOpen, Github, Globe, Heart, Info, KeyRound, Loader2, LucideIcon, Mail, Monitor, Moon, Package, Scale, ShieldMinus, SquareTerminal, Sun, Terminal, Timer, Trash, TriangleAlert, WandSparkles, Wifi, Wrench } from "lucide-react"; import { BadgeCheck, BellRing, BrushCleaning, Bug, CircleCheck, Cookie, ExternalLink, FilePen, FileVideo, Folder, FolderOpen, Github, Globe, Heart, Info, KeyRound, Loader2, LucideIcon, Mail, Monitor, Moon, Package, Scale, ShieldMinus, SquareTerminal, Sun, Terminal, Timer, Trash, TriangleAlert, WandSparkles, Wifi, Wrench } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { Slider } from "@/components/ui/slider"; import { Slider } from "@/components/ui/slider";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
@@ -282,9 +282,11 @@ function AppAppearanceSettings() {
); );
} }
function AppFolderSettings() { function AppFilesystemSettings() {
const { saveSettingsKey } = useSettings(); const { saveSettingsKey } = useSettings();
const isFlatpak = useEnvironmentStore(state => state.isFlatpak);
const formResetTrigger = useSettingsPageStatesStore(state => state.formResetTrigger); const formResetTrigger = useSettingsPageStatesStore(state => state.formResetTrigger);
const acknowledgeFormReset = useSettingsPageStatesStore(state => state.acknowledgeFormReset); const acknowledgeFormReset = useSettingsPageStatesStore(state => state.acknowledgeFormReset);
@@ -293,6 +295,8 @@ function AppFolderSettings() {
const setPath = useBasePathsStore((state) => state.setPath); const setPath = useBasePathsStore((state) => state.setPath);
const filenameTemplate = useSettingsPageStatesStore(state => state.settings.filename_template); const filenameTemplate = useSettingsPageStatesStore(state => state.settings.filename_template);
const windowsFilenames = useSettingsPageStatesStore(state => state.settings.windows_filenames);
const restrictFilenames = useSettingsPageStatesStore(state => state.settings.restrict_filenames);
const downloadStates = useDownloadStatesStore(state => state.downloadStates); const downloadStates = useDownloadStatesStore(state => state.downloadStates);
const ongoingDownloads = downloadStates.filter(state => const ongoingDownloads = downloadStates.filter(state =>
@@ -364,6 +368,7 @@ function AppFolderSettings() {
<Input className="focus-visible:ring-0" type="text" placeholder="Select download directory" value={downloadDirPath ?? 'Unknown'} readOnly/> <Input className="focus-visible:ring-0" type="text" placeholder="Select download directory" value={downloadDirPath ?? 'Unknown'} readOnly/>
<Button <Button
variant="outline" variant="outline"
disabled={isFlatpak}
onClick={async () => { onClick={async () => {
try { try {
const folder = await open({ const folder = await open({
@@ -395,7 +400,7 @@ function AppFolderSettings() {
<AlertDialogTrigger asChild> <AlertDialogTrigger asChild>
<Button <Button
variant="destructive" variant="destructive"
disabled={ongoingDownloads.length > 0} disabled={ongoingDownloads.length > 0 || isFlatpak}
> >
<BrushCleaning className="size-4" /> Clean <BrushCleaning className="size-4" /> Clean
</Button> </Button>
@@ -445,6 +450,26 @@ function AppFolderSettings() {
</form> </form>
</Form> </Form>
</div> </div>
<div className="sanitize-filenames">
<h3 className="font-semibold">Sanitize Filenames</h3>
<p className="text-xs text-muted-foreground mb-3">Make filenames windows-compatible, allow only ASCII characters and replace spaces with underscore (recommended, disabling it may cause issue with some downloads, also it may cause paused downloads to re-start from begining)</p>
<div className="flex items-center space-x-2 mb-3">
<Switch
id="windows-filenames"
checked={windowsFilenames}
onCheckedChange={(checked) => saveSettingsKey('windows_filenames', checked)}
/>
<Label htmlFor="windows-filenames">Windows Compatibility</Label>
</div>
<div className="flex items-center space-x-2">
<Switch
id="restrict-filenames"
checked={restrictFilenames}
onCheckedChange={(checked) => saveSettingsKey('restrict_filenames', checked)}
/>
<Label htmlFor="restrict-filenames">Force ASCII Only</Label>
</div>
</div>
</> </>
); );
} }
@@ -782,6 +807,8 @@ function AppNetworkSettings() {
function AppCookiesSettings() { function AppCookiesSettings() {
const { saveSettingsKey } = useSettings(); const { saveSettingsKey } = useSettings();
const isFlatpak = useEnvironmentStore(state => state.isFlatpak);
const useCookies = useSettingsPageStatesStore(state => state.settings.use_cookies); const useCookies = useSettingsPageStatesStore(state => state.settings.use_cookies);
const importCookiesFrom = useSettingsPageStatesStore(state => state.settings.import_cookies_from); const importCookiesFrom = useSettingsPageStatesStore(state => state.settings.import_cookies_from);
const cookiesBrowser = useSettingsPageStatesStore(state => state.settings.cookies_browser); const cookiesBrowser = useSettingsPageStatesStore(state => state.settings.cookies_browser);
@@ -798,7 +825,7 @@ function AppCookiesSettings() {
id="use-cookies" id="use-cookies"
checked={useCookies} checked={useCookies}
onCheckedChange={(checked) => saveSettingsKey('use_cookies', checked)} onCheckedChange={(checked) => saveSettingsKey('use_cookies', checked)}
disabled={useCustomCommands} disabled={useCustomCommands || isFlatpak}
/> />
<Label htmlFor="use-cookies">Use Cookies</Label> <Label htmlFor="use-cookies">Use Cookies</Label>
</div> </div>
@@ -807,7 +834,7 @@ function AppCookiesSettings() {
className="flex items-center gap-4" className="flex items-center gap-4"
value={importCookiesFrom} value={importCookiesFrom}
onValueChange={(value) => saveSettingsKey('import_cookies_from', value)} onValueChange={(value) => saveSettingsKey('import_cookies_from', value)}
disabled={!useCookies || useCustomCommands} disabled={!useCookies || useCustomCommands || isFlatpak}
> >
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<RadioGroupItem value="browser" id="cookies-browser" /> <RadioGroupItem value="browser" id="cookies-browser" />
@@ -823,7 +850,7 @@ function AppCookiesSettings() {
<Select <Select
value={cookiesBrowser} value={cookiesBrowser}
onValueChange={(value) => saveSettingsKey('cookies_browser', value)} onValueChange={(value) => saveSettingsKey('cookies_browser', value)}
disabled={importCookiesFrom !== "browser" || !useCookies || useCustomCommands} disabled={importCookiesFrom !== "browser" || !useCookies || useCustomCommands || isFlatpak}
> >
<SelectTrigger className="w-57.5 ring-0 focus:ring-0"> <SelectTrigger className="w-57.5 ring-0 focus:ring-0">
<SelectValue placeholder="Select browser to import cookies" /> <SelectValue placeholder="Select browser to import cookies" />
@@ -850,7 +877,7 @@ function AppCookiesSettings() {
<Input className="focus-visible:ring-0" type="text" placeholder="Select cookies text file" value={cookiesFile ?? ''} disabled={importCookiesFrom !== "file" || !useCookies} readOnly/> <Input className="focus-visible:ring-0" type="text" placeholder="Select cookies text file" value={cookiesFile ?? ''} disabled={importCookiesFrom !== "file" || !useCookies} readOnly/>
<Button <Button
variant="outline" variant="outline"
disabled={importCookiesFrom !== "file" || !useCookies || useCustomCommands} disabled={importCookiesFrom !== "file" || !useCookies || useCustomCommands || isFlatpak}
onClick={async () => { onClick={async () => {
try { try {
const file = await open({ const file = await open({
@@ -1249,6 +1276,8 @@ function AppDelaySettings() {
} }
function AppPoTokenSettings() { function AppPoTokenSettings() {
const isFlatpak = useEnvironmentStore(state => state.isFlatpak);
const formResetTrigger = useSettingsPageStatesStore(state => state.formResetTrigger); const formResetTrigger = useSettingsPageStatesStore(state => state.formResetTrigger);
const acknowledgeFormReset = useSettingsPageStatesStore(state => state.acknowledgeFormReset); const acknowledgeFormReset = useSettingsPageStatesStore(state => state.acknowledgeFormReset);
@@ -1320,7 +1349,7 @@ function AppPoTokenSettings() {
await stopPotServer(); await stopPotServer();
} }
}} }}
disabled={useCustomCommands || isStartingPotServer || isChangingPotServerPort} disabled={useCustomCommands || isStartingPotServer || isChangingPotServerPort || isFlatpak}
/> />
<Label htmlFor="use-potoken">Use PO Token</Label> <Label htmlFor="use-potoken">Use PO Token</Label>
</div> </div>
@@ -1346,7 +1375,7 @@ function AppPoTokenSettings() {
id="disable-innertube" id="disable-innertube"
checked={disableInnertube} checked={disableInnertube}
onCheckedChange={(checked) => saveSettingsKey('disable_innertube', checked)} onCheckedChange={(checked) => saveSettingsKey('disable_innertube', checked)}
disabled={useCustomCommands || !usePotoken} disabled={useCustomCommands || !usePotoken || isFlatpak}
/> />
</div> </div>
</div> </div>
@@ -1359,7 +1388,7 @@ function AppPoTokenSettings() {
<FormField <FormField
control={potServerPortForm.control} control={potServerPortForm.control}
name="port" name="port"
disabled={!usePotoken || useCustomCommands || isChangingPotServerPort || isStartingPotServer} disabled={!usePotoken || isChangingPotServerPort || isStartingPotServer || isFlatpak}
render={({ field }) => ( render={({ field }) => (
<FormItem className="w-full"> <FormItem className="w-full">
<FormControl> <FormControl>
@@ -1378,7 +1407,7 @@ function AppPoTokenSettings() {
/> />
<Button <Button
type="submit" type="submit"
disabled={!watchedPotServerPort || Number(watchedPotServerPort) === potServerPort || Object.keys(potServerPortFormErrors).length > 0 || !usePotoken || useCustomCommands || isChangingPotServerPort || isStartingPotServer} disabled={!watchedPotServerPort || Number(watchedPotServerPort) === potServerPort || Object.keys(potServerPortFormErrors).length > 0 || !usePotoken || useCustomCommands || isChangingPotServerPort || isStartingPotServer || isFlatpak}
> >
{isChangingPotServerPort ? ( {isChangingPotServerPort ? (
<> <>
@@ -1400,6 +1429,8 @@ function AppPoTokenSettings() {
function AppNotificationSettings() { function AppNotificationSettings() {
const { saveSettingsKey } = useSettings(); const { saveSettingsKey } = useSettings();
const isFlatpak = useEnvironmentStore(state => state.isFlatpak);
const enableNotifications = useSettingsPageStatesStore(state => state.settings.enable_notifications); const enableNotifications = useSettingsPageStatesStore(state => state.settings.enable_notifications);
const updateNotification = useSettingsPageStatesStore(state => state.settings.update_notification); const updateNotification = useSettingsPageStatesStore(state => state.settings.update_notification);
const downloadCompletionNotification = useSettingsPageStatesStore(state => state.settings.download_completion_notification); const downloadCompletionNotification = useSettingsPageStatesStore(state => state.settings.download_completion_notification);
@@ -1413,6 +1444,7 @@ function AppNotificationSettings() {
<Switch <Switch
id="enable-notifications" id="enable-notifications"
checked={enableNotifications} checked={enableNotifications}
disabled={isFlatpak}
onCheckedChange={async (checked) => { onCheckedChange={async (checked) => {
if (checked) { if (checked) {
const granted = await isPermissionGranted(); const granted = await isPermissionGranted();
@@ -1438,7 +1470,7 @@ function AppNotificationSettings() {
id="update-notification" id="update-notification"
checked={updateNotification} checked={updateNotification}
onCheckedChange={(checked) => saveSettingsKey('update_notification', checked)} onCheckedChange={(checked) => saveSettingsKey('update_notification', checked)}
disabled={!enableNotifications} disabled={!enableNotifications || isFlatpak}
/> />
<Label htmlFor="update-notification">App Updates</Label> <Label htmlFor="update-notification">App Updates</Label>
</div> </div>
@@ -1447,7 +1479,7 @@ function AppNotificationSettings() {
id="download-completion-notification" id="download-completion-notification"
checked={downloadCompletionNotification} checked={downloadCompletionNotification}
onCheckedChange={(checked) => saveSettingsKey('download_completion_notification', checked)} onCheckedChange={(checked) => saveSettingsKey('download_completion_notification', checked)}
disabled={!enableNotifications} disabled={!enableNotifications || isFlatpak}
/> />
<Label htmlFor="download-completion-notification">Download Completion</Label> <Label htmlFor="download-completion-notification">Download Completion</Label>
</div> </div>
@@ -1686,6 +1718,9 @@ function AppDebugSettings() {
} }
function AppInfoSettings() { function AppInfoSettings() {
const isFlatpak = useEnvironmentStore(state => state.isFlatpak);
const isAppimage = useEnvironmentStore(state => state.isAppimage);
const appVersion = useSettingsPageStatesStore(state => state.appVersion); const appVersion = useSettingsPageStatesStore(state => state.appVersion);
const binDepsList = [ const binDepsList = [
@@ -1799,6 +1834,35 @@ function AppInfoSettings() {
</Button> </Button>
</Card> </Card>
</div> </div>
<div className="healthcheck">
<h3 className="font-semibold">Health Check</h3>
<p className="text-xs text-muted-foreground mb-3">Ensure everything is working fine</p>
{isFlatpak ? (
<Alert className="">
<TriangleAlert className="size-4 stroke-primary" />
<AlertTitle className="text-sm">Flatpak Sandbox Detected!</AlertTitle>
<AlertDescription className="text-xs">
It looks like you are running NeoDLP in a Flatpak sandbox. Some features like browser integration, desktop notifications, cookies, po tokens, changing download folder, revealing completed downloads in explorer, automatic yt-dlp updates and auto-launch on startup are not available in Flatpak due to sandbox restrictions. To use these features, please install the native linux build (DEB, RPM or AUR) of NeoDLP.
</AlertDescription>
</Alert>
) : isAppimage ? (
<Alert className="">
<TriangleAlert className="size-4 stroke-primary" />
<AlertTitle className="text-sm">Appimage Environment Detected!</AlertTitle>
<AlertDescription className="text-xs">
Looks like you are using NeoDLP Appimage. NeoDLP's browser integration features are not available on Appimage environment due to it's limitations. To use NeoDLP's browser integration features please install the native linux build (DEB, RPM or AUR) of NeoDLP.
</AlertDescription>
</Alert>
) : (
<Alert className="">
<CircleCheck className="size-4 stroke-primary" />
<AlertTitle className="text-sm">All Set! Cheers :)</AlertTitle>
<AlertDescription className="text-xs">
NeoDLP is running as normal without any limitations! You should be able to use all the features of NeoDLP without any issues. If you face any problem, feel free to report it to us.
</AlertDescription>
</Alert>
)}
</div>
<div className="bug-report"> <div className="bug-report">
<h3 className="font-semibold">Bug Report</h3> <h3 className="font-semibold">Bug Report</h3>
<p className="text-xs text-muted-foreground mb-3">Noticed any bug or inconsistencies? Report it to help us improve</p> <p className="text-xs text-muted-foreground mb-3">Noticed any bug or inconsistencies? Report it to help us improve</p>
@@ -1869,6 +1933,8 @@ function AppInfoSettings() {
} }
export function ApplicationSettings() { export function ApplicationSettings() {
const isFlatpak = useEnvironmentStore(state => state.isFlatpak);
const activeSubAppTab = useSettingsPageStatesStore(state => state.activeSubAppTab); const activeSubAppTab = useSettingsPageStatesStore(state => state.activeSubAppTab);
const setActiveSubAppTab = useSettingsPageStatesStore(state => state.setActiveSubAppTab); const setActiveSubAppTab = useSettingsPageStatesStore(state => state.setActiveSubAppTab);
@@ -1889,7 +1955,7 @@ export function ApplicationSettings() {
const tabsList = [ const tabsList = [
{ key: 'general', label: 'General', icon: Wrench, component: <AppGeneralSettings /> }, { key: 'general', label: 'General', icon: Wrench, component: <AppGeneralSettings /> },
{ key: 'appearance', label: 'Appearance', icon: WandSparkles, component: <AppAppearanceSettings /> }, { key: 'appearance', label: 'Appearance', icon: WandSparkles, component: <AppAppearanceSettings /> },
{ key: 'folders', label: 'Folders', icon: Folder, component: <AppFolderSettings /> }, { key: 'filesystem', label: 'Filesystem', icon: Folder, component: <AppFilesystemSettings /> },
{ key: 'formats', label: 'Formats', icon: FileVideo, component: <AppFormatSettings /> }, { key: 'formats', label: 'Formats', icon: FileVideo, component: <AppFormatSettings /> },
{ key: 'embedding', label: 'Embedding', icon: FilePen, component: <AppEmbeddingSettings /> }, { key: 'embedding', label: 'Embedding', icon: FilePen, component: <AppEmbeddingSettings /> },
{ key: 'network', label: 'Network', icon: Wifi, component: <AppNetworkSettings /> }, { key: 'network', label: 'Network', icon: Wifi, component: <AppNetworkSettings /> },
@@ -1926,12 +1992,14 @@ export function ApplicationSettings() {
<Switch <Switch
id="ytdlp-auto-update" id="ytdlp-auto-update"
checked={ytDlpAutoUpdate} checked={ytDlpAutoUpdate}
disabled={isFlatpak}
onCheckedChange={(checked) => saveSettingsKey('ytdlp_auto_update', checked)} onCheckedChange={(checked) => saveSettingsKey('ytdlp_auto_update', checked)}
/> />
<Label htmlFor="ytdlp-auto-update">Auto Update</Label> <Label htmlFor="ytdlp-auto-update">Auto Update</Label>
</div> </div>
<Select <Select
value={ytDlpUpdateChannel} value={ytDlpUpdateChannel}
disabled={isFlatpak}
onValueChange={(value) => saveSettingsKey('ytdlp_update_channel', value)} onValueChange={(value) => saveSettingsKey('ytdlp_update_channel', value)}
> >
<SelectTrigger className="w-37.5 ring-0 focus:ring-0"> <SelectTrigger className="w-37.5 ring-0 focus:ring-0">
@@ -1946,7 +2014,7 @@ export function ApplicationSettings() {
</SelectContent> </SelectContent>
</Select> </Select>
<Button <Button
disabled={ytDlpAutoUpdate || isUpdatingYtDlp || ongoingDownloads.length > 0} disabled={ytDlpAutoUpdate || isUpdatingYtDlp || ongoingDownloads.length > 0 || isFlatpak}
onClick={async () => await updateYtDlp()} onClick={async () => await updateYtDlp()}
> >
{isUpdatingYtDlp ? ( {isUpdatingYtDlp ? (

View File

@@ -1,7 +1,7 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { Card } from "@/components/ui/card"; import { Card } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { useSettingsPageStatesStore } from "@/services/store"; import { useEnvironmentStore, useSettingsPageStatesStore } from "@/services/store";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { toast } from "sonner"; import { toast } from "sonner";
@@ -15,8 +15,7 @@ import { invoke } from "@tauri-apps/api/core";
import { SlidingButton } from "@/components/custom/slidingButton"; import { SlidingButton } from "@/components/custom/slidingButton";
import clsx from "clsx"; import clsx from "clsx";
import { NumberInput } from "@/components/custom/numberInput"; import { NumberInput } from "@/components/custom/numberInput";
import { openUrl } from "@tauri-apps/plugin-opener"; import { platform } from "@tauri-apps/plugin-os";
import { useLogger } from "@/helpers/use-logger";
const websocketPortSchema = z.object({ const websocketPortSchema = z.object({
port: z.coerce.number<number>({ port: z.coerce.number<number>({
@@ -33,21 +32,17 @@ const websocketPortSchema = z.object({
}); });
function ExtInstallSettings() { function ExtInstallSettings() {
const LOG = useLogger(); const currentPlatform = platform();
const openLink = async (url: string, app: string | undefined) => { const isFlatpak = useEnvironmentStore(state => state.isFlatpak);
const openLink = async (url: string, app: string | null) => {
try { try {
// await invoke('open_file_with_app', { filePath: url, appName: app }).then(() => { await invoke('open_link_with_app', { url: url, appName: app }).then(() => {
// toast.info("Opening link", {
// description: `Opening link with ${app ? app : 'default app'}.`,
// })
// });
await openUrl(url, app).then(() => {
toast.info("Opening link", { toast.info("Opening link", {
description: `Opening link with ${app ? app : 'default app'}.`, description: `Opening link with ${app ? app : 'default app'}.`,
}) })
}); });
} catch (e) { } catch (e) {
LOG.error('NEODLP', `Failed to open link: ${e}`);
console.error(e); console.error(e);
toast.error("Failed to open link", { toast.error("Failed to open link", {
description: "An error occurred while trying to open the link.", description: "An error occurred while trying to open the link.",
@@ -67,7 +62,7 @@ function ExtInstallSettings() {
<span>Get Now</span> <span>Get Now</span>
</div> </div>
} }
onClick={() => openLink('https://chromewebstore.google.com/detail/neo-downloader-plus/mehopeailfjmiloiiohgicphlcgpompf', 'chrome')} onClick={() => openLink('https://chromewebstore.google.com/detail/neo-downloader-plus/mehopeailfjmiloiiohgicphlcgpompf', isFlatpak ? null : currentPlatform === "linux" ? 'google-chrome' : 'chrome')}
> >
<span className="font-semibold flex items-center gap-2"> <span className="font-semibold flex items-center gap-2">
<svg className="size-4 fill-primary-foreground" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <svg className="size-4 fill-primary-foreground" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
@@ -84,7 +79,7 @@ function ExtInstallSettings() {
<span>Get Now</span> <span>Get Now</span>
</div> </div>
} }
onClick={() => openLink('https://addons.mozilla.org/en-US/firefox/addon/neo-downloader-plus', 'firefox')} onClick={() => openLink('https://addons.mozilla.org/en-US/firefox/addon/neo-downloader-plus', isFlatpak ? null : 'firefox')}
> >
<span className="font-semibold flex items-center gap-2"> <span className="font-semibold flex items-center gap-2">
<svg className="size-4 fill-primary-foreground" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <svg className="size-4 fill-primary-foreground" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
@@ -96,11 +91,11 @@ function ExtInstallSettings() {
</SlidingButton> </SlidingButton>
</div> </div>
<div className="flex gap-2 mb-4"> <div className="flex gap-2 mb-4">
<Button variant="outline" onClick={() => openLink('https://chromewebstore.google.com/detail/neo-downloader-plus/mehopeailfjmiloiiohgicphlcgpompf', 'msedge')}>Edge</Button> <Button variant="outline" onClick={() => openLink('https://chromewebstore.google.com/detail/neo-downloader-plus/mehopeailfjmiloiiohgicphlcgpompf', isFlatpak ? null : 'msedge')}>Edge</Button>
<Button variant="outline" onClick={() => openLink('https://chromewebstore.google.com/detail/neo-downloader-plus/mehopeailfjmiloiiohgicphlcgpompf', 'opera')}>Opera</Button> <Button variant="outline" onClick={() => openLink('https://chromewebstore.google.com/detail/neo-downloader-plus/mehopeailfjmiloiiohgicphlcgpompf', isFlatpak ? null : 'opera')}>Opera</Button>
<Button variant="outline" onClick={() => openLink('https://chromewebstore.google.com/detail/neo-downloader-plus/mehopeailfjmiloiiohgicphlcgpompf', 'brave')}>Brave</Button> <Button variant="outline" onClick={() => openLink('https://chromewebstore.google.com/detail/neo-downloader-plus/mehopeailfjmiloiiohgicphlcgpompf', isFlatpak ? null : 'brave')}>Brave</Button>
<Button variant="outline" onClick={() => openLink('https://chromewebstore.google.com/detail/neo-downloader-plus/mehopeailfjmiloiiohgicphlcgpompf', 'vivaldi')}>Vivaldi</Button> <Button variant="outline" onClick={() => openLink('https://chromewebstore.google.com/detail/neo-downloader-plus/mehopeailfjmiloiiohgicphlcgpompf', isFlatpak ? null : 'vivaldi')}>Vivaldi</Button>
<Button variant="outline" onClick={() => openLink('https://addons.mozilla.org/en-US/firefox/addon/neo-downloader-plus', 'zen')}>Zen</Button> <Button variant="outline" onClick={() => openLink('https://addons.mozilla.org/en-US/firefox/addon/neo-downloader-plus', isFlatpak ? null : 'zen')}>Zen</Button>
</div> </div>
<p className="text-xs text-muted-foreground mb-2">* These links opens with coresponding browsers only. Make sure the browser is installed before clicking the link</p> <p className="text-xs text-muted-foreground mb-2">* These links opens with coresponding browsers only. Make sure the browser is installed before clicking the link</p>
</div> </div>

View File

@@ -46,7 +46,7 @@ export function AppSidebar() {
]; ];
useEffect(() => { useEffect(() => {
let timeout: NodeJS.Timeout; let timeout: ReturnType<typeof setTimeout>;
if (open) { if (open) {
timeout = setTimeout(() => { timeout = setTimeout(() => {
setShowBadge(true); setShowBadge(true);

View File

@@ -4,6 +4,7 @@ import { relaunch as relaunchApp } from "@tauri-apps/plugin-process";
import { useSettingsPageStatesStore } from "@/services/store"; import { useSettingsPageStatesStore } from "@/services/store";
import { useLogger } from "@/helpers/use-logger"; import { useLogger } from "@/helpers/use-logger";
import { sendNotification } from '@tauri-apps/plugin-notification'; import { sendNotification } from '@tauri-apps/plugin-notification';
import usePotServer from "@/helpers/use-pot-server";
export default function useAppUpdater() { export default function useAppUpdater() {
const setIsCheckingAppUpdate = useSettingsPageStatesStore(state => state.setIsCheckingAppUpdate); const setIsCheckingAppUpdate = useSettingsPageStatesStore(state => state.setIsCheckingAppUpdate);
@@ -12,6 +13,9 @@ export default function useAppUpdater() {
const setDownloadProgress = useSettingsPageStatesStore(state => state.setAppUpdateDownloadProgress); const setDownloadProgress = useSettingsPageStatesStore(state => state.setAppUpdateDownloadProgress);
const enableNotifications = useSettingsPageStatesStore(state => state.settings.enable_notifications); const enableNotifications = useSettingsPageStatesStore(state => state.settings.enable_notifications);
const updateNotification = useSettingsPageStatesStore(state => state.settings.update_notification); const updateNotification = useSettingsPageStatesStore(state => state.settings.update_notification);
const isRunningPotServer = useSettingsPageStatesStore(state => state.isRunningPotServer);
const { stopPotServer } = usePotServer();
const LOG = useLogger(); const LOG = useLogger();
const checkForAppUpdate = async () => { const checkForAppUpdate = async () => {
@@ -38,6 +42,10 @@ export default function useAppUpdater() {
const downloadAndInstallAppUpdate = async (update: Update) => { const downloadAndInstallAppUpdate = async (update: Update) => {
setIsUpdating(true); setIsUpdating(true);
if (isRunningPotServer) {
LOG.info('NEODLP', 'Stopping POT Server before starting app update');
await stopPotServer();
}
LOG.info('NEODLP', `Downloading and installing app update v${update.version}`); LOG.info('NEODLP', `Downloading and installing app update v${update.version}`);
let downloaded = 0; let downloaded = 0;
let contentLength: number | undefined = 0; let contentLength: number | undefined = 0;

View File

@@ -72,6 +72,8 @@ export default function useDownloader() {
use_potoken: USE_POTOKEN, use_potoken: USE_POTOKEN,
disable_innertube: DISABLE_INNERTUBE, disable_innertube: DISABLE_INNERTUBE,
pot_server_port: POT_SERVER_PORT, pot_server_port: POT_SERVER_PORT,
windows_filenames: WINDOWS_FILENAMES,
restrict_filenames: RESTRICT_FILENAMES
} = useSettingsPageStatesStore(state => state.settings); } = useSettingsPageStatesStore(state => state.settings);
const isRunningPotServer = useSettingsPageStatesStore(state => state.isRunningPotServer); const isRunningPotServer = useSettingsPageStatesStore(state => state.isRunningPotServer);
@@ -196,7 +198,10 @@ export default function useDownloader() {
} }
} }
const command = Command.sidecar('binaries/yt-dlp', args); const isFlatpak = await invoke<boolean>('is_flatpak');
const command = isFlatpak
? Command.create('sh', ['-c', `yt-dlp ${args.map(arg => `'${arg.replace(/'/g, "'\\''")}'`).join(' ')}`])
: Command.sidecar('binaries/yt-dlp', args);
let jsonOutput = ''; let jsonOutput = '';
@@ -329,8 +334,6 @@ export default function useDownloader() {
`temp:${tempDownloadDirPath}`, `temp:${tempDownloadDirPath}`,
'--paths', '--paths',
`home:${downloadDirPath}`, `home:${downloadDirPath}`,
'--windows-filenames',
'--restrict-filenames',
'--exec', '--exec',
'after_move:echo Finalpath: {}', 'after_move:echo Finalpath: {}',
'--no-mtime', '--no-mtime',
@@ -348,6 +351,14 @@ export default function useDownloader() {
args.push('--output', `${FILENAME_TEMPLATE}[${downloadId}].%(ext)s`); args.push('--output', `${FILENAME_TEMPLATE}[${downloadId}].%(ext)s`);
} }
if (WINDOWS_FILENAMES) {
args.push('--windows-filenames');
}
if (RESTRICT_FILENAMES) {
args.push('--restrict-filenames');
}
if ((!USE_CUSTOM_COMMANDS && !resumeState?.custom_command) && USE_DELAY) { if ((!USE_CUSTOM_COMMANDS && !resumeState?.custom_command) && USE_DELAY) {
if (!DELAY_PLAYLIST_ONLY) { if (!DELAY_PLAYLIST_ONLY) {
if (DELAY_MODE === 'auto') { if (DELAY_MODE === 'auto') {
@@ -557,7 +568,10 @@ export default function useDownloader() {
} }
console.log('Starting download with args:', args); console.log('Starting download with args:', args);
const command = Command.sidecar('binaries/yt-dlp', args); const isFlatpak = await invoke<boolean>('is_flatpak');
const command = isFlatpak
? Command.create('sh', ['-c', `yt-dlp ${args.map(arg => `'${arg.replace(/'/g, "'\\''")}'`).join(' ')}`])
: Command.sidecar('binaries/yt-dlp', args);
command.on('close', async (data) => { command.on('close', async (data) => {
if (data.code !== 0) { if (data.code !== 0) {

View File

@@ -2,7 +2,7 @@ import { join, resourceDir, homeDir } from "@tauri-apps/api/path";
import * as fs from "@tauri-apps/plugin-fs"; import * as fs from "@tauri-apps/plugin-fs";
import { useKvPairs } from "@/helpers/use-kvpairs"; import { useKvPairs } from "@/helpers/use-kvpairs";
import { useSettingsPageStatesStore } from "@/services/store"; import { useSettingsPageStatesStore } from "@/services/store";
import { Command } from "@tauri-apps/plugin-shell"; // import { Command } from "@tauri-apps/plugin-shell";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
interface FileMap { interface FileMap {
@@ -21,20 +21,20 @@ export function useLinuxRegisterer() {
const isFlatpak = await invoke<boolean>('is_flatpak'); const isFlatpak = await invoke<boolean>('is_flatpak');
const resourceDirPath = isFlatpak ? '/app/lib/neodlp' : await resourceDir(); const resourceDirPath = isFlatpak ? '/app/lib/neodlp' : await resourceDir();
const homeDirPath = await homeDir(); const homeDirPath = await homeDir();
const flatpakChromeManifestContent = { // const flatpakChromeManifestContent = {
name: "com.neosubhamoy.neodlp", // name: "com.neosubhamoy.neodlp",
description: "NeoDLP MsgHost", // description: "NeoDLP MsgHost",
path: `${homeDirPath}/.local/bin/neodlp-msghost`, // path: `${homeDirPath}/.local/bin/neodlp-msghost`,
type: "stdio", // type: "stdio",
allowed_origins: ["chrome-extension://mehopeailfjmiloiiohgicphlcgpompf/"] // allowed_origins: ["chrome-extension://mehopeailfjmiloiiohgicphlcgpompf/"]
}; // };
const flatpakFirefoxManifestContent = { // const flatpakFirefoxManifestContent = {
name: "com.neosubhamoy.neodlp", // name: "com.neosubhamoy.neodlp",
description: "NeoDLP MsgHost", // description: "NeoDLP MsgHost",
path: `${homeDirPath}/.local/bin/neodlp-msghost`, // path: `${homeDirPath}/.local/bin/neodlp-msghost`,
type: "stdio", // type: "stdio",
allowed_extensions: ["neodlp@neosubhamoy.com"] // allowed_extensions: ["neodlp@neosubhamoy.com"]
}; // };
const filesToCopy: FileMap[] = [ const filesToCopy: FileMap[] = [
{ source: 'yt-dlp-plugins/bgutil-ytdlp-pot-provider/yt_dlp_plugins/extractor/getpot_bgutil.py', destination: 'yt-dlp-plugins/bgutil-ytdlp-pot-provider/yt_dlp_plugins/extractor/getpot_bgutil.py', dir: 'yt-dlp-plugins/bgutil-ytdlp-pot-provider/yt_dlp_plugins/extractor/' }, { source: 'yt-dlp-plugins/bgutil-ytdlp-pot-provider/yt_dlp_plugins/extractor/getpot_bgutil.py', destination: 'yt-dlp-plugins/bgutil-ytdlp-pot-provider/yt_dlp_plugins/extractor/getpot_bgutil.py', dir: 'yt-dlp-plugins/bgutil-ytdlp-pot-provider/yt_dlp_plugins/extractor/' },
@@ -42,39 +42,47 @@ export function useLinuxRegisterer() {
{ source: 'yt-dlp-plugins/bgutil-ytdlp-pot-provider/yt_dlp_plugins/extractor/getpot_bgutil_http.py', destination: 'yt-dlp-plugins/bgutil-ytdlp-pot-provider/yt_dlp_plugins/extractor/getpot_bgutil_http.py', dir: 'yt-dlp-plugins/bgutil-ytdlp-pot-provider/yt_dlp_plugins/extractor/' }, { source: 'yt-dlp-plugins/bgutil-ytdlp-pot-provider/yt_dlp_plugins/extractor/getpot_bgutil_http.py', destination: 'yt-dlp-plugins/bgutil-ytdlp-pot-provider/yt_dlp_plugins/extractor/getpot_bgutil_http.py', dir: 'yt-dlp-plugins/bgutil-ytdlp-pot-provider/yt_dlp_plugins/extractor/' },
]; ];
const filesToCopyFlatpak: FileMap[] = [ // const filesToCopyFlatpak: FileMap[] = [
{ source: 'chrome.json', destination: '.config/google-chrome/NativeMessagingHosts/com.neosubhamoy.neodlp.json', dir: '.config/google-chrome/NativeMessagingHosts/', content: JSON.stringify(flatpakChromeManifestContent) }, // { source: 'chrome.json', destination: '.config/google-chrome/NativeMessagingHosts/com.neosubhamoy.neodlp.json', dir: '.config/google-chrome/NativeMessagingHosts/', content: JSON.stringify(flatpakChromeManifestContent) },
{ source: 'chrome.json', destination: '.config/chromium/NativeMessagingHosts/com.neosubhamoy.neodlp.json', dir: '.config/chromium/NativeMessagingHosts/', content: JSON.stringify(flatpakChromeManifestContent) }, // { source: 'chrome.json', destination: '.config/chromium/NativeMessagingHosts/com.neosubhamoy.neodlp.json', dir: '.config/chromium/NativeMessagingHosts/', content: JSON.stringify(flatpakChromeManifestContent) },
{ source: 'firefox.json', destination: '.mozilla/native-messaging-hosts/com.neosubhamoy.neodlp.json', dir: '.mozilla/native-messaging-hosts/', content: JSON.stringify(flatpakFirefoxManifestContent) }, // { source: 'firefox.json', destination: '.mozilla/native-messaging-hosts/com.neosubhamoy.neodlp.json', dir: '.mozilla/native-messaging-hosts/', content: JSON.stringify(flatpakFirefoxManifestContent) },
{ source: 'neodlp-msghost', destination: '.local/bin/neodlp-msghost', dir: '.local/bin/' }, // { source: 'neodlp-msghost', destination: '.local/bin/neodlp-msghost', dir: '.local/bin/' },
];
// { source: 'yt-dlp-plugins/bgutil-ytdlp-pot-provider/yt_dlp_plugins/extractor/getpot_bgutil.py', destination: '.var/app/com.neosubhamoy.neodlp/config/yt-dlp-plugins/bgutil-ytdlp-pot-provider/yt_dlp_plugins/extractor/getpot_bgutil.py', dir: '.var/app/com.neosubhamoy.neodlp/config/yt-dlp-plugins/bgutil-ytdlp-pot-provider/yt_dlp_plugins/extractor/' },
// { source: 'yt-dlp-plugins/bgutil-ytdlp-pot-provider/yt_dlp_plugins/extractor/getpot_bgutil_cli.py', destination: '.var/app/com.neosubhamoy.neodlp/config/yt-dlp-plugins/bgutil-ytdlp-pot-provider/yt_dlp_plugins/extractor/getpot_bgutil_cli.py', dir: '.var/app/com.neosubhamoy.neodlp/config/yt-dlp-plugins/bgutil-ytdlp-pot-provider/yt_dlp_plugins/extractor/' },
// { source: 'yt-dlp-plugins/bgutil-ytdlp-pot-provider/yt_dlp_plugins/extractor/getpot_bgutil_http.py', destination: '.var/app/com.neosubhamoy.neodlp/config/yt-dlp-plugins/bgutil-ytdlp-pot-provider/yt_dlp_plugins/extractor/getpot_bgutil_http.py', dir: '.var/app/com.neosubhamoy.neodlp/config/yt-dlp-plugins/bgutil-ytdlp-pot-provider/yt_dlp_plugins/extractor/' },
// ];
if (isFlatpak) { if (isFlatpak) {
for (const file of filesToCopyFlatpak) { // Skip linux registration for Flatpak
const sourcePath = await join(resourceDirPath, file.source); console.log('Flatpak sandbox detected! Skipping Linux registration...');
const destinationPath = await join(homeDirPath, file.destination);
const escapedContent = file.content?.replace(/'/g, `'\\''`) || '';
const copyCommand = Command.create('sh', ['-c', `cp "${sourcePath}" "${destinationPath}"`]);
const writeCommand = Command.create('sh', ['-c', `printf '%s' '${escapedContent}' > "${destinationPath}"`]);
if (file.content) { // for (const file of filesToCopyFlatpak) {
const writeOutput = await writeCommand.execute(); // const sourcePath = await join(resourceDirPath, file.source);
if (writeOutput.code === 0) { // const destinationDir = await join(homeDirPath, file.dir);
console.log(`File ${file.destination} created successfully at ${destinationPath}`); // const destinationPath = await join(homeDirPath, file.destination);
} else { // const escapedContent = file.content?.replace(/'/g, `'\\''`) || '';
console.error(`Failed to create file ${file.destination} at ${destinationPath}:`, writeOutput.stderr); // const copyCommand = Command.create('sh', ['-c', `mkdir -p "${destinationDir}" && cp "${sourcePath}" "${destinationPath}"`]);
return { success: false, message: 'Failed to register' }; // const writeCommand = Command.create('sh', ['-c', `printf '%s' '${escapedContent}' > "${destinationPath}"`]);
}
} else { // if (file.content) {
const copyOutput = await copyCommand.execute(); // const writeOutput = await writeCommand.execute();
if (copyOutput.code === 0) { // if (writeOutput.code === 0) {
console.log(`File ${file.source} copied successfully to ${destinationPath}`); // console.log(`File ${file.destination} created successfully at ${destinationPath}`);
} else { // } else {
console.error(`Failed to copy file ${file.source} to ${destinationPath}:`, copyOutput.stderr); // console.error(`Failed to create file ${file.destination} at ${destinationPath}:`, writeOutput.stderr);
return { success: false, message: 'Failed to register' }; // return { success: false, message: 'Failed to register' };
} // }
} // } else {
} // const copyOutput = await copyCommand.execute();
// if (copyOutput.code === 0) {
// console.log(`File ${file.source} copied successfully to ${destinationPath}`);
// } else {
// console.error(`Failed to copy file ${file.source} to ${destinationPath}:`, copyOutput.stderr);
// return { success: false, message: 'Failed to register' };
// }
// }
// }
} else { } else {
for (const file of filesToCopy) { for (const file of filesToCopy) {
const sourcePath = await join(resourceDirPath, file.source); const sourcePath = await join(resourceDirPath, file.source);

View File

@@ -17,7 +17,13 @@ export default function usePotServer() {
}; };
const startPotServer = async (port?: number) => { const startPotServer = async (port?: number) => {
const runCommand = Command.sidecar('binaries/neodlp-pot', [ const isFlatpak = await invoke<boolean>('is_flatpak');
const runCommand = isFlatpak
? Command.create('sh', [
'-c',
`neodlp-pot server --port ${port ? port.toString() : potServerPort.toString()}`
])
: Command.sidecar('binaries/neodlp-pot', [
'server', 'server',
'--port', '--port',
port ? port.toString() : potServerPort.toString(), port ? port.toString() : potServerPort.toString(),

View File

@@ -21,7 +21,13 @@ export function useYtDlpUpdater() {
setIsUpdatingYtDlp(true); setIsUpdatingYtDlp(true);
LOG.info('NEODLP', 'Updating yt-dlp to latest version'); LOG.info('NEODLP', 'Updating yt-dlp to latest version');
try { try {
const command = currentPlatform === 'linux' && !isFlatpak ? Command.create('pkexec', ['yt-dlp', '--update-to', ytDlpUpdateChannel]) : Command.sidecar('binaries/yt-dlp', ['--update-to', ytDlpUpdateChannel]); const command = currentPlatform === 'linux' && isFlatpak
? ytDlpUpdateChannel === 'nightly'
? Command.create('sh', ['-c', 'pip3 install -U --pre "yt-dlp[default,curl-cffi]"'])
: Command.create('sh', ['-c', 'pip3 install -U "yt-dlp[default,curl-cffi]"'])
: currentPlatform === 'linux'
? Command.create('pkexec', ['yt-dlp', '--update-to', ytDlpUpdateChannel])
: Command.sidecar('binaries/yt-dlp', ['--update-to', ytDlpUpdateChannel]);
const output = await command.execute(); const output = await command.execute();
if (output.code === 0) { if (output.code === 0) {
console.log("yt-dlp updated successfully:", output.stdout); console.log("yt-dlp updated successfully:", output.stdout);

View File

@@ -1,4 +1,4 @@
import { BasePathsStore, CurrentVideoMetadataStore, DownloadActionStatesStore, DownloaderPageStatesStore, DownloadStatesStore, KvPairsStatesStore, LibraryPageStatesStore, LogsStore, SettingsPageStatesStore } from '@/types/store'; import { BasePathsStore, CurrentVideoMetadataStore, DownloadActionStatesStore, DownloaderPageStatesStore, DownloadStatesStore, EnvironmentStore, KvPairsStatesStore, LibraryPageStatesStore, LogsStore, SettingsPageStatesStore } from '@/types/store';
import { create } from 'zustand'; import { create } from 'zustand';
export const useBasePathsStore = create<BasePathsStore>((set) => ({ export const useBasePathsStore = create<BasePathsStore>((set) => ({
@@ -221,6 +221,8 @@ export const useSettingsPageStatesStore = create<SettingsPageStatesStore>((set)
use_potoken: false, use_potoken: false,
disable_innertube: false, disable_innertube: false,
pot_server_port: 4416, pot_server_port: 4416,
windows_filenames: true,
restrict_filenames: true,
// extension settings // extension settings
websocket_port: 53511 websocket_port: 53511
}, },
@@ -306,6 +308,8 @@ export const useSettingsPageStatesStore = create<SettingsPageStatesStore>((set)
use_potoken: false, use_potoken: false,
disable_innertube: false, disable_innertube: false,
pot_server_port: 4416, pot_server_port: 4416,
windows_filenames: true,
restrict_filenames: true,
// extension settings // extension settings
websocket_port: 53511 websocket_port: 53511
}, },
@@ -352,3 +356,12 @@ export const useLogsStore = create<LogsStore>((set) => ({
addLog: (log) => set((state) => ({ logs: [...state.logs, log] })), addLog: (log) => set((state) => ({ logs: [...state.logs, log] })),
clearLogs: () => set(() => ({ logs: [] })) clearLogs: () => set(() => ({ logs: [] }))
})); }));
export const useEnvironmentStore = create<EnvironmentStore>((set) => ({
isFlatpak: false,
isAppimage: false,
appDirPath: null,
setIsFlatpak: (isFlatpak) => set(() => ({ isFlatpak })),
setIsAppimage: (isAppimage) => set(() => ({ isAppimage })),
setAppDirPath: (path) => set(() => ({ appDirPath: path }))
}));

View File

@@ -64,6 +64,8 @@ export interface Settings {
use_potoken: boolean; use_potoken: boolean;
disable_innertube: boolean; disable_innertube: boolean;
pot_server_port: number; pot_server_port: number;
windows_filenames: boolean;
restrict_filenames: boolean;
// extension settings // extension settings
websocket_port: number; websocket_port: number;
} }

View File

@@ -152,3 +152,12 @@ export interface LogsStore {
addLog: (log: Log) => void; addLog: (log: Log) => void;
clearLogs: () => void; clearLogs: () => void;
} }
export interface EnvironmentStore {
isFlatpak: boolean;
isAppimage: boolean;
appDirPath: string | null;
setIsFlatpak: (isFlatpak: boolean) => void;
setIsAppimage: (isAppimage: boolean) => void;
setAppDirPath: (path: string) => void;
}