Add Mega-Debrid unrestrict workarounds and bump to 1.1.18
Some checks are pending
Build and Release / build (push) Waiting to run
Some checks are pending
Build and Release / build (push) Waiting to run
This commit is contained in:
parent
7ac61ce64a
commit
704826b421
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.1.17",
|
"version": "1.1.18",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.1.17",
|
"version": "1.1.18",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"adm-zip": "^0.5.16",
|
"adm-zip": "^0.5.16",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.1.17",
|
"version": "1.1.18",
|
||||||
"description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)",
|
"description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)",
|
||||||
"main": "build/main/main/main.js",
|
"main": "build/main/main/main.js",
|
||||||
"author": "Sucukdeluxe",
|
"author": "Sucukdeluxe",
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import os from "node:os";
|
|||||||
import { AppSettings } from "../shared/types";
|
import { AppSettings } from "../shared/types";
|
||||||
|
|
||||||
export const APP_NAME = "Debrid Download Manager";
|
export const APP_NAME = "Debrid Download Manager";
|
||||||
export const APP_VERSION = "1.1.17";
|
export const APP_VERSION = "1.1.18";
|
||||||
export const API_BASE_URL = "https://api.real-debrid.com/rest/1.0";
|
export const API_BASE_URL = "https://api.real-debrid.com/rest/1.0";
|
||||||
|
|
||||||
export const DCRYPT_UPLOAD_URL = "https://dcrypt.it/decrypt/upload";
|
export const DCRYPT_UPLOAD_URL = "https://dcrypt.it/decrypt/upload";
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { AppSettings, DebridProvider } from "../shared/types";
|
import { AppSettings, DebridProvider } from "../shared/types";
|
||||||
|
import { createHash } from "node:crypto";
|
||||||
import { REQUEST_RETRIES } from "./constants";
|
import { REQUEST_RETRIES } from "./constants";
|
||||||
import { RealDebridClient, UnrestrictedLink } from "./realdebrid";
|
import { RealDebridClient, UnrestrictedLink } from "./realdebrid";
|
||||||
import { compactErrorText, filenameFromUrl, looksLikeOpaqueFilename, sleep } from "./utils";
|
import { compactErrorText, filenameFromUrl, looksLikeOpaqueFilename, sleep } from "./utils";
|
||||||
@ -194,16 +195,52 @@ class MegaDebridClient {
|
|||||||
this.token = token;
|
this.token = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async unrestrictLink(link: string): Promise<UnrestrictedLink> {
|
private normalizeMegaCandidates(link: string): string[] {
|
||||||
let lastError = "";
|
const result = new Set<string>();
|
||||||
for (let attempt = 1; attempt <= REQUEST_RETRIES; attempt += 1) {
|
const trimmed = link.trim();
|
||||||
|
if (trimmed) {
|
||||||
|
result.add(trimmed);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const body = new URLSearchParams({ link });
|
const parsed = new URL(trimmed);
|
||||||
const response = await fetch(`${MEGA_DEBRID_API}?action=getLink&token=${encodeURIComponent(this.token)}`, {
|
const host = parsed.hostname.toLowerCase();
|
||||||
|
if (host.includes("rapidgator.net")) {
|
||||||
|
const parts = parsed.pathname.split("/").filter(Boolean);
|
||||||
|
const fileIdx = parts.findIndex((part) => part.toLowerCase() === "file");
|
||||||
|
if (fileIdx >= 0 && parts[fileIdx + 1]) {
|
||||||
|
const hash = parts[fileIdx + 1];
|
||||||
|
result.add(`https://rapidgator.net/file/${hash}`);
|
||||||
|
result.add(`http://rapidgator.net/file/${hash}`);
|
||||||
|
if (parts[fileIdx + 2]) {
|
||||||
|
const name = parts[fileIdx + 2].replace(/\.html$/i, "");
|
||||||
|
result.add(`https://rapidgator.net/file/${hash}/${name}.html`);
|
||||||
|
result.add(`http://rapidgator.net/file/${hash}/${name}.html`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore malformed URL
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...result];
|
||||||
|
}
|
||||||
|
|
||||||
|
private async requestMega(link: string, includePasswordField: boolean, useGetLinkParam: boolean): Promise<UnrestrictedLink> {
|
||||||
|
const url = `${MEGA_DEBRID_API}?action=getLink&token=${encodeURIComponent(this.token)}${useGetLinkParam ? `&link=${encodeURIComponent(link)}` : ""}`;
|
||||||
|
const body = new URLSearchParams();
|
||||||
|
if (!useGetLinkParam) {
|
||||||
|
body.set("link", link);
|
||||||
|
}
|
||||||
|
if (includePasswordField) {
|
||||||
|
body.set("password", createHash("md5").update("").digest("hex"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
"User-Agent": "RD-Node-Downloader/1.1.12"
|
"User-Agent": "RD-Node-Downloader/1.1.17"
|
||||||
},
|
},
|
||||||
body
|
body
|
||||||
});
|
});
|
||||||
@ -211,12 +248,7 @@ class MegaDebridClient {
|
|||||||
const payload = asRecord(parseJson(text));
|
const payload = asRecord(parseJson(text));
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const reason = parseError(response.status, text, payload);
|
throw new Error(parseError(response.status, text, payload));
|
||||||
if (shouldRetryStatus(response.status) && attempt < REQUEST_RETRIES) {
|
|
||||||
await sleep(retryDelay(attempt));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
throw new Error(reason);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const responseCode = pickString(payload, ["response_code"]);
|
const responseCode = pickString(payload, ["response_code"]);
|
||||||
@ -235,8 +267,37 @@ class MegaDebridClient {
|
|||||||
fileName,
|
fileName,
|
||||||
directUrl,
|
directUrl,
|
||||||
fileSize,
|
fileSize,
|
||||||
retriesUsed: attempt - 1
|
retriesUsed: 0
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async unrestrictLink(link: string): Promise<UnrestrictedLink> {
|
||||||
|
let lastError = "";
|
||||||
|
for (let attempt = 1; attempt <= REQUEST_RETRIES; attempt += 1) {
|
||||||
|
try {
|
||||||
|
const candidates = this.normalizeMegaCandidates(link);
|
||||||
|
const variants = [
|
||||||
|
{ includePasswordField: false, useGetLinkParam: false },
|
||||||
|
{ includePasswordField: true, useGetLinkParam: false },
|
||||||
|
{ includePasswordField: false, useGetLinkParam: true },
|
||||||
|
{ includePasswordField: true, useGetLinkParam: true }
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
for (const variant of variants) {
|
||||||
|
try {
|
||||||
|
const out = await this.requestMega(candidate, variant.includePasswordField, variant.useGetLinkParam);
|
||||||
|
out.retriesUsed = attempt - 1;
|
||||||
|
return out;
|
||||||
|
} catch (error) {
|
||||||
|
lastError = compactErrorText(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/token error|vip_end/i.test(lastError)) {
|
||||||
|
throw new Error(lastError);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
lastError = compactErrorText(error);
|
lastError = compactErrorText(error);
|
||||||
if (attempt >= REQUEST_RETRIES) {
|
if (attempt >= REQUEST_RETRIES) {
|
||||||
|
|||||||
@ -143,6 +143,45 @@ describe("debrid service", () => {
|
|||||||
expect(result.fileSize).toBe(4096);
|
expect(result.fileSize).toBe(4096);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("retries Mega-Debrid with alternate request variants", async () => {
|
||||||
|
const settings = {
|
||||||
|
...defaultSettings(),
|
||||||
|
token: "",
|
||||||
|
megaToken: "mega-token",
|
||||||
|
bestToken: "",
|
||||||
|
allDebridToken: "",
|
||||||
|
providerPrimary: "megadebrid" as const,
|
||||||
|
providerSecondary: "megadebrid" as const,
|
||||||
|
providerTertiary: "megadebrid" as const,
|
||||||
|
autoProviderFallback: true
|
||||||
|
};
|
||||||
|
|
||||||
|
let calls = 0;
|
||||||
|
globalThis.fetch = (async (input: RequestInfo | URL): Promise<Response> => {
|
||||||
|
calls += 1;
|
||||||
|
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
||||||
|
if (url.includes("mega-debrid.eu/api.php?action=getLink") && !url.includes("&link=")) {
|
||||||
|
return new Response(JSON.stringify({ response_code: "UNRESTRICTING_ERROR_1", response_text: "UNRESTRICTING_ERROR_1" }), {
|
||||||
|
status: 200,
|
||||||
|
headers: { "Content-Type": "application/json" }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (url.includes("mega-debrid.eu/api.php?action=getLink") && url.includes("&link=")) {
|
||||||
|
return new Response(JSON.stringify({ response_code: "ok", debridLink: "https://mega.example/file2.bin", filename: "file2.bin" }), {
|
||||||
|
status: 200,
|
||||||
|
headers: { "Content-Type": "application/json" }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return new Response("not-found", { status: 404 });
|
||||||
|
}) as typeof fetch;
|
||||||
|
|
||||||
|
const service = new DebridService(settings);
|
||||||
|
const result = await service.unrestrictLink("https://rapidgator.net/file/abc/name.part1.rar.html");
|
||||||
|
expect(result.provider).toBe("megadebrid");
|
||||||
|
expect(result.fileName).toBe("file2.bin");
|
||||||
|
expect(calls).toBeGreaterThan(1);
|
||||||
|
});
|
||||||
|
|
||||||
it("respects provider selection and does not append hidden fallback providers", async () => {
|
it("respects provider selection and does not append hidden fallback providers", async () => {
|
||||||
const settings = {
|
const settings = {
|
||||||
...defaultSettings(),
|
...defaultSettings(),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user