feat: add bzzhr downloader - #2699
Conversation
Greptile SummaryThe PR adds Bzzhr as a supported direct-download hoster, including URI detection, link resolution with SteamRip headers, and integration with both download paths and user interfaces.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| src/main/services/hosters/bzzhr.ts | Adds token extraction and persistent, ID-correlated redirect handling; the previously reported cross-game listener race is no longer present. |
| src/main/services/download/download-manager.ts | Integrates Bzzhr URL resolution into the JavaScript downloader options and download payload paths. |
| src/shared/index.ts | Routes supported Bzzhr share and direct-link domains to the new downloader. |
| src/shared/constants.ts | Adds the Bzzhr downloader enum value consumed across the application. |
Sequence Diagram
sequenceDiagram
participant DM as Download Manager
participant B as BzzhrApi
participant Page as bzzhr.to
participant CDN as ts.bzzhr.to/io
DM->>B: getDownloadUrl(uri)
B->>Page: GET /id with SteamRip Referer
Page-->>B: tokenized download path
B->>Page: GET token path
Page-->>CDN: redirect
B-->>DM: direct CDN URL
Reviews (2): Last reviewed commit: "fix: String.raw in bzzhr" | Re-trigger Greptile
|
Moyasee
left a comment
There was a problem hiding this comment.
The wiring is complete: enum value, both DOWNLOADER_NAME maps, getDownloadersForUri and both download-manager switches cover exactly the same set of places the existing hosters register, so nothing is missing there.
One blocker in the resolver, inline on the request that discovers the redirect target. It follows the redirect and streams the whole payload into a discarded buffer, so every download pulls the file twice over the wire. The fix also lets most of this file go away.
Second, smaller one on the token path. Once both are addressed I'm happy to merge this.
|
|
||
| this.upsertPending(id, { resolve, reject, timer }); | ||
|
|
||
| const req = net.request(`${this.BZZHR_BASE_URL}${tokenPath}`); |
There was a problem hiding this comment.
This request follows the redirect it is trying to observe. net.request defaults to redirect: "follow", the listener resolves the promise as soon as the redirect fires, and nothing aborts the request afterwards, so the response body keeps streaming into res.on("data", () => {}) until the file is fully transferred. Then the download manager fetches the same file again for the real download.
Repro with a local server that 302s to a 50 MB body, first shape mirrors this code, second uses manual redirect plus abort:
{"mode":"webrequest","resolvedUrl":"http://127.0.0.1:49635/d/file.bin","resolvedAfterMs":30,"mbServedAtResolve":0,"mbServedAfter3s":50,"mbOffered":50}
{"mode":"manual","resolvedUrl":"http://127.0.0.1:49639/d/file.bin","resolvedAfterMs":12,"mbServedAtResolve":0,"mbServedAfter3s":0,"mbOffered":50}
Both resolve the same URL in about the same time. The first one goes on to transfer all 50 MB for nothing.
const req = net.request({
url: `${this.BZZHR_BASE_URL}${tokenPath}`,
redirect: "manual",
});
req.on("redirect", (_status, _method, redirectUrl) => {
req.abort();
resolve(redirectUrl);
});Headers stay as they are. Two other things fall out of this once the redirect is read from the request itself:
session.defaultSession.webRequest.onBeforeRedirect only keeps one listener per session, last writer wins. A hoster module claiming it globally means whoever registers that handler next silently takes the resolver offline, and the failure mode is a 30 second hang followed by an expired-link error.
The pending map is keyed by the short id from the original URL, but the lookup uses the first path segment of the token path. Those are only the same string as long as the site keeps them equal, and if they ever diverge every resolution times out. Reading the redirect off the request drops the correlation problem entirely, along with the listener, the map, failPending, upsertPending, removePending and extractIdFromRequest.
| ); | ||
| } | ||
|
|
||
| return match[1].replaceAll(String.raw`\/`, "/"); |
There was a problem hiding this comment.
The caller concatenates this onto the base URL, but COPY_DOWNLOAD_LINK_TOKEN captures ([^']+?download\?t=[^']+) with no leading slash required, so an absolute URL inside copyDownloadLink(...) yields a doubled origin and the request fails as an invalid URL. Only DOWNLOAD_TOKEN guarantees the leading slash.
Resolving here handles both shapes and lets the caller use the value directly:
return new URL(match[1].replaceAll(String.raw`\/`, "/"), this.BZZHR_BASE_URL).toString();|
The wiring looks complete, but this downloader would not help with the SteamRIP source people actually use in Hydra. The JSON for that source currently has no Bzzhr / Buzzheavier URIs. A dump from today (1351 games, 1574 links) only contains Gofile, 1fichier, Datanodes and VikingFile. The origin site may have moved to Bzzhr, but this source never ingested those links. Hydra does not scrape the origin: the launcher only posts the source URL to the API ( On the resolver, same two issues already flagged:
Even with those fixes, this still needs a source that actually emits Bzzhr links. Hardcoding a SteamRIP Referer is reasonable only after that, given Bzzhr is now SteamRIP-only. |



When submitting this pull request, I confirm the following (please check the boxes):
Fill in the PR content:
Adds a downloader for
bzzhrrepack links only from SteamRip.This resolves Bzzhr links with the Referer https://steamrip.com/ header, since bzzhr.to/{id} only serves the download page when the request comes from the original site, otherwise it redirects to the site home. I checked the other download sources available through Hydra and none of them use bzzhr. If a future source ever adopts Bzzhr links, the referer would need to be made configurable per source. I'm just flagging this as a known limitation.
Changes by file:
src/shared/constants.ts:Downloader.Bzzhrto theDownloaderenum.src/shared/index.ts:getDownloadersForUrinow mapsbzzhr.to,ts.bzzhr.toandts.bzzhr.ioto the new downloader.src/main/services/hosters/bzzhr.ts(new):src/main/services/hosters/index.ts:src/main/services/download/download-manager.ts:src/renderer/src/constants.ts:src/big-picture/src/constants.ts:I checked that Hydra now resolves a
Bzzhrshort link to itsts.bzzhr.todirect URL and downloads the file.