feat: updating real-debrid translations

This commit is contained in:
Chubby Granny Chaser 2024-05-29 16:22:30 +01:00
parent 183b85d66a
commit 08a336b392
No known key found for this signature in database
38 changed files with 284 additions and 228 deletions

2
.gitignore vendored
View File

@ -1,6 +1,6 @@
.vscode
node_modules
aria2*
aria2/
fastlist.exe
__pycache__
dist

View File

@ -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": "حفظ التغييرات"
},

View File

@ -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"
},

View File

@ -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": {

View File

@ -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"
},

View File

@ -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": "ذخیره تغییرات"
},

View File

@ -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": "변경 사항 저장"
},

View File

@ -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"
},

View File

@ -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"
},

View File

@ -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"
},

View File

@ -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": "Сохранить изменения"
},

View File

@ -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": "保存更改"
},

View File

@ -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)

View File

@ -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!);
}
};

View File

@ -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);

View File

@ -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

View 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);
}
});
});
};

View File

@ -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")) {
await this.aria2.open();
this.connected = true;
resolve(true);
}
});
});
private static async connect() {
await startAria2();
await this.aria2.open();
this.connected = 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)!;
const options = {
dir: game.downloadPath!,
};
if (game) {
const options = {
dir: game.downloadPath!,
};
if (game.downloader === Downloader.RealDebrid) {
this.realDebridTorrentId = await RealDebridClient.getTorrentId(
game!.repack.magnet
);
} else {
this.gid = await this.aria2.call("addUri", [game.repack.magnet], options);
if (game.downloader === Downloader.RealDebrid) {
const downloadUrl = decodeURIComponent(
await RealDebridClient.getDownloadUrl(game)
);
this.gid = await this.aria2.call("addUri", [downloadUrl], options);
} else {
this.gid = await this.aria2.call(
"addUri",
[game.repack.magnet],
options
);
}
this.gameId = gameId;
this.downloads.set(gameId, this.gid);
this.downloads.set(game.id, this.gid);
}
this.game = game;
}
}

View File

@ -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;
}
}

View File

@ -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]", {

View File

@ -134,17 +134,18 @@ export function App() {
<section ref={contentRef} className={styles.content}>
<Outlet />
<Toast
visible={toast.visible}
message={toast.message}
type={toast.type}
onClose={handleToastClose}
/>
</section>
</article>
</main>
<BottomPanel />
<Toast
visible={toast.visible}
message={toast.message}
type={toast.type}
onClose={handleToastClose}
/>
</>
);
}

View File

@ -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",
},
});

View File

@ -109,6 +109,6 @@ export const shopIcon = style({
});
export const noDownloadsLabel = style({
color: vars.color.bodyText,
color: vars.color.body,
fontWeight: "bold",
});

View File

@ -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",

View File

@ -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,
});

View File

@ -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",

View File

@ -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>;

View File

@ -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;
},

View File

@ -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",

View File

@ -174,5 +174,5 @@ globalStyle(`${description} img`, {
});
globalStyle(`${description} a`, {
color: vars.color.bodyText,
color: vars.color.body,
});

View File

@ -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`,
});

View File

@ -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({

View File

@ -125,7 +125,7 @@ export function SelectFolderModal({
{selectedDownloader === Downloader.RealDebrid && (
<CheckCircleFillIcon />
)}
Real Debrid
Real-Debrid
</Button>
</div>
</div>

View File

@ -88,5 +88,5 @@ export const howLongToBeatCategorySkeleton = style({
globalStyle(`${requirementsDetails} a`, {
display: "flex",
color: vars.color.bodyText,
color: vars.color.body,
});

View File

@ -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`,
});

View File

@ -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) =>

View File

@ -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",
},
});

View File

@ -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;