From f8ac284bc2d2601b3abf1b2a1b5f22e1da817315 Mon Sep 17 00:00:00 2001 From: Wkeynhk Date: Thu, 25 Dec 2025 14:16:11 +0300 Subject: [PATCH 1/5] New hosters --- .../events/torrenting/start-game-download.ts | 37 ++++- .../services/download/download-manager.ts | 131 ++++++++++++++---- src/main/services/hosters/buzzheavier.ts | 82 +++++++++++ src/main/services/hosters/fuckingfast.ts | 129 +++++++++++++++++ src/main/services/hosters/index.ts | 2 + src/renderer/src/constants.ts | 2 + src/shared/constants.ts | 2 + src/shared/index.ts | 10 ++ 8 files changed, 365 insertions(+), 30 deletions(-) create mode 100644 src/main/services/hosters/buzzheavier.ts create mode 100644 src/main/services/hosters/fuckingfast.ts diff --git a/src/main/events/torrenting/start-game-download.ts b/src/main/events/torrenting/start-game-download.ts index 4375698f..940f3025 100644 --- a/src/main/events/torrenting/start-game-download.ts +++ b/src/main/events/torrenting/start-game-download.ts @@ -82,7 +82,6 @@ const startGameDownload = async ( queued: true, extracting: false, automaticallyExtract, - extractionProgress: 0, }; try { @@ -124,6 +123,42 @@ const startGameDownload = async ( } if (err instanceof Error) { + if (downloader === Downloader.Buzzheavier) { + if (err.message.includes("Rate limit")) { + return { + ok: false, + error: "Buzzheavier: Rate limit exceeded", + }; + } + if ( + err.message.includes("not found") || + err.message.includes("deleted") + ) { + return { + ok: false, + error: "Buzzheavier: File not found", + }; + } + } + + if (downloader === Downloader.FuckingFast) { + if (err.message.includes("Rate limit")) { + return { + ok: false, + error: "FuckingFast: Rate limit exceeded", + }; + } + if ( + err.message.includes("not found") || + err.message.includes("deleted") + ) { + return { + ok: false, + error: "FuckingFast: File not found", + }; + } + } + return { ok: false, error: err.message }; } diff --git a/src/main/services/download/download-manager.ts b/src/main/services/download/download-manager.ts index c208fa32..ce06e5e4 100644 --- a/src/main/services/download/download-manager.ts +++ b/src/main/services/download/download-manager.ts @@ -20,14 +20,47 @@ import { RealDebridClient } from "./real-debrid"; import path from "path"; import { logger } from "../logger"; import { db, downloadsSublevel, gamesSublevel, levelKeys } from "@main/level"; -import { orderBy } from "lodash-es"; +import { sortBy } from "lodash-es"; import { TorBoxClient } from "./torbox"; import { GameFilesManager } from "../game-files-manager"; import { HydraDebridClient } from "./hydra-debrid"; +import { BuzzheavierApi, FuckingFastApi } from "@main/services/hosters"; export class DownloadManager { private static downloadingGameId: string | null = null; + private static extractFilename(url: string, originalUrl?: string): string | undefined { + if (originalUrl && originalUrl.includes('#')) { + const hashPart = originalUrl.split('#')[1]; + if (hashPart && !hashPart.startsWith('http')) { + return hashPart; + } + } + + if (url.includes('#')) { + const hashPart = url.split('#')[1]; + if (hashPart && !hashPart.startsWith('http')) { + return hashPart; + } + } + + try { + const urlObj = new URL(url); + const pathname = urlObj.pathname; + const filename = pathname.split('/').pop(); + if (filename && filename.length > 0) { + return filename; + } + } catch { + } + + return undefined; + } + + private static sanitizeFilename(filename: string): string { + return filename.replace(/[<>:"/\\|?*]/g, '_'); + } + public static async startRPC( download?: Download, downloadsToSeed?: Download[] @@ -126,10 +159,21 @@ export class DownloadManager { } ); + if (WindowManager.mainWindow && download) { + WindowManager.mainWindow.setProgressBar(progress === 1 ? -1 : progress); + WindowManager.mainWindow.webContents.send( + "on-download-progress", + JSON.parse( + JSON.stringify({ + ...status, + game, + }) + ) + ); + } + const shouldExtract = download.automaticallyExtract; - // Handle download completion BEFORE sending progress to renderer - // This ensures extraction starts and DB is updated before UI reacts if (progress === 1 && download) { publishDownloadCompleteNotification(game); @@ -143,7 +187,6 @@ export class DownloadManager { shouldSeed: true, queued: false, extracting: shouldExtract, - extractionProgress: shouldExtract ? 0 : download.extractionProgress, }); } else { await downloadsSublevel.put(gameId, { @@ -152,22 +195,12 @@ export class DownloadManager { shouldSeed: false, queued: false, extracting: shouldExtract, - extractionProgress: shouldExtract ? 0 : download.extractionProgress, }); this.cancelDownload(gameId); } if (shouldExtract) { - // Send initial extraction progress BEFORE download progress - // This ensures the UI shows extraction immediately - WindowManager.mainWindow?.webContents.send( - "on-extraction-progress", - game.shop, - game.objectId, - 0 - ); - const gameFilesManager = new GameFilesManager( game.shop, game.objectId @@ -194,10 +227,10 @@ export class DownloadManager { .values() .all() .then((games) => { - return orderBy( + return sortBy( games.filter((game) => game.status === "paused" && game.queued), "timestamp", - "desc" + "DESC" ); }); @@ -209,18 +242,6 @@ export class DownloadManager { this.downloadingGameId = null; } } - - // Send progress to renderer after completion handling - if (WindowManager.mainWindow && download) { - WindowManager.mainWindow.setProgressBar(progress === 1 ? -1 : progress); - WindowManager.mainWindow.webContents.send( - "on-download-progress", - structuredClone({ - ...status, - game, - }) - ); - } } } @@ -360,6 +381,58 @@ export class DownloadManager { save_path: download.downloadPath, }; } + case Downloader.Buzzheavier: { + logger.log(`[DownloadManager] Processing Buzzheavier download for URI: ${download.uri}`); + try { + const directUrl = await BuzzheavierApi.getDirectLink(download.uri); + logger.log(`[DownloadManager] Buzzheavier direct URL obtained`); + + const filename = this.extractFilename(directUrl, download.uri); + const sanitizedFilename = filename ? this.sanitizeFilename(filename) : undefined; + + if (sanitizedFilename) { + logger.log(`[DownloadManager] Using filename: ${sanitizedFilename}`); + } + + return { + action: "start", + game_id: downloadId, + url: directUrl, + save_path: download.downloadPath, + out: sanitizedFilename, + allow_multiple_connections: true, + }; + } catch (error) { + logger.error(`[DownloadManager] Error processing Buzzheavier download:`, error); + throw error; + } + } + case Downloader.FuckingFast: { + logger.log(`[DownloadManager] Processing FuckingFast download for URI: ${download.uri}`); + try { + const directUrl = await FuckingFastApi.getDirectLink(download.uri); + logger.log(`[DownloadManager] FuckingFast direct URL obtained`); + + const filename = this.extractFilename(directUrl, download.uri); + const sanitizedFilename = filename ? this.sanitizeFilename(filename) : undefined; + + if (sanitizedFilename) { + logger.log(`[DownloadManager] Using filename: ${sanitizedFilename}`); + } + + return { + action: "start", + game_id: downloadId, + url: directUrl, + save_path: download.downloadPath, + out: sanitizedFilename, + allow_multiple_connections: true, + }; + } catch (error) { + logger.error(`[DownloadManager] Error processing FuckingFast download:`, error); + throw error; + } + } case Downloader.Mediafire: { const downloadUrl = await MediafireApi.getDownloadUrl(download.uri); @@ -426,4 +499,4 @@ export class DownloadManager { await PythonRPC.rpc.post("/action", payload); this.downloadingGameId = levelKeys.game(download.shop, download.objectId); } -} +} \ No newline at end of file diff --git a/src/main/services/hosters/buzzheavier.ts b/src/main/services/hosters/buzzheavier.ts new file mode 100644 index 00000000..7f8b7733 --- /dev/null +++ b/src/main/services/hosters/buzzheavier.ts @@ -0,0 +1,82 @@ +import axios from "axios"; +import { + HOSTER_USER_AGENT, + extractHosterFilename, + handleHosterError, +} from "./fuckingfast"; +import { logger } from "@main/services"; + +export class BuzzheavierApi { + private static readonly BUZZHEAVIER_DOMAINS = [ + "buzzheavier.com", + "bzzhr.co", + "fuckingfast.net", + ]; + + private static isSupportedDomain(url: string): boolean { + const lowerUrl = url.toLowerCase(); + return this.BUZZHEAVIER_DOMAINS.some((domain) => lowerUrl.includes(domain)); + } + + private static async getBuzzheavierDirectLink(url: string): Promise { + try { + const baseUrl = url.split("#")[0]; + logger.log(`[Buzzheavier] Starting download link extraction for: ${baseUrl}`); + + await axios.get(baseUrl, { + headers: { "User-Agent": HOSTER_USER_AGENT }, + timeout: 30000, + }); + + const downloadUrl = `${baseUrl}/download`; + logger.log(`[Buzzheavier] Making HEAD request to: ${downloadUrl}`); + const headResponse = await axios.head(downloadUrl, { + headers: { + "hx-current-url": baseUrl, + "hx-request": "true", + referer: baseUrl, + "User-Agent": HOSTER_USER_AGENT, + }, + maxRedirects: 0, + validateStatus: (status) => + status === 200 || status === 204 || status === 301 || status === 302, + timeout: 30000, + }); + + const hxRedirect = headResponse.headers["hx-redirect"]; + logger.log(`[Buzzheavier] Received hx-redirect header: ${hxRedirect}`); + if (!hxRedirect) { + logger.error(`[Buzzheavier] No hx-redirect header found. Status: ${headResponse.status}`); + throw new Error( + "Could not extract download link. File may be deleted or is a directory." + ); + } + + const domain = new URL(baseUrl).hostname; + const directLink = hxRedirect.startsWith("/dl/") + ? `https://${domain}${hxRedirect}` + : hxRedirect; + logger.log(`[Buzzheavier] Extracted direct link`); + return directLink; + } catch (error) { + logger.error(`[Buzzheavier] Error in getBuzzheavierDirectLink:`, error); + handleHosterError(error); + } + } + + public static async getDirectLink(url: string): Promise { + if (!this.isSupportedDomain(url)) { + throw new Error( + `Unsupported domain. Supported domains: ${this.BUZZHEAVIER_DOMAINS.join(", ")}` + ); + } + return this.getBuzzheavierDirectLink(url); + } + + public static async getFilename( + url: string, + directUrl?: string + ): Promise { + return extractHosterFilename(url, directUrl); + } +} \ No newline at end of file diff --git a/src/main/services/hosters/fuckingfast.ts b/src/main/services/hosters/fuckingfast.ts new file mode 100644 index 00000000..c9001d9a --- /dev/null +++ b/src/main/services/hosters/fuckingfast.ts @@ -0,0 +1,129 @@ +import axios from "axios"; +import { logger } from "@main/services"; + +export const HOSTER_USER_AGENT = + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:144.0) Gecko/20100101 Firefox/144.0"; + +export async function extractHosterFilename( + url: string, + directUrl?: string +): Promise { + if (url.includes("#")) { + const fragment = url.split("#")[1]; + if (fragment && !fragment.startsWith("http")) { + return fragment; + } + } + + if (directUrl) { + try { + const response = await axios.head(directUrl, { + timeout: 10000, + headers: { "User-Agent": HOSTER_USER_AGENT }, + }); + + const contentDisposition = response.headers["content-disposition"]; + if (contentDisposition) { + const filenameMatch = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec( + contentDisposition + ); + if (filenameMatch && filenameMatch[1]) { + return filenameMatch[1].replace(/['"]/g, ""); + } + } + } catch { + // Ignore errors + } + + const urlPath = new URL(directUrl).pathname; + const filename = urlPath.split("/").pop()?.split("?")[0]; + if (filename) { + return filename; + } + } + + return "downloaded_file"; +} + +export function handleHosterError(error: unknown): never { + if (axios.isAxiosError(error)) { + if (error.response?.status === 404) { + throw new Error("File not found"); + } + if (error.response?.status === 429) { + throw new Error("Rate limit exceeded. Please try again later."); + } + if (error.response?.status === 403) { + throw new Error("Access denied. File may be private or deleted."); + } + throw new Error(`Network error: ${error.response?.status || "Unknown"}`); + } + throw error; +} + +// ============================================ +// FuckingFast API Class +// ============================================ +export class FuckingFastApi { + private static readonly FUCKINGFAST_DOMAINS = ["fuckingfast.co"]; + + private static readonly FUCKINGFAST_REGEX = + /window\.open\("(https:\/\/fuckingfast\.co\/dl\/[^"]*)"\)/; + + private static isSupportedDomain(url: string): boolean { + const lowerUrl = url.toLowerCase(); + return this.FUCKINGFAST_DOMAINS.some((domain) => lowerUrl.includes(domain)); + } + + private static async getFuckingFastDirectLink(url: string): Promise { + try { + logger.log(`[FuckingFast] Starting download link extraction for: ${url}`); + const response = await axios.get(url, { + headers: { "User-Agent": HOSTER_USER_AGENT }, + timeout: 30000, + }); + + const html = response.data; + + if (html.toLowerCase().includes("rate limit")) { + logger.error(`[FuckingFast] Rate limit detected`); + throw new Error( + "Rate limit exceeded. Please wait a few minutes and try again." + ); + } + + if (html.includes("File Not Found Or Deleted")) { + logger.error(`[FuckingFast] File not found or deleted`); + throw new Error("File not found or deleted"); + } + + const match = this.FUCKINGFAST_REGEX.exec(html); + if (!match || !match[1]) { + logger.error(`[FuckingFast] Could not extract download link`); + throw new Error("Could not extract download link from page"); + } + + logger.log(`[FuckingFast] Successfully extracted direct link`); + return match[1]; + } catch (error) { + logger.error(`[FuckingFast] Error:`, error); + handleHosterError(error); + } + } + + public static async getDirectLink(url: string): Promise { + if (!this.isSupportedDomain(url)) { + throw new Error( + `Unsupported domain. Supported domains: ${this.FUCKINGFAST_DOMAINS.join(", ")}` + ); + } + return this.getFuckingFastDirectLink(url); + } + + public static async getFilename( + url: string, + directUrl?: string + ): Promise { + return extractHosterFilename(url, directUrl); + } +} \ No newline at end of file diff --git a/src/main/services/hosters/index.ts b/src/main/services/hosters/index.ts index 3f3b9ac9..5f918811 100644 --- a/src/main/services/hosters/index.ts +++ b/src/main/services/hosters/index.ts @@ -3,3 +3,5 @@ export * from "./qiwi"; export * from "./datanodes"; export * from "./mediafire"; export * from "./pixeldrain"; +export * from "./buzzheavier"; +export * from "./fuckingfast"; diff --git a/src/renderer/src/constants.ts b/src/renderer/src/constants.ts index 3329a0cc..89de5503 100644 --- a/src/renderer/src/constants.ts +++ b/src/renderer/src/constants.ts @@ -10,6 +10,8 @@ export const DOWNLOADER_NAME = { [Downloader.Qiwi]: "Qiwi", [Downloader.Datanodes]: "Datanodes", [Downloader.Mediafire]: "Mediafire", + [Downloader.Buzzheavier]: "Buzzheavier", + [Downloader.FuckingFast]: "FuckingFast", [Downloader.TorBox]: "TorBox", [Downloader.Hydra]: "Nimbus", }; diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 619dca65..6e900175 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -8,6 +8,8 @@ export enum Downloader { Mediafire, TorBox, Hydra, + Buzzheavier, + FuckingFast, } export enum DownloadSourceStatus { diff --git a/src/shared/index.ts b/src/shared/index.ts index 615fa36e..d54ef387 100644 --- a/src/shared/index.ts +++ b/src/shared/index.ts @@ -114,6 +114,16 @@ export const getDownloadersForUri = (uri: string) => { if (uri.startsWith("https://datanodes.to")) return [Downloader.Datanodes]; if (uri.startsWith("https://www.mediafire.com")) return [Downloader.Mediafire]; + if ( + uri.startsWith("https://buzzheavier.com") || + uri.startsWith("https://bzzhr.co") || + uri.startsWith("https://fuckingfast.net") + ) { + return [Downloader.Buzzheavier]; + } + if (uri.startsWith("https://fuckingfast.co")) { + return [Downloader.FuckingFast]; + } if (realDebridHosts.some((host) => uri.startsWith(host))) return [Downloader.RealDebrid]; From 37f085e2c0e0d5d400c8f09b76993695b81e3316 Mon Sep 17 00:00:00 2001 From: Wkeynhk Date: Fri, 26 Dec 2025 01:13:36 +0300 Subject: [PATCH 2/5] . --- .../events/torrenting/start-game-download.ts | 4 +- .../services/download/download-manager.ts | 153 ++++++------------ 2 files changed, 47 insertions(+), 110 deletions(-) diff --git a/src/main/events/torrenting/start-game-download.ts b/src/main/events/torrenting/start-game-download.ts index 940f3025..97de9f28 100644 --- a/src/main/events/torrenting/start-game-download.ts +++ b/src/main/events/torrenting/start-game-download.ts @@ -41,7 +41,6 @@ const startGameDownload = async ( const game = await gamesSublevel.get(gameKey); const gameAssets = await gamesShopAssetsSublevel.get(gameKey); - /* Delete any previous download */ await downloadsSublevel.del(gameKey); if (game) { @@ -82,6 +81,7 @@ const startGameDownload = async ( queued: true, extracting: false, automaticallyExtract, + extractionProgress: null, }; try { @@ -166,4 +166,4 @@ const startGameDownload = async ( } }; -registerEvent("startGameDownload", startGameDownload); +registerEvent("startGameDownload", startGameDownload); \ No newline at end of file diff --git a/src/main/services/download/download-manager.ts b/src/main/services/download/download-manager.ts index ce06e5e4..8cd16487 100644 --- a/src/main/services/download/download-manager.ts +++ b/src/main/services/download/download-manager.ts @@ -30,29 +30,21 @@ export class DownloadManager { private static downloadingGameId: string | null = null; private static extractFilename(url: string, originalUrl?: string): string | undefined { - if (originalUrl && originalUrl.includes('#')) { + if (originalUrl?.includes('#')) { const hashPart = originalUrl.split('#')[1]; - if (hashPart && !hashPart.startsWith('http')) { - return hashPart; - } + if (hashPart && !hashPart.startsWith('http')) return hashPart; } if (url.includes('#')) { const hashPart = url.split('#')[1]; - if (hashPart && !hashPart.startsWith('http')) { - return hashPart; - } + if (hashPart && !hashPart.startsWith('http')) return hashPart; } try { const urlObj = new URL(url); - const pathname = urlObj.pathname; - const filename = pathname.split('/').pop(); - if (filename && filename.length > 0) { - return filename; - } - } catch { - } + const filename = urlObj.pathname.split('/').pop(); + if (filename?.length) return filename; + } catch {} return undefined; } @@ -61,6 +53,24 @@ export class DownloadManager { return filename.replace(/[<>:"/\\|?*]/g, '_'); } + private static createDownloadPayload(directUrl: string, originalUrl: string, downloadId: string, savePath: string) { + const filename = this.extractFilename(directUrl, originalUrl); + const sanitizedFilename = filename ? this.sanitizeFilename(filename) : undefined; + + if (sanitizedFilename) { + logger.log(`[DownloadManager] Using filename: ${sanitizedFilename}`); + } + + return { + action: "start" as const, + game_id: downloadId, + url: directUrl, + save_path: savePath, + out: sanitizedFilename, + allow_multiple_connections: true, + }; + } + public static async startRPC( download?: Download, downloadsToSeed?: Download[] @@ -86,9 +96,7 @@ export class DownloadManager { } private static async getDownloadStatus() { - const response = await PythonRPC.rpc.get( - "/status" - ); + const response = await PythonRPC.rpc.get("/status"); if (response.data === null || !this.downloadingGameId) return null; const downloadId = this.downloadingGameId; @@ -104,8 +112,7 @@ export class DownloadManager { status, } = response.data; - const isDownloadingMetadata = - status === LibtorrentStatus.DownloadingMetadata; + const isDownloadingMetadata = status === LibtorrentStatus.DownloadingMetadata; const isCheckingFiles = status === LibtorrentStatus.CheckingFiles; const download = await downloadsSublevel.get(downloadId); @@ -154,21 +161,14 @@ export class DownloadManager { const userPreferences = await db.get( levelKeys.userPreferences, - { - valueEncoding: "json", - } + { valueEncoding: "json" } ); if (WindowManager.mainWindow && download) { WindowManager.mainWindow.setProgressBar(progress === 1 ? -1 : progress); WindowManager.mainWindow.webContents.send( "on-download-progress", - JSON.parse( - JSON.stringify({ - ...status, - game, - }) - ) + JSON.parse(JSON.stringify({ ...status, game })) ); } @@ -177,10 +177,7 @@ export class DownloadManager { if (progress === 1 && download) { publishDownloadCompleteNotification(game); - if ( - userPreferences?.seedAfterDownloadComplete && - download.downloader === Downloader.Torrent - ) { + if (userPreferences?.seedAfterDownloadComplete && download.downloader === Downloader.Torrent) { await downloadsSublevel.put(gameId, { ...download, status: "seeding", @@ -201,38 +198,25 @@ export class DownloadManager { } if (shouldExtract) { - const gameFilesManager = new GameFilesManager( - game.shop, - game.objectId - ); + const gameFilesManager = new GameFilesManager(game.shop, game.objectId); - if ( - FILE_EXTENSIONS_TO_EXTRACT.some((ext) => - download.folderName?.endsWith(ext) - ) - ) { + if (FILE_EXTENSIONS_TO_EXTRACT.some((ext) => download.folderName?.endsWith(ext))) { gameFilesManager.extractDownloadedFile(); } else { gameFilesManager - .extractFilesInDirectory( - path.join(download.downloadPath, download.folderName!) - ) - .then(() => { - gameFilesManager.setExtractionComplete(); - }); + .extractFilesInDirectory(path.join(download.downloadPath, download.folderName!)) + .then(() => gameFilesManager.setExtractionComplete()); } } const downloads = await downloadsSublevel .values() .all() - .then((games) => { - return sortBy( - games.filter((game) => game.status === "paused" && game.queued), - "timestamp", - "DESC" - ); - }); + .then((games) => sortBy( + games.filter((game) => game.status === "paused" && game.queued), + "timestamp", + "DESC" + )); const [nextItemOnQueue] = downloads; @@ -259,9 +243,7 @@ export class DownloadManager { if (!download) return; - const totalSize = await getDirSize( - path.join(download.downloadPath, status.folderName) - ); + const totalSize = await getDirSize(path.join(download.downloadPath, status.folderName)); if (totalSize < status.fileSize) { await this.cancelDownload(status.gameId); @@ -282,10 +264,7 @@ export class DownloadManager { static async pauseDownload(downloadKey = this.downloadingGameId) { await PythonRPC.rpc - .post("/action", { - action: "pause", - game_id: downloadKey, - } as PauseDownloadPayload) + .post("/action", { action: "pause", game_id: downloadKey } as PauseDownloadPayload) .catch(() => {}); if (downloadKey === this.downloadingGameId) { @@ -300,13 +279,8 @@ export class DownloadManager { static async cancelDownload(downloadKey = this.downloadingGameId) { await PythonRPC.rpc - .post("/action", { - action: "cancel", - game_id: downloadKey, - }) - .catch((err) => { - logger.error("Failed to cancel game download", err); - }); + .post("/action", { action: "cancel", game_id: downloadKey }) + .catch((err) => logger.error("Failed to cancel game download", err)); if (downloadKey === this.downloadingGameId) { WindowManager.mainWindow?.setProgressBar(-1); @@ -339,7 +313,6 @@ export class DownloadManager { const id = download.uri.split("/").pop(); const token = await GofileApi.authorize(); const downloadLink = await GofileApi.getDownloadLink(id!); - await GofileApi.checkDownloadUrl(downloadLink); return { @@ -386,22 +359,7 @@ export class DownloadManager { try { const directUrl = await BuzzheavierApi.getDirectLink(download.uri); logger.log(`[DownloadManager] Buzzheavier direct URL obtained`); - - const filename = this.extractFilename(directUrl, download.uri); - const sanitizedFilename = filename ? this.sanitizeFilename(filename) : undefined; - - if (sanitizedFilename) { - logger.log(`[DownloadManager] Using filename: ${sanitizedFilename}`); - } - - return { - action: "start", - game_id: downloadId, - url: directUrl, - save_path: download.downloadPath, - out: sanitizedFilename, - allow_multiple_connections: true, - }; + return this.createDownloadPayload(directUrl, download.uri, downloadId, download.downloadPath); } catch (error) { logger.error(`[DownloadManager] Error processing Buzzheavier download:`, error); throw error; @@ -412,22 +370,7 @@ export class DownloadManager { try { const directUrl = await FuckingFastApi.getDirectLink(download.uri); logger.log(`[DownloadManager] FuckingFast direct URL obtained`); - - const filename = this.extractFilename(directUrl, download.uri); - const sanitizedFilename = filename ? this.sanitizeFilename(filename) : undefined; - - if (sanitizedFilename) { - logger.log(`[DownloadManager] Using filename: ${sanitizedFilename}`); - } - - return { - action: "start", - game_id: downloadId, - url: directUrl, - save_path: download.downloadPath, - out: sanitizedFilename, - allow_multiple_connections: true, - }; + return this.createDownloadPayload(directUrl, download.uri, downloadId, download.downloadPath); } catch (error) { logger.error(`[DownloadManager] Error processing FuckingFast download:`, error); throw error; @@ -435,7 +378,6 @@ export class DownloadManager { } case Downloader.Mediafire: { const downloadUrl = await MediafireApi.getDownloadUrl(download.uri); - return { action: "start", game_id: downloadId, @@ -452,7 +394,6 @@ export class DownloadManager { }; case Downloader.RealDebrid: { const downloadUrl = await RealDebridClient.getDownloadUrl(download.uri); - if (!downloadUrl) throw new Error(DownloadError.NotCachedOnRealDebrid); return { @@ -465,7 +406,6 @@ export class DownloadManager { } case Downloader.TorBox: { const { name, url } = await TorBoxClient.getDownloadInfo(download.uri); - if (!url) return; return { action: "start", @@ -477,10 +417,7 @@ export class DownloadManager { }; } case Downloader.Hydra: { - const downloadUrl = await HydraDebridClient.getDownloadUrl( - download.uri - ); - + const downloadUrl = await HydraDebridClient.getDownloadUrl(download.uri); if (!downloadUrl) throw new Error(DownloadError.NotCachedOnHydra); return { From 067f7a00bef41e8ede399de88b58161a76c985f9 Mon Sep 17 00:00:00 2001 From: Wkeynhk Date: Fri, 26 Dec 2025 01:19:04 +0300 Subject: [PATCH 3/5] . --- src/main/events/torrenting/start-game-download.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/events/torrenting/start-game-download.ts b/src/main/events/torrenting/start-game-download.ts index 97de9f28..7e57dff2 100644 --- a/src/main/events/torrenting/start-game-download.ts +++ b/src/main/events/torrenting/start-game-download.ts @@ -81,7 +81,7 @@ const startGameDownload = async ( queued: true, extracting: false, automaticallyExtract, - extractionProgress: null, + extractionProgress: 0, }; try { From 7f0dc5eee46bd8fddb4dfe13fc39063790a2af13 Mon Sep 17 00:00:00 2001 From: Wkeynhk Date: Fri, 26 Dec 2025 01:25:56 +0300 Subject: [PATCH 4/5] . --- src/main/services/download/download-manager.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/services/download/download-manager.ts b/src/main/services/download/download-manager.ts index 8cd16487..68890a66 100644 --- a/src/main/services/download/download-manager.ts +++ b/src/main/services/download/download-manager.ts @@ -44,7 +44,9 @@ export class DownloadManager { const urlObj = new URL(url); const filename = urlObj.pathname.split('/').pop(); if (filename?.length) return filename; - } catch {} + } catch { + // Invalid URL + } return undefined; } From 358f41b4ba33e1755ecac968f9f606775f431e04 Mon Sep 17 00:00:00 2001 From: Wkeynhk Date: Fri, 26 Dec 2025 01:34:09 +0300 Subject: [PATCH 5/5] . --- src/main/events/torrenting/start-game-download.ts | 2 +- src/main/services/download/download-manager.ts | 2 +- src/main/services/hosters/buzzheavier.ts | 2 +- src/main/services/hosters/fuckingfast.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/events/torrenting/start-game-download.ts b/src/main/events/torrenting/start-game-download.ts index 7e57dff2..e44ba936 100644 --- a/src/main/events/torrenting/start-game-download.ts +++ b/src/main/events/torrenting/start-game-download.ts @@ -166,4 +166,4 @@ const startGameDownload = async ( } }; -registerEvent("startGameDownload", startGameDownload); \ No newline at end of file +registerEvent("startGameDownload", startGameDownload); diff --git a/src/main/services/download/download-manager.ts b/src/main/services/download/download-manager.ts index 68890a66..bc9ddf1d 100644 --- a/src/main/services/download/download-manager.ts +++ b/src/main/services/download/download-manager.ts @@ -438,4 +438,4 @@ export class DownloadManager { await PythonRPC.rpc.post("/action", payload); this.downloadingGameId = levelKeys.game(download.shop, download.objectId); } -} \ No newline at end of file +} diff --git a/src/main/services/hosters/buzzheavier.ts b/src/main/services/hosters/buzzheavier.ts index 7f8b7733..819c8f25 100644 --- a/src/main/services/hosters/buzzheavier.ts +++ b/src/main/services/hosters/buzzheavier.ts @@ -79,4 +79,4 @@ export class BuzzheavierApi { ): Promise { return extractHosterFilename(url, directUrl); } -} \ No newline at end of file +} diff --git a/src/main/services/hosters/fuckingfast.ts b/src/main/services/hosters/fuckingfast.ts index c9001d9a..00d0ff58 100644 --- a/src/main/services/hosters/fuckingfast.ts +++ b/src/main/services/hosters/fuckingfast.ts @@ -126,4 +126,4 @@ export class FuckingFastApi { ): Promise { return extractHosterFilename(url, directUrl); } -} \ No newline at end of file +}