diff --git a/src/main/events/index.ts b/src/main/events/index.ts
index 8465843f..a0b2296b 100644
--- a/src/main/events/index.ts
+++ b/src/main/events/index.ts
@@ -47,6 +47,7 @@ import "./torrenting/resume-game-download";
import "./torrenting/start-game-download";
import "./torrenting/pause-game-seed";
import "./torrenting/resume-game-seed";
+import "./torrenting/check-debrid-availability";
import "./user-preferences/get-user-preferences";
import "./user-preferences/update-user-preferences";
import "./user-preferences/auto-launch";
diff --git a/src/main/events/torrenting/check-debrid-availability.ts b/src/main/events/torrenting/check-debrid-availability.ts
new file mode 100644
index 00000000..447c3e45
--- /dev/null
+++ b/src/main/events/torrenting/check-debrid-availability.ts
@@ -0,0 +1,11 @@
+import { HydraDebridClient } from "@main/services/download/hydra-debrid";
+import { registerEvent } from "../register-event";
+
+const checkDebridAvailability = async (
+ _event: Electron.IpcMainInvokeEvent,
+ magnets: string[]
+) => {
+ return HydraDebridClient.getAvailableMagnets(magnets);
+};
+
+registerEvent("checkDebridAvailability", checkDebridAvailability);
diff --git a/src/main/services/download/download-manager.ts b/src/main/services/download/download-manager.ts
index 35841d33..7401683e 100644
--- a/src/main/services/download/download-manager.ts
+++ b/src/main/services/download/download-manager.ts
@@ -23,6 +23,7 @@ import { db, downloadsSublevel, gamesSublevel, levelKeys } from "@main/level";
import { sortBy } from "lodash-es";
import { TorBoxClient } from "./torbox";
import { GameFilesManager } from "../game-files-manager";
+import { HydraDebridClient } from "./hydra-debrid";
export class DownloadManager {
private static downloadingGameId: string | null = null;
@@ -387,6 +388,21 @@ export class DownloadManager {
allow_multiple_connections: true,
};
}
+ case Downloader.Hydra: {
+ const downloadUrl = await HydraDebridClient.getDownloadUrl(
+ download.uri
+ );
+
+ if (!downloadUrl) throw new Error(DownloadError.NotCachedInHydra);
+
+ return {
+ action: "start",
+ game_id: downloadId,
+ url: downloadUrl,
+ save_path: download.downloadPath,
+ allow_multiple_connections: true,
+ };
+ }
}
}
diff --git a/src/main/services/download/hydra-debrid.ts b/src/main/services/download/hydra-debrid.ts
new file mode 100644
index 00000000..de754947
--- /dev/null
+++ b/src/main/services/download/hydra-debrid.ts
@@ -0,0 +1,25 @@
+import { HydraApi } from "../hydra-api";
+
+export class HydraDebridClient {
+ public static getAvailableMagnets(
+ magnets: string[]
+ ): Promise> {
+ return HydraApi.put(
+ "/debrid/check-availability",
+ {
+ magnets,
+ },
+ { needsAuth: false }
+ );
+ }
+
+ public static getDownloadUrl(magnet: string) {
+ try {
+ return HydraApi.post("/debrid/request-file", {
+ magnet,
+ }).then((response) => response.downloadUrl);
+ } catch (error) {
+ return null;
+ }
+ }
+}
diff --git a/src/preload/index.ts b/src/preload/index.ts
index 280c0cc4..a7e06f90 100644
--- a/src/preload/index.ts
+++ b/src/preload/index.ts
@@ -55,6 +55,8 @@ contextBridge.exposeInMainWorld("electron", {
ipcRenderer.on("on-seeding-status", listener);
return () => ipcRenderer.removeListener("on-seeding-status", listener);
},
+ checkDebridAvailability: (magnets: string[]) =>
+ ipcRenderer.invoke("checkDebridAvailability", magnets),
/* Catalogue */
searchGames: (payload: CatalogueSearchPayload, take: number, skip: number) =>
diff --git a/src/renderer/src/assets/meteor.svg b/src/renderer/src/assets/meteor.svg
new file mode 100644
index 00000000..95174efa
--- /dev/null
+++ b/src/renderer/src/assets/meteor.svg
@@ -0,0 +1,24 @@
+
diff --git a/src/renderer/src/components/debrid-badge/debrid-badge.scss b/src/renderer/src/components/debrid-badge/debrid-badge.scss
new file mode 100644
index 00000000..16ef5464
--- /dev/null
+++ b/src/renderer/src/components/debrid-badge/debrid-badge.scss
@@ -0,0 +1,11 @@
+.debrid-badge {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ border-radius: 4px;
+ border: 1px solid rgba(12, 241, 202, 0.3);
+ background: rgba(12, 241, 202, 0.05);
+ color: #0cf1ca;
+ padding: 4px 8px;
+ font-size: 12px;
+}
diff --git a/src/renderer/src/components/debrid-badge/debrid-badge.tsx b/src/renderer/src/components/debrid-badge/debrid-badge.tsx
new file mode 100644
index 00000000..de8e3b75
--- /dev/null
+++ b/src/renderer/src/components/debrid-badge/debrid-badge.tsx
@@ -0,0 +1,15 @@
+import Meteor from "@renderer/assets/meteor.svg?react";
+import "./debrid-badge.scss";
+
+export interface DebridBadgeProps {
+ collapsed?: boolean;
+}
+
+export function DebridBadge({ collapsed }: Readonly) {
+ return (
+
+
+ {!collapsed && "Baixe até 2x mais rápido com Nimbus"}
+
+ );
+}
diff --git a/src/renderer/src/components/index.ts b/src/renderer/src/components/index.ts
index 65d07440..8373e0dc 100644
--- a/src/renderer/src/components/index.ts
+++ b/src/renderer/src/components/index.ts
@@ -14,3 +14,4 @@ export * from "./toast/toast";
export * from "./badge/badge";
export * from "./confirmation-modal/confirmation-modal";
export * from "./suspense-wrapper/suspense-wrapper";
+export * from "./debrid-badge/debrid-badge";
diff --git a/src/renderer/src/constants.ts b/src/renderer/src/constants.ts
index 3706d7d2..472ed3a7 100644
--- a/src/renderer/src/constants.ts
+++ b/src/renderer/src/constants.ts
@@ -11,6 +11,7 @@ export const DOWNLOADER_NAME = {
[Downloader.Datanodes]: "Datanodes",
[Downloader.Mediafire]: "Mediafire",
[Downloader.TorBox]: "TorBox",
+ [Downloader.Hydra]: "Nimbus",
};
export const MAX_MINUTES_TO_SHOW_IN_PLAYTIME = 120;
diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts
index 791370a2..dd2f24d7 100644
--- a/src/renderer/src/declaration.d.ts
+++ b/src/renderer/src/declaration.d.ts
@@ -59,6 +59,9 @@ declare global {
cb: (value: SeedingStatus[]) => void
) => () => Electron.IpcRenderer;
onHardDelete: (cb: () => void) => () => Electron.IpcRenderer;
+ checkDebridAvailability: (
+ magnets: string[]
+ ) => Promise>;
/* Catalogue */
searchGames: (
diff --git a/src/renderer/src/pages/downloads/download-group.tsx b/src/renderer/src/pages/downloads/download-group.tsx
index 33f4b812..fa0be02a 100644
--- a/src/renderer/src/pages/downloads/download-group.tsx
+++ b/src/renderer/src/pages/downloads/download-group.tsx
@@ -374,6 +374,21 @@ export function DownloadGroup({
)}
+
+ {game.download?.downloader === Downloader.Hydra && (
+
+ )}
);
})}
diff --git a/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx b/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx
index 0200609f..d2237439 100644
--- a/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx
+++ b/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx
@@ -83,6 +83,10 @@ export function DownloadSettingsModal({
const getDefaultDownloader = useCallback(
(availableDownloaders: Downloader[]) => {
+ if (availableDownloaders.includes(Downloader.Hydra)) {
+ return Downloader.Hydra;
+ }
+
if (availableDownloaders.includes(Downloader.TorBox)) {
return Downloader.TorBox;
}
diff --git a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx
index b3879758..5b225380 100644
--- a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx
+++ b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx
@@ -1,7 +1,13 @@
import { useContext, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
-import { Badge, Button, Modal, TextField } from "@renderer/components";
+import {
+ Badge,
+ Button,
+ DebridBadge,
+ Modal,
+ TextField,
+} from "@renderer/components";
import type { GameRepack } from "@types";
import { DownloadSettingsModal } from "./download-settings-modal";
@@ -31,16 +37,52 @@ export function RepacksModal({
const [repack, setRepack] = useState(null);
const [showSelectFolderModal, setShowSelectFolderModal] = useState(false);
+ const [hashesInDebrid, setHashesInDebrid] = useState>(
+ {}
+ );
+
const { repacks, game } = useContext(gameDetailsContext);
const { t } = useTranslation("game_details");
const { formatDate } = useDate();
- const sortedRepacks = useMemo(() => {
- return orderBy(repacks, (repack) => repack.uploadDate, "desc");
+ const getHashFromMagnet = (magnet: string) => {
+ if (!magnet || typeof magnet !== "string") {
+ return null;
+ }
+
+ const hashRegex = /xt=urn:btih:([a-zA-Z0-9]+)/i;
+ const match = magnet.match(hashRegex);
+
+ return match ? match[1].toLowerCase() : null;
+ };
+
+ useEffect(() => {
+ const magnets = repacks.flatMap((repack) =>
+ repack.uris.filter((uri) => uri.startsWith("magnet:"))
+ );
+
+ window.electron.checkDebridAvailability(magnets).then((availableHashes) => {
+ setHashesInDebrid(availableHashes);
+ });
}, [repacks]);
+ const sortedRepacks = useMemo(() => {
+ return orderBy(
+ repacks,
+ [
+ (repack) => {
+ const magnet = repack.uris.find((uri) => uri.startsWith("magnet:"));
+ const hash = magnet ? getHashFromMagnet(magnet) : null;
+ return hash ? (hashesInDebrid[hash] ?? false) : false;
+ },
+ (repack) => repack.uploadDate,
+ ],
+ ["desc", "desc"]
+ );
+ }, [repacks, hashesInDebrid]);
+
useEffect(() => {
setFilteredRepacks(sortedRepacks);
}, [sortedRepacks, visible, game]);
@@ -110,6 +152,10 @@ export function RepacksModal({
{repack.fileSize} - {repack.repacker} -{" "}
{repack.uploadDate ? formatDate(repack.uploadDate) : ""}
+
+ {hashesInDebrid[getHashFromMagnet(repack.uris[0]) ?? ""] && (
+
+ )}
);
})}
diff --git a/src/shared/constants.ts b/src/shared/constants.ts
index 1b3dc9a4..54827ce3 100644
--- a/src/shared/constants.ts
+++ b/src/shared/constants.ts
@@ -7,6 +7,7 @@ export enum Downloader {
Datanodes,
Mediafire,
TorBox,
+ Hydra,
}
export enum DownloadSourceStatus {
@@ -56,6 +57,7 @@ export enum DownloadError {
NotCachedInTorbox = "download_error_not_cached_in_torbox",
GofileQuotaExceeded = "download_error_gofile_quota_exceeded",
RealDebridAccountNotAuthorized = "download_error_real_debrid_account_not_authorized",
+ NotCachedInHydra = "download_error_not_cached_in_hydra",
}
export const FILE_EXTENSIONS_TO_EXTRACT = [".rar", ".zip", ".7z"];
diff --git a/src/shared/index.ts b/src/shared/index.ts
index 0470728c..f5e097bc 100644
--- a/src/shared/index.ts
+++ b/src/shared/index.ts
@@ -111,7 +111,12 @@ export const getDownloadersForUri = (uri: string) => {
return [Downloader.RealDebrid];
if (uri.startsWith("magnet:")) {
- return [Downloader.Torrent, Downloader.TorBox, Downloader.RealDebrid];
+ return [
+ Downloader.Torrent,
+ Downloader.Hydra,
+ Downloader.TorBox,
+ Downloader.RealDebrid,
+ ];
}
return [];