Merge pull request #211 from lilezek/feat/real-debrid-integration

Improvements over the real debrid integration.
This commit is contained in:
Hydra 2024-05-07 05:51:00 +01:00 committed by GitHub
commit 13be9c3814
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 688 additions and 463 deletions

View file

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

View file

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

View file

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

View file

@ -1,2 +1,2 @@
export * from "./http.downloader"; export * from "./real-debrid.downloader";
export * from "./torrent.downloader"; export * from "./torrent.downloader";

View file

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

View file

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

View file

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

1012
yarn.lock

File diff suppressed because it is too large Load diff