1
1
mirror of https://github.com/neosubhamoy/pytubepp-helper.git synced 2026-02-04 11:22:22 +05:30

(feat/refactor): added support for linux (debian) and updated project structure

This commit is contained in:
2024-09-17 10:20:11 +05:30
Unverified
parent 31af106173
commit 322ed6e83a
116 changed files with 9723 additions and 0 deletions

7
windows/src-tauri/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# Generated by Tauri
# will have schema files for capabilities auto-completion
/gen/schemas

4767
windows/src-tauri/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,30 @@
[package]
name = "pytubepp-helper"
version = "0.1.0"
description = "PytubePP Helper"
authors = ["neosubhamoy"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "1", features = [] }
[dependencies]
tauri = { version = "1", features = [ "process-relaunch", "window-start-dragging", "window-close", "window-unmaximize", "process-exit", "window-show", "window-unminimize", "window-hide", "window-minimize", "window-maximize", "system-tray", "shell-all"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1.39.2", features = ["full"] }
tokio-tungstenite = "*"
futures-util = "0.3.30"
[features]
# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!
custom-protocol = ["tauri/custom-protocol"]
[workspace]
members = [
".",
"msghost",
"autostart"
]

7
windows/src-tauri/autostart/Cargo.lock generated Normal file
View File

@@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "pytubepp-helper-autostart"
version = "0.1.0"

View File

@@ -0,0 +1,16 @@
[package]
name = "pytubepp-helper-autostart"
version = "0.1.0"
description = "PytubePP Helper (Autostart)"
authors = ["neosubhamoy"]
edition = "2021"
build = "build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
websocket = "0.27.1"
serde_json = "1.0"
[build-dependencies]
winresource = "0.1.17"

View File

@@ -0,0 +1,8 @@
extern crate winresource;
fn main() {
if std::env::var("CARGO_CFG_TARGET_OS").unwrap() == "windows" {
let res = winresource::WindowsResource::new();
res.compile().unwrap();
}
}

View File

@@ -0,0 +1,55 @@
#![windows_subsystem = "windows"]
use std::process::Command;
use websocket::client::ClientBuilder;
use websocket::OwnedMessage;
use std::thread::sleep;
use std::time::Duration;
fn connect_with_retry(url: &str, max_attempts: u32) -> Result<websocket::sync::Client<std::net::TcpStream>, Box<dyn std::error::Error>> {
let mut attempts = 0;
loop {
match ClientBuilder::new(url).unwrap().connect_insecure() {
Ok(client) => {
eprintln!("Successfully connected to Tauri app :)");
return Ok(client);
}
Err(e) => {
attempts += 1;
if attempts >= max_attempts {
return Err(Box::new(e));
}
let wait_time = Duration::from_secs(2u64.pow(attempts));
eprintln!("Connection attempt {} failed. Retrying in {:?}...", attempts, wait_time);
sleep(wait_time);
}
}
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Launch the main application
let _ = Command::new("pytubepp-helper.exe")
.spawn();
// Connect with the Tauri app
let websocket_url = "ws://localhost:3030";
eprintln!("Attempting to connect to {}", websocket_url);
let mut client = match connect_with_retry(websocket_url, 2) {
Ok(client) => client,
Err(e) => {
eprintln!("Failed to connect after multiple attempts: {:?}", e);
return Err(e);
}
};
// Send message to Tauri app
client.send_message(&OwnedMessage::Text(serde_json::json!({
"url": "",
"command": "autostart",
"argument": ""
}).to_string()))?;
Ok(())
}

View File

@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<DirectoryRef Id="TARGETDIR">
<Component Id="PytubeppHelperFragmentRegistryEntries" Guid="*">
<RegistryKey Root="HKCU" Key="Software\Google\Chrome\NativeMessagingHosts\com.neosubhamoy.pytubepp.helper" Action="createAndRemoveOnUninstall">
<RegistryValue Type="string" Value="[INSTALLDIR]pytubepp-helper-msghost.json" KeyPath="no" />
</RegistryKey>
<RegistryKey Root="HKCU" Key="Software\Mozilla\NativeMessagingHosts\com.neosubhamoy.pytubepp.helper" Action="createAndRemoveOnUninstall">
<RegistryValue Type="string" Value="[INSTALLDIR]pytubepp-helper-msghost-moz.json" KeyPath="no" />
</RegistryKey>
<RegistryKey Root="HKCU" Key="Software\Microsoft\Windows\CurrentVersion\Run">
<RegistryValue Name="pytubepp-helper" Type="string" Value="[INSTALLDIR]pytubepp-helper-autostart.exe" KeyPath="no" />
</RegistryKey>
</Component>
</DirectoryRef>
</Fragment>
</Wix>

7
windows/src-tauri/msghost/Cargo.lock generated Normal file
View File

@@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "pytubepp-helper-msghost"
version = "0.1.0"

View File

@@ -0,0 +1,16 @@
[package]
name = "pytubepp-helper-msghost"
version = "0.1.0"
description = "PytubePP Helper Native Messaging Host"
authors = ["neosubhamoy"]
edition = "2021"
build = "build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
websocket = "0.27.1"
serde_json = "1.0"
[build-dependencies]
winresource = "0.1.17"

View File

@@ -0,0 +1,8 @@
extern crate winresource;
fn main() {
if std::env::var("CARGO_CFG_TARGET_OS").unwrap() == "windows" {
let res = winresource::WindowsResource::new();
res.compile().unwrap();
}
}

View File

@@ -0,0 +1,103 @@
use std::io::{self, Read, Write};
use websocket::client::ClientBuilder;
use websocket::OwnedMessage;
use std::thread::sleep;
use std::time::Duration;
use serde_json::Value;
fn connect_with_retry(url: &str, max_attempts: u32) -> Result<websocket::sync::Client<std::net::TcpStream>, Box<dyn std::error::Error>> {
let mut attempts = 0;
loop {
match ClientBuilder::new(url).unwrap().connect_insecure() {
Ok(client) => {
eprintln!("Successfully connected to Tauri app :)");
return Ok(client);
}
Err(e) => {
attempts += 1;
if attempts >= max_attempts {
return Err(Box::new(e));
}
let wait_time = Duration::from_secs(2u64.pow(attempts));
eprintln!("Connection attempt {} failed. Retrying in {:?}...", attempts, wait_time);
sleep(wait_time);
}
}
}
}
fn read_stdin_message() -> Result<String, Box<dyn std::error::Error>> {
let mut stdin = io::stdin();
let mut length_bytes = [0u8; 4];
stdin.read_exact(&mut length_bytes)?;
let length = u32::from_ne_bytes(length_bytes) as usize;
let mut buffer = vec![0u8; length];
stdin.read_exact(&mut buffer)?;
let message = String::from_utf8(buffer)?;
Ok(message)
}
fn write_stdout_message(message: &str) -> Result<(), Box<dyn std::error::Error>> {
let message_bytes = message.as_bytes();
let message_size = message_bytes.len();
io::stdout().write_all(&(message_size as u32).to_ne_bytes())?;
io::stdout().write_all(message_bytes)?;
io::stdout().flush()?;
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
eprintln!("Waiting for message from extension...");
let input = match read_stdin_message() {
Ok(msg) => {
eprintln!("Received message: {}", msg);
msg
},
Err(e) => {
eprintln!("Error reading message: {:?}", e);
return Err(e);
}
};
// Send immediate response to the extension
write_stdout_message(&serde_json::json!({
"status": "received",
"message": "Message received by native host"
}).to_string())?;
let parsed: Value = serde_json::from_str(&input)?;
let websocket_url = "ws://localhost:3030";
eprintln!("Attempting to connect to {}", websocket_url);
let mut client = match connect_with_retry(websocket_url, 2) {
Ok(client) => client,
Err(e) => {
eprintln!("Failed to connect after multiple attempts: {:?}", e);
write_stdout_message(&serde_json::json!({
"status": "error",
"message": "Failed to connect to Tauri app"
}).to_string())?;
return Err(e);
}
};
// Send message to Tauri app
client.send_message(&OwnedMessage::Text(parsed.to_string()))?;
// Receive response from Tauri app
let message = client.recv_message()?;
// Send Tauri app's response back to browser extension
if let OwnedMessage::Text(text) = message {
write_stdout_message(&serde_json::json!({
"status": "success",
"response": text
}).to_string())?;
}
Ok(())
}

View File

@@ -0,0 +1,7 @@
{
"name": "com.neosubhamoy.pytubepp.helper",
"description": "A helper app for pytubepp-extention to communicate with pytubepp-cli",
"path": "pytubepp-helper-msghost.exe",
"type": "stdio",
"allowed_extensions": ["pytubepp-addon@neosubhamoy.com"]
}

View File

@@ -0,0 +1,7 @@
{
"name": "com.neosubhamoy.pytubepp.helper",
"description": "A helper app for pytubepp-extension to communicate with pytubepp-cli",
"path": "pytubepp-helper-msghost.exe",
"type": "stdio",
"allowed_origins": ["chrome-extension://adebedkaedobamilbbobbajepnnkkfcg/", "chrome-extension://mmhhbpdhkogpcieblpdilflfoimajepp/", "chrome-extension://ebneapoekcjelholncnlpdohjbjabhbi/", "chrome-extension://cohjehldppmnbfogjdjpbjknhlhmfhjj/"]
}

View File

@@ -0,0 +1,266 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use std::{process::Command, sync::Arc, env};
use serde_json::Value;
use tauri::{CustomMenuItem, Manager, SystemTray, SystemTrayEvent, SystemTrayMenu};
use tokio::{net::{TcpListener, TcpStream}, sync::{Mutex, oneshot}};
use tokio_tungstenite::accept_async;
use futures_util::{SinkExt, StreamExt};
struct ResponseChannel {
sender: Option<oneshot::Sender<String>>,
}
struct WebSocketState {
sender: Option<futures_util::stream::SplitSink<tokio_tungstenite::WebSocketStream<TcpStream>, tokio_tungstenite::tungstenite::Message>>,
response_channel: ResponseChannel,
}
// #[tauri::command]
// async fn handle_websocket_message(message: String) -> Result<String, String> {
// Ok(format!("{}", message))
// }
#[tauri::command]
async fn send_to_extension(
message: String,
state: tauri::State<'_, Arc<Mutex<WebSocketState>>>,
) -> Result<(), String> {
let mut state = state.lock().await;
if let Some(sender) = &mut state.sender {
sender.send(tokio_tungstenite::tungstenite::Message::Text(message)).await
.map_err(|e| format!("Failed to send message: {}", e))?;
Ok(())
} else {
Err("No active WebSocket connection".to_string())
}
}
#[tauri::command]
async fn receive_frontend_response(
response: String,
state: tauri::State<'_, Arc<Mutex<WebSocketState>>>,
) -> Result<(), String> {
let mut state = state.lock().await;
if let Some(sender) = state.response_channel.sender.take() {
sender.send(response).map_err(|e| format!("Failed to send response: {:?}", e))?;
}
Ok(())
}
#[tauri::command]
fn fetch_video_info(url: String) {
#[cfg(target_os = "windows")]
{
let command = format!("pytubepp \"{}\" -i", &url);
Command::new("cmd")
.args(["/k", command.as_str()])
.spawn()
.unwrap();
}
#[cfg(target_os = "linux")]
{
let command = format!("pytubepp \"{}\" -i", &url);
Command::new("gnome-terminal")
.args(["--", command.as_str()])
.spawn()
.unwrap();
}
#[cfg(target_os = "macos")]
{
let command = format!("pytubepp \"{}\" -i", &url);
Command::new("osascript")
.arg("-e")
.arg(format!(
"tell app \"Terminal\" to activate do script \"{}\"",
command
))
.spawn()
.unwrap();
}
}
#[tauri::command]
fn install_program(installer: String ,program: String) {
#[cfg(target_os = "windows")]
{
let command = format!("{} install {}", &installer, &program);
Command::new("cmd")
.args(["/k", command.as_str()])
.spawn()
.unwrap();
}
#[cfg(target_os = "linux")]
{
let command = format!("{} install {}", &installer, &program);
Command::new("gnome-terminal")
.args(["--", command.as_str()])
.spawn()
.unwrap();
}
#[cfg(target_os = "macos")]
{
let command = format!("{} install {}", &installer, &program);
Command::new("osascript")
.arg("-e")
.arg(format!(
"tell app \"Terminal\" to activate do script \"{}\"",
command
))
.spawn()
.unwrap();
}
}
#[tauri::command]
fn download_stream(url: String, stream: String) {
#[cfg(target_os = "windows")]
{
let command = format!("pytubepp \"{}\" -s {}", &url, &stream);
Command::new("cmd")
.args(["/k", command.as_str()])
.spawn()
.unwrap();
}
#[cfg(target_os = "linux")]
{
let command = format!("pytubepp \"{}\" -s {}", &url, &stream);
Command::new("gnome-terminal")
.args(["--", command.as_str()])
.spawn()
.unwrap();
}
#[cfg(target_os = "macos")]
{
let command = format!("pytubepp \"{}\" -s {}", &url, &stream);
Command::new("osascript")
.arg("-e")
.arg(format!(
"tell app \"Terminal\" to activate do script \"{}\"",
command
))
.spawn()
.unwrap();
}
}
#[tokio::main]
async fn main() {
let websocket_state = Arc::new(Mutex::new(WebSocketState {
sender: None,
response_channel: ResponseChannel { sender: None },
}));
let tray_menu = SystemTrayMenu::new()
.add_item(CustomMenuItem::new("show".to_string(), "Show"))
.add_item(CustomMenuItem::new("quit".to_string(), "Quit"));
let system_tray = SystemTray::new().with_menu(tray_menu).with_tooltip("PytubePP Helper");
tauri::Builder::default()
.system_tray(system_tray)
.on_system_tray_event(|app, event| match event {
SystemTrayEvent::LeftClick {
position: _,
size: _,
..
} => {
let window = app.get_window("main").unwrap();
window.show().unwrap();
window.set_focus().unwrap();
}
SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() {
"show" => {
let window = app.get_window("main").unwrap();
window.show().unwrap();
window.set_focus().unwrap();
}
"quit" => {
app.exit(0);
}
_ => {}
},
_ => {}
})
.manage(websocket_state.clone())
.setup(move |app| {
let app_handle = app.handle();
let ws_state = websocket_state.clone();
tokio::spawn(async move {
let listener = TcpListener::bind("127.0.0.1:3030").await.unwrap();
println!("WebSocket server listening on ws://127.0.0.1:3030");
while let Ok((stream, _)) = listener.accept().await {
let app_handle = app_handle.clone();
let ws_state = ws_state.clone();
tokio::spawn(handle_connection(stream, app_handle, ws_state));
}
});
Ok(())
})
.invoke_handler(tauri::generate_handler![
// handle_websocket_message,
send_to_extension,
fetch_video_info,
install_program,
download_stream,
receive_frontend_response
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
async fn handle_connection(stream: TcpStream, app_handle: tauri::AppHandle, ws_state: Arc<Mutex<WebSocketState>>) {
let ws_stream = accept_async(stream).await.unwrap();
let (ws_sender, mut ws_receiver) = ws_stream.split();
// Store the sender in the shared state
{
let mut state = ws_state.lock().await;
state.sender = Some(ws_sender);
}
println!("New WebSocket connection established");
while let Some(msg) = ws_receiver.next().await {
if let Ok(msg) = msg {
if let Ok(text) = msg.to_text() {
println!("Received message: {}", text);
// Parse the JSON message
if let Ok(json_value) = serde_json::from_str::<Value>(text) {
// Create a new channel for this request
let (response_sender, response_receiver) = oneshot::channel();
{
let mut state = ws_state.lock().await;
state.response_channel.sender = Some(response_sender);
}
// Emit an event to the frontend
app_handle.emit_all("websocket-message", json_value).unwrap();
// Wait for the response from the frontend
let response = response_receiver.await
.unwrap_or_else(|e| format!("Error receiving response: {:?}", e));
// Send the response back through WebSocket
let mut state = ws_state.lock().await;
if let Some(sender) = &mut state.sender {
let _ = sender.send(tokio_tungstenite::tungstenite::Message::Text(response)).await;
}
}
}
}
}
println!("WebSocket connection closed");
// Remove the sender from the shared state when the connection closes
let mut state = ws_state.lock().await;
state.sender = None;
}

View File

@@ -0,0 +1,121 @@
{
"build": {
"beforeDevCommand": "cargo build --manifest-path=./src-tauri/msghost/Cargo.toml && cargo build --manifest-path=./src-tauri/autostart/Cargo.toml && npm run dev",
"beforeBuildCommand": "cargo build --release --manifest-path=./src-tauri/msghost/Cargo.toml && cargo build --release --manifest-path=./src-tauri/autostart/Cargo.toml && node signFiles.js && node copyFiles.js && npm run build",
"devPath": "http://localhost:1420",
"distDir": "../dist"
},
"package": {
"productName": "pytubepp-helper",
"version": "0.1.0"
},
"tauri": {
"allowlist": {
"all": false,
"shell": {
"all": true,
"execute": true,
"sidecar": true,
"open": true,
"scope": [
{
"name": "is-winget-installed",
"cmd": "winget",
"args": ["--version"]
},
{
"name": "is-python-installed",
"cmd": "python",
"args": ["--version"]
},
{
"name": "is-pip-installed",
"cmd": "pip",
"args": ["--version"]
},
{
"name": "is-ffmpeg-installed",
"cmd": "ffmpeg",
"args": ["-version"]
},
{
"name": "is-pytubepp-installed",
"cmd": "pytubepp",
"args": ["--version"]
},
{
"name": "fetch-video-info",
"cmd": "pytubefix",
"args": [{ "validator": "\\S+"}, "--list"]
}
]
},
"fs": {
"scope": [
"$RESOURCE/pytubepp-helper-msghost.json",
"$RESOURCE/pytubepp-helper-msghost-moz.json",
"$RESOURCE/pytubepp-helper-msghost.exe",
"$RESOURCE/pytubepp-helper-autostart.exe"
]
},
"window": {
"all": false,
"close": true,
"hide": true,
"show": true,
"maximize": true,
"minimize": true,
"unmaximize": true,
"unminimize": true,
"startDragging": true
},
"process": {
"all": false,
"exit": true,
"relaunch": true
}
},
"windows": [
{
"title": "PytubePP Helper",
"width": 500,
"height": 320
}
],
"security": {
"csp": null
},
"bundle": {
"active": true,
"targets": "all",
"identifier": "com.neosubhamoy.pytubepp.helper",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"windows": {
"certificateThumbprint": "c12a1579698a3cc86ef3b2c942172cd995149b10",
"digestAlgorithm": "sha256",
"timestampUrl": "http://timestamp.sectigo.com",
"wix": {
"fragmentPaths": ["installer/windows/wix-fragment-registry.wxs"],
"componentRefs": ["PytubeppHelperFragmentRegistryEntries"]
}
},
"resources": [
"pytubepp-helper-msghost.json",
"pytubepp-helper-msghost-moz.json",
"pytubepp-helper-msghost.exe",
"pytubepp-helper-autostart.exe"
]
},
"systemTray": {
"iconPath": "icons/icon.ico",
"iconAsTemplate": true
}
}
}