Stream filename scan updates and add provider fallback in v1.3.5
This commit is contained in:
parent
973885a147
commit
0de5a59a64
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.3.4",
|
"version": "1.3.5",
|
||||||
"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",
|
||||||
|
|||||||
@ -551,21 +551,34 @@ export class DebridService {
|
|||||||
this.allDebridClient = new AllDebridClient(next.allDebridToken);
|
this.allDebridClient = new AllDebridClient(next.allDebridToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async resolveFilenames(links: string[]): Promise<Map<string, string>> {
|
public async resolveFilenames(
|
||||||
|
links: string[],
|
||||||
|
onResolved?: (link: string, fileName: string) => void
|
||||||
|
): Promise<Map<string, string>> {
|
||||||
const unresolved = links.filter((link) => looksLikeOpaqueFilename(filenameFromUrl(link)));
|
const unresolved = links.filter((link) => looksLikeOpaqueFilename(filenameFromUrl(link)));
|
||||||
if (unresolved.length === 0) {
|
if (unresolved.length === 0) {
|
||||||
return new Map<string, string>();
|
return new Map<string, string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
const clean = new Map<string, string>();
|
const clean = new Map<string, string>();
|
||||||
|
const reportResolved = (link: string, fileName: string): void => {
|
||||||
|
const normalized = fileName.trim();
|
||||||
|
if (!normalized || looksLikeOpaqueFilename(normalized) || normalized.toLowerCase() === "download.bin") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (clean.get(link) === normalized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clean.set(link, normalized);
|
||||||
|
onResolved?.(link, normalized);
|
||||||
|
};
|
||||||
|
|
||||||
const token = this.settings.allDebridToken.trim();
|
const token = this.settings.allDebridToken.trim();
|
||||||
if (token) {
|
if (token) {
|
||||||
try {
|
try {
|
||||||
const infos = await this.allDebridClient.getLinkInfos(unresolved);
|
const infos = await this.allDebridClient.getLinkInfos(unresolved);
|
||||||
for (const [link, fileName] of infos.entries()) {
|
for (const [link, fileName] of infos.entries()) {
|
||||||
if (fileName.trim() && !looksLikeOpaqueFilename(fileName.trim())) {
|
reportResolved(link, fileName);
|
||||||
clean.set(link, fileName.trim());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// ignore and continue with host page fallback
|
// ignore and continue with host page fallback
|
||||||
@ -573,10 +586,18 @@ export class DebridService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const remaining = unresolved.filter((link) => !clean.has(link) && isRapidgatorLink(link));
|
const remaining = unresolved.filter((link) => !clean.has(link) && isRapidgatorLink(link));
|
||||||
await runWithConcurrency(remaining, 3, async (link) => {
|
await runWithConcurrency(remaining, 6, async (link) => {
|
||||||
const fromPage = await resolveRapidgatorFilename(link);
|
const fromPage = await resolveRapidgatorFilename(link);
|
||||||
if (fromPage && !looksLikeOpaqueFilename(fromPage)) {
|
reportResolved(link, fromPage);
|
||||||
clean.set(link, fromPage);
|
});
|
||||||
|
|
||||||
|
const stillUnresolved = unresolved.filter((link) => !clean.has(link));
|
||||||
|
await runWithConcurrency(stillUnresolved, 4, async (link) => {
|
||||||
|
try {
|
||||||
|
const unrestricted = await this.unrestrictLink(link);
|
||||||
|
reportResolved(link, unrestricted.fileName || "");
|
||||||
|
} catch {
|
||||||
|
// ignore final fallback errors
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -497,22 +497,21 @@ export class DownloadManager extends EventEmitter {
|
|||||||
|
|
||||||
private async resolveQueuedFilenames(unresolvedByLink: Map<string, string[]>): Promise<void> {
|
private async resolveQueuedFilenames(unresolvedByLink: Map<string, string[]>): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const resolved = await this.debridService.resolveFilenames(Array.from(unresolvedByLink.keys()));
|
let changed = false;
|
||||||
if (resolved.size === 0) {
|
const applyResolvedName = (link: string, fileName: string): void => {
|
||||||
|
const itemIds = unresolvedByLink.get(link);
|
||||||
|
if (!itemIds || itemIds.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let changed = false;
|
|
||||||
for (const [link, itemIds] of unresolvedByLink.entries()) {
|
|
||||||
const fileName = resolved.get(link);
|
|
||||||
if (!fileName || fileName.toLowerCase() === "download.bin") {
|
if (!fileName || fileName.toLowerCase() === "download.bin") {
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
const normalized = sanitizeFilename(fileName);
|
const normalized = sanitizeFilename(fileName);
|
||||||
if (!normalized || normalized.toLowerCase() === "download.bin") {
|
if (!normalized || normalized.toLowerCase() === "download.bin") {
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let changedForLink = false;
|
||||||
for (const itemId of itemIds) {
|
for (const itemId of itemIds) {
|
||||||
const item = this.session.items[itemId];
|
const item = this.session.items[itemId];
|
||||||
if (!item) {
|
if (!item) {
|
||||||
@ -528,8 +527,16 @@ export class DownloadManager extends EventEmitter {
|
|||||||
item.targetPath = path.join(this.session.packages[item.packageId]?.outputDir || this.settings.outputDir, normalized);
|
item.targetPath = path.join(this.session.packages[item.packageId]?.outputDir || this.settings.outputDir, normalized);
|
||||||
item.updatedAt = nowMs();
|
item.updatedAt = nowMs();
|
||||||
changed = true;
|
changed = true;
|
||||||
|
changedForLink = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (changedForLink) {
|
||||||
|
this.persistSoon();
|
||||||
|
this.emitState();
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.debridService.resolveFilenames(Array.from(unresolvedByLink.keys()), applyResolvedName);
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
this.persistSoon();
|
this.persistSoon();
|
||||||
|
|||||||
@ -310,4 +310,61 @@ describe("debrid service", () => {
|
|||||||
const resolved = await service.resolveFilenames([link]);
|
const resolved = await service.resolveFilenames([link]);
|
||||||
expect(resolved.get(link)).toBe("Bulletproof.S01E01.German.DL.DD20.Synced.720p.AmazonHD.h264-GDR.part01.rar");
|
expect(resolved.get(link)).toBe("Bulletproof.S01E01.German.DL.DD20.Synced.720p.AmazonHD.h264-GDR.part01.rar");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("falls back to provider unrestrict for unresolved filename scan", async () => {
|
||||||
|
const settings = {
|
||||||
|
...defaultSettings(),
|
||||||
|
token: "rd-token",
|
||||||
|
providerPrimary: "realdebrid" as const,
|
||||||
|
providerSecondary: "none" as const,
|
||||||
|
providerTertiary: "none" as const,
|
||||||
|
autoProviderFallback: true,
|
||||||
|
allDebridToken: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
const linkFromPage = "https://rapidgator.net/file/11111111111111111111111111111111";
|
||||||
|
const linkFromProvider = "https://hoster.example/file/22222222222222222222222222222222";
|
||||||
|
|
||||||
|
globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
||||||
|
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
||||||
|
|
||||||
|
if (url === linkFromPage) {
|
||||||
|
return new Response("<html><head><title>Download file from-page.part1.rar</title></head></html>", {
|
||||||
|
status: 200,
|
||||||
|
headers: { "Content-Type": "text/html" }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.includes("api.real-debrid.com/rest/1.0/unrestrict/link")) {
|
||||||
|
const body = init?.body;
|
||||||
|
const bodyText = body instanceof URLSearchParams ? body.toString() : String(body || "");
|
||||||
|
const linkValue = new URLSearchParams(bodyText).get("link") || "";
|
||||||
|
if (linkValue === linkFromProvider) {
|
||||||
|
return new Response(JSON.stringify({
|
||||||
|
download: "https://cdn.example/from-provider",
|
||||||
|
filename: "from-provider.part2.rar",
|
||||||
|
filesize: 1024
|
||||||
|
}), {
|
||||||
|
status: 200,
|
||||||
|
headers: { "Content-Type": "application/json" }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response("not-found", { status: 404 });
|
||||||
|
}) as typeof fetch;
|
||||||
|
|
||||||
|
const service = new DebridService(settings);
|
||||||
|
const events: Array<{ link: string; fileName: string }> = [];
|
||||||
|
const resolved = await service.resolveFilenames([linkFromPage, linkFromProvider], (link, fileName) => {
|
||||||
|
events.push({ link, fileName });
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(resolved.get(linkFromPage)).toBe("from-page.part1.rar");
|
||||||
|
expect(resolved.get(linkFromProvider)).toBe("from-provider.part2.rar");
|
||||||
|
expect(events).toEqual(expect.arrayContaining([
|
||||||
|
{ link: linkFromPage, fileName: "from-page.part1.rar" },
|
||||||
|
{ link: linkFromProvider, fileName: "from-provider.part2.rar" }
|
||||||
|
]));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user