Fix lints and add back the option to seed after download complete

This commit is contained in:
mircea32000 2025-02-11 00:58:47 +02:00
parent d08d14a9e4
commit 9e6143ebc9
15 changed files with 360 additions and 295 deletions

View file

@ -7,12 +7,12 @@ const authenticateAllDebrid = async (
) => { ) => {
AllDebridClient.authorize(apiKey); AllDebridClient.authorize(apiKey);
const result = await AllDebridClient.getUser(); const result = await AllDebridClient.getUser();
if ('error_code' in result) { if ("error_code" in result) {
return { error_code: result.error_code }; return { error_code: result.error_code };
} }
return result.user; return result.user;
}; };
registerEvent("authenticateAllDebrid", authenticateAllDebrid); registerEvent("authenticateAllDebrid", authenticateAllDebrid);

View file

@ -31,9 +31,7 @@ const updateUserPreferences = async (
} }
if (preferences.allDebridApiKey) { if (preferences.allDebridApiKey) {
preferences.allDebridApiKey = Crypto.encrypt( preferences.allDebridApiKey = Crypto.encrypt(preferences.allDebridApiKey);
preferences.allDebridApiKey
);
} }
if (preferences.torBoxApiToken) { if (preferences.torBoxApiToken) {

View file

@ -45,15 +45,11 @@ export const loadState = async () => {
} }
if (userPreferences?.allDebridApiKey) { if (userPreferences?.allDebridApiKey) {
AllDebridClient.authorize( AllDebridClient.authorize(Crypto.decrypt(userPreferences.allDebridApiKey));
Crypto.decrypt(userPreferences.allDebridApiKey)
);
} }
if (userPreferences?.torBoxApiToken) { if (userPreferences?.torBoxApiToken) {
TorBoxClient.authorize( TorBoxClient.authorize(Crypto.decrypt(userPreferences.torBoxApiToken));
Crypto.decrypt(userPreferences.torBoxApiToken)
);
} }
Ludusavi.addManifestToLudusaviConfig(); Ludusavi.addManifestToLudusaviConfig();
@ -126,7 +122,8 @@ const migrateFromSqlite = async () => {
.select("*") .select("*")
.then(async (userPreferences) => { .then(async (userPreferences) => {
if (userPreferences.length > 0) { if (userPreferences.length > 0) {
const { realDebridApiToken, allDebridApiKey, ...rest } = userPreferences[0]; const { realDebridApiToken, allDebridApiKey, ...rest } =
userPreferences[0];
await db.put<string, UserPreferences>( await db.put<string, UserPreferences>(
levelKeys.userPreferences, levelKeys.userPreferences,

View file

@ -3,269 +3,310 @@ import type { AllDebridUser } from "@types";
import { logger } from "@main/services"; import { logger } from "@main/services";
interface AllDebridMagnetStatus { interface AllDebridMagnetStatus {
id: number; id: number;
filename: string;
size: number;
status: string;
statusCode: number;
downloaded: number;
uploaded: number;
seeders: number;
downloadSpeed: number;
uploadSpeed: number;
uploadDate: number;
completionDate: number;
links: Array<{
link: string;
filename: string; filename: string;
size: number; size: number;
status: string; }>;
statusCode: number;
downloaded: number;
uploaded: number;
seeders: number;
downloadSpeed: number;
uploadSpeed: number;
uploadDate: number;
completionDate: number;
links: Array<{
link: string;
filename: string;
size: number;
}>;
} }
interface AllDebridError { interface AllDebridError {
code: string; code: string;
message: string; message: string;
} }
interface AllDebridDownloadUrl { interface AllDebridDownloadUrl {
link: string; link: string;
size?: number; size?: number;
filename?: string; filename?: string;
} }
export class AllDebridClient { export class AllDebridClient {
private static instance: AxiosInstance; private static instance: AxiosInstance;
private static readonly baseURL = "https://api.alldebrid.com/v4"; private static readonly baseURL = "https://api.alldebrid.com/v4";
static authorize(apiKey: string) { static authorize(apiKey: string) {
logger.info("[AllDebrid] Authorizing with key:", apiKey ? "***" : "empty"); logger.info("[AllDebrid] Authorizing with key:", apiKey ? "***" : "empty");
this.instance = axios.create({ this.instance = axios.create({
baseURL: this.baseURL, baseURL: this.baseURL,
params: { params: {
agent: "hydra", agent: "hydra",
apikey: apiKey apikey: apiKey,
} },
}); });
} }
static async getUser() { static async getUser() {
try { try {
const response = await this.instance.get<{ const response = await this.instance.get<{
status: string; status: string;
data?: { user: AllDebridUser }; data?: { user: AllDebridUser };
error?: AllDebridError; error?: AllDebridError;
}>("/user"); }>("/user");
logger.info("[AllDebrid] API Response:", response.data); logger.info("[AllDebrid] API Response:", response.data);
if (response.data.status === "error") { if (response.data.status === "error") {
const error = response.data.error; const error = response.data.error;
logger.error("[AllDebrid] API Error:", error); logger.error("[AllDebrid] API Error:", error);
if (error?.code === "AUTH_MISSING_APIKEY") { if (error?.code === "AUTH_MISSING_APIKEY") {
return { error_code: "alldebrid_missing_key" }; return { error_code: "alldebrid_missing_key" };
}
if (error?.code === "AUTH_BAD_APIKEY") {
return { error_code: "alldebrid_invalid_key" };
}
if (error?.code === "AUTH_BLOCKED") {
return { error_code: "alldebrid_blocked" };
}
if (error?.code === "AUTH_USER_BANNED") {
return { error_code: "alldebrid_banned" };
}
return { error_code: "alldebrid_unknown_error" };
}
if (!response.data.data?.user) {
logger.error("[AllDebrid] No user data in response");
return { error_code: "alldebrid_invalid_response" };
}
logger.info("[AllDebrid] Successfully got user:", response.data.data.user.username);
return { user: response.data.data.user };
} catch (error: any) {
logger.error("[AllDebrid] Request Error:", error);
if (error.response?.data?.error) {
return { error_code: "alldebrid_invalid_key" };
}
return { error_code: "alldebrid_network_error" };
} }
} if (error?.code === "AUTH_BAD_APIKEY") {
return { error_code: "alldebrid_invalid_key" };
private static async uploadMagnet(magnet: string) {
try {
logger.info("[AllDebrid] Uploading magnet with params:", { magnet });
const response = await this.instance.get("/magnet/upload", {
params: {
magnets: [magnet]
}
});
logger.info("[AllDebrid] Upload Magnet Raw Response:", JSON.stringify(response.data, null, 2));
if (response.data.status === "error") {
throw new Error(response.data.error?.message || "Unknown error");
}
const magnetInfo = response.data.data.magnets[0];
logger.info("[AllDebrid] Magnet Info:", JSON.stringify(magnetInfo, null, 2));
if (magnetInfo.error) {
throw new Error(magnetInfo.error.message);
}
return magnetInfo.id;
} catch (error: any) {
logger.error("[AllDebrid] Upload Magnet Error:", error);
throw error;
} }
if (error?.code === "AUTH_BLOCKED") {
return { error_code: "alldebrid_blocked" };
}
if (error?.code === "AUTH_USER_BANNED") {
return { error_code: "alldebrid_banned" };
}
return { error_code: "alldebrid_unknown_error" };
}
if (!response.data.data?.user) {
logger.error("[AllDebrid] No user data in response");
return { error_code: "alldebrid_invalid_response" };
}
logger.info(
"[AllDebrid] Successfully got user:",
response.data.data.user.username
);
return { user: response.data.data.user };
} catch (error: any) {
logger.error("[AllDebrid] Request Error:", error);
if (error.response?.data?.error) {
return { error_code: "alldebrid_invalid_key" };
}
return { error_code: "alldebrid_network_error" };
} }
}
private static async checkMagnetStatus(magnetId: number): Promise<AllDebridMagnetStatus> { private static async uploadMagnet(magnet: string) {
try { try {
logger.info("[AllDebrid] Checking magnet status for ID:", magnetId); logger.info("[AllDebrid] Uploading magnet with params:", { magnet });
const response = await this.instance.get(`/magnet/status`, {
params: {
id: magnetId
}
});
logger.info("[AllDebrid] Check Magnet Status Raw Response:", JSON.stringify(response.data, null, 2)); const response = await this.instance.get("/magnet/upload", {
params: {
magnets: [magnet],
},
});
if (!response.data) { logger.info(
throw new Error("No response data received"); "[AllDebrid] Upload Magnet Raw Response:",
} JSON.stringify(response.data, null, 2)
);
if (response.data.status === "error") { if (response.data.status === "error") {
throw new Error(response.data.error?.message || "Unknown error"); throw new Error(response.data.error?.message || "Unknown error");
} }
// Verificăm noua structură a răspunsului const magnetInfo = response.data.data.magnets[0];
const magnetData = response.data.data?.magnets; logger.info(
if (!magnetData || typeof magnetData !== 'object') { "[AllDebrid] Magnet Info:",
logger.error("[AllDebrid] Invalid response structure:", JSON.stringify(response.data, null, 2)); JSON.stringify(magnetInfo, null, 2)
throw new Error("Invalid magnet status response format"); );
}
// Convertim răspunsul în formatul așteptat if (magnetInfo.error) {
const magnetStatus: AllDebridMagnetStatus = { throw new Error(magnetInfo.error.message);
id: magnetData.id, }
filename: magnetData.filename,
size: magnetData.size, return magnetInfo.id;
status: magnetData.status, } catch (error: any) {
statusCode: magnetData.statusCode, logger.error("[AllDebrid] Upload Magnet Error:", error);
downloaded: magnetData.downloaded, throw error;
uploaded: magnetData.uploaded, }
seeders: magnetData.seeders, }
downloadSpeed: magnetData.downloadSpeed,
uploadSpeed: magnetData.uploadSpeed, private static async checkMagnetStatus(
uploadDate: magnetData.uploadDate, magnetId: number
completionDate: magnetData.completionDate, ): Promise<AllDebridMagnetStatus> {
links: magnetData.links.map(link => ({ try {
link: link.link, logger.info("[AllDebrid] Checking magnet status for ID:", magnetId);
const response = await this.instance.get(`/magnet/status`, {
params: {
id: magnetId,
},
});
logger.info(
"[AllDebrid] Check Magnet Status Raw Response:",
JSON.stringify(response.data, null, 2)
);
if (!response.data) {
throw new Error("No response data received");
}
if (response.data.status === "error") {
throw new Error(response.data.error?.message || "Unknown error");
}
// Verificăm noua structură a răspunsului
const magnetData = response.data.data?.magnets;
if (!magnetData || typeof magnetData !== "object") {
logger.error(
"[AllDebrid] Invalid response structure:",
JSON.stringify(response.data, null, 2)
);
throw new Error("Invalid magnet status response format");
}
// Convertim răspunsul în formatul așteptat
const magnetStatus: AllDebridMagnetStatus = {
id: magnetData.id,
filename: magnetData.filename,
size: magnetData.size,
status: magnetData.status,
statusCode: magnetData.statusCode,
downloaded: magnetData.downloaded,
uploaded: magnetData.uploaded,
seeders: magnetData.seeders,
downloadSpeed: magnetData.downloadSpeed,
uploadSpeed: magnetData.uploadSpeed,
uploadDate: magnetData.uploadDate,
completionDate: magnetData.completionDate,
links: magnetData.links.map((link) => ({
link: link.link,
filename: link.filename,
size: link.size,
})),
};
logger.info(
"[AllDebrid] Magnet Status:",
JSON.stringify(magnetStatus, null, 2)
);
return magnetStatus;
} catch (error: any) {
logger.error("[AllDebrid] Check Magnet Status Error:", error);
throw error;
}
}
private static async unlockLink(link: string) {
try {
const response = await this.instance.get<{
status: string;
data?: { link: string };
error?: AllDebridError;
}>("/link/unlock", {
params: {
link,
},
});
if (response.data.status === "error") {
throw new Error(response.data.error?.message || "Unknown error");
}
const unlockedLink = response.data.data?.link;
if (!unlockedLink) {
throw new Error("No download link received from AllDebrid");
}
return unlockedLink;
} catch (error: any) {
logger.error("[AllDebrid] Unlock Link Error:", error);
throw error;
}
}
public static async getDownloadUrls(
uri: string
): Promise<AllDebridDownloadUrl[]> {
try {
logger.info("[AllDebrid] Getting download URLs for URI:", uri);
if (uri.startsWith("magnet:")) {
logger.info("[AllDebrid] Detected magnet link, uploading...");
// 1. Upload magnet
const magnetId = await this.uploadMagnet(uri);
logger.info("[AllDebrid] Magnet uploaded, ID:", magnetId);
// 2. Verificăm statusul până când avem link-uri
let retries = 0;
let magnetStatus: AllDebridMagnetStatus;
do {
magnetStatus = await this.checkMagnetStatus(magnetId);
logger.info(
"[AllDebrid] Magnet status:",
magnetStatus.status,
"statusCode:",
magnetStatus.statusCode
);
if (magnetStatus.statusCode === 4) {
// Ready
// Deblocăm fiecare link în parte și aruncăm eroare dacă oricare eșuează
const unlockedLinks = await Promise.all(
magnetStatus.links.map(async (link) => {
try {
const unlockedLink = await this.unlockLink(link.link);
logger.info(
"[AllDebrid] Successfully unlocked link:",
unlockedLink
);
return {
link: unlockedLink,
size: link.size,
filename: link.filename, filename: link.filename,
size: link.size };
})) } catch (error) {
}; logger.error(
"[AllDebrid] Failed to unlock link:",
logger.info("[AllDebrid] Magnet Status:", JSON.stringify(magnetStatus, null, 2)); link.link,
error
return magnetStatus; );
} catch (error: any) { throw new Error("Failed to unlock all links");
logger.error("[AllDebrid] Check Magnet Status Error:", error);
throw error;
}
}
private static async unlockLink(link: string) {
try {
const response = await this.instance.get<{
status: string;
data?: { link: string };
error?: AllDebridError;
}>("/link/unlock", {
params: {
link
} }
}); })
);
if (response.data.status === "error") { logger.info(
throw new Error(response.data.error?.message || "Unknown error"); "[AllDebrid] Got unlocked download links:",
} unlockedLinks
);
return unlockedLinks;
}
const unlockedLink = response.data.data?.link; if (retries++ > 30) {
if (!unlockedLink) { // Maximum 30 de încercări
throw new Error("No download link received from AllDebrid"); throw new Error("Timeout waiting for magnet to be ready");
} }
return unlockedLink; await new Promise((resolve) => setTimeout(resolve, 2000)); // Așteptăm 2 secunde între verificări
} catch (error: any) { } while (true);
logger.error("[AllDebrid] Unlock Link Error:", error); } else {
throw error; logger.info("[AllDebrid] Regular link, unlocking...");
} // Pentru link-uri normale, doar debridam link-ul
} const downloadUrl = await this.unlockLink(uri);
logger.info("[AllDebrid] Got unlocked download URL:", downloadUrl);
public static async getDownloadUrls(uri: string): Promise<AllDebridDownloadUrl[]> { return [
try { {
logger.info("[AllDebrid] Getting download URLs for URI:", uri); link: downloadUrl,
},
if (uri.startsWith("magnet:")) { ];
logger.info("[AllDebrid] Detected magnet link, uploading..."); }
// 1. Upload magnet } catch (error: any) {
const magnetId = await this.uploadMagnet(uri); logger.error("[AllDebrid] Get Download URLs Error:", error);
logger.info("[AllDebrid] Magnet uploaded, ID:", magnetId); throw error;
// 2. Verificăm statusul până când avem link-uri
let retries = 0;
let magnetStatus: AllDebridMagnetStatus;
do {
magnetStatus = await this.checkMagnetStatus(magnetId);
logger.info("[AllDebrid] Magnet status:", magnetStatus.status, "statusCode:", magnetStatus.statusCode);
if (magnetStatus.statusCode === 4) { // Ready
// Deblocăm fiecare link în parte și aruncăm eroare dacă oricare eșuează
const unlockedLinks = await Promise.all(
magnetStatus.links.map(async link => {
try {
const unlockedLink = await this.unlockLink(link.link);
logger.info("[AllDebrid] Successfully unlocked link:", unlockedLink);
return {
link: unlockedLink,
size: link.size,
filename: link.filename
};
} catch (error) {
logger.error("[AllDebrid] Failed to unlock link:", link.link, error);
throw new Error("Failed to unlock all links");
}
})
);
logger.info("[AllDebrid] Got unlocked download links:", unlockedLinks);
return unlockedLinks;
}
if (retries++ > 30) { // Maximum 30 de încercări
throw new Error("Timeout waiting for magnet to be ready");
}
await new Promise(resolve => setTimeout(resolve, 2000)); // Așteptăm 2 secunde între verificări
} while (true);
} else {
logger.info("[AllDebrid] Regular link, unlocking...");
// Pentru link-uri normale, doar debridam link-ul
const downloadUrl = await this.unlockLink(uri);
logger.info("[AllDebrid] Got unlocked download URL:", downloadUrl);
return [{
link: downloadUrl
}];
}
} catch (error: any) {
logger.error("[AllDebrid] Get Download URLs Error:", error);
throw error;
}
} }
}
} }

View file

@ -17,17 +17,6 @@ import { db, downloadsSublevel, gamesSublevel, levelKeys } from "@main/level";
import { sortBy } from "lodash-es"; import { sortBy } from "lodash-es";
import { TorBoxClient } from "./torbox"; import { TorBoxClient } from "./torbox";
import { AllDebridClient } from "./all-debrid"; import { AllDebridClient } from "./all-debrid";
import { spawn } from "child_process";
interface GamePayload {
action: string;
game_id: string;
url: string | string[];
save_path: string;
header?: string;
out?: string;
total_size?: number;
}
export class DownloadManager { export class DownloadManager {
private static downloadingGameId: string | null = null; private static downloadingGameId: string | null = null;
@ -44,6 +33,7 @@ export class DownloadManager {
}) })
: undefined, : undefined,
downloadsToSeed?.map((download) => ({ downloadsToSeed?.map((download) => ({
action: "seed",
game_id: levelKeys.game(download.shop, download.objectId), game_id: levelKeys.game(download.shop, download.objectId),
url: download.uri, url: download.uri,
save_path: download.downloadPath, save_path: download.downloadPath,
@ -145,15 +135,45 @@ export class DownloadManager {
if (progress === 1 && download) { if (progress === 1 && download) {
publishDownloadCompleteNotification(game); publishDownloadCompleteNotification(game);
await downloadsSublevel.put(gameId, { if (
...download, userPreferences?.seedAfterDownloadComplete &&
status: "complete", download.downloader === Downloader.Torrent
shouldSeed: false, ) {
queued: false, downloadsSublevel.put(gameId, {
}); ...download,
status: "seeding",
shouldSeed: true,
queued: false,
});
} else {
downloadsSublevel.put(gameId, {
...download,
status: "complete",
shouldSeed: false,
queued: false,
});
await this.cancelDownload(gameId); this.cancelDownload(gameId);
this.downloadingGameId = null; }
const downloads = await downloadsSublevel
.values()
.all()
.then((games) => {
return sortBy(
games.filter((game) => game.status === "paused" && game.queued),
"timestamp",
"DESC"
);
});
const [nextItemOnQueue] = downloads;
if (nextItemOnQueue) {
this.resumeDownload(nextItemOnQueue);
} else {
this.downloadingGameId = null;
}
} }
} }
} }
@ -296,6 +316,21 @@ export class DownloadManager {
save_path: download.downloadPath, save_path: download.downloadPath,
}; };
} }
case Downloader.AllDebrid: {
const downloadUrls = await AllDebridClient.getDownloadUrls(download.uri);
if (!downloadUrls.length) throw new Error(DownloadError.NotCachedInAllDebrid);
const totalSize = downloadUrls.reduce((total, url) => total + (url.size || 0), 0);
return {
action: "start",
game_id: downloadId,
url: downloadUrls.map(d => d.link),
save_path: download.downloadPath,
total_size: totalSize
};
}
case Downloader.Torrent: case Downloader.Torrent:
return { return {
action: "start", action: "start",
@ -315,21 +350,6 @@ export class DownloadManager {
save_path: download.downloadPath, save_path: download.downloadPath,
}; };
} }
case Downloader.AllDebrid: {
const downloadUrls = await AllDebridClient.getDownloadUrls(download.uri);
if (!downloadUrls.length) throw new Error(DownloadError.NotCachedInAllDebrid);
const totalSize = downloadUrls.reduce((total, url) => total + (url.size || 0), 0);
return {
action: "start",
game_id: downloadId,
url: downloadUrls.map(d => d.link),
save_path: download.downloadPath,
total_size: totalSize
};
}
case Downloader.TorBox: { case Downloader.TorBox: {
const { name, url } = await TorBoxClient.getDownloadInfo(download.uri); const { name, url } = await TorBoxClient.getDownloadInfo(download.uri);
@ -350,4 +370,4 @@ export class DownloadManager {
await PythonRPC.rpc.post("/action", payload); await PythonRPC.rpc.post("/action", payload);
this.downloadingGameId = levelKeys.game(download.shop, download.objectId); this.downloadingGameId = levelKeys.game(download.shop, download.objectId);
} }
} }

View file

@ -19,7 +19,7 @@ export const calculateETA = (
export const getDirSize = async (dir: string): Promise<number> => { export const getDirSize = async (dir: string): Promise<number> => {
try { try {
const stat = await fs.promises.stat(dir); const stat = await fs.promises.stat(dir);
// If it's a file, return its size directly // If it's a file, return its size directly
if (!stat.isDirectory()) { if (!stat.isDirectory()) {
return stat.size; return stat.size;

View file

@ -10,9 +10,13 @@ import { Readable } from "node:stream";
import { app, dialog } from "electron"; import { app, dialog } from "electron";
interface GamePayload { interface GamePayload {
action: string;
game_id: string; game_id: string;
url: string; url: string | string[];
save_path: string; save_path: string;
header?: string;
out?: string;
total_size?: number;
} }
const binaryNameByPlatform: Partial<Record<NodeJS.Platform, string>> = { const binaryNameByPlatform: Partial<Record<NodeJS.Platform, string>> = {
@ -105,4 +109,4 @@ export class PythonRPC {
this.pythonProcess = null; this.pythonProcess = null;
} }
} }
} }

View file

@ -29,6 +29,7 @@ import type {
LibraryGame, LibraryGame,
GameRunning, GameRunning,
TorBoxUser, TorBoxUser,
AllDebridUser,
} from "@types"; } from "@types";
import type { AxiosProgressEvent } from "axios"; import type { AxiosProgressEvent } from "axios";
import type disk from "diskusage"; import type disk from "diskusage";
@ -150,6 +151,7 @@ declare global {
minimized: boolean; minimized: boolean;
}) => Promise<void>; }) => Promise<void>;
authenticateRealDebrid: (apiToken: string) => Promise<RealDebridUser>; authenticateRealDebrid: (apiToken: string) => Promise<RealDebridUser>;
authenticateAllDebrid: (apiKey: string) => Promise<AllDebridUser | { error_code: string }>;
authenticateTorBox: (apiToken: string) => Promise<TorBoxUser>; authenticateTorBox: (apiToken: string) => Promise<TorBoxUser>;
onAchievementUnlocked: (cb: () => void) => () => Electron.IpcRenderer; onAchievementUnlocked: (cb: () => void) => () => Electron.IpcRenderer;

View file

@ -196,7 +196,6 @@ export function HeroPanelActions() {
{game.favorite ? <HeartFillIcon /> : <HeartIcon />} {game.favorite ? <HeartFillIcon /> : <HeartIcon />}
</Button> </Button>
<Button <Button
onClick={() => setShowGameOptionsModal(true)} onClick={() => setShowGameOptionsModal(true)}
theme="outline" theme="outline"

View file

@ -49,4 +49,4 @@
&__change-path-button { &__change-path-button {
align-self: flex-end; align-self: flex-end;
} }
} }

View file

@ -176,7 +176,6 @@ export function DownloadSettingsModal({
} }
onClick={() => setSelectedDownloader(downloader)} onClick={() => setSelectedDownloader(downloader)}
> >
{selectedDownloader === downloader && ( {selectedDownloader === downloader && (
<CheckCircleFillIcon className="download-settings-modal__downloader-icon" /> <CheckCircleFillIcon className="download-settings-modal__downloader-icon" />
)} )}

View file

@ -9,4 +9,4 @@
margin: 0; margin: 0;
color: var(--text-secondary); color: var(--text-secondary);
} }
} }

View file

@ -53,7 +53,7 @@ export function SettingsAllDebrid() {
form.allDebridApiKey form.allDebridApiKey
); );
if ('error_code' in result) { if ("error_code" in result) {
showErrorToast(t(result.error_code)); showErrorToast(t(result.error_code));
return; return;
} }
@ -126,4 +126,4 @@ export function SettingsAllDebrid() {
)} )}
</form> </form>
); );
} }

View file

@ -95,7 +95,12 @@ export const getDownloadersForUri = (uri: string) => {
return [Downloader.RealDebrid]; return [Downloader.RealDebrid];
if (uri.startsWith("magnet:")) { if (uri.startsWith("magnet:")) {
return [Downloader.Torrent, Downloader.TorBox, Downloader.RealDebrid, Downloader.AllDebrid]; return [
Downloader.Torrent,
Downloader.TorBox,
Downloader.RealDebrid,
Downloader.AllDebrid,
];
} }
return []; return [];

View file

@ -181,4 +181,4 @@ export interface AllDebridUser {
email: string; email: string;
isPremium: boolean; isPremium: boolean;
premiumUntil: string; premiumUntil: string;
} }