mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-03-09 15:40:26 +00:00
Merge pull request #211 from lilezek/feat/real-debrid-integration
Improvements over the real debrid integration.
This commit is contained in:
commit
13be9c3814
8 changed files with 688 additions and 463 deletions
|
@ -51,7 +51,7 @@
|
||||||
"jsdom": "^24.0.0",
|
"jsdom": "^24.0.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"lottie-react": "^2.4.0",
|
"lottie-react": "^2.4.0",
|
||||||
"node-unrar-js": "^2.0.2",
|
"node-7z-archive": "^1.1.7",
|
||||||
"parse-torrent": "^11.0.16",
|
"parse-torrent": "^11.0.16",
|
||||||
"ps-list": "^8.1.1",
|
"ps-list": "^8.1.1",
|
||||||
"react-i18next": "^14.1.0",
|
"react-i18next": "^14.1.0",
|
||||||
|
|
|
@ -26,7 +26,7 @@ const startGameDownload = async (
|
||||||
});
|
});
|
||||||
|
|
||||||
const downloader = userPreferences?.realDebridApiToken
|
const downloader = userPreferences?.realDebridApiToken
|
||||||
? Downloader.Http
|
? Downloader.RealDebrid
|
||||||
: Downloader.Torrent;
|
: Downloader.Torrent;
|
||||||
|
|
||||||
const [game, repack] = await Promise.all([
|
const [game, repack] = await Promise.all([
|
||||||
|
|
|
@ -4,7 +4,7 @@ import type { Game } from "@main/entity";
|
||||||
import { Downloader } from "@shared";
|
import { Downloader } from "@shared";
|
||||||
|
|
||||||
import { writePipe } from "./fifo";
|
import { writePipe } from "./fifo";
|
||||||
import { HTTPDownloader } from "./downloaders";
|
import { RealDebridDownloader } from "./downloaders";
|
||||||
|
|
||||||
export class DownloadManager {
|
export class DownloadManager {
|
||||||
private static gameDownloading: Game;
|
private static gameDownloading: Game;
|
||||||
|
@ -25,7 +25,7 @@ export class DownloadManager {
|
||||||
) {
|
) {
|
||||||
writePipe.write({ action: "cancel" });
|
writePipe.write({ action: "cancel" });
|
||||||
} else {
|
} else {
|
||||||
HTTPDownloader.destroy();
|
RealDebridDownloader.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ export class DownloadManager {
|
||||||
) {
|
) {
|
||||||
writePipe.write({ action: "pause" });
|
writePipe.write({ action: "pause" });
|
||||||
} else {
|
} else {
|
||||||
HTTPDownloader.destroy();
|
RealDebridDownloader.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ export class DownloadManager {
|
||||||
save_path: game!.downloadPath,
|
save_path: game!.downloadPath,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
HTTPDownloader.startDownload(game!);
|
RealDebridDownloader.startDownload(game!);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.gameDownloading = game!;
|
this.gameDownloading = game!;
|
||||||
|
@ -68,7 +68,7 @@ export class DownloadManager {
|
||||||
save_path: game!.downloadPath,
|
save_path: game!.downloadPath,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
HTTPDownloader.startDownload(game!);
|
RealDebridDownloader.startDownload(game!);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.gameDownloading = game!;
|
this.gameDownloading = game!;
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
export * from "./http.downloader";
|
export * from "./real-debrid.downloader";
|
||||||
export * from "./torrent.downloader";
|
export * from "./torrent.downloader";
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
import { Game } from "@main/entity";
|
import { Game } from "@main/entity";
|
||||||
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
|
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import fs from "node:fs";
|
|
||||||
import EasyDL from "easydl";
|
import EasyDL from "easydl";
|
||||||
import { GameStatus } from "@shared";
|
import { GameStatus } from "@shared";
|
||||||
|
import { fullArchive } from "node-7z-archive";
|
||||||
|
|
||||||
import { Downloader } from "./downloader";
|
import { Downloader } from "./downloader";
|
||||||
import { RealDebridClient } from "../real-debrid";
|
import { RealDebridClient } from "../real-debrid";
|
||||||
|
|
||||||
export class HTTPDownloader extends Downloader {
|
function getFileNameWithoutExtension(fullPath: string) {
|
||||||
|
return path.basename(fullPath, path.extname(fullPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RealDebridDownloader extends Downloader {
|
||||||
private static download: EasyDL;
|
private static download: EasyDL;
|
||||||
private static downloadSize = 0;
|
private static downloadSize = 0;
|
||||||
|
|
||||||
|
@ -22,52 +26,14 @@ export class HTTPDownloader extends Downloader {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
if (!torrent) {
|
|
||||||
const magnet = await RealDebridClient.addMagnet(game!.repack.magnet);
|
|
||||||
|
|
||||||
if (magnet && magnet.id) {
|
|
||||||
await RealDebridClient.selectAllFiles(magnet.id);
|
|
||||||
torrent = await RealDebridClient.getInfo(magnet.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static createFolderIfNotExists(path: string) {
|
|
||||||
if (!fs.existsSync(path)) {
|
|
||||||
fs.mkdirSync(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static async startDownload(game: Game) {
|
static async startDownload(game: Game) {
|
||||||
if (this.download) this.download.destroy();
|
if (this.download) this.download.destroy();
|
||||||
const downloadUrl = await this.getDownloadUrl(game);
|
const downloadUrl = await RealDebridClient.getDownloadUrl(game);
|
||||||
|
|
||||||
const filename = path.basename(downloadUrl);
|
this.download = new EasyDL(
|
||||||
const folderName = path.basename(filename, path.extname(filename));
|
downloadUrl,
|
||||||
|
path.join(game.downloadPath!, ".rd")
|
||||||
const fullDownloadPath = path.join(game.downloadPath!, folderName);
|
);
|
||||||
|
|
||||||
this.createFolderIfNotExists(fullDownloadPath);
|
|
||||||
|
|
||||||
this.download = new EasyDL(downloadUrl, fullDownloadPath);
|
|
||||||
|
|
||||||
const metadata = await this.download.metadata();
|
const metadata = await this.download.metadata();
|
||||||
|
|
||||||
|
@ -76,7 +42,7 @@ export class HTTPDownloader extends Downloader {
|
||||||
const updatePayload: QueryDeepPartialEntity<Game> = {
|
const updatePayload: QueryDeepPartialEntity<Game> = {
|
||||||
status: GameStatus.Downloading,
|
status: GameStatus.Downloading,
|
||||||
fileSize: metadata.size,
|
fileSize: metadata.size,
|
||||||
folderName: folderName,
|
folderName: getFileNameWithoutExtension(metadata.savedFilePath),
|
||||||
};
|
};
|
||||||
|
|
||||||
const downloadStatus = {
|
const downloadStatus = {
|
||||||
|
@ -87,11 +53,8 @@ export class HTTPDownloader extends Downloader {
|
||||||
|
|
||||||
this.download.on("progress", async ({ total }) => {
|
this.download.on("progress", async ({ total }) => {
|
||||||
const updatePayload: QueryDeepPartialEntity<Game> = {
|
const updatePayload: QueryDeepPartialEntity<Game> = {
|
||||||
status:
|
status: GameStatus.Downloading,
|
||||||
total.percentage === 100
|
progress: Math.min(0.99, total.percentage / 100),
|
||||||
? GameStatus.Finished
|
|
||||||
: GameStatus.Downloading,
|
|
||||||
progress: total.percentage / 100,
|
|
||||||
bytesDownloaded: total.bytes,
|
bytesDownloaded: total.bytes,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -102,6 +65,33 @@ export class HTTPDownloader extends Downloader {
|
||||||
|
|
||||||
await this.updateGameProgress(game.id, updatePayload, downloadStatus);
|
await this.updateGameProgress(game.id, updatePayload, downloadStatus);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.download.on("end", async () => {
|
||||||
|
const updatePayload: QueryDeepPartialEntity<Game> = {
|
||||||
|
status: GameStatus.Decompressing,
|
||||||
|
progress: 0.99,
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.updateGameProgress(game.id, updatePayload, {
|
||||||
|
timeRemaining: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.startDecompression(this.download.savedFilePath!, getFileNameWithoutExtension(metadata.savedFilePath), game);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async startDecompression(rarFile: string, destiny: string, game: Game) {
|
||||||
|
const directory = path.join(game.downloadPath!, destiny);
|
||||||
|
|
||||||
|
await fullArchive(rarFile, directory);
|
||||||
|
const updatePayload: QueryDeepPartialEntity<Game> = {
|
||||||
|
status: GameStatus.Finished,
|
||||||
|
progress: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.updateGameProgress(game.id, updatePayload, {
|
||||||
|
timeRemaining: 0,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static destroy() {
|
static destroy() {
|
|
@ -62,6 +62,34 @@ export class RealDebridClient {
|
||||||
return magnet.match(/btih:([0-9a-fA-F]*)/)?.[1].toLowerCase();
|
return magnet.match(/btih:([0-9a-fA-F]*)/)?.[1].toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (!torrent) {
|
||||||
|
const magnet = await RealDebridClient.addMagnet(game!.repack.magnet);
|
||||||
|
|
||||||
|
if (magnet && magnet.id) {
|
||||||
|
await RealDebridClient.selectAllFiles(magnet.id);
|
||||||
|
torrent = await RealDebridClient.getInfo(magnet.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 async authorize(apiToken: string) {
|
||||||
this.instance = axios.create({
|
this.instance = axios.create({
|
||||||
baseURL: base,
|
baseURL: base,
|
||||||
|
|
|
@ -5,11 +5,12 @@ export enum GameStatus {
|
||||||
CheckingFiles = "checking_files",
|
CheckingFiles = "checking_files",
|
||||||
DownloadingMetadata = "downloading_metadata",
|
DownloadingMetadata = "downloading_metadata",
|
||||||
Cancelled = "cancelled",
|
Cancelled = "cancelled",
|
||||||
|
Decompressing = "decompressing",
|
||||||
Finished = "finished",
|
Finished = "finished",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum Downloader {
|
export enum Downloader {
|
||||||
Http,
|
RealDebrid,
|
||||||
Torrent,
|
Torrent,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue