Skip to content

feat: add bzzhr downloader - #2699

Open
ele-a-be wants to merge 6 commits into
hydralauncher:mainfrom
ele-a-be:feat/bzzhr-downloader
Open

feat: add bzzhr downloader#2699
ele-a-be wants to merge 6 commits into
hydralauncher:mainfrom
ele-a-be:feat/bzzhr-downloader

Conversation

@ele-a-be

Copy link
Copy Markdown

When submitting this pull request, I confirm the following (please check the boxes):

  • I have read the Hydra documentation.
  • I have checked that there are no duplicate pull requests related to this request.
  • I have considered, and confirm that this submission is valuable to others.
  • I accept that this submission may not be used and the pull request may be closed at the discretion of the maintainers.

Fill in the PR content:

Adds a downloader for bzzhr repack 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:

  1. src/shared/constants.ts:
    • Added Downloader.Bzzhr to the Downloader enum.
  2. src/shared/index.ts:
    • getDownloadersForUri now maps bzzhr.to, ts.bzzhr.to and ts.bzzhr.io to the new downloader.
  3. src/main/services/hosters/bzzhr.ts (new):
    • Fetches the bzzhr page with the SteamRip referer.
    • Reads the download token from it, then follows the redirect to the direct ts.bzzhr.to link.
    • Links that are already direct are returned unchanged.
  4. src/main/services/hosters/index.ts:
    • Exports the new bzzhr file.
  5. src/main/services/download/download-manager.ts:
    • Imports the bzzhr api and adds its download options, following the existing hoster pattern.
  6. src/renderer/src/constants.ts:
    • Added the Bzzhr downloader name.
  7. src/big-picture/src/constants.ts:
    • Added the Bzzhr downloader name.

I checked that Hydra now resolves a Bzzhr short link to its ts.bzzhr.to direct URL and downloads the file.

@greptile-apps

greptile-apps Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The 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.

  • Adds Bzzhr downloader metadata to shared, renderer, and Big Picture constants.
  • Resolves Bzzhr share links into direct CDN URLs through a correlated redirect listener.
  • Connects Bzzhr resolution to download validation and startup.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

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
Loading

Reviews (2): Last reviewed commit: "fix: String.raw in bzzhr" | Re-trigger Greptile

Comment thread src/main/services/hosters/bzzhr.ts Outdated
@sonarqubecloud

Copy link
Copy Markdown

@Moyasee

Moyasee commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

@greptile

@Moyasee Moyasee left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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`\/`, "/");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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();

@Zergiv

Zergiv commented Aug 19, 2026

Copy link
Copy Markdown

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 (POST /download-sources) and later reads matched repacks from /games/{shop}/{objectId}/download-sources. Adding a Bzzhr hoster in the client therefore has nothing to resolve until the source itself publishes Bzzhr URIs.

On the resolver, same two issues already flagged:

  1. net.request follows the redirect and streams the file into a discarded buffer, so every download pulls the payload twice. Use redirect: "manual", read the redirect URL, and abort. That also removes the global webRequest.onBeforeRedirect listener (last writer wins per session).
  2. Resolve the token with new URL(match[1], BZZHR_BASE_URL) so an absolute copyDownloadLink(...) value does not get concatenated onto the origin.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants