mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-02 16:23:48 +03:00
feat: updating real-debrid translations
This commit is contained in:
parent
183b85d66a
commit
08a336b392
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,6 +1,6 @@
|
||||
.vscode
|
||||
node_modules
|
||||
aria2*
|
||||
aria2/
|
||||
fastlist.exe
|
||||
__pycache__
|
||||
dist
|
||||
|
@ -134,7 +134,7 @@
|
||||
"delete_modal_title": "هل أنت متأكد؟",
|
||||
"delete_modal_description": "سيؤدي هذا إلى إزالة جميع ملفات التثبيت من جهاز الكمبيوتر الخاص بك",
|
||||
"install": "تثبيت",
|
||||
"real_debrid": "Real Debrid",
|
||||
"real_debrid": "Real-Debrid",
|
||||
"torrent": "تورنت"
|
||||
},
|
||||
"settings": {
|
||||
@ -145,13 +145,13 @@
|
||||
"enable_repack_list_notifications": "عند إضافة حزمة جديدة",
|
||||
"telemetry": "القياس عن بعد",
|
||||
"telemetry_description": "تفعيل إحصائيات الاستخدام مجهولة المصدر",
|
||||
"real_debrid_api_token_label": "رمز واجهة برمجة التطبيقات (API) لـReal Debrid ",
|
||||
"real_debrid_api_token_label": "رمز واجهة برمجة التطبيقات (API) لـReal-Debrid ",
|
||||
"quit_app_instead_hiding": "إنهاء هايدرا بدلاً من التصغير الى شريط الحالة",
|
||||
"launch_with_system": "تشغيل هايدرا عند بدء تشغيل النظام",
|
||||
"general": "عام",
|
||||
"behavior": "السلوك",
|
||||
"enable_real_debrid": "تفعيل Real Debrid ",
|
||||
"real_debrid": "Real Debrid",
|
||||
"enable_real_debrid": "تفعيل Real-Debrid ",
|
||||
"real_debrid": "Real-Debrid",
|
||||
"real_debrid_api_token_hint": "يمكنك الحصول على مفتاح API الخاص بك هنا.",
|
||||
"save_changes": "حفظ التغييرات"
|
||||
},
|
||||
|
@ -128,7 +128,7 @@
|
||||
"delete_modal_title": "Er du sikker?",
|
||||
"delete_modal_description": "Dette vil fjerne alle installations filerne fra din computer",
|
||||
"install": "Installér",
|
||||
"real_debrid": "Real Debrid",
|
||||
"real_debrid": "Real-Debrid",
|
||||
"torrent": "Torrent"
|
||||
},
|
||||
"settings": {
|
||||
@ -139,13 +139,13 @@
|
||||
"enable_repack_list_notifications": "Når en ny repack bliver tilføjet",
|
||||
"telemetry": "Telemetri",
|
||||
"telemetry_description": "Slå anonymt brugs statistik til",
|
||||
"real_debrid_api_token_description": "Real Debrid API token",
|
||||
"real_debrid_api_token_description": "Real-Debrid API token",
|
||||
"quit_app_instead_hiding": "Afslut Hydra instedet for at minimere til processlinjen",
|
||||
"launch_with_system": "Åben Hydra ved start af systemet",
|
||||
"general": "Generelt",
|
||||
"behavior": "Opførsel",
|
||||
"enable_real_debrid": "Slå Real Debrid til",
|
||||
"real_debrid": "Real Debrid",
|
||||
"enable_real_debrid": "Slå Real-Debrid til",
|
||||
"real_debrid": "Real-Debrid",
|
||||
"real_debrid_api_token_hint": "Du kan få din API nøgle <0>her</0>.",
|
||||
"save_changes": "Gem ændringer"
|
||||
},
|
||||
|
@ -133,7 +133,7 @@
|
||||
"delete_modal_title": "Are you sure?",
|
||||
"delete_modal_description": "This will remove all the installation files from your computer",
|
||||
"install": "Install",
|
||||
"real_debrid": "Real Debrid",
|
||||
"real_debrid": "Real-Debrid",
|
||||
"torrent": "Torrent"
|
||||
},
|
||||
"settings": {
|
||||
@ -144,14 +144,15 @@
|
||||
"enable_repack_list_notifications": "When a new repack is added",
|
||||
"telemetry": "Telemetry",
|
||||
"telemetry_description": "Enable anonymous usage statistics",
|
||||
"real_debrid_api_token_label": "Real Debrid API token",
|
||||
"real_debrid_api_token_label": "Real-Debrid API token",
|
||||
"quit_app_instead_hiding": "Quit Hydra instead of minimizing to tray",
|
||||
"launch_with_system": "Launch Hydra on system start-up",
|
||||
"general": "General",
|
||||
"behavior": "Behavior",
|
||||
"enable_real_debrid": "Enable Real Debrid",
|
||||
"real_debrid": "Real Debrid",
|
||||
"real_debrid_api_token_hint": "You can get your API key <0>here</0>.",
|
||||
"enable_real_debrid": "Enable Real-Debrid",
|
||||
"real_debrid": "Real-Debrid",
|
||||
"real_debrid_description": "Real-Debrid is an unrestricted downloader that allows you to download files instantly and at the best of your Internet speed.",
|
||||
"real_debrid_api_token_hint": "You can get your API token <0>here</0>.",
|
||||
"save_changes": "Save changes"
|
||||
},
|
||||
"notifications": {
|
||||
|
@ -134,7 +134,7 @@
|
||||
"delete_modal_title": "¿Estás seguro?",
|
||||
"delete_modal_description": "Esto eliminará todos los archivos de instalación de tu computadora.",
|
||||
"install": "Instalar",
|
||||
"real_debrid": "Real Debrid",
|
||||
"real_debrid": "Real-Debrid",
|
||||
"torrent": "Torrent"
|
||||
},
|
||||
"settings": {
|
||||
@ -145,13 +145,13 @@
|
||||
"enable_repack_list_notifications": "Cuando se añade un repack nuevo",
|
||||
"telemetry": "Telemetría",
|
||||
"telemetry_description": "Habilitar recopilación de datos de manera anónima",
|
||||
"real_debrid_api_token_label": "Token API de Real Debrid",
|
||||
"real_debrid_api_token_label": "Token API de Real-Debrid",
|
||||
"quit_app_instead_hiding": "Salir de Hydra en vez de minimizar en la bandeja del sistema",
|
||||
"launch_with_system": "Iniciar Hydra al inicio del sistema",
|
||||
"general": "General",
|
||||
"behavior": "Otros",
|
||||
"enable_real_debrid": "Activar Real Debrid",
|
||||
"real_debrid": "Real Debrid",
|
||||
"enable_real_debrid": "Activar Real-Debrid",
|
||||
"real_debrid": "Real-Debrid",
|
||||
"real_debrid_api_token_hint": "Puedes obtener tu clave de API <0>aquí</0>.",
|
||||
"save_changes": "Guardar cambios"
|
||||
},
|
||||
|
@ -128,7 +128,7 @@
|
||||
"delete_modal_title": "مطمئنی؟",
|
||||
"delete_modal_description": "این کار تمام فایلهای اینستالر را از کامپیوتر شما حذف میکند",
|
||||
"install": "نصف",
|
||||
"real_debrid": "Real Debrid",
|
||||
"real_debrid": "Real-Debrid",
|
||||
"torrent": "تورنت"
|
||||
},
|
||||
"settings": {
|
||||
@ -139,13 +139,13 @@
|
||||
"enable_repack_list_notifications": "زمانی که یک ریپک جدید اضافه شد",
|
||||
"telemetry": "تلمتری",
|
||||
"telemetry_description": "فعال کردن آمارگیری استفاده ناشناس",
|
||||
"real_debrid_api_token_description": "توکن Real Debrid",
|
||||
"real_debrid_api_token_description": "توکن Real-Debrid",
|
||||
"quit_app_instead_hiding": "به جای کوچک کردن، از هایدرا خارج شو",
|
||||
"launch_with_system": "زمانی که سیستم روشن میشود، هایدرا را باز کن",
|
||||
"general": "کلی",
|
||||
"behavior": "رفتار",
|
||||
"enable_real_debrid": "فعالسازی Real Debrid",
|
||||
"real_debrid": "Real Debrid",
|
||||
"enable_real_debrid": "فعالسازی Real-Debrid",
|
||||
"real_debrid": "Real-Debrid",
|
||||
"real_debrid_api_token_hint": "کلید API خود را از <ب0>اینجا</0> بگیرید.",
|
||||
"save_changes": "ذخیره تغییرات"
|
||||
},
|
||||
|
@ -128,7 +128,7 @@
|
||||
"delete_modal_title": "정말로 하시겠습니까?",
|
||||
"delete_modal_description": "이 기기의 모든 설치 파일들이 제거될 것입니다",
|
||||
"install": "설치",
|
||||
"real_debrid": "Real Debrid",
|
||||
"real_debrid": "Real-Debrid",
|
||||
"torrent": "Torrent"
|
||||
},
|
||||
"settings": {
|
||||
@ -139,13 +139,13 @@
|
||||
"enable_repack_list_notifications": "새 리팩이 추가되었을 때",
|
||||
"telemetry": "자동 데이터 수집",
|
||||
"telemetry_description": "익명 사용 통계를 활성화",
|
||||
"real_debrid_api_token_description": "Real Debrid API 토큰",
|
||||
"real_debrid_api_token_description": "Real-Debrid API 토큰",
|
||||
"quit_app_instead_hiding": "작업 표시줄 트레이로 최소화하는 대신 Hydra를 종료",
|
||||
"launch_with_system": "컴퓨터가 시작되었을 때 Hydra 실행",
|
||||
"general": "일반",
|
||||
"behavior": "행동",
|
||||
"enable_real_debrid": "Real Debrid 활성화",
|
||||
"real_debrid": "Real Debrid",
|
||||
"enable_real_debrid": "Real-Debrid 활성화",
|
||||
"real_debrid": "Real-Debrid",
|
||||
"real_debrid_api_token_hint": "API 키를 <0>이곳</0>에서 얻으세요.",
|
||||
"save_changes": "변경 사항 저장"
|
||||
},
|
||||
|
@ -128,7 +128,7 @@
|
||||
"delete_modal_title": "Weet je het zeker?",
|
||||
"delete_modal_description": "Hiermee worden alle installatiebestanden van uw computer verwijderd",
|
||||
"install": "Installeren",
|
||||
"real_debrid": "Real Debrid",
|
||||
"real_debrid": "Real-Debrid",
|
||||
"torrent": "Torrent"
|
||||
},
|
||||
"settings": {
|
||||
@ -139,13 +139,13 @@
|
||||
"enable_repack_list_notifications": "Wanneer een nieuwe herverpakking wordt toegevoegd",
|
||||
"telemetry": "Telemetrie",
|
||||
"telemetry_description": "Schakel anonieme gebruiksstatistieken in",
|
||||
"real_debrid_api_token_label": "Real Debrid API token",
|
||||
"real_debrid_api_token_label": "Real-Debrid API token",
|
||||
"quit_app_instead_hiding": "Sluit Hydra af in plaats van te minimaliseren naar de lade",
|
||||
"launch_with_system": "Start Hydra bij het opstarten van het systeem",
|
||||
"general": "Algemeen",
|
||||
"behavior": "Gedrag",
|
||||
"enable_real_debrid": "Enable Real Debrid",
|
||||
"real_debrid": "Real Debrid",
|
||||
"enable_real_debrid": "Enable Real-Debrid",
|
||||
"real_debrid": "Real-Debrid",
|
||||
"real_debrid_api_token_hint": "U kunt uw API-sleutel <0>hier</0> verkrijgen.",
|
||||
"save_changes": "Wijzigingen opslaan"
|
||||
},
|
||||
|
@ -134,7 +134,7 @@
|
||||
"delete_modal_title": "Czy na pewno?",
|
||||
"delete_modal_description": "Spowoduje to usunięcie wszystkich plików instalacyjnych z komputera",
|
||||
"install": "Instaluj",
|
||||
"real_debrid": "Real Debrid",
|
||||
"real_debrid": "Real-Debrid",
|
||||
"torrent": "Torrent"
|
||||
},
|
||||
"settings": {
|
||||
@ -145,13 +145,13 @@
|
||||
"enable_repack_list_notifications": "Gdy dodawany jest nowy repack",
|
||||
"telemetry": "Telemetria",
|
||||
"telemetry_description": "Włącz anonimowe statystyki użycia",
|
||||
"real_debrid_api_token_label": "Real Debrid API token",
|
||||
"real_debrid_api_token_label": "Real-Debrid API token",
|
||||
"quit_app_instead_hiding": "Zamknij Hydr zamiast minimalizować do zasobnika",
|
||||
"launch_with_system": "Uruchom Hydra przy starcie systemu",
|
||||
"general": "Ogólne",
|
||||
"behavior": "Zachowania",
|
||||
"enable_real_debrid": "Włącz Real Debrid",
|
||||
"real_debrid": "Real Debrid",
|
||||
"enable_real_debrid": "Włącz Real-Debrid",
|
||||
"real_debrid": "Real-Debrid",
|
||||
"real_debrid_api_token_hint": "Możesz uzyskać swój klucz API <0>tutaj</0>.",
|
||||
"save_changes": "Zapisz zmiany"
|
||||
},
|
||||
|
@ -131,7 +131,7 @@
|
||||
"deleting": "Excluindo instalador…",
|
||||
"install": "Instalar",
|
||||
"torrent": "Torrent",
|
||||
"real_debrid": "Real Debrid"
|
||||
"real_debrid": "Real-Debrid"
|
||||
},
|
||||
"settings": {
|
||||
"downloads_path": "Diretório dos downloads",
|
||||
@ -141,13 +141,13 @@
|
||||
"enable_repack_list_notifications": "Quando a lista de repacks for atualizada",
|
||||
"telemetry": "Telemetria",
|
||||
"telemetry_description": "Habilitar estatísticas de uso anônimas",
|
||||
"real_debrid_api_token_label": "Token de API do Real Debrid",
|
||||
"real_debrid_api_token_label": "Token de API do Real-Debrid",
|
||||
"quit_app_instead_hiding": "Fechar o aplicativo em vez de minimizá-lo",
|
||||
"launch_with_system": "Iniciar aplicativo na inicialização do sistema",
|
||||
"general": "Geral",
|
||||
"behavior": "Comportamento",
|
||||
"enable_real_debrid": "Habilitar Real Debrid",
|
||||
"real_debrid": "Real Debrid",
|
||||
"enable_real_debrid": "Habilitar Real-Debrid",
|
||||
"real_debrid": "Real-Debrid",
|
||||
"real_debrid_api_token_hint": "Você pode obter sua chave de API <0>aqui</0>.",
|
||||
"save_changes": "Salvar mudanças"
|
||||
},
|
||||
|
@ -134,7 +134,7 @@
|
||||
"delete_modal_title": "Вы уверены?",
|
||||
"delete_modal_description": "Это удалит все установщики с вашего компьютера",
|
||||
"install": "Установить",
|
||||
"real_debrid": "Real Debrid",
|
||||
"real_debrid": "Real-Debrid",
|
||||
"torrent": "Torrent"
|
||||
},
|
||||
"settings": {
|
||||
@ -145,13 +145,13 @@
|
||||
"enable_repack_list_notifications": "При добавлении нового репака",
|
||||
"telemetry": "Телеметрия",
|
||||
"telemetry_description": "Отправлять анонимную статистику использования",
|
||||
"real_debrid_api_token_label": "Real Debrid API-токен",
|
||||
"real_debrid_api_token_label": "Real-Debrid API-токен",
|
||||
"quit_app_instead_hiding": "Закрывать Hydra вместо того, чтобы сворачивать его в трей",
|
||||
"launch_with_system": "Запуск Hydra вместе с системой",
|
||||
"general": "Основные",
|
||||
"behavior": "Поведение",
|
||||
"enable_real_debrid": "Включить Real Debrid",
|
||||
"real_debrid": "Real Debrid",
|
||||
"enable_real_debrid": "Включить Real-Debrid",
|
||||
"real_debrid": "Real-Debrid",
|
||||
"real_debrid_api_token_hint": "API ключ можно получить <0>здесь</0>.",
|
||||
"save_changes": "Сохранить изменения"
|
||||
},
|
||||
|
@ -132,7 +132,7 @@
|
||||
"delete_modal_title": "您确定吗?",
|
||||
"delete_modal_description": "这将从您的电脑上移除所有的安装文件",
|
||||
"install": "安装",
|
||||
"real_debrid": "Real Debrid",
|
||||
"real_debrid": "Real-Debrid",
|
||||
"torrent": "种子"
|
||||
},
|
||||
"settings": {
|
||||
@ -143,13 +143,13 @@
|
||||
"enable_repack_list_notifications": "添加新重打包时",
|
||||
"telemetry": "遥测",
|
||||
"telemetry_description": "启用匿名使用统计",
|
||||
"real_debrid_api_token_description": "Real Debrid API密钥",
|
||||
"real_debrid_api_token_description": "Real-Debrid API密钥",
|
||||
"behavior": "行为",
|
||||
"general": "常规",
|
||||
"quit_app_instead_hiding": "关闭应用程序而不是最小化到托盘",
|
||||
"launch_with_system": "随系统启动时运行应用程序",
|
||||
"enable_real_debrid": "启用 Real Debrid",
|
||||
"real_debrid": "Real Debrid",
|
||||
"enable_real_debrid": "启用 Real-Debrid",
|
||||
"real_debrid": "Real-Debrid",
|
||||
"real_debrid_api_token_hint": "您可以从<0>这里</0>获取API密钥.",
|
||||
"save_changes": "保存更改"
|
||||
},
|
||||
|
@ -29,7 +29,7 @@ const resumeGameDownload = async (
|
||||
.getRepository(Game)
|
||||
.update({ status: "active", progress: Not(1) }, { status: "paused" });
|
||||
|
||||
await DownloadManager.resumeDownload(gameId);
|
||||
await DownloadManager.resumeDownload(game);
|
||||
|
||||
await transactionalEntityManager
|
||||
.getRepository(Game)
|
||||
|
@ -19,6 +19,7 @@ const startGameDownload = async (
|
||||
where: {
|
||||
objectID,
|
||||
},
|
||||
relations: { repack: true },
|
||||
}),
|
||||
repackRepository.findOne({
|
||||
where: {
|
||||
@ -50,9 +51,7 @@ const startGameDownload = async (
|
||||
}
|
||||
);
|
||||
|
||||
await DownloadManager.startDownload(game.id);
|
||||
|
||||
return { ...game, stauts: "active" };
|
||||
return DownloadManager.startDownload(game);
|
||||
} else {
|
||||
const steamGame = stateManager
|
||||
.getValue("steamGames")
|
||||
@ -62,8 +61,8 @@ const startGameDownload = async (
|
||||
? getSteamAppAsset("icon", objectID, steamGame.clientIcon)
|
||||
: null;
|
||||
|
||||
const createdGame = await gameRepository
|
||||
.save({
|
||||
await gameRepository
|
||||
.insert({
|
||||
title,
|
||||
iconUrl,
|
||||
objectID,
|
||||
@ -83,9 +82,14 @@ const startGameDownload = async (
|
||||
return result;
|
||||
});
|
||||
|
||||
await DownloadManager.startDownload(createdGame.id);
|
||||
const createdGame = await gameRepository.findOne({
|
||||
where: {
|
||||
objectID,
|
||||
},
|
||||
relations: { repack: true },
|
||||
});
|
||||
|
||||
return createdGame;
|
||||
return DownloadManager.startDownload(createdGame!);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2,23 +2,17 @@ import { userPreferencesRepository } from "@main/repository";
|
||||
import { registerEvent } from "../register-event";
|
||||
|
||||
import type { UserPreferences } from "@types";
|
||||
import { RealDebridClient } from "@main/services/real-debrid";
|
||||
|
||||
const updateUserPreferences = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
preferences: Partial<UserPreferences>
|
||||
) => {
|
||||
if (preferences.realDebridApiToken) {
|
||||
RealDebridClient.authorize(preferences.realDebridApiToken);
|
||||
}
|
||||
|
||||
await userPreferencesRepository.upsert(
|
||||
) =>
|
||||
userPreferencesRepository.upsert(
|
||||
{
|
||||
id: 1,
|
||||
...preferences,
|
||||
},
|
||||
["id"]
|
||||
);
|
||||
};
|
||||
|
||||
registerEvent("updateUserPreferences", updateUserPreferences);
|
||||
|
@ -97,7 +97,7 @@ const loadState = async (userPreferences: UserPreferences | null) => {
|
||||
relations: { repack: true },
|
||||
});
|
||||
|
||||
if (game) DownloadManager.startDownload(game.id);
|
||||
if (game) DownloadManager.startDownload(game);
|
||||
};
|
||||
|
||||
userPreferencesRepository
|
||||
|
27
src/main/services/aria2.ts
Normal file
27
src/main/services/aria2.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import path from "node:path";
|
||||
import { spawn } from "node:child_process";
|
||||
import type { ChildProcessWithoutNullStreams } from "node:child_process";
|
||||
import { app } from "electron";
|
||||
|
||||
export const startAria2 = (): Promise<ChildProcessWithoutNullStreams> => {
|
||||
return new Promise((resolve) => {
|
||||
const binaryPath = app.isPackaged
|
||||
? path.join(process.resourcesPath, "aria2", "aria2c")
|
||||
: path.join(__dirname, "..", "..", "aria2", "aria2c");
|
||||
|
||||
const cp = spawn(binaryPath, [
|
||||
"--enable-rpc",
|
||||
"--rpc-listen-all",
|
||||
"--file-allocation=none",
|
||||
"--allow-overwrite=true",
|
||||
]);
|
||||
|
||||
cp.stdout.on("data", async (data) => {
|
||||
const msg = Buffer.from(data).toString("utf-8");
|
||||
|
||||
if (msg.includes("IPv6 RPC: listening on TCP")) {
|
||||
resolve(cp);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
@ -1,57 +1,39 @@
|
||||
import Aria2, { StatusResponse } from "aria2";
|
||||
import { spawn } from "node:child_process";
|
||||
|
||||
import { gameRepository, userPreferencesRepository } from "@main/repository";
|
||||
|
||||
import path from "node:path";
|
||||
import { WindowManager } from "./window-manager";
|
||||
import { RealDebridClient } from "./real-debrid";
|
||||
import { Notification, app } from "electron";
|
||||
import { Notification } from "electron";
|
||||
import { t } from "i18next";
|
||||
import { Downloader } from "@shared";
|
||||
import { DownloadProgress } from "@types";
|
||||
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
|
||||
import { Game } from "@main/entity";
|
||||
import { startAria2 } from "./aria2";
|
||||
|
||||
export class DownloadManager {
|
||||
private static downloads = new Map<number, string>();
|
||||
|
||||
private static connected = false;
|
||||
private static gid: string | null = null;
|
||||
private static gameId: number | null = null;
|
||||
private static game: Game | null = null;
|
||||
private static realDebridTorrentId: string | null = null;
|
||||
|
||||
private static aria2 = new Aria2({});
|
||||
|
||||
private static connect(): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const binaryPath = app.isPackaged
|
||||
? path.join(process.resourcesPath, "aria2", "aria2c")
|
||||
: path.join(__dirname, "..", "..", "aria2", "aria2c");
|
||||
|
||||
const cp = spawn(binaryPath, [
|
||||
"--enable-rpc",
|
||||
"--rpc-listen-all",
|
||||
"--file-allocation=none",
|
||||
"--allow-overwrite=true",
|
||||
]);
|
||||
|
||||
cp.stdout.on("data", async (data) => {
|
||||
const msg = Buffer.from(data).toString("utf-8");
|
||||
|
||||
if (msg.includes("IPv6 RPC: listening on TCP")) {
|
||||
private static async connect() {
|
||||
await startAria2();
|
||||
await this.aria2.open();
|
||||
this.connected = true;
|
||||
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static getETA(status: StatusResponse) {
|
||||
const remainingBytes =
|
||||
Number(status.totalLength) - Number(status.completedLength);
|
||||
const speed = Number(status.downloadSpeed);
|
||||
private static getETA(
|
||||
totalLength: number,
|
||||
completedLength: number,
|
||||
speed: number
|
||||
) {
|
||||
const remainingBytes = totalLength - completedLength;
|
||||
|
||||
if (remainingBytes >= 0 && speed > 0) {
|
||||
return (remainingBytes / speed) * 1000;
|
||||
@ -65,9 +47,7 @@ export class DownloadManager {
|
||||
where: { id: 1 },
|
||||
});
|
||||
|
||||
if (userPreferences?.downloadNotificationsEnabled && this.gameId) {
|
||||
const game = await this.getGame(this.gameId);
|
||||
|
||||
if (userPreferences?.downloadNotificationsEnabled && this.game) {
|
||||
new Notification({
|
||||
title: t("download_complete", {
|
||||
ns: "notifications",
|
||||
@ -76,7 +56,7 @@ export class DownloadManager {
|
||||
body: t("game_ready_to_install", {
|
||||
ns: "notifications",
|
||||
lng: userPreferences.language,
|
||||
title: game?.title,
|
||||
title: this.game.title,
|
||||
}),
|
||||
}).show();
|
||||
}
|
||||
@ -87,8 +67,73 @@ export class DownloadManager {
|
||||
return "";
|
||||
}
|
||||
|
||||
private static async getRealDebridDownloadUrl() {
|
||||
if (this.realDebridTorrentId) {
|
||||
const torrentInfo = await RealDebridClient.getTorrentInfo(
|
||||
this.realDebridTorrentId
|
||||
);
|
||||
|
||||
const { status, links } = torrentInfo;
|
||||
|
||||
if (status === "waiting_files_selection") {
|
||||
await RealDebridClient.selectAllFiles(this.realDebridTorrentId);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (status === "downloaded") {
|
||||
const [link] = links;
|
||||
const { download } = await RealDebridClient.unrestrictLink(link);
|
||||
return decodeURIComponent(download);
|
||||
}
|
||||
|
||||
if (WindowManager.mainWindow) {
|
||||
const progress = torrentInfo.progress / 100;
|
||||
const totalDownloaded = progress * torrentInfo.bytes;
|
||||
|
||||
WindowManager.mainWindow.setProgressBar(progress === 1 ? -1 : progress);
|
||||
|
||||
const payload = {
|
||||
numPeers: 0,
|
||||
numSeeds: torrentInfo.seeders,
|
||||
downloadSpeed: torrentInfo.speed,
|
||||
timeRemaining: this.getETA(
|
||||
torrentInfo.bytes,
|
||||
totalDownloaded,
|
||||
torrentInfo.speed
|
||||
),
|
||||
isDownloadingMetadata: status === "magnet_conversion",
|
||||
game: {
|
||||
...this.game,
|
||||
bytesDownloaded: progress * torrentInfo.bytes,
|
||||
progress,
|
||||
},
|
||||
} as DownloadProgress;
|
||||
|
||||
WindowManager.mainWindow.webContents.send(
|
||||
"on-download-progress",
|
||||
JSON.parse(JSON.stringify(payload))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static async watchDownloads() {
|
||||
if (!this.gid || !this.gameId) return;
|
||||
if (!this.game) return;
|
||||
|
||||
if (!this.gid && this.realDebridTorrentId) {
|
||||
const options = { dir: this.game.downloadPath! };
|
||||
const downloadUrl = await this.getRealDebridDownloadUrl();
|
||||
|
||||
if (downloadUrl) {
|
||||
this.gid = await this.aria2.call("addUri", [downloadUrl], options);
|
||||
this.downloads.set(this.game.id, this.gid);
|
||||
this.realDebridTorrentId = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.gid) return;
|
||||
|
||||
const status = await this.aria2.call("tellStatus", this.gid);
|
||||
|
||||
@ -96,7 +141,7 @@ export class DownloadManager {
|
||||
|
||||
if (status.followedBy?.length) {
|
||||
this.gid = status.followedBy[0];
|
||||
this.downloads.set(this.gameId, this.gid);
|
||||
this.downloads.set(this.game.id, this.gid);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -113,7 +158,7 @@ export class DownloadManager {
|
||||
if (!isNaN(progress)) update.progress = progress;
|
||||
|
||||
await gameRepository.update(
|
||||
{ id: this.gameId },
|
||||
{ id: this.game.id },
|
||||
{
|
||||
...update,
|
||||
status: status.status,
|
||||
@ -123,17 +168,18 @@ export class DownloadManager {
|
||||
}
|
||||
|
||||
const game = await gameRepository.findOne({
|
||||
where: { id: this.gameId, isDeleted: false },
|
||||
where: { id: this.game.id, isDeleted: false },
|
||||
relations: { repack: true },
|
||||
});
|
||||
|
||||
if (progress === 1 && game && !isDownloadingMetadata) {
|
||||
if (progress === 1 && this.game && !isDownloadingMetadata) {
|
||||
await this.publishNotification();
|
||||
|
||||
/*
|
||||
Only cancel bittorrent downloads to stop seeding
|
||||
*/
|
||||
if (status.bittorrent) {
|
||||
await this.cancelDownload(game.id);
|
||||
await this.cancelDownload(this.game.id);
|
||||
} else {
|
||||
this.clearCurrentDownload();
|
||||
}
|
||||
@ -143,13 +189,14 @@ export class DownloadManager {
|
||||
WindowManager.mainWindow.setProgressBar(progress === 1 ? -1 : progress);
|
||||
|
||||
const payload = {
|
||||
progress,
|
||||
bytesDownloaded: Number(status.completedLength),
|
||||
fileSize: Number(status.totalLength),
|
||||
numPeers: Number(status.connections),
|
||||
numSeeds: Number(status.numSeeders ?? 0),
|
||||
downloadSpeed: Number(status.downloadSpeed),
|
||||
timeRemaining: this.getETA(status),
|
||||
timeRemaining: this.getETA(
|
||||
Number(status.totalLength),
|
||||
Number(status.completedLength),
|
||||
Number(status.downloadSpeed)
|
||||
),
|
||||
isDownloadingMetadata: !!isDownloadingMetadata,
|
||||
game,
|
||||
} as DownloadProgress;
|
||||
@ -161,20 +208,12 @@ export class DownloadManager {
|
||||
}
|
||||
}
|
||||
|
||||
static async getGame(gameId: number) {
|
||||
return gameRepository.findOne({
|
||||
where: { id: gameId, isDeleted: false },
|
||||
relations: {
|
||||
repack: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private static clearCurrentDownload() {
|
||||
if (this.gameId) {
|
||||
this.downloads.delete(this.gameId);
|
||||
if (this.game) {
|
||||
this.downloads.delete(this.game.id);
|
||||
this.gid = null;
|
||||
this.gameId = null;
|
||||
this.game = null;
|
||||
this.realDebridTorrentId = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,50 +237,42 @@ export class DownloadManager {
|
||||
if (this.gid) {
|
||||
await this.aria2.call("forcePause", this.gid);
|
||||
this.gid = null;
|
||||
this.gameId = null;
|
||||
this.game = null;
|
||||
this.realDebridTorrentId = null;
|
||||
|
||||
WindowManager.mainWindow?.setProgressBar(-1);
|
||||
}
|
||||
}
|
||||
|
||||
static async resumeDownload(gameId: number) {
|
||||
if (this.downloads.has(gameId)) {
|
||||
const gid = this.downloads.get(gameId)!;
|
||||
static async resumeDownload(game: Game) {
|
||||
if (this.downloads.has(game.id)) {
|
||||
const gid = this.downloads.get(game.id)!;
|
||||
await this.aria2.call("unpause", gid);
|
||||
|
||||
this.gid = gid;
|
||||
this.gameId = gameId;
|
||||
this.game = game;
|
||||
} else {
|
||||
return this.startDownload(gameId);
|
||||
return this.startDownload(game);
|
||||
}
|
||||
}
|
||||
|
||||
static async startDownload(gameId: number) {
|
||||
static async startDownload(game: Game) {
|
||||
if (!this.connected) await this.connect();
|
||||
|
||||
const game = await this.getGame(gameId)!;
|
||||
|
||||
if (game) {
|
||||
const options = {
|
||||
dir: game.downloadPath!,
|
||||
};
|
||||
|
||||
if (game.downloader === Downloader.RealDebrid) {
|
||||
const downloadUrl = decodeURIComponent(
|
||||
await RealDebridClient.getDownloadUrl(game)
|
||||
this.realDebridTorrentId = await RealDebridClient.getTorrentId(
|
||||
game!.repack.magnet
|
||||
);
|
||||
|
||||
this.gid = await this.aria2.call("addUri", [downloadUrl], options);
|
||||
} else {
|
||||
this.gid = await this.aria2.call(
|
||||
"addUri",
|
||||
[game.repack.magnet],
|
||||
options
|
||||
);
|
||||
this.gid = await this.aria2.call("addUri", [game.repack.magnet], options);
|
||||
|
||||
this.downloads.set(game.id, this.gid);
|
||||
}
|
||||
|
||||
this.gameId = gameId;
|
||||
this.downloads.set(gameId, this.gid);
|
||||
}
|
||||
this.game = game;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Game } from "@main/entity";
|
||||
import axios, { AxiosInstance } from "axios";
|
||||
import parseTorrent from "parse-torrent";
|
||||
import type {
|
||||
RealDebridAddMagnet,
|
||||
RealDebridTorrentInfo,
|
||||
@ -7,10 +7,18 @@ import type {
|
||||
RealDebridUser,
|
||||
} from "@types";
|
||||
|
||||
const base = "https://api.real-debrid.com/rest/1.0";
|
||||
|
||||
export class RealDebridClient {
|
||||
private static instance: AxiosInstance;
|
||||
private static baseURL = "https://api.real-debrid.com/rest/1.0";
|
||||
|
||||
static authorize(apiToken: string) {
|
||||
this.instance = axios.create({
|
||||
baseURL: this.baseURL,
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static async addMagnet(magnet: string) {
|
||||
const searchParams = new URLSearchParams({ magnet });
|
||||
@ -23,7 +31,7 @@ export class RealDebridClient {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
static async getInfo(id: string) {
|
||||
static async getTorrentInfo(id: string) {
|
||||
const response = await this.instance.get<RealDebridTorrentInfo>(
|
||||
`/torrents/info/${id}`
|
||||
);
|
||||
@ -55,50 +63,24 @@ export class RealDebridClient {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
static async getAllTorrentsFromUser() {
|
||||
private static async getAllTorrentsFromUser() {
|
||||
const response =
|
||||
await this.instance.get<RealDebridTorrentInfo[]>("/torrents");
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
static extractSHA1FromMagnet(magnet: string) {
|
||||
return magnet.match(/btih:([0-9a-fA-F]*)/)?.[1].toLowerCase();
|
||||
}
|
||||
static async getTorrentId(magnetUri: string) {
|
||||
const userTorrents = await RealDebridClient.getAllTorrentsFromUser();
|
||||
|
||||
static async getDownloadUrl(game: Game) {
|
||||
const torrents = await RealDebridClient.getAllTorrentsFromUser();
|
||||
const hash = RealDebridClient.extractSHA1FromMagnet(game!.repack.magnet);
|
||||
let torrent = torrents.find((t) => t.hash === hash);
|
||||
const { infoHash } = await parseTorrent(magnetUri);
|
||||
const userTorrent = userTorrents.find(
|
||||
(userTorrent) => userTorrent.hash === infoHash
|
||||
);
|
||||
|
||||
// User haven't downloaded this torrent yet
|
||||
if (!torrent) {
|
||||
const magnet = await RealDebridClient.addMagnet(game!.repack.magnet);
|
||||
if (userTorrent) return userTorrent.id;
|
||||
|
||||
if (magnet) {
|
||||
await RealDebridClient.selectAllFiles(magnet.id);
|
||||
torrent = await RealDebridClient.getInfo(magnet.id);
|
||||
|
||||
const { links } = torrent;
|
||||
const { download } = await RealDebridClient.unrestrictLink(links[0]);
|
||||
|
||||
if (!download) {
|
||||
throw new Error("Torrent not cached on Real Debrid");
|
||||
}
|
||||
|
||||
return download;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
static authorize(apiToken: string) {
|
||||
this.instance = axios.create({
|
||||
baseURL: base,
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
},
|
||||
});
|
||||
const torrent = await RealDebridClient.addMagnet(magnetUri);
|
||||
return torrent.id;
|
||||
}
|
||||
}
|
||||
|
@ -26,9 +26,9 @@ globalStyle("body", {
|
||||
overflow: "hidden",
|
||||
userSelect: "none",
|
||||
fontFamily: "'Fira Mono', monospace",
|
||||
fontSize: vars.size.bodyFontSize,
|
||||
fontSize: vars.size.body,
|
||||
background: vars.color.background,
|
||||
color: vars.color.bodyText,
|
||||
color: vars.color.body,
|
||||
margin: "0",
|
||||
});
|
||||
|
||||
@ -68,7 +68,7 @@ globalStyle(
|
||||
);
|
||||
|
||||
globalStyle("label", {
|
||||
fontSize: vars.size.bodyFontSize,
|
||||
fontSize: vars.size.body,
|
||||
});
|
||||
|
||||
globalStyle("input[type=number]", {
|
||||
|
@ -134,6 +134,11 @@ export function App() {
|
||||
|
||||
<section ref={contentRef} className={styles.content}>
|
||||
<Outlet />
|
||||
</section>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<BottomPanel />
|
||||
|
||||
<Toast
|
||||
visible={toast.visible}
|
||||
@ -141,10 +146,6 @@ export function App() {
|
||||
type={toast.type}
|
||||
onClose={handleToastClose}
|
||||
/>
|
||||
</section>
|
||||
</article>
|
||||
</main>
|
||||
<BottomPanel />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import { SPACING_UNIT, vars } from "../../theme.css";
|
||||
export const bottomPanel = style({
|
||||
width: "100%",
|
||||
borderTop: `solid 1px ${vars.color.border}`,
|
||||
backgroundColor: vars.color.background,
|
||||
padding: `${SPACING_UNIT / 2}px ${SPACING_UNIT * 2}px`,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
@ -14,10 +15,10 @@ export const bottomPanel = style({
|
||||
});
|
||||
|
||||
export const downloadsButton = style({
|
||||
color: vars.color.bodyText,
|
||||
color: vars.color.body,
|
||||
borderBottom: "1px solid transparent",
|
||||
":hover": {
|
||||
borderBottom: `1px solid ${vars.color.bodyText}`,
|
||||
borderBottom: `1px solid ${vars.color.body}`,
|
||||
cursor: "pointer",
|
||||
},
|
||||
});
|
||||
|
@ -109,6 +109,6 @@ export const shopIcon = style({
|
||||
});
|
||||
|
||||
export const noDownloadsLabel = style({
|
||||
color: vars.color.bodyText,
|
||||
color: vars.color.body,
|
||||
fontWeight: "bold",
|
||||
});
|
||||
|
@ -108,7 +108,7 @@ export const section = style({
|
||||
|
||||
export const backButton = recipe({
|
||||
base: {
|
||||
color: vars.color.bodyText,
|
||||
color: vars.color.body,
|
||||
cursor: "pointer",
|
||||
WebkitAppRegion: "no-drag",
|
||||
position: "absolute",
|
||||
|
@ -23,7 +23,7 @@ export const modal = recipe({
|
||||
backgroundColor: vars.color.background,
|
||||
borderRadius: "5px",
|
||||
maxWidth: "600px",
|
||||
color: vars.color.bodyText,
|
||||
color: vars.color.body,
|
||||
maxHeight: "100%",
|
||||
border: `solid 1px ${vars.color.border}`,
|
||||
overflow: "hidden",
|
||||
@ -65,5 +65,5 @@ export const closeModalButton = style({
|
||||
});
|
||||
|
||||
export const closeModalButtonIcon = style({
|
||||
color: vars.color.bodyText,
|
||||
color: vars.color.body,
|
||||
});
|
||||
|
@ -25,12 +25,13 @@ export const toast = recipe({
|
||||
borderRadius: "4px",
|
||||
border: `solid 1px ${vars.color.border}`,
|
||||
left: "50%",
|
||||
/* Bottom panel height + spacing */
|
||||
/* Bottom panel height + 16px */
|
||||
bottom: `${26 + SPACING_UNIT * 2}px`,
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between",
|
||||
zIndex: "0",
|
||||
},
|
||||
variants: {
|
||||
closing: {
|
||||
@ -66,7 +67,7 @@ export const progress = style({
|
||||
});
|
||||
|
||||
export const closeButton = style({
|
||||
color: vars.color.bodyText,
|
||||
color: vars.color.body,
|
||||
cursor: "pointer",
|
||||
padding: "0",
|
||||
margin: "0",
|
||||
|
2
src/renderer/src/declaration.d.ts
vendored
2
src/renderer/src/declaration.d.ts
vendored
@ -22,7 +22,7 @@ declare global {
|
||||
|
||||
interface Electron {
|
||||
/* Torrenting */
|
||||
startGameDownload: (payload: StartGameDownloadPayload) => Promise<Game>;
|
||||
startGameDownload: (payload: StartGameDownloadPayload) => Promise<void>;
|
||||
cancelGameDownload: (gameId: number) => Promise<void>;
|
||||
pauseGameDownload: (gameId: number) => Promise<void>;
|
||||
resumeGameDownload: (gameId: number) => Promise<void>;
|
||||
|
@ -19,7 +19,6 @@ export const toastSlice = createSlice({
|
||||
initialState,
|
||||
reducers: {
|
||||
showToast: (state, action: PayloadAction<Omit<ToastState, "visible">>) => {
|
||||
console.log(action.payload);
|
||||
state.message = action.payload.message;
|
||||
state.visible = true;
|
||||
},
|
||||
|
@ -12,7 +12,7 @@ export const downloadTitleWrapper = style({
|
||||
export const downloadTitle = style({
|
||||
fontWeight: "bold",
|
||||
cursor: "pointer",
|
||||
color: vars.color.bodyText,
|
||||
color: vars.color.body,
|
||||
textAlign: "left",
|
||||
fontSize: "16px",
|
||||
display: "block",
|
||||
|
@ -174,5 +174,5 @@ globalStyle(`${description} img`, {
|
||||
});
|
||||
|
||||
globalStyle(`${description} a`, {
|
||||
color: vars.color.bodyText,
|
||||
color: vars.color.body,
|
||||
});
|
||||
|
@ -13,6 +13,6 @@ export const repackButton = style({
|
||||
flexDirection: "column",
|
||||
alignItems: "flex-start",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
color: vars.color.bodyText,
|
||||
color: vars.color.body,
|
||||
padding: `${SPACING_UNIT * 2}px`,
|
||||
});
|
||||
|
@ -15,7 +15,7 @@ export const downloadsPathField = style({
|
||||
|
||||
export const hintText = style({
|
||||
fontSize: "12px",
|
||||
color: vars.color.bodyText,
|
||||
color: vars.color.body,
|
||||
});
|
||||
|
||||
export const downloaders = style({
|
||||
|
@ -125,7 +125,7 @@ export function SelectFolderModal({
|
||||
{selectedDownloader === Downloader.RealDebrid && (
|
||||
<CheckCircleFillIcon />
|
||||
)}
|
||||
Real Debrid
|
||||
Real-Debrid
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -88,5 +88,5 @@ export const howLongToBeatCategorySkeleton = style({
|
||||
|
||||
globalStyle(`${requirementsDetails} a`, {
|
||||
display: "flex",
|
||||
color: vars.color.bodyText,
|
||||
color: vars.color.body,
|
||||
});
|
||||
|
@ -7,3 +7,8 @@ export const form = style({
|
||||
flexDirection: "column",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
});
|
||||
|
||||
export const description = style({
|
||||
fontFamily: "'Fira Sans', sans-serif",
|
||||
marginBottom: `${SPACING_UNIT}px`,
|
||||
});
|
||||
|
@ -52,8 +52,6 @@ export function SettingsRealDebrid({
|
||||
form.realDebridApiToken!
|
||||
);
|
||||
|
||||
console.log(user);
|
||||
|
||||
if (user.type === "premium") {
|
||||
dispatch(
|
||||
showToast({
|
||||
@ -61,18 +59,22 @@ export function SettingsRealDebrid({
|
||||
type: "success",
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// updateUserPreferences({
|
||||
// realDebridApiToken: form.useRealDebrid ? form.realDebridApiToken : null,
|
||||
// });
|
||||
updateUserPreferences({
|
||||
realDebridApiToken: form.useRealDebrid
|
||||
? form.realDebridApiToken
|
||||
: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const isButtonDisabled = form.useRealDebrid && !form.realDebridApiToken;
|
||||
|
||||
return (
|
||||
<form className={styles.form} onSubmit={handleFormSubmit}>
|
||||
<p className={styles.description}>{t("real_debrid_description")}</p>
|
||||
|
||||
<CheckboxField
|
||||
label={t("enable_real_debrid")}
|
||||
checked={form.useRealDebrid}
|
||||
@ -86,7 +88,7 @@ export function SettingsRealDebrid({
|
||||
|
||||
{form.useRealDebrid && (
|
||||
<TextField
|
||||
label={t("real_debrid_api_token_label")}
|
||||
label="API Private Token"
|
||||
value={form.realDebridApiToken ?? ""}
|
||||
type="password"
|
||||
onChange={(event) =>
|
||||
|
@ -7,7 +7,7 @@ export const [themeClass, vars] = createTheme({
|
||||
background: "#1c1c1c",
|
||||
darkBackground: "#151515",
|
||||
muted: "#c0c1c7",
|
||||
bodyText: "#8e919b",
|
||||
body: "#8e919b",
|
||||
border: "#424244",
|
||||
success: "#1c9749",
|
||||
danger: "#e11d48",
|
||||
@ -17,6 +17,6 @@ export const [themeClass, vars] = createTheme({
|
||||
active: "0.7",
|
||||
},
|
||||
size: {
|
||||
bodyFontSize: "14px",
|
||||
body: "14px",
|
||||
},
|
||||
});
|
||||
|
@ -115,9 +115,6 @@ export interface DownloadProgress {
|
||||
numPeers: number;
|
||||
numSeeds: number;
|
||||
isDownloadingMetadata: boolean;
|
||||
progress: number;
|
||||
bytesDownloaded: number;
|
||||
fileSize: number;
|
||||
game: Omit<Game, "repacks">;
|
||||
}
|
||||
|
||||
@ -197,7 +194,18 @@ export interface RealDebridTorrentInfo {
|
||||
host: string;
|
||||
split: number;
|
||||
progress: number;
|
||||
status: string;
|
||||
status:
|
||||
| "magnet_error"
|
||||
| "magnet_conversion"
|
||||
| "waiting_files_selection"
|
||||
| "queued"
|
||||
| "downloading"
|
||||
| "downloaded"
|
||||
| "error"
|
||||
| "virus"
|
||||
| "compressing"
|
||||
| "uploading"
|
||||
| "dead";
|
||||
added: string;
|
||||
files: {
|
||||
id: number;
|
||||
|
Loading…
Reference in New Issue
Block a user