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
 | 
			
		||||
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:
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.download = None
 | 
			
		||||
        self.aria2 = aria2p.API(
 | 
			
		||||
            aria2p.Client(
 | 
			
		||||
                host="http://localhost",
 | 
			
		||||
                port=6800,
 | 
			
		||||
                secret=""
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        self.downloads = []  # vom păstra toate download-urile active
 | 
			
		||||
        self.aria2 = API(Client(host="http://localhost", port=6800))
 | 
			
		||||
        self.download = None  # pentru compatibilitate cu codul vechi
 | 
			
		||||
 | 
			
		||||
    def start_download(self, url: str, save_path: str, header: str, out: str = None):
 | 
			
		||||
        if self.download:
 | 
			
		||||
            self.aria2.resume([self.download])
 | 
			
		||||
    def unlock_alldebrid_link(self, link: str) -> str:
 | 
			
		||||
        """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()
 | 
			
		||||
            
 | 
			
		||||
            if data.get("status") == "success":
 | 
			
		||||
                return data["data"]["link"]
 | 
			
		||||
            else:
 | 
			
		||||
            downloads = self.aria2.add(url, options={"header": header, "dir": save_path, "out": out})
 | 
			
		||||
                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
 | 
			
		||||
 | 
			
		||||
            self.download = downloads[0]
 | 
			
		||||
    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:
 | 
			
		||||
            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
 | 
			
		||||
                
 | 
			
		||||
            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):
 | 
			
		||||
        if self.download:
 | 
			
		||||
            self.aria2.pause([self.download])
 | 
			
		||||
        try:
 | 
			
		||||
            for download in self.downloads:
 | 
			
		||||
                download.pause()
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            logger.error(f"Error pausing downloads: {str(e)}")
 | 
			
		||||
    
 | 
			
		||||
    def cancel_download(self):
 | 
			
		||||
        if self.download:
 | 
			
		||||
            self.aria2.remove([self.download])
 | 
			
		||||
            self.download = None
 | 
			
		||||
        try:
 | 
			
		||||
            for download in self.downloads:
 | 
			
		||||
                download.remove()
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            logger.error(f"Error canceling downloads: {str(e)}")
 | 
			
		||||
 | 
			
		||||
    def get_download_status(self):
 | 
			
		||||
        if self.download == None:
 | 
			
		||||
        try:
 | 
			
		||||
            if not self.downloads:
 | 
			
		||||
                return None
 | 
			
		||||
 | 
			
		||||
        download = self.aria2.get_download(self.download.gid)
 | 
			
		||||
            total_size = 0
 | 
			
		||||
            downloaded = 0
 | 
			
		||||
            download_speed = 0
 | 
			
		||||
            active_downloads = []
 | 
			
		||||
 | 
			
		||||
        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,
 | 
			
		||||
            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"
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        return response
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            logger.error(f"Error getting download status: {str(e)}")
 | 
			
		||||
            return None
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,19 +23,27 @@ torrent_session = lt.session({'listen_interfaces': '0.0.0.0:{port}'.format(port=
 | 
			
		|||
if start_download_payload:
 | 
			
		||||
    initial_download = json.loads(urllib.parse.unquote(start_download_payload))
 | 
			
		||||
    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)
 | 
			
		||||
        downloads[initial_download['game_id']] = torrent_downloader
 | 
			
		||||
        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:
 | 
			
		||||
            print("Error starting torrent download", e)
 | 
			
		||||
    else:
 | 
			
		||||
        http_downloader = HttpDownloader()
 | 
			
		||||
        downloads[initial_download['game_id']] = http_downloader
 | 
			
		||||
        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:
 | 
			
		||||
            print("Error starting http download", e)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -135,10 +143,18 @@ def action():
 | 
			
		|||
 | 
			
		||||
    if action == 'start':
 | 
			
		||||
        url = data.get('url')
 | 
			
		||||
        print(f"Starting download with URL: {url}")
 | 
			
		||||
 | 
			
		||||
        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):
 | 
			
		||||
                existing_downloader.start_download(url, data['save_path'])
 | 
			
		||||
            else:
 | 
			
		||||
| 
						 | 
				
			
			@ -172,7 +188,6 @@ def action():
 | 
			
		|||
        downloader = downloads.get(game_id)
 | 
			
		||||
        if downloader:
 | 
			
		||||
            downloader.cancel_download()
 | 
			
		||||
 | 
			
		||||
    else:
 | 
			
		||||
        return jsonify({"error": "Invalid action"}), 400
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,31 @@ import axios, { AxiosInstance } from "axios";
 | 
			
		|||
import type { AllDebridUser } from "@types";
 | 
			
		||||
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 {
 | 
			
		||||
    private static instance: AxiosInstance;
 | 
			
		||||
    private static readonly baseURL = "https://api.alldebrid.com/v4";
 | 
			
		||||
| 
						 | 
				
			
			@ -22,10 +47,7 @@ export class AllDebridClient {
 | 
			
		|||
            const response = await this.instance.get<{
 | 
			
		||||
                status: string;
 | 
			
		||||
                data?: { user: AllDebridUser };
 | 
			
		||||
                error?: {
 | 
			
		||||
                    code: string;
 | 
			
		||||
                    message: string;
 | 
			
		||||
                };
 | 
			
		||||
                error?: AllDebridError;
 | 
			
		||||
            }>("/user");
 | 
			
		||||
 | 
			
		||||
            logger.info("[AllDebrid] API Response:", response.data);
 | 
			
		||||
| 
						 | 
				
			
			@ -63,4 +85,175 @@ export class AllDebridClient {
 | 
			
		|||
            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 { sortBy } from "lodash-es";
 | 
			
		||||
import { TorBoxClient } from "./torbox";
 | 
			
		||||
import { AllDebridClient } from "./all-debrid";
 | 
			
		||||
import { spawn } from "child_process";
 | 
			
		||||
 | 
			
		||||
export class DownloadManager {
 | 
			
		||||
  private static downloadingGameId: string | null = null;
 | 
			
		||||
| 
						 | 
				
			
			@ -333,6 +335,18 @@ export class DownloadManager {
 | 
			
		|||
          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: {
 | 
			
		||||
        const { name, url } = await TorBoxClient.getDownloadInfo(download.uri);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,6 +8,8 @@ import crypto from "node:crypto";
 | 
			
		|||
import { pythonRpcLogger } from "./logger";
 | 
			
		||||
import { Readable } from "node:stream";
 | 
			
		||||
import { app, dialog } from "electron";
 | 
			
		||||
import { db, levelKeys } from "@main/level";
 | 
			
		||||
import type { UserPreferences } from "@types";
 | 
			
		||||
 | 
			
		||||
interface GamePayload {
 | 
			
		||||
  game_id: string;
 | 
			
		||||
| 
						 | 
				
			
			@ -42,7 +44,7 @@ export class PythonRPC {
 | 
			
		|||
    readable.on("data", pythonRpcLogger.log);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static spawn(
 | 
			
		||||
  public static async spawn(
 | 
			
		||||
    initialDownload?: GamePayload,
 | 
			
		||||
    initialSeeding?: GamePayload[]
 | 
			
		||||
  ) {
 | 
			
		||||
| 
						 | 
				
			
			@ -54,6 +56,15 @@ export class PythonRPC {
 | 
			
		|||
      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) {
 | 
			
		||||
      const binaryName = binaryNameByPlatform[process.platform]!;
 | 
			
		||||
      const binaryPath = path.join(
 | 
			
		||||
| 
						 | 
				
			
			@ -74,6 +85,7 @@ export class PythonRPC {
 | 
			
		|||
      const childProcess = cp.spawn(binaryPath, commonArgs, {
 | 
			
		||||
        windowsHide: true,
 | 
			
		||||
        stdio: ["inherit", "inherit"],
 | 
			
		||||
        env
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      this.logStderr(childProcess.stderr);
 | 
			
		||||
| 
						 | 
				
			
			@ -90,6 +102,7 @@ export class PythonRPC {
 | 
			
		|||
 | 
			
		||||
      const childProcess = cp.spawn("python3", [scriptPath, ...commonArgs], {
 | 
			
		||||
        stdio: ["inherit", "inherit"],
 | 
			
		||||
        env
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      this.logStderr(childProcess.stderr);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,6 +11,7 @@ export const DOWNLOADER_NAME = {
 | 
			
		|||
  [Downloader.Datanodes]: "Datanodes",
 | 
			
		||||
  [Downloader.Mediafire]: "Mediafire",
 | 
			
		||||
  [Downloader.TorBox]: "TorBox",
 | 
			
		||||
  [Downloader.AllDebrid]: "All-Debrid",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
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 &&
 | 
			
		||||
        !userPreferences?.realDebridApiToken) ||
 | 
			
		||||
      (download?.downloader === Downloader.TorBox &&
 | 
			
		||||
        !userPreferences?.torBoxApiToken);
 | 
			
		||||
        !userPreferences?.torBoxApiToken) ||
 | 
			
		||||
      (download?.downloader === Downloader.AllDebrid &&
 | 
			
		||||
        !userPreferences?.allDebridApiKey);
 | 
			
		||||
 | 
			
		||||
    return [
 | 
			
		||||
      {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,6 +27,11 @@
 | 
			
		|||
 | 
			
		||||
  &__downloader-option {
 | 
			
		||||
    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 {
 | 
			
		||||
      grid-column: 1 / -1;
 | 
			
		||||
| 
						 | 
				
			
			@ -36,6 +41,8 @@
 | 
			
		|||
  &__downloader-icon {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    left: calc(globals.$spacing-unit * 2);
 | 
			
		||||
    top: 50%;
 | 
			
		||||
    transform: translateY(-50%);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &__path-error {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -87,6 +87,8 @@ export function DownloadSettingsModal({
 | 
			
		|||
        return userPreferences?.realDebridApiToken;
 | 
			
		||||
      if (downloader === Downloader.TorBox)
 | 
			
		||||
        return userPreferences?.torBoxApiToken;
 | 
			
		||||
      if (downloader === Downloader.AllDebrid)
 | 
			
		||||
        return userPreferences?.allDebridApiKey;
 | 
			
		||||
      return true;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -100,6 +102,8 @@ export function DownloadSettingsModal({
 | 
			
		|||
    userPreferences?.downloadsPath,
 | 
			
		||||
    downloaders,
 | 
			
		||||
    userPreferences?.realDebridApiToken,
 | 
			
		||||
    userPreferences?.torBoxApiToken,
 | 
			
		||||
    userPreferences?.allDebridApiKey,
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  const handleChooseDownloadsPath = async () => {
 | 
			
		||||
| 
						 | 
				
			
			@ -163,8 +167,10 @@ export function DownloadSettingsModal({
 | 
			
		|||
                  selectedDownloader === downloader ? "primary" : "outline"
 | 
			
		||||
                }
 | 
			
		||||
                disabled={
 | 
			
		||||
                  downloader === Downloader.RealDebrid &&
 | 
			
		||||
                  !userPreferences?.realDebridApiToken
 | 
			
		||||
                  (downloader === Downloader.RealDebrid &&
 | 
			
		||||
                    !userPreferences?.realDebridApiToken) ||
 | 
			
		||||
                  (downloader === Downloader.AllDebrid &&
 | 
			
		||||
                    !userPreferences?.allDebridApiKey)
 | 
			
		||||
                }
 | 
			
		||||
                onClick={() => setSelectedDownloader(downloader)}
 | 
			
		||||
              >
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,7 @@ export enum Downloader {
 | 
			
		|||
  Datanodes,
 | 
			
		||||
  Mediafire,
 | 
			
		||||
  TorBox,
 | 
			
		||||
  AllDebrid,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum DownloadSourceStatus {
 | 
			
		||||
| 
						 | 
				
			
			@ -54,6 +55,7 @@ export enum AuthPage {
 | 
			
		|||
export enum DownloadError {
 | 
			
		||||
  NotCachedInRealDebrid = "download_error_not_cached_in_real_debrid",
 | 
			
		||||
  NotCachedInTorbox = "download_error_not_cached_in_torbox",
 | 
			
		||||
  NotCachedInAllDebrid = "download_error_not_cached_in_alldebrid",
 | 
			
		||||
  GofileQuotaExceeded = "download_error_gofile_quota_exceeded",
 | 
			
		||||
  RealDebridAccountNotAuthorized = "download_error_real_debrid_account_not_authorized",
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -95,7 +95,7 @@ export const getDownloadersForUri = (uri: string) => {
 | 
			
		|||
    return [Downloader.RealDebrid];
 | 
			
		||||
 | 
			
		||||
  if (uri.startsWith("magnet:")) {
 | 
			
		||||
    return [Downloader.Torrent, Downloader.TorBox, Downloader.RealDebrid];
 | 
			
		||||
    return [Downloader.Torrent, Downloader.TorBox, Downloader.RealDebrid, Downloader.AllDebrid];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return [];
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -182,3 +182,30 @@ export interface AllDebridUser {
 | 
			
		|||
  isPremium: boolean;
 | 
			
		||||
  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