diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json
index ee9fb16b..417df3b7 100644
--- a/src/locales/en/translation.json
+++ b/src/locales/en/translation.json
@@ -34,8 +34,8 @@
"bottom_panel": {
"no_downloads_in_progress": "No downloads in progress",
"downloading_metadata": "Downloading {{title}} metadata…",
- "checking_files": "Checking {{title}} files… ({{percentage}} complete)",
- "downloading": "Downloading {{title}}… ({{percentage}} complete) - Conclusion {{eta}} - {{speed}}"
+ "downloading": "Downloading {{title}}… ({{percentage}} complete) - Conclusion {{eta}} - {{speed}}",
+ "calculating_eta": "Downloading {{title}}… ({{percentage}} complete) - Calculating remaining time…"
},
"catalogue": {
"next_page": "Next page",
@@ -55,15 +55,15 @@
"remove_from_list": "Remove",
"space_left_on_disk": "{{space}} left on disk",
"eta": "Conclusion {{eta}}",
+ "calculating_eta": "Calculating remaining time…",
"downloading_metadata": "Downloading metadata…",
- "checking_files": "Checking files…",
"filter": "Filter repacks",
"requirements": "System requirements",
"minimum": "Minimum",
"recommended": "Recommended",
"no_minimum_requirements": "{{title}} doesn't provide minimum requirements information",
"no_recommended_requirements": "{{title}} doesn't provide recommended requirements information",
- "paused_progress": "{{progress}} (Paused)",
+ "paused": "Paused",
"release_date": "Released on {{date}}",
"publisher": "Published by {{publisher}}",
"copy_link_to_clipboard": "Copy link",
@@ -126,7 +126,6 @@
"filter": "Filter downloaded games",
"remove": "Remove",
"downloading_metadata": "Downloading metadata…",
- "checking_files": "Checking files…",
"starting_download": "Starting download…",
"deleting": "Deleting installer…",
"delete": "Remove installer",
diff --git a/src/main/events/index.ts b/src/main/events/index.ts
index debca0e4..70f483d5 100644
--- a/src/main/events/index.ts
+++ b/src/main/events/index.ts
@@ -30,6 +30,7 @@ import "./user-preferences/auto-launch";
import "./autoupdater/check-for-updates";
import "./autoupdater/restart-and-install-update";
import "./autoupdater/continue-to-main-window";
+import "./user-preferences/authenticate-real-debrid";
ipcMain.handle("ping", () => "pong");
ipcMain.handle("getVersion", () => app.getVersion());
diff --git a/src/main/events/user-preferences/authenticate-real-debrid.ts b/src/main/events/user-preferences/authenticate-real-debrid.ts
new file mode 100644
index 00000000..01705db7
--- /dev/null
+++ b/src/main/events/user-preferences/authenticate-real-debrid.ts
@@ -0,0 +1,14 @@
+import { RealDebridClient } from "@main/services/real-debrid";
+import { registerEvent } from "../register-event";
+
+const authenticateRealDebrid = async (
+ _event: Electron.IpcMainInvokeEvent,
+ apiToken: string
+) => {
+ RealDebridClient.authorize(apiToken);
+
+ const user = await RealDebridClient.getUser();
+ return user;
+};
+
+registerEvent("authenticateRealDebrid", authenticateRealDebrid);
diff --git a/src/main/main.ts b/src/main/main.ts
index 50f18202..63162bbf 100644
--- a/src/main/main.ts
+++ b/src/main/main.ts
@@ -86,7 +86,7 @@ const loadState = async (userPreferences: UserPreferences | null) => {
import("./events");
if (userPreferences?.realDebridApiToken)
- await RealDebridClient.authorize(userPreferences?.realDebridApiToken);
+ RealDebridClient.authorize(userPreferences?.realDebridApiToken);
const game = await gameRepository.findOne({
where: {
diff --git a/src/main/services/download-manager.ts b/src/main/services/download-manager.ts
index 3511dde5..b14e0b14 100644
--- a/src/main/services/download-manager.ts
+++ b/src/main/services/download-manager.ts
@@ -92,7 +92,7 @@ export class DownloadManager {
const status = await this.aria2.call("tellStatus", this.gid);
- const downloadingMetadata = status.bittorrent && !status.bittorrent?.info;
+ const isDownloadingMetadata = status.bittorrent && !status.bittorrent?.info;
if (status.followedBy?.length) {
this.gid = status.followedBy[0];
@@ -103,7 +103,7 @@ export class DownloadManager {
const progress =
Number(status.completedLength) / Number(status.totalLength);
- if (!downloadingMetadata) {
+ if (!isDownloadingMetadata) {
const update: QueryDeepPartialEntity = {
bytesDownloaded: Number(status.completedLength),
fileSize: Number(status.totalLength),
@@ -127,7 +127,7 @@ export class DownloadManager {
relations: { repack: true },
});
- if (progress === 1 && game && !downloadingMetadata) {
+ if (progress === 1 && game && !isDownloadingMetadata) {
await this.publishNotification();
/*
Only cancel bittorrent downloads to stop seeding
@@ -150,7 +150,7 @@ export class DownloadManager {
numSeeds: Number(status.numSeeders ?? 0),
downloadSpeed: Number(status.downloadSpeed),
timeRemaining: this.getETA(status),
- downloadingMetadata: !!downloadingMetadata,
+ isDownloadingMetadata: !!isDownloadingMetadata,
game,
} as DownloadProgress;
diff --git a/src/main/services/real-debrid.ts b/src/main/services/real-debrid.ts
index 355a59b3..bf22e5ae 100644
--- a/src/main/services/real-debrid.ts
+++ b/src/main/services/real-debrid.ts
@@ -1,10 +1,11 @@
import { Game } from "@main/entity";
+import axios, { AxiosInstance } from "axios";
import type {
RealDebridAddMagnet,
RealDebridTorrentInfo,
RealDebridUnrestrictLink,
-} from "./real-debrid.types";
-import axios, { AxiosInstance } from "axios";
+ RealDebridUser,
+} from "@types";
const base = "https://api.real-debrid.com/rest/1.0";
@@ -29,6 +30,11 @@ export class RealDebridClient {
return response.data;
}
+ static async getUser() {
+ const response = await this.instance.get(`/user`);
+ return response.data;
+ }
+
static async selectAllFiles(id: string) {
const searchParams = new URLSearchParams({ files: "all" });
@@ -65,30 +71,29 @@ export class RealDebridClient {
const hash = RealDebridClient.extractSHA1FromMagnet(game!.repack.magnet);
let torrent = torrents.find((t) => t.hash === hash);
+ // User haven't downloaded this torrent yet
if (!torrent) {
const magnet = await RealDebridClient.addMagnet(game!.repack.magnet);
- if (magnet && magnet.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;
}
}
- if (torrent) {
- 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 async authorize(apiToken: string) {
+ static authorize(apiToken: string) {
this.instance = axios.create({
baseURL: base,
headers: {
diff --git a/src/main/services/real-debrid.types.ts b/src/main/services/real-debrid.types.ts
deleted file mode 100644
index 6707641f..00000000
--- a/src/main/services/real-debrid.types.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-export interface RealDebridUnrestrictLink {
- id: string;
- filename: string;
- mimeType: string;
- filesize: number;
- link: string;
- host: string;
- host_icon: string;
- chunks: number;
- crc: number;
- download: string;
- streamable: number;
-}
-
-export interface RealDebridAddMagnet {
- id: string;
- // URL of the created ressource
- uri: string;
-}
-
-export interface RealDebridTorrentInfo {
- id: string;
- filename: string;
- original_filename: string; // Original name of the torrent
- hash: string; // SHA1 Hash of the torrent
- bytes: number; // Size of selected files only
- original_bytes: number; // Total size of the torrent
- host: string; // Host main domain
- split: number; // Split size of links
- progress: number; // Possible values: 0 to 100
- status: string; // Current status of the torrent: magnet_error, magnet_conversion, waiting_files_selection, queued, downloading, downloaded, error, virus, compressing, uploading, dead
- added: string; // jsonDate
- files: [
- {
- id: number;
- path: string; // Path to the file inside the torrent, starting with "/"
- bytes: number;
- selected: number; // 0 or 1
- },
- {
- id: number;
- path: string; // Path to the file inside the torrent, starting with "/"
- bytes: number;
- selected: number; // 0 or 1
- },
- ];
- links: string[];
- ended: string; // !! Only present when finished, jsonDate
- speed: number; // !! Only present in "downloading", "compressing", "uploading" status
- seeders: number; // !! Only present in "downloading", "magnet_conversion" status
-}
diff --git a/src/preload/index.ts b/src/preload/index.ts
index ecd328df..57ddc43b 100644
--- a/src/preload/index.ts
+++ b/src/preload/index.ts
@@ -49,6 +49,8 @@ contextBridge.exposeInMainWorld("electron", {
updateUserPreferences: (preferences: UserPreferences) =>
ipcRenderer.invoke("updateUserPreferences", preferences),
autoLaunch: (enabled: boolean) => ipcRenderer.invoke("autoLaunch", enabled),
+ authenticateRealDebrid: (apiToken: string) =>
+ ipcRenderer.invoke("authenticateRealDebrid", apiToken),
/* Library */
addGameToLibrary: (
diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx
index 3a10b98c..28abbd71 100644
--- a/src/renderer/src/app.tsx
+++ b/src/renderer/src/app.tsx
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useRef } from "react";
-import { Sidebar, BottomPanel, Header } from "@renderer/components";
+import { Sidebar, BottomPanel, Header, Toast } from "@renderer/components";
import {
useAppDispatch,
@@ -18,6 +18,7 @@ import {
clearSearch,
setUserPreferences,
toggleDraggingDisabled,
+ closeToast,
} from "@renderer/features";
document.body.classList.add(themeClass);
@@ -41,6 +42,7 @@ export function App() {
const draggingDisabled = useAppSelector(
(state) => state.window.draggingDisabled
);
+ const toast = useAppSelector((state) => state.toast);
useEffect(() => {
Promise.all([window.electron.getUserPreferences(), updateLibrary()]).then(
@@ -108,6 +110,10 @@ export function App() {
});
}, [dispatch, draggingDisabled]);
+ const handleToastClose = useCallback(() => {
+ dispatch(closeToast());
+ }, [dispatch]);
+
return (
<>
{window.electron.platform === "win32" && (
@@ -128,6 +134,13 @@ export function App() {
diff --git a/src/renderer/src/assets/epic-games-logo.svg b/src/renderer/src/assets/epic-games-logo.svg
deleted file mode 100644
index a6c53dbc..00000000
--- a/src/renderer/src/assets/epic-games-logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/renderer/src/assets/telegram-icon.svg b/src/renderer/src/assets/telegram-icon.svg
deleted file mode 100644
index 962ab45f..00000000
--- a/src/renderer/src/assets/telegram-icon.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/renderer/src/assets/x-icon.svg b/src/renderer/src/assets/x-icon.svg
deleted file mode 100644
index c394d154..00000000
--- a/src/renderer/src/assets/x-icon.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/renderer/src/components/bottom-panel/bottom-panel.css.ts b/src/renderer/src/components/bottom-panel/bottom-panel.css.ts
index f339e0d5..39aef69f 100644
--- a/src/renderer/src/components/bottom-panel/bottom-panel.css.ts
+++ b/src/renderer/src/components/bottom-panel/bottom-panel.css.ts
@@ -9,6 +9,7 @@ export const bottomPanel = style({
alignItems: "center",
transition: "all ease 0.2s",
justifyContent: "space-between",
+ position: "relative",
zIndex: "1",
});
diff --git a/src/renderer/src/components/bottom-panel/bottom-panel.tsx b/src/renderer/src/components/bottom-panel/bottom-panel.tsx
index e52cc3b0..db654c8a 100644
--- a/src/renderer/src/components/bottom-panel/bottom-panel.tsx
+++ b/src/renderer/src/components/bottom-panel/bottom-panel.tsx
@@ -1,10 +1,10 @@
+import { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { useDownload } from "@renderer/hooks";
import * as styles from "./bottom-panel.css";
-import { vars } from "../../theme.css";
-import { useEffect, useMemo, useState } from "react";
+
import { useNavigate } from "react-router-dom";
import { VERSION_CODENAME } from "@renderer/constants";
@@ -25,6 +25,16 @@ export function BottomPanel() {
const status = useMemo(() => {
if (isGameDownloading) {
+ if (lastPacket?.isDownloadingMetadata)
+ return t("downloading_metadata", { title: lastPacket?.game.title });
+
+ if (!eta) {
+ return t("calculating_eta", {
+ title: lastPacket?.game.title,
+ percentage: progress,
+ });
+ }
+
return t("downloading", {
title: lastPacket?.game.title,
percentage: progress,
@@ -34,17 +44,18 @@ export function BottomPanel() {
}
return t("no_downloads_in_progress");
- }, [t, isGameDownloading, lastPacket?.game, progress, eta, downloadSpeed]);
+ }, [
+ t,
+ isGameDownloading,
+ lastPacket?.game,
+ lastPacket?.isDownloadingMetadata,
+ progress,
+ eta,
+ downloadSpeed,
+ ]);
return (
-
@@ -87,7 +88,9 @@ export function HeroPanel() {
return (
<>
-
{t("paused_progress", { progress: formattedProgress })}
+
+ {formattedProgress} {t("paused")}
+
{formatBytes(game.bytesDownloaded)} / {finalDownloadSize}
diff --git a/src/renderer/src/pages/settings/settings-real-debrid.tsx b/src/renderer/src/pages/settings/settings-real-debrid.tsx
index f64a6c8d..67525a71 100644
--- a/src/renderer/src/pages/settings/settings-real-debrid.tsx
+++ b/src/renderer/src/pages/settings/settings-real-debrid.tsx
@@ -5,6 +5,8 @@ import { Button, CheckboxField, Link, TextField } from "@renderer/components";
import * as styles from "./settings-real-debrid.css";
import type { UserPreferences } from "@types";
import { SPACING_UNIT } from "@renderer/theme.css";
+import { showToast } from "@renderer/features";
+import { useAppDispatch } from "@renderer/hooks";
const REAL_DEBRID_API_TOKEN_URL = "https://real-debrid.com/apitoken";
@@ -22,6 +24,8 @@ export function SettingsRealDebrid({
realDebridApiToken: null as string | null,
});
+ const dispatch = useAppDispatch();
+
const { t } = useTranslation("settings");
useEffect(() => {
@@ -33,11 +37,36 @@ export function SettingsRealDebrid({
}
}, [userPreferences]);
- const handleFormSubmit: React.FormEventHandler = (event) => {
+ const handleFormSubmit: React.FormEventHandler = async (
+ event
+ ) => {
event.preventDefault();
- updateUserPreferences({
- realDebridApiToken: form.useRealDebrid ? form.realDebridApiToken : null,
- });
+ dispatch(
+ showToast({
+ message: t("real_debrid_authenticated"),
+ type: "success",
+ })
+ );
+ if (form.useRealDebrid) {
+ const user = await window.electron.authenticateRealDebrid(
+ form.realDebridApiToken!
+ );
+
+ console.log(user);
+
+ if (user.type === "premium") {
+ dispatch(
+ showToast({
+ message: t("real_debrid_authenticated"),
+ type: "success",
+ })
+ );
+ }
+ }
+
+ // updateUserPreferences({
+ // realDebridApiToken: form.useRealDebrid ? form.realDebridApiToken : null,
+ // });
};
const isButtonDisabled = form.useRealDebrid && !form.realDebridApiToken;
diff --git a/src/renderer/src/pages/settings/settings.tsx b/src/renderer/src/pages/settings/settings.tsx
index fe7638c6..28e3e7a4 100644
--- a/src/renderer/src/pages/settings/settings.tsx
+++ b/src/renderer/src/pages/settings/settings.tsx
@@ -1,4 +1,4 @@
-import { useCallback, useEffect, useState } from "react";
+import { useEffect, useState } from "react";
import { Button } from "@renderer/components";
import * as styles from "./settings.css";
@@ -7,7 +7,6 @@ import { UserPreferences } from "@types";
import { SettingsRealDebrid } from "./settings-real-debrid";
import { SettingsGeneral } from "./settings-general";
import { SettingsBehavior } from "./settings-behavior";
-import { Toast } from "@renderer/components/toast/toast";
const categories = ["general", "behavior", "real_debrid"];
@@ -15,7 +14,6 @@ export function Settings() {
const [currentCategory, setCurrentCategory] = useState(categories.at(0)!);
const [userPreferences, setUserPreferences] =
useState(null);
- const [isToastVisible, setIsToastVisible] = useState(false);
const { t } = useTranslation("settings");
@@ -28,12 +26,9 @@ export function Settings() {
const handleUpdateUserPreferences = async (
values: Partial
) => {
- setIsToastVisible(false);
-
await window.electron.updateUserPreferences(values);
window.electron.getUserPreferences().then((userPreferences) => {
setUserPreferences(userPreferences);
- setIsToastVisible(true);
});
};
@@ -64,37 +59,24 @@ export function Settings() {
);
};
- const handleToastClose = useCallback(() => {
- setIsToastVisible(false);
- }, []);
-
return (
- <>
-
-
-
- {categories.map((category) => (
-
- ))}
-
+
+
+
+ {categories.map((category) => (
+
+ ))}
+
-
{t(currentCategory)}
- {renderCategory()}
-
-
-
-
- >
+
{t(currentCategory)}
+ {renderCategory()}
+
+
);
}
diff --git a/src/renderer/src/store.ts b/src/renderer/src/store.ts
index 2865c30b..a321ee4f 100644
--- a/src/renderer/src/store.ts
+++ b/src/renderer/src/store.ts
@@ -5,6 +5,7 @@ import {
librarySlice,
searchSlice,
userPreferencesSlice,
+ toastSlice,
} from "@renderer/features";
export const store = configureStore({
@@ -14,6 +15,7 @@ export const store = configureStore({
library: librarySlice.reducer,
userPreferences: userPreferencesSlice.reducer,
download: downloadSlice.reducer,
+ toast: toastSlice.reducer,
},
});
diff --git a/src/types/index.ts b/src/types/index.ts
index 8ac492d3..5de56ee9 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -114,7 +114,7 @@ export interface DownloadProgress {
timeRemaining: number;
numPeers: number;
numSeeds: number;
- downloadingMetadata: boolean;
+ isDownloadingMetadata: boolean;
progress: number;
bytesDownloaded: number;
fileSize: number;
@@ -166,3 +166,59 @@ export interface StartGameDownloadPayload {
downloadPath: string;
downloader: Downloader;
}
+
+export interface RealDebridUnrestrictLink {
+ id: string;
+ filename: string;
+ mimeType: string;
+ filesize: number;
+ link: string;
+ host: string;
+ host_icon: string;
+ chunks: number;
+ crc: number;
+ download: string;
+ streamable: number;
+}
+
+export interface RealDebridAddMagnet {
+ id: string;
+ // URL of the created ressource
+ uri: string;
+}
+
+export interface RealDebridTorrentInfo {
+ id: string;
+ filename: string;
+ original_filename: string;
+ hash: string;
+ bytes: number;
+ original_bytes: number;
+ host: string;
+ split: number;
+ progress: number;
+ status: string;
+ added: string;
+ files: {
+ id: number;
+ path: string;
+ bytes: number;
+ selected: number;
+ }[];
+ links: string[];
+ ended: string;
+ speed: number;
+ seeders: number;
+}
+
+export interface RealDebridUser {
+ id: number;
+ username: string;
+ email: string;
+ points: number;
+ locale: string;
+ avatar: string;
+ type: string;
+ premium: number;
+ expiration: string;
+}