mirror of
https://github.com/neosubhamoy/neodlp.git
synced 2026-03-22 18:15:50 +05:30
Compare commits
26 Commits
v0.4.1
...
570ca76087
@@ -83,9 +83,10 @@ After installing the extension you can do the following directly from the browse
|
||||
| Platform (OS) | Distribution Channel | Installation Command / Instruction |
|
||||
| :---- | :---- | :---- |
|
||||
| Windows x86_64 / ARM64 | WinGet | `winget install neosubhamoy.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` |
|
||||
| Linux x86_64 / ARM64 | Curl-Bash Installer | `curl -sSL https://neodlp.neosubhamoy.com/linux_installer.sh \| bash` |
|
||||
| Arch Linux x86_64 / ARM64 | AUR | `yay -S neodlp` |
|
||||
| Arch Linux x86_64 / ARM64 | AUR | `yay -S neodlp` or `paru -S neodlp` |
|
||||
|
||||
## 🧪 Package Testing Status
|
||||
|
||||
|
||||
10
com.neosubhamoy.neodlp.desktop
Normal file
10
com.neosubhamoy.neodlp.desktop
Normal file
@@ -0,0 +1,10 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
|
||||
Name=NeoDLP
|
||||
Comment=Modern video/audio downloader based on yt-dlp with browser integration.
|
||||
Icon=com.neosubhamoy.neodlp
|
||||
Exec=neodlp
|
||||
Terminal=false
|
||||
Categories=Utility;
|
||||
Keywords=neodlp;downloader;yt-dlp-gui;
|
||||
31
com.neosubhamoy.neodlp.metainfo.xml
Normal file
31
com.neosubhamoy.neodlp.metainfo.xml
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<component type="desktop-application">
|
||||
<id>com.neosubhamoy.neodlp</id>
|
||||
<name>NeoDLP</name>
|
||||
<summary>Modern video/audio downloader based on yt-dlp with browser integration</summary>
|
||||
<developer_name>Subhamoy Biswas</developer_name>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>MIT</project_license>
|
||||
<url type="homepage">https://neodlp.neosubhamoy.com</url>
|
||||
<url type="bugtracker">https://github.com/neosubhamoy/neodlp/issues</url>
|
||||
<description>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</description>
|
||||
<launchable type="desktop-id">com.neosubhamoy.neodlp.desktop</launchable>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>https://raw.githubusercontent.com/neosubhamoy/neodlp/main/.github/images/downloader.png</image>
|
||||
<caption>NeoDLP Downloader</caption>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<content_rating type="oars-1.1" />
|
||||
<releases>
|
||||
<release version="0.4.2" date="2026-02-23">
|
||||
<url type="details">https://github.com/neosubhamoy/neodlp/releases/tag/v0.4.2</url>
|
||||
</release>
|
||||
</releases>
|
||||
</component>
|
||||
@@ -64,6 +64,11 @@
|
||||
"name": "powershell",
|
||||
"cmd": "powershell",
|
||||
"args": true
|
||||
},
|
||||
{
|
||||
"name": "sh",
|
||||
"cmd": "sh",
|
||||
"args": true
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -21,6 +21,7 @@ use std::{
|
||||
use tauri::{
|
||||
menu::{Menu, MenuItem},
|
||||
tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
|
||||
path::BaseDirectory,
|
||||
Emitter, Manager, State,
|
||||
};
|
||||
use tauri_plugin_opener::OpenerExt;
|
||||
@@ -184,6 +185,16 @@ fn get_current_app_path() -> Result<String, String> {
|
||||
.into_owned())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn is_flatpak() -> bool {
|
||||
std::env::var("FLATPAK").is_ok()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_appimage_path() -> Option<String> {
|
||||
std::env::var("APPDIR").ok()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn update_config(
|
||||
new_config: Config,
|
||||
@@ -574,6 +585,20 @@ pub async fn run() {
|
||||
.build(app)
|
||||
.map_err(|e| format!("Failed to create tray: {}", e))?;
|
||||
|
||||
// Fix tray icon in sandboxed environments (e.g., Flatpak)
|
||||
// libappindicator uses the full path of the icon in dbus messages,
|
||||
// so the path needs to be accessible from both the host and the sandbox.
|
||||
// The default /tmp path doesn't work across sandbox boundaries.
|
||||
if let Ok(local_data_path) = app
|
||||
.path()
|
||||
.resolve("tray-icon", BaseDirectory::AppLocalData)
|
||||
{
|
||||
let _ = fs::create_dir_all(&local_data_path);
|
||||
let _ = tray.set_temp_dir_path(Some(local_data_path));
|
||||
// Re-set the icon so it gets written to the new temp dir path
|
||||
let _ = tray.set_icon(Some(app.default_window_icon().unwrap().clone()));
|
||||
}
|
||||
|
||||
app.manage(tray);
|
||||
|
||||
let window = app.get_webview_window("main").unwrap();
|
||||
@@ -604,6 +629,8 @@ pub async fn run() {
|
||||
get_config_file_path,
|
||||
restart_websocket_server,
|
||||
get_current_app_path,
|
||||
is_flatpak,
|
||||
get_appimage_path
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"targets": ["deb", "rpm"],
|
||||
"createUpdaterArtifacts": true,
|
||||
"licenseFile": "../LICENSE",
|
||||
"category": "Utility",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
|
||||
51
src-tauri/tauri.linux-flatpak.conf.json
Normal file
51
src-tauri/tauri.linux-flatpak.conf.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"identifier": "com.neosubhamoy.neodlp",
|
||||
"build": {
|
||||
"beforeDevCommand": "cargo build --manifest-path=./src-tauri/msghost/Cargo.toml && npm run dev",
|
||||
"beforeBuildCommand": "cargo build --release --manifest-path=./src-tauri/msghost/Cargo.toml && npm run build",
|
||||
"devUrl": "http://localhost:1420",
|
||||
"frontendDist": "../dist"
|
||||
},
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"title": "NeoDLP",
|
||||
"width": 1080,
|
||||
"height": 680,
|
||||
"decorations": false,
|
||||
"visible": false
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": null,
|
||||
"capabilities": [
|
||||
"default",
|
||||
"shell-scope"
|
||||
]
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": ["deb"],
|
||||
"createUpdaterArtifacts": true,
|
||||
"licenseFile": "../LICENSE",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"externalBin": [
|
||||
"binaries/yt-dlp",
|
||||
"binaries/ffmpeg",
|
||||
"binaries/ffprobe",
|
||||
"binaries/aria2c",
|
||||
"binaries/deno",
|
||||
"binaries/neodlp-pot"
|
||||
],
|
||||
"resources": {
|
||||
"resources/plugins/yt-dlp-plugins/": "yt-dlp-plugins/"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@
|
||||
"targets": ["deb", "rpm", "appimage"],
|
||||
"createUpdaterArtifacts": true,
|
||||
"licenseFile": "../LICENSE",
|
||||
"category": "Utility",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
|
||||
19
src/App.tsx
19
src/App.tsx
@@ -11,7 +11,7 @@ import { useUpdateDownloadStatus } from "@/services/mutations";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useFetchAllDownloadStates, useFetchAllkVPairs, useFetchAllSettings } from "@/services/queries";
|
||||
import { config } from "@/config";
|
||||
import * as fs from "@tauri-apps/plugin-fs";
|
||||
// import * as fs from "@tauri-apps/plugin-fs";
|
||||
import { useYtDlpUpdater } from "@/helpers/use-ytdlp-updater";
|
||||
import { getVersion } from "@tauri-apps/api/app";
|
||||
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||
@@ -27,6 +27,8 @@ import { useLogger } from "@/helpers/use-logger";
|
||||
import useDownloader from "@/helpers/use-downloader";
|
||||
import usePotServer from "@/helpers/use-pot-server";
|
||||
import { useLinuxRegisterer } from "@/helpers/use-linux-registerer";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
|
||||
export default function App({ children }: { children: React.ReactNode }) {
|
||||
const { data: downloadStates, isSuccess: isSuccessFetchingDownloadStates } = useFetchAllDownloadStates();
|
||||
@@ -182,22 +184,23 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
try {
|
||||
const currentArch = arch();
|
||||
const currentExeExtension = exeExtension();
|
||||
const isFlatpak = await invoke<boolean>('is_flatpak');
|
||||
const downloadDirPath = await downloadDir();
|
||||
const tempDirPath = await tempDir();
|
||||
const resourceDirPath = await resourceDir();
|
||||
|
||||
const ffmpegPath = await join(resourceDirPath, 'binaries', `ffmpeg-${currentArch}${currentExeExtension ? '.' + currentExeExtension : ''}`);
|
||||
const tempDownloadDirPath = await join(tempDirPath, config.appPkgName, 'downloads');
|
||||
const tempDownloadDirPath = isFlatpak ? await join(downloadDirPath, config.appName, '.tempdownloads') : await join(tempDirPath, config.appPkgName, 'downloads');
|
||||
const appDownloadDirPath = await join(downloadDirPath, config.appName);
|
||||
|
||||
if (!await fs.exists(tempDownloadDirPath)) fs.mkdir(tempDownloadDirPath, { recursive: true }).then(() => { console.log(`Created DIR: ${tempDownloadDirPath}`) });
|
||||
// if (!await fs.exists(tempDownloadDirPath)) fs.mkdir(tempDownloadDirPath, { recursive: true }).then(() => { console.log(`Created DIR: ${tempDownloadDirPath}`) });
|
||||
|
||||
setPath('ffmpegPath', ffmpegPath);
|
||||
setPath('tempDownloadDirPath', tempDownloadDirPath);
|
||||
if (DOWNLOAD_DIR) {
|
||||
setPath('downloadDirPath', DOWNLOAD_DIR);
|
||||
} else {
|
||||
if(!await fs.exists(appDownloadDirPath)) fs.mkdir(appDownloadDirPath, { recursive: true }).then(() => { console.log(`Created DIR: ${appDownloadDirPath}`) });
|
||||
// if(!await fs.exists(appDownloadDirPath)) fs.mkdir(appDownloadDirPath, { recursive: true }).then(() => { console.log(`Created DIR: ${appDownloadDirPath}`) });
|
||||
setPath('downloadDirPath', appDownloadDirPath);
|
||||
}
|
||||
console.log('Paths initialized:', { ffmpegPath, tempDownloadDirPath, downloadDirPath: DOWNLOAD_DIR || appDownloadDirPath });
|
||||
@@ -268,6 +271,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
|
||||
// Check for yt-dlp auto-update
|
||||
useEffect(() => {
|
||||
const handleYtDlpAutoUpdate = async () => {
|
||||
// Only run once when both settings and KV pairs are loaded
|
||||
if (!isSettingsStatePropagated || !isKvPairsStatePropagated) {
|
||||
console.log("Skipping yt-dlp auto-update check, waiting for configs to load...");
|
||||
@@ -278,6 +282,11 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
console.log("Auto-update check already performed in this session, skipping");
|
||||
return;
|
||||
}
|
||||
const isFlatpak = await invoke<boolean>('is_flatpak');
|
||||
if (isFlatpak) {
|
||||
console.log("Flatpak detected! Skipping yt-dlp auto-update");
|
||||
return;
|
||||
}
|
||||
hasRunYtDlpAutoUpdateRef.current = true;
|
||||
console.log("Checking yt-dlp auto-update with loaded config values:", {
|
||||
autoUpdate: YTDLP_AUTO_UPDATE,
|
||||
@@ -292,6 +301,8 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
} else {
|
||||
console.log("Skipping yt-dlp auto-update, either disabled or recently updated.");
|
||||
}
|
||||
}
|
||||
handleYtDlpAutoUpdate()
|
||||
}, [isSettingsStatePropagated, isKvPairsStatePropagated]);
|
||||
|
||||
// Check POT server status and auto-start if enabled
|
||||
|
||||
@@ -2,11 +2,14 @@ import { join, resourceDir, homeDir } from "@tauri-apps/api/path";
|
||||
import * as fs from "@tauri-apps/plugin-fs";
|
||||
import { useKvPairs } from "@/helpers/use-kvpairs";
|
||||
import { useSettingsPageStatesStore } from "@/services/store";
|
||||
import { Command } from "@tauri-apps/plugin-shell";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
interface FileMap {
|
||||
source: string;
|
||||
destination: string;
|
||||
dir: string;
|
||||
content?: string;
|
||||
}
|
||||
|
||||
export function useLinuxRegisterer() {
|
||||
@@ -15,15 +18,64 @@ export function useLinuxRegisterer() {
|
||||
|
||||
const registerToLinux = async () => {
|
||||
try {
|
||||
const isFlatpak = await invoke<boolean>('is_flatpak');
|
||||
const resourceDirPath = isFlatpak ? '/app/lib/neodlp' : await resourceDir();
|
||||
const homeDirPath = await homeDir();
|
||||
const flatpakChromeManifestContent = {
|
||||
name: "com.neosubhamoy.neodlp",
|
||||
description: "NeoDLP MsgHost",
|
||||
path: `${homeDirPath}/.local/bin/neodlp-msghost`,
|
||||
type: "stdio",
|
||||
allowed_origins: ["chrome-extension://mehopeailfjmiloiiohgicphlcgpompf/"]
|
||||
};
|
||||
const flatpakFirefoxManifestContent = {
|
||||
name: "com.neosubhamoy.neodlp",
|
||||
description: "NeoDLP MsgHost",
|
||||
path: `${homeDirPath}/.local/bin/neodlp-msghost`,
|
||||
type: "stdio",
|
||||
allowed_extensions: ["neodlp@neosubhamoy.com"]
|
||||
};
|
||||
|
||||
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_cli.py', destination: 'yt-dlp-plugins/bgutil-ytdlp-pot-provider/yt_dlp_plugins/extractor/getpot_bgutil_cli.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 resourceDirPath = await resourceDir();
|
||||
const homeDirPath = await homeDir();
|
||||
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/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: 'neodlp-msghost', destination: '.local/bin/neodlp-msghost', dir: '.local/bin/' },
|
||||
];
|
||||
|
||||
if (isFlatpak) {
|
||||
for (const file of filesToCopyFlatpak) {
|
||||
const sourcePath = await join(resourceDirPath, file.source);
|
||||
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) {
|
||||
const writeOutput = await writeCommand.execute();
|
||||
if (writeOutput.code === 0) {
|
||||
console.log(`File ${file.destination} created successfully at ${destinationPath}`);
|
||||
} else {
|
||||
console.error(`Failed to create file ${file.destination} at ${destinationPath}:`, writeOutput.stderr);
|
||||
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 {
|
||||
for (const file of filesToCopy) {
|
||||
const sourcePath = await join(resourceDirPath, file.source);
|
||||
const destinationDir = await join(homeDirPath, file.dir);
|
||||
@@ -40,6 +92,7 @@ export function useLinuxRegisterer() {
|
||||
console.log(`File ${file.source} copied successfully to ${destinationPath}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
saveKvPair('linux_registered_version', appVersion);
|
||||
return { success: true, message: 'Registered successfully' }
|
||||
} catch (error) {
|
||||
|
||||
@@ -17,10 +17,11 @@ export function useYtDlpUpdater() {
|
||||
|
||||
const updateYtDlp = async () => {
|
||||
const CURRENT_TIMESTAMP = Date.now();
|
||||
const isFlatpak = await invoke<boolean>('is_flatpak');
|
||||
setIsUpdatingYtDlp(true);
|
||||
LOG.info('NEODLP', 'Updating yt-dlp to latest version');
|
||||
try {
|
||||
const command = currentPlatform === 'linux' ? Command.create('pkexec', ['yt-dlp', '--update-to', ytDlpUpdateChannel]) : Command.sidecar('binaries/yt-dlp', ['--update-to', ytDlpUpdateChannel]);
|
||||
const command = currentPlatform === 'linux' && !isFlatpak ? Command.create('pkexec', ['yt-dlp', '--update-to', ytDlpUpdateChannel]) : Command.sidecar('binaries/yt-dlp', ['--update-to', ytDlpUpdateChannel]);
|
||||
const output = await command.execute();
|
||||
if (output.code === 0) {
|
||||
console.log("yt-dlp updated successfully:", output.stdout);
|
||||
|
||||
Reference in New Issue
Block a user