mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-03-09 15:40:26 +00:00
Add Somewhat working logic
This commit is contained in:
parent
2a4221e787
commit
5e9aa2b0ea
13 changed files with 461 additions and 54 deletions
|
@ -1,48 +1,162 @@
|
||||||
import aria2p
|
import aria2p
|
||||||
|
from typing import Union, List
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from aria2p import API, Client, Download
|
||||||
|
import requests
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class HttpDownloader:
|
class HttpDownloader:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.download = None
|
self.downloads = [] # vom păstra toate download-urile active
|
||||||
self.aria2 = aria2p.API(
|
self.aria2 = API(Client(host="http://localhost", port=6800))
|
||||||
aria2p.Client(
|
self.download = None # pentru compatibilitate cu codul vechi
|
||||||
host="http://localhost",
|
|
||||||
port=6800,
|
def unlock_alldebrid_link(self, link: str) -> str:
|
||||||
secret=""
|
"""Deblochează un link AllDebrid și returnează link-ul real de descărcare."""
|
||||||
|
api_key = os.getenv('ALLDEBRID_API_KEY')
|
||||||
|
if not api_key:
|
||||||
|
logger.error("AllDebrid API key nu a fost găsită în variabilele de mediu")
|
||||||
|
return link
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(
|
||||||
|
"https://api.alldebrid.com/v4/link/unlock",
|
||||||
|
params={
|
||||||
|
"agent": "hydra",
|
||||||
|
"apikey": api_key,
|
||||||
|
"link": link
|
||||||
|
}
|
||||||
)
|
)
|
||||||
)
|
data = response.json()
|
||||||
|
|
||||||
def start_download(self, url: str, save_path: str, header: str, out: str = None):
|
if data.get("status") == "success":
|
||||||
if self.download:
|
return data["data"]["link"]
|
||||||
self.aria2.resume([self.download])
|
else:
|
||||||
|
logger.error(f"Eroare la deblocarea link-ului AllDebrid: {data.get('error', {}).get('message', 'Unknown error')}")
|
||||||
|
return link
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Eroare la apelul API AllDebrid: {str(e)}")
|
||||||
|
return link
|
||||||
|
|
||||||
|
def start_download(self, url: Union[str, List[str]], save_path: str, header: str = None, out: str = None):
|
||||||
|
logger.info(f"Starting download with URL: {url}, save_path: {save_path}, header: {header}, out: {out}")
|
||||||
|
|
||||||
|
# Pentru AllDebrid care returnează un link per fișier
|
||||||
|
if isinstance(url, list):
|
||||||
|
logger.info(f"Multiple URLs detected: {len(url)} files to download")
|
||||||
|
self.downloads = []
|
||||||
|
|
||||||
|
# Deblocăm toate link-urile AllDebrid
|
||||||
|
unlocked_urls = []
|
||||||
|
for single_url in url:
|
||||||
|
logger.info(f"Unlocking AllDebrid URL: {single_url}")
|
||||||
|
unlocked_url = self.unlock_alldebrid_link(single_url)
|
||||||
|
if unlocked_url:
|
||||||
|
unlocked_urls.append(unlocked_url)
|
||||||
|
logger.info(f"URL deblocat cu succes: {unlocked_url}")
|
||||||
|
|
||||||
|
# Descărcăm folosind link-urile deblocate
|
||||||
|
for unlocked_url in unlocked_urls:
|
||||||
|
logger.info(f"Adding download for unlocked URL: {unlocked_url}")
|
||||||
|
options = {
|
||||||
|
"dir": save_path
|
||||||
|
}
|
||||||
|
if header:
|
||||||
|
if isinstance(header, list):
|
||||||
|
options["header"] = header
|
||||||
|
else:
|
||||||
|
options["header"] = [header]
|
||||||
|
|
||||||
|
try:
|
||||||
|
download = self.aria2.add_uris([unlocked_url], options=options)
|
||||||
|
logger.info(f"Download added successfully: {download.gid}")
|
||||||
|
self.downloads.append(download)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error adding download for URL {unlocked_url}: {str(e)}")
|
||||||
|
|
||||||
|
if self.downloads:
|
||||||
|
self.download = self.downloads[0] # păstrăm primul pentru referință
|
||||||
|
else:
|
||||||
|
logger.error("No downloads were successfully added!")
|
||||||
|
|
||||||
|
# Pentru RealDebrid/alte servicii care returnează un singur link pentru tot
|
||||||
else:
|
else:
|
||||||
downloads = self.aria2.add(url, options={"header": header, "dir": save_path, "out": out})
|
logger.info(f"Single URL download: {url}")
|
||||||
|
options = {
|
||||||
|
"dir": save_path
|
||||||
|
}
|
||||||
|
if header:
|
||||||
|
if isinstance(header, list):
|
||||||
|
options["header"] = header
|
||||||
|
else:
|
||||||
|
options["header"] = [header]
|
||||||
|
if out:
|
||||||
|
options["out"] = out
|
||||||
|
|
||||||
self.download = downloads[0]
|
try:
|
||||||
|
download = self.aria2.add_uris([url], options=options)
|
||||||
|
self.download = download
|
||||||
|
self.downloads = [self.download]
|
||||||
|
logger.info(f"Single download added successfully: {self.download.gid}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error adding single download: {str(e)}")
|
||||||
|
|
||||||
def pause_download(self):
|
def pause_download(self):
|
||||||
if self.download:
|
try:
|
||||||
self.aria2.pause([self.download])
|
for download in self.downloads:
|
||||||
|
download.pause()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error pausing downloads: {str(e)}")
|
||||||
|
|
||||||
def cancel_download(self):
|
def cancel_download(self):
|
||||||
if self.download:
|
try:
|
||||||
self.aria2.remove([self.download])
|
for download in self.downloads:
|
||||||
self.download = None
|
download.remove()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error canceling downloads: {str(e)}")
|
||||||
|
|
||||||
def get_download_status(self):
|
def get_download_status(self):
|
||||||
if self.download == None:
|
try:
|
||||||
|
if not self.downloads:
|
||||||
|
return None
|
||||||
|
|
||||||
|
total_size = 0
|
||||||
|
downloaded = 0
|
||||||
|
download_speed = 0
|
||||||
|
active_downloads = []
|
||||||
|
|
||||||
|
for download in self.downloads:
|
||||||
|
try:
|
||||||
|
download.update()
|
||||||
|
if download.is_active:
|
||||||
|
active_downloads.append(download)
|
||||||
|
total_size += download.total_length
|
||||||
|
downloaded += download.completed_length
|
||||||
|
download_speed += download.download_speed
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error updating download status for {download.gid}: {str(e)}")
|
||||||
|
|
||||||
|
if not active_downloads:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Folosim primul download pentru numele folderului
|
||||||
|
folder_path = os.path.dirname(active_downloads[0].files[0].path)
|
||||||
|
folder_name = os.path.basename(folder_path)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"progress": downloaded / total_size if total_size > 0 else 0,
|
||||||
|
"numPeers": 0, # nu este relevant pentru HTTP
|
||||||
|
"numSeeds": 0, # nu este relevant pentru HTTP
|
||||||
|
"downloadSpeed": download_speed,
|
||||||
|
"bytesDownloaded": downloaded,
|
||||||
|
"fileSize": total_size,
|
||||||
|
"folderName": folder_name,
|
||||||
|
"status": "downloading"
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting download status: {str(e)}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
download = self.aria2.get_download(self.download.gid)
|
|
||||||
|
|
||||||
response = {
|
|
||||||
'folderName': download.name,
|
|
||||||
'fileSize': download.total_length,
|
|
||||||
'progress': download.completed_length / download.total_length if download.total_length else 0,
|
|
||||||
'downloadSpeed': download.download_speed,
|
|
||||||
'numPeers': 0,
|
|
||||||
'numSeeds': 0,
|
|
||||||
'status': download.status,
|
|
||||||
'bytesDownloaded': download.completed_length,
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
|
@ -23,19 +23,27 @@ torrent_session = lt.session({'listen_interfaces': '0.0.0.0:{port}'.format(port=
|
||||||
if start_download_payload:
|
if start_download_payload:
|
||||||
initial_download = json.loads(urllib.parse.unquote(start_download_payload))
|
initial_download = json.loads(urllib.parse.unquote(start_download_payload))
|
||||||
downloading_game_id = initial_download['game_id']
|
downloading_game_id = initial_download['game_id']
|
||||||
|
url = initial_download['url']
|
||||||
|
|
||||||
if initial_download['url'].startswith('magnet'):
|
# Verificăm dacă avem un URL de tip magnet (fie direct, fie primul dintr-o listă)
|
||||||
|
is_magnet = False
|
||||||
|
if isinstance(url, str):
|
||||||
|
is_magnet = url.startswith('magnet')
|
||||||
|
elif isinstance(url, list) and url:
|
||||||
|
is_magnet = False # Pentru AllDebrid, chiar dacă vine dintr-un magnet, primim HTTP links
|
||||||
|
|
||||||
|
if is_magnet:
|
||||||
torrent_downloader = TorrentDownloader(torrent_session)
|
torrent_downloader = TorrentDownloader(torrent_session)
|
||||||
downloads[initial_download['game_id']] = torrent_downloader
|
downloads[initial_download['game_id']] = torrent_downloader
|
||||||
try:
|
try:
|
||||||
torrent_downloader.start_download(initial_download['url'], initial_download['save_path'])
|
torrent_downloader.start_download(url, initial_download['save_path'])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Error starting torrent download", e)
|
print("Error starting torrent download", e)
|
||||||
else:
|
else:
|
||||||
http_downloader = HttpDownloader()
|
http_downloader = HttpDownloader()
|
||||||
downloads[initial_download['game_id']] = http_downloader
|
downloads[initial_download['game_id']] = http_downloader
|
||||||
try:
|
try:
|
||||||
http_downloader.start_download(initial_download['url'], initial_download['save_path'], initial_download.get('header'), initial_download.get("out"))
|
http_downloader.start_download(url, initial_download['save_path'], initial_download.get('header'), initial_download.get("out"))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Error starting http download", e)
|
print("Error starting http download", e)
|
||||||
|
|
||||||
|
@ -135,10 +143,18 @@ def action():
|
||||||
|
|
||||||
if action == 'start':
|
if action == 'start':
|
||||||
url = data.get('url')
|
url = data.get('url')
|
||||||
|
print(f"Starting download with URL: {url}")
|
||||||
|
|
||||||
existing_downloader = downloads.get(game_id)
|
existing_downloader = downloads.get(game_id)
|
||||||
|
|
||||||
if url.startswith('magnet'):
|
# Verificăm dacă avem un URL de tip magnet (fie direct, fie primul dintr-o listă)
|
||||||
|
is_magnet = False
|
||||||
|
if isinstance(url, str):
|
||||||
|
is_magnet = url.startswith('magnet')
|
||||||
|
elif isinstance(url, list) and url:
|
||||||
|
is_magnet = False # Pentru AllDebrid, chiar dacă vine dintr-un magnet, primim HTTP links
|
||||||
|
|
||||||
|
if is_magnet:
|
||||||
if existing_downloader and isinstance(existing_downloader, TorrentDownloader):
|
if existing_downloader and isinstance(existing_downloader, TorrentDownloader):
|
||||||
existing_downloader.start_download(url, data['save_path'])
|
existing_downloader.start_download(url, data['save_path'])
|
||||||
else:
|
else:
|
||||||
|
@ -172,7 +188,6 @@ def action():
|
||||||
downloader = downloads.get(game_id)
|
downloader = downloads.get(game_id)
|
||||||
if downloader:
|
if downloader:
|
||||||
downloader.cancel_download()
|
downloader.cancel_download()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return jsonify({"error": "Invalid action"}), 400
|
return jsonify({"error": "Invalid action"}), 400
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,31 @@ import axios, { AxiosInstance } from "axios";
|
||||||
import type { AllDebridUser } from "@types";
|
import type { AllDebridUser } from "@types";
|
||||||
import { logger } from "@main/services";
|
import { logger } from "@main/services";
|
||||||
|
|
||||||
|
interface AllDebridMagnetStatus {
|
||||||
|
id: number;
|
||||||
|
filename: string;
|
||||||
|
size: number;
|
||||||
|
status: string;
|
||||||
|
statusCode: number;
|
||||||
|
downloaded: number;
|
||||||
|
uploaded: number;
|
||||||
|
seeders: number;
|
||||||
|
downloadSpeed: number;
|
||||||
|
uploadSpeed: number;
|
||||||
|
uploadDate: number;
|
||||||
|
completionDate: number;
|
||||||
|
links: Array<{
|
||||||
|
link: string;
|
||||||
|
filename: string;
|
||||||
|
size: number;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AllDebridError {
|
||||||
|
code: string;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class AllDebridClient {
|
export class AllDebridClient {
|
||||||
private static instance: AxiosInstance;
|
private static instance: AxiosInstance;
|
||||||
private static readonly baseURL = "https://api.alldebrid.com/v4";
|
private static readonly baseURL = "https://api.alldebrid.com/v4";
|
||||||
|
@ -9,11 +34,11 @@ export class AllDebridClient {
|
||||||
static authorize(apiKey: string) {
|
static authorize(apiKey: string) {
|
||||||
logger.info("[AllDebrid] Authorizing with key:", apiKey ? "***" : "empty");
|
logger.info("[AllDebrid] Authorizing with key:", apiKey ? "***" : "empty");
|
||||||
this.instance = axios.create({
|
this.instance = axios.create({
|
||||||
baseURL: this.baseURL,
|
baseURL: this.baseURL,
|
||||||
params: {
|
params: {
|
||||||
agent: "hydra",
|
agent: "hydra",
|
||||||
apikey: apiKey
|
apikey: apiKey
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,10 +47,7 @@ export class AllDebridClient {
|
||||||
const response = await this.instance.get<{
|
const response = await this.instance.get<{
|
||||||
status: string;
|
status: string;
|
||||||
data?: { user: AllDebridUser };
|
data?: { user: AllDebridUser };
|
||||||
error?: {
|
error?: AllDebridError;
|
||||||
code: string;
|
|
||||||
message: string;
|
|
||||||
};
|
|
||||||
}>("/user");
|
}>("/user");
|
||||||
|
|
||||||
logger.info("[AllDebrid] API Response:", response.data);
|
logger.info("[AllDebrid] API Response:", response.data);
|
||||||
|
@ -63,4 +85,175 @@ export class AllDebridClient {
|
||||||
return { error_code: "alldebrid_network_error" };
|
return { error_code: "alldebrid_network_error" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async uploadMagnet(magnet: string) {
|
||||||
|
try {
|
||||||
|
logger.info("[AllDebrid] Uploading magnet with params:", { magnet });
|
||||||
|
|
||||||
|
const response = await this.instance.get("/magnet/upload", {
|
||||||
|
params: {
|
||||||
|
magnets: [magnet]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info("[AllDebrid] Upload Magnet Raw Response:", JSON.stringify(response.data, null, 2));
|
||||||
|
|
||||||
|
if (response.data.status === "error") {
|
||||||
|
throw new Error(response.data.error?.message || "Unknown error");
|
||||||
|
}
|
||||||
|
|
||||||
|
const magnetInfo = response.data.data.magnets[0];
|
||||||
|
logger.info("[AllDebrid] Magnet Info:", JSON.stringify(magnetInfo, null, 2));
|
||||||
|
|
||||||
|
if (magnetInfo.error) {
|
||||||
|
throw new Error(magnetInfo.error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return magnetInfo.id;
|
||||||
|
} catch (error: any) {
|
||||||
|
logger.error("[AllDebrid] Upload Magnet Error:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async checkMagnetStatus(magnetId: number): Promise<AllDebridMagnetStatus> {
|
||||||
|
try {
|
||||||
|
logger.info("[AllDebrid] Checking magnet status for ID:", magnetId);
|
||||||
|
|
||||||
|
const response = await this.instance.get(`/magnet/status`, {
|
||||||
|
params: {
|
||||||
|
id: magnetId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info("[AllDebrid] Check Magnet Status Raw Response:", JSON.stringify(response.data, null, 2));
|
||||||
|
|
||||||
|
if (!response.data) {
|
||||||
|
throw new Error("No response data received");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.data.status === "error") {
|
||||||
|
throw new Error(response.data.error?.message || "Unknown error");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificăm noua structură a răspunsului
|
||||||
|
const magnetData = response.data.data?.magnets;
|
||||||
|
if (!magnetData || typeof magnetData !== 'object') {
|
||||||
|
logger.error("[AllDebrid] Invalid response structure:", JSON.stringify(response.data, null, 2));
|
||||||
|
throw new Error("Invalid magnet status response format");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convertim răspunsul în formatul așteptat
|
||||||
|
const magnetStatus: AllDebridMagnetStatus = {
|
||||||
|
id: magnetData.id,
|
||||||
|
filename: magnetData.filename,
|
||||||
|
size: magnetData.size,
|
||||||
|
status: magnetData.status,
|
||||||
|
statusCode: magnetData.statusCode,
|
||||||
|
downloaded: magnetData.downloaded,
|
||||||
|
uploaded: magnetData.uploaded,
|
||||||
|
seeders: magnetData.seeders,
|
||||||
|
downloadSpeed: magnetData.downloadSpeed,
|
||||||
|
uploadSpeed: magnetData.uploadSpeed,
|
||||||
|
uploadDate: magnetData.uploadDate,
|
||||||
|
completionDate: magnetData.completionDate,
|
||||||
|
links: magnetData.links.map(link => ({
|
||||||
|
link: link.link,
|
||||||
|
filename: link.filename,
|
||||||
|
size: link.size
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
|
logger.info("[AllDebrid] Magnet Status:", JSON.stringify(magnetStatus, null, 2));
|
||||||
|
|
||||||
|
return magnetStatus;
|
||||||
|
} catch (error: any) {
|
||||||
|
logger.error("[AllDebrid] Check Magnet Status Error:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async unlockLink(link: string) {
|
||||||
|
try {
|
||||||
|
const response = await this.instance.get<{
|
||||||
|
status: string;
|
||||||
|
data?: { link: string };
|
||||||
|
error?: AllDebridError;
|
||||||
|
}>("/link/unlock", {
|
||||||
|
params: {
|
||||||
|
link
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.data.status === "error") {
|
||||||
|
throw new Error(response.data.error?.message || "Unknown error");
|
||||||
|
}
|
||||||
|
|
||||||
|
const unlockedLink = response.data.data?.link;
|
||||||
|
if (!unlockedLink) {
|
||||||
|
throw new Error("No download link received from AllDebrid");
|
||||||
|
}
|
||||||
|
|
||||||
|
return unlockedLink;
|
||||||
|
} catch (error: any) {
|
||||||
|
logger.error("[AllDebrid] Unlock Link Error:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getDownloadUrls(uri: string): Promise<string[]> {
|
||||||
|
try {
|
||||||
|
logger.info("[AllDebrid] Getting download URLs for URI:", uri);
|
||||||
|
|
||||||
|
if (uri.startsWith("magnet:")) {
|
||||||
|
logger.info("[AllDebrid] Detected magnet link, uploading...");
|
||||||
|
// 1. Upload magnet
|
||||||
|
const magnetId = await this.uploadMagnet(uri);
|
||||||
|
logger.info("[AllDebrid] Magnet uploaded, ID:", magnetId);
|
||||||
|
|
||||||
|
// 2. Verificăm statusul până când avem link-uri
|
||||||
|
let retries = 0;
|
||||||
|
let magnetStatus: AllDebridMagnetStatus;
|
||||||
|
|
||||||
|
do {
|
||||||
|
magnetStatus = await this.checkMagnetStatus(magnetId);
|
||||||
|
logger.info("[AllDebrid] Magnet status:", magnetStatus.status, "statusCode:", magnetStatus.statusCode);
|
||||||
|
|
||||||
|
if (magnetStatus.statusCode === 4) { // Ready
|
||||||
|
// Deblocăm fiecare link în parte și aruncăm eroare dacă oricare eșuează
|
||||||
|
const unlockedLinks = await Promise.all(
|
||||||
|
magnetStatus.links.map(async link => {
|
||||||
|
try {
|
||||||
|
const unlockedLink = await this.unlockLink(link.link);
|
||||||
|
logger.info("[AllDebrid] Successfully unlocked link:", unlockedLink);
|
||||||
|
return unlockedLink;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("[AllDebrid] Failed to unlock link:", link.link, error);
|
||||||
|
throw new Error("Failed to unlock all links");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.info("[AllDebrid] Got unlocked download links:", unlockedLinks);
|
||||||
|
return unlockedLinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retries++ > 30) { // Maximum 30 de încercări
|
||||||
|
throw new Error("Timeout waiting for magnet to be ready");
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000)); // Așteptăm 2 secunde între verificări
|
||||||
|
} while (true);
|
||||||
|
} else {
|
||||||
|
logger.info("[AllDebrid] Regular link, unlocking...");
|
||||||
|
// Pentru link-uri normale, doar debridam link-ul
|
||||||
|
const downloadUrl = await this.unlockLink(uri);
|
||||||
|
logger.info("[AllDebrid] Got unlocked download URL:", downloadUrl);
|
||||||
|
return [downloadUrl];
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
logger.error("[AllDebrid] Get Download URLs Error:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@ import { logger } from "../logger";
|
||||||
import { db, downloadsSublevel, gamesSublevel, levelKeys } from "@main/level";
|
import { db, downloadsSublevel, gamesSublevel, levelKeys } from "@main/level";
|
||||||
import { sortBy } from "lodash-es";
|
import { sortBy } from "lodash-es";
|
||||||
import { TorBoxClient } from "./torbox";
|
import { TorBoxClient } from "./torbox";
|
||||||
|
import { AllDebridClient } from "./all-debrid";
|
||||||
|
import { spawn } from "child_process";
|
||||||
|
|
||||||
export class DownloadManager {
|
export class DownloadManager {
|
||||||
private static downloadingGameId: string | null = null;
|
private static downloadingGameId: string | null = null;
|
||||||
|
@ -333,6 +335,18 @@ export class DownloadManager {
|
||||||
save_path: download.downloadPath,
|
save_path: download.downloadPath,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case Downloader.AllDebrid: {
|
||||||
|
const downloadUrls = await AllDebridClient.getDownloadUrls(download.uri);
|
||||||
|
|
||||||
|
if (!downloadUrls.length) throw new Error(DownloadError.NotCachedInAllDebrid);
|
||||||
|
|
||||||
|
return {
|
||||||
|
action: "start",
|
||||||
|
game_id: downloadId,
|
||||||
|
url: downloadUrls,
|
||||||
|
save_path: download.downloadPath,
|
||||||
|
};
|
||||||
|
}
|
||||||
case Downloader.TorBox: {
|
case Downloader.TorBox: {
|
||||||
const { name, url } = await TorBoxClient.getDownloadInfo(download.uri);
|
const { name, url } = await TorBoxClient.getDownloadInfo(download.uri);
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,8 @@ import crypto from "node:crypto";
|
||||||
import { pythonRpcLogger } from "./logger";
|
import { pythonRpcLogger } from "./logger";
|
||||||
import { Readable } from "node:stream";
|
import { Readable } from "node:stream";
|
||||||
import { app, dialog } from "electron";
|
import { app, dialog } from "electron";
|
||||||
|
import { db, levelKeys } from "@main/level";
|
||||||
|
import type { UserPreferences } from "@types";
|
||||||
|
|
||||||
interface GamePayload {
|
interface GamePayload {
|
||||||
game_id: string;
|
game_id: string;
|
||||||
|
@ -42,7 +44,7 @@ export class PythonRPC {
|
||||||
readable.on("data", pythonRpcLogger.log);
|
readable.on("data", pythonRpcLogger.log);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static spawn(
|
public static async spawn(
|
||||||
initialDownload?: GamePayload,
|
initialDownload?: GamePayload,
|
||||||
initialSeeding?: GamePayload[]
|
initialSeeding?: GamePayload[]
|
||||||
) {
|
) {
|
||||||
|
@ -54,6 +56,15 @@ export class PythonRPC {
|
||||||
initialSeeding ? JSON.stringify(initialSeeding) : "",
|
initialSeeding ? JSON.stringify(initialSeeding) : "",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const userPreferences = await db.get<string, UserPreferences | null>(levelKeys.userPreferences, {
|
||||||
|
valueEncoding: "json",
|
||||||
|
});
|
||||||
|
|
||||||
|
const env = {
|
||||||
|
...process.env,
|
||||||
|
ALLDEBRID_API_KEY: userPreferences?.allDebridApiKey || ""
|
||||||
|
};
|
||||||
|
|
||||||
if (app.isPackaged) {
|
if (app.isPackaged) {
|
||||||
const binaryName = binaryNameByPlatform[process.platform]!;
|
const binaryName = binaryNameByPlatform[process.platform]!;
|
||||||
const binaryPath = path.join(
|
const binaryPath = path.join(
|
||||||
|
@ -74,6 +85,7 @@ export class PythonRPC {
|
||||||
const childProcess = cp.spawn(binaryPath, commonArgs, {
|
const childProcess = cp.spawn(binaryPath, commonArgs, {
|
||||||
windowsHide: true,
|
windowsHide: true,
|
||||||
stdio: ["inherit", "inherit"],
|
stdio: ["inherit", "inherit"],
|
||||||
|
env
|
||||||
});
|
});
|
||||||
|
|
||||||
this.logStderr(childProcess.stderr);
|
this.logStderr(childProcess.stderr);
|
||||||
|
@ -90,6 +102,7 @@ export class PythonRPC {
|
||||||
|
|
||||||
const childProcess = cp.spawn("python3", [scriptPath, ...commonArgs], {
|
const childProcess = cp.spawn("python3", [scriptPath, ...commonArgs], {
|
||||||
stdio: ["inherit", "inherit"],
|
stdio: ["inherit", "inherit"],
|
||||||
|
env
|
||||||
});
|
});
|
||||||
|
|
||||||
this.logStderr(childProcess.stderr);
|
this.logStderr(childProcess.stderr);
|
||||||
|
|
|
@ -11,6 +11,7 @@ export const DOWNLOADER_NAME = {
|
||||||
[Downloader.Datanodes]: "Datanodes",
|
[Downloader.Datanodes]: "Datanodes",
|
||||||
[Downloader.Mediafire]: "Mediafire",
|
[Downloader.Mediafire]: "Mediafire",
|
||||||
[Downloader.TorBox]: "TorBox",
|
[Downloader.TorBox]: "TorBox",
|
||||||
|
[Downloader.AllDebrid]: "All-Debrid",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MAX_MINUTES_TO_SHOW_IN_PLAYTIME = 120;
|
export const MAX_MINUTES_TO_SHOW_IN_PLAYTIME = 120;
|
||||||
|
|
13
src/renderer/src/constants/downloader.ts
Normal file
13
src/renderer/src/constants/downloader.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { Downloader } from "@shared";
|
||||||
|
|
||||||
|
export const DOWNLOADER_NAME: Record<Downloader, string> = {
|
||||||
|
[Downloader.Gofile]: "Gofile",
|
||||||
|
[Downloader.PixelDrain]: "PixelDrain",
|
||||||
|
[Downloader.Qiwi]: "Qiwi",
|
||||||
|
[Downloader.Datanodes]: "Datanodes",
|
||||||
|
[Downloader.Mediafire]: "Mediafire",
|
||||||
|
[Downloader.Torrent]: "Torrent",
|
||||||
|
[Downloader.RealDebrid]: "Real-Debrid",
|
||||||
|
[Downloader.AllDebrid]: "All-Debrid",
|
||||||
|
[Downloader.TorBox]: "TorBox",
|
||||||
|
};
|
|
@ -240,7 +240,9 @@ export function DownloadGroup({
|
||||||
(download?.downloader === Downloader.RealDebrid &&
|
(download?.downloader === Downloader.RealDebrid &&
|
||||||
!userPreferences?.realDebridApiToken) ||
|
!userPreferences?.realDebridApiToken) ||
|
||||||
(download?.downloader === Downloader.TorBox &&
|
(download?.downloader === Downloader.TorBox &&
|
||||||
!userPreferences?.torBoxApiToken);
|
!userPreferences?.torBoxApiToken) ||
|
||||||
|
(download?.downloader === Downloader.AllDebrid &&
|
||||||
|
!userPreferences?.allDebridApiKey);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|
|
@ -27,6 +27,11 @@
|
||||||
|
|
||||||
&__downloader-option {
|
&__downloader-option {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
padding: calc(globals.$spacing-unit * 1.5) calc(globals.$spacing-unit * 6);
|
||||||
|
text-align: left;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 150px;
|
||||||
|
|
||||||
&:only-child {
|
&:only-child {
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
|
@ -36,6 +41,8 @@
|
||||||
&__downloader-icon {
|
&__downloader-icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: calc(globals.$spacing-unit * 2);
|
left: calc(globals.$spacing-unit * 2);
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__path-error {
|
&__path-error {
|
||||||
|
|
|
@ -87,6 +87,8 @@ export function DownloadSettingsModal({
|
||||||
return userPreferences?.realDebridApiToken;
|
return userPreferences?.realDebridApiToken;
|
||||||
if (downloader === Downloader.TorBox)
|
if (downloader === Downloader.TorBox)
|
||||||
return userPreferences?.torBoxApiToken;
|
return userPreferences?.torBoxApiToken;
|
||||||
|
if (downloader === Downloader.AllDebrid)
|
||||||
|
return userPreferences?.allDebridApiKey;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -100,6 +102,8 @@ export function DownloadSettingsModal({
|
||||||
userPreferences?.downloadsPath,
|
userPreferences?.downloadsPath,
|
||||||
downloaders,
|
downloaders,
|
||||||
userPreferences?.realDebridApiToken,
|
userPreferences?.realDebridApiToken,
|
||||||
|
userPreferences?.torBoxApiToken,
|
||||||
|
userPreferences?.allDebridApiKey,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleChooseDownloadsPath = async () => {
|
const handleChooseDownloadsPath = async () => {
|
||||||
|
@ -163,8 +167,10 @@ export function DownloadSettingsModal({
|
||||||
selectedDownloader === downloader ? "primary" : "outline"
|
selectedDownloader === downloader ? "primary" : "outline"
|
||||||
}
|
}
|
||||||
disabled={
|
disabled={
|
||||||
downloader === Downloader.RealDebrid &&
|
(downloader === Downloader.RealDebrid &&
|
||||||
!userPreferences?.realDebridApiToken
|
!userPreferences?.realDebridApiToken) ||
|
||||||
|
(downloader === Downloader.AllDebrid &&
|
||||||
|
!userPreferences?.allDebridApiKey)
|
||||||
}
|
}
|
||||||
onClick={() => setSelectedDownloader(downloader)}
|
onClick={() => setSelectedDownloader(downloader)}
|
||||||
>
|
>
|
||||||
|
|
|
@ -7,6 +7,7 @@ export enum Downloader {
|
||||||
Datanodes,
|
Datanodes,
|
||||||
Mediafire,
|
Mediafire,
|
||||||
TorBox,
|
TorBox,
|
||||||
|
AllDebrid,
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum DownloadSourceStatus {
|
export enum DownloadSourceStatus {
|
||||||
|
@ -54,6 +55,7 @@ export enum AuthPage {
|
||||||
export enum DownloadError {
|
export enum DownloadError {
|
||||||
NotCachedInRealDebrid = "download_error_not_cached_in_real_debrid",
|
NotCachedInRealDebrid = "download_error_not_cached_in_real_debrid",
|
||||||
NotCachedInTorbox = "download_error_not_cached_in_torbox",
|
NotCachedInTorbox = "download_error_not_cached_in_torbox",
|
||||||
|
NotCachedInAllDebrid = "download_error_not_cached_in_alldebrid",
|
||||||
GofileQuotaExceeded = "download_error_gofile_quota_exceeded",
|
GofileQuotaExceeded = "download_error_gofile_quota_exceeded",
|
||||||
RealDebridAccountNotAuthorized = "download_error_real_debrid_account_not_authorized",
|
RealDebridAccountNotAuthorized = "download_error_real_debrid_account_not_authorized",
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,7 @@ export const getDownloadersForUri = (uri: string) => {
|
||||||
return [Downloader.RealDebrid];
|
return [Downloader.RealDebrid];
|
||||||
|
|
||||||
if (uri.startsWith("magnet:")) {
|
if (uri.startsWith("magnet:")) {
|
||||||
return [Downloader.Torrent, Downloader.TorBox, Downloader.RealDebrid];
|
return [Downloader.Torrent, Downloader.TorBox, Downloader.RealDebrid, Downloader.AllDebrid];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
|
|
|
@ -182,3 +182,30 @@ export interface AllDebridUser {
|
||||||
isPremium: boolean;
|
isPremium: boolean;
|
||||||
premiumUntil: string;
|
premiumUntil: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum Downloader {
|
||||||
|
Gofile = "gofile",
|
||||||
|
PixelDrain = "pixeldrain",
|
||||||
|
Qiwi = "qiwi",
|
||||||
|
Datanodes = "datanodes",
|
||||||
|
Mediafire = "mediafire",
|
||||||
|
Torrent = "torrent",
|
||||||
|
RealDebrid = "realdebrid",
|
||||||
|
AllDebrid = "alldebrid",
|
||||||
|
TorBox = "torbox",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum DownloadError {
|
||||||
|
NotCachedInRealDebrid = "not_cached_in_realdebrid",
|
||||||
|
NotCachedInAllDebrid = "not_cached_in_alldebrid",
|
||||||
|
// ... alte erori existente
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GamePayload {
|
||||||
|
action: string;
|
||||||
|
game_id: string;
|
||||||
|
url: string | string[]; // Modificăm pentru a accepta și array de URL-uri
|
||||||
|
save_path: string;
|
||||||
|
header?: string;
|
||||||
|
out?: string;
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue