mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-01-23 13:34:54 +03:00
feat: adding proper path for real debrid downloads
This commit is contained in:
commit
5ec56bda5b
@ -53,6 +53,7 @@
|
||||
"jsdom": "^24.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lottie-react": "^2.4.0",
|
||||
"node-7z-archive": "^1.1.7",
|
||||
"parse-torrent": "^11.0.16",
|
||||
"ps-list": "^8.1.1",
|
||||
"react-i18next": "^14.1.0",
|
||||
|
@ -127,7 +127,9 @@
|
||||
"remove_from_list": "Remove",
|
||||
"delete_modal_title": "Are you sure?",
|
||||
"delete_modal_description": "This will remove all the installation files from your computer",
|
||||
"install": "Install"
|
||||
"install": "Install",
|
||||
"real_debrid": "Real Debrid",
|
||||
"torrent": "Torrent"
|
||||
},
|
||||
"settings": {
|
||||
"downloads_path": "Downloads path",
|
||||
@ -138,9 +140,11 @@
|
||||
"telemetry": "Telemetry",
|
||||
"telemetry_description": "Enable anonymous usage statistics",
|
||||
"real_debrid_api_token_description": "(Optional) Real Debrid API token",
|
||||
"quit_app_instead_hiding": "Quit Hydra instead of minimizing to tray",
|
||||
"launch_with_system": "Launch Hydra on system start-up",
|
||||
"general": "General",
|
||||
"behavior": "Behavior",
|
||||
"quit_app_instead_hiding": "Close app instead of minimizing to tray",
|
||||
"launch_with_system": "Launch app on system start-up"
|
||||
"real_debrid": "Real Debrid"
|
||||
},
|
||||
"notifications": {
|
||||
"download_complete": "Download complete",
|
||||
|
@ -26,7 +26,7 @@ const startGameDownload = async (
|
||||
});
|
||||
|
||||
const downloader = userPreferences?.realDebridApiToken
|
||||
? Downloader.Http
|
||||
? Downloader.RealDebrid
|
||||
: Downloader.Torrent;
|
||||
|
||||
const [game, repack] = await Promise.all([
|
||||
|
@ -4,7 +4,7 @@ import type { Game } from "@main/entity";
|
||||
import { Downloader } from "@shared";
|
||||
|
||||
import { writePipe } from "./fifo";
|
||||
import { HTTPDownloader } from "./downloaders";
|
||||
import { RealDebridDownloader } from "./downloaders";
|
||||
|
||||
export class DownloadManager {
|
||||
private static gameDownloading: Game;
|
||||
@ -25,7 +25,7 @@ export class DownloadManager {
|
||||
) {
|
||||
writePipe.write({ action: "cancel" });
|
||||
} else {
|
||||
HTTPDownloader.destroy();
|
||||
RealDebridDownloader.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ export class DownloadManager {
|
||||
) {
|
||||
writePipe.write({ action: "pause" });
|
||||
} else {
|
||||
HTTPDownloader.destroy();
|
||||
RealDebridDownloader.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ export class DownloadManager {
|
||||
save_path: game!.downloadPath,
|
||||
});
|
||||
} else {
|
||||
HTTPDownloader.startDownload(game!);
|
||||
RealDebridDownloader.startDownload(game!);
|
||||
}
|
||||
|
||||
this.gameDownloading = game!;
|
||||
@ -68,7 +68,7 @@ export class DownloadManager {
|
||||
save_path: game!.downloadPath,
|
||||
});
|
||||
} else {
|
||||
HTTPDownloader.startDownload(game!);
|
||||
RealDebridDownloader.startDownload(game!);
|
||||
}
|
||||
|
||||
this.gameDownloading = game!;
|
||||
|
@ -1,2 +1,2 @@
|
||||
export * from "./http.downloader";
|
||||
export * from "./real-debrid.downloader";
|
||||
export * from "./torrent.downloader";
|
||||
|
@ -4,11 +4,12 @@ import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
import EasyDL from "easydl";
|
||||
import { GameStatus } from "@shared";
|
||||
import { fullArchive } from "node-7z-archive";
|
||||
|
||||
import { Downloader } from "./downloader";
|
||||
import { RealDebridClient } from "../real-debrid";
|
||||
|
||||
export class HTTPDownloader extends Downloader {
|
||||
export class RealDebridDownloader extends Downloader {
|
||||
private static download: EasyDL;
|
||||
private static downloadSize = 0;
|
||||
|
||||
@ -22,52 +23,48 @@ export class HTTPDownloader extends Downloader {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static async getDownloadUrl(game: Game) {
|
||||
const torrents = await RealDebridClient.getAllTorrentsFromUser();
|
||||
const hash = RealDebridClient.extractSHA1FromMagnet(game!.repack.magnet);
|
||||
let torrent = torrents.find((t) => t.hash === hash);
|
||||
|
||||
if (!torrent) {
|
||||
const magnet = await RealDebridClient.addMagnet(game!.repack.magnet);
|
||||
|
||||
if (magnet && magnet.id) {
|
||||
await RealDebridClient.selectAllFiles(magnet.id);
|
||||
torrent = await RealDebridClient.getInfo(magnet.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (torrent) {
|
||||
const { links } = torrent;
|
||||
const { download } = await RealDebridClient.unrestrictLink(links[0]);
|
||||
|
||||
if (!download) {
|
||||
throw new Error("Torrent not cached on Real Debrid");
|
||||
}
|
||||
|
||||
return download;
|
||||
}
|
||||
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
private static createFolderIfNotExists(path: string) {
|
||||
if (!fs.existsSync(path)) {
|
||||
fs.mkdirSync(path);
|
||||
}
|
||||
}
|
||||
|
||||
private static async startDecompression(
|
||||
rarFile: string,
|
||||
dest: string,
|
||||
game: Game
|
||||
) {
|
||||
await fullArchive(rarFile, dest);
|
||||
|
||||
const updatePayload: QueryDeepPartialEntity<Game> = {
|
||||
status: GameStatus.Finished,
|
||||
progress: 1,
|
||||
};
|
||||
|
||||
await this.updateGameProgress(game.id, updatePayload, {
|
||||
timeRemaining: 0,
|
||||
});
|
||||
}
|
||||
|
||||
static destroy() {
|
||||
if (this.download) {
|
||||
this.download.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
static async startDownload(game: Game) {
|
||||
if (this.download) this.download.destroy();
|
||||
const downloadUrl = await this.getDownloadUrl(game);
|
||||
const downloadUrl = decodeURIComponent(
|
||||
await RealDebridClient.getDownloadUrl(game)
|
||||
);
|
||||
|
||||
const filename = path.basename(downloadUrl);
|
||||
const folderName = path.basename(filename, path.extname(filename));
|
||||
|
||||
const fullDownloadPath = path.join(game.downloadPath!, folderName);
|
||||
const downloadPath = path.join(game.downloadPath!, folderName);
|
||||
this.createFolderIfNotExists(downloadPath);
|
||||
|
||||
this.createFolderIfNotExists(fullDownloadPath);
|
||||
|
||||
this.download = new EasyDL(downloadUrl, fullDownloadPath);
|
||||
this.download = new EasyDL(downloadUrl, path.join(downloadPath, filename));
|
||||
|
||||
const metadata = await this.download.metadata();
|
||||
|
||||
@ -76,7 +73,7 @@ export class HTTPDownloader extends Downloader {
|
||||
const updatePayload: QueryDeepPartialEntity<Game> = {
|
||||
status: GameStatus.Downloading,
|
||||
fileSize: metadata.size,
|
||||
folderName: folderName,
|
||||
folderName,
|
||||
};
|
||||
|
||||
const downloadStatus = {
|
||||
@ -87,11 +84,8 @@ export class HTTPDownloader extends Downloader {
|
||||
|
||||
this.download.on("progress", async ({ total }) => {
|
||||
const updatePayload: QueryDeepPartialEntity<Game> = {
|
||||
status:
|
||||
total.percentage === 100
|
||||
? GameStatus.Finished
|
||||
: GameStatus.Downloading,
|
||||
progress: total.percentage / 100,
|
||||
status: GameStatus.Downloading,
|
||||
progress: Math.min(0.99, total.percentage / 100),
|
||||
bytesDownloaded: total.bytes,
|
||||
};
|
||||
|
||||
@ -102,11 +96,22 @@ export class HTTPDownloader extends Downloader {
|
||||
|
||||
await this.updateGameProgress(game.id, updatePayload, downloadStatus);
|
||||
});
|
||||
}
|
||||
|
||||
static destroy() {
|
||||
if (this.download) {
|
||||
this.download.destroy();
|
||||
}
|
||||
this.download.on("end", async () => {
|
||||
const updatePayload: QueryDeepPartialEntity<Game> = {
|
||||
status: GameStatus.Decompressing,
|
||||
progress: 0.99,
|
||||
};
|
||||
|
||||
await this.updateGameProgress(game.id, updatePayload, {
|
||||
timeRemaining: 0,
|
||||
});
|
||||
|
||||
this.startDecompression(
|
||||
path.join(downloadPath, filename),
|
||||
downloadPath,
|
||||
game
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
@ -62,6 +62,34 @@ export class RealDebridClient {
|
||||
return magnet.match(/btih:([0-9a-fA-F]*)/)?.[1].toLowerCase();
|
||||
}
|
||||
|
||||
static async getDownloadUrl(game: Game) {
|
||||
const torrents = await RealDebridClient.getAllTorrentsFromUser();
|
||||
const hash = RealDebridClient.extractSHA1FromMagnet(game!.repack.magnet);
|
||||
let torrent = torrents.find((t) => t.hash === hash);
|
||||
|
||||
if (!torrent) {
|
||||
const magnet = await RealDebridClient.addMagnet(game!.repack.magnet);
|
||||
|
||||
if (magnet && magnet.id) {
|
||||
await RealDebridClient.selectAllFiles(magnet.id);
|
||||
torrent = await RealDebridClient.getInfo(magnet.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (torrent) {
|
||||
const { links } = torrent;
|
||||
const { download } = await RealDebridClient.unrestrictLink(links[0]);
|
||||
|
||||
if (!download) {
|
||||
throw new Error("Torrent not cached on Real Debrid");
|
||||
}
|
||||
|
||||
return download;
|
||||
}
|
||||
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
static async authorize(apiToken: string) {
|
||||
this.instance = axios.create({
|
||||
baseURL: base,
|
||||
|
@ -66,7 +66,7 @@ export function Downloads() {
|
||||
};
|
||||
|
||||
const downloaderName = {
|
||||
[Downloader.Http]: t("real_debrid"),
|
||||
[Downloader.RealDebrid]: t("real_debrid"),
|
||||
[Downloader.Torrent]: t("torrent"),
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { ShopDetails, SteamMovies, SteamScreenshot } from "@types";
|
||||
import { ChevronRightIcon, ChevronLeftIcon } from "@primer/octicons-react";
|
||||
import * as styles from "./game-details.css";
|
||||
import * as styles from "./gallery-slider.css";
|
||||
|
||||
export interface GallerySliderProps {
|
||||
gameDetails: ShopDetails | null;
|
||||
@ -22,6 +22,7 @@ export function GallerySlider({ gameDetails }: GallerySliderProps) {
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
const [mediaIndex, setMediaIndex] = useState<number>(0);
|
||||
const [arrowShow, setArrowShow] = useState(false);
|
||||
|
||||
@ -41,6 +42,10 @@ export function GallerySlider({ gameDetails }: GallerySliderProps) {
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setMediaIndex(0);
|
||||
}, [gameDetails]);
|
||||
|
||||
useEffect(() => {
|
||||
if (scrollContainerRef.current) {
|
||||
const container = scrollContainerRef.current;
|
||||
@ -49,10 +54,10 @@ export function GallerySlider({ gameDetails }: GallerySliderProps) {
|
||||
const scrollLeft = mediaIndex * itemWidth;
|
||||
container.scrollLeft = scrollLeft;
|
||||
}
|
||||
}, [mediaIndex, mediaCount]);
|
||||
}, [gameDetails, mediaIndex, mediaCount]);
|
||||
|
||||
const hasScreenshots = gameDetails && gameDetails.screenshots.length > 0;
|
||||
const hasMovies = gameDetails && gameDetails.movies.length > 0;
|
||||
const hasScreenshots = gameDetails && gameDetails.screenshots.length;
|
||||
const hasMovies = gameDetails && gameDetails.movies?.length;
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -72,6 +77,7 @@ export function GallerySlider({ gameDetails }: GallerySliderProps) {
|
||||
poster={video.thumbnail}
|
||||
style={{ translate: `${-100 * mediaIndex}%` }}
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
>
|
||||
<source src={video.webm.max.replace("http", "https")} />
|
||||
@ -112,7 +118,7 @@ export function GallerySlider({ gameDetails }: GallerySliderProps) {
|
||||
|
||||
<div className={styles.gallerySliderPreview} ref={scrollContainerRef}>
|
||||
{hasMovies &&
|
||||
gameDetails.movies.map((video: SteamMovies, i: number) => (
|
||||
gameDetails.movies?.map((video: SteamMovies, i: number) => (
|
||||
<img
|
||||
key={video.id}
|
||||
onClick={() => setMediaIndex(i)}
|
||||
|
@ -79,95 +79,6 @@ export const descriptionContent = style({
|
||||
height: "100%",
|
||||
});
|
||||
|
||||
export const gallerySliderContainer = style({
|
||||
padding: `${SPACING_UNIT * 3}px ${SPACING_UNIT * 2}px`,
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
});
|
||||
|
||||
export const gallerySliderMedia = style({
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
display: "block",
|
||||
flexShrink: 0,
|
||||
flexGrow: "0",
|
||||
transition: "translate 0.3s ease-in-out",
|
||||
borderRadius: "4px",
|
||||
});
|
||||
|
||||
export const gallerySliderAnimationContainer = style({
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
position: "relative",
|
||||
overflow: "hidden",
|
||||
"@media": {
|
||||
"(min-width: 1280px)": {
|
||||
width: "60%",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const gallerySliderPreview = style({
|
||||
width: "100%",
|
||||
paddingTop: "0.5rem",
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
position: "relative",
|
||||
overflowX: "auto",
|
||||
overflowY: "hidden",
|
||||
"@media": {
|
||||
"(min-width: 1280px)": {
|
||||
width: "60%",
|
||||
},
|
||||
},
|
||||
"::-webkit-scrollbar-thumb": {
|
||||
width: "20%",
|
||||
},
|
||||
});
|
||||
|
||||
export const gallerySliderMediaPreview = style({
|
||||
cursor: "pointer",
|
||||
width: "20%",
|
||||
height: "20%",
|
||||
display: "block",
|
||||
flexShrink: 0,
|
||||
flexGrow: 0,
|
||||
opacity: 0.3,
|
||||
paddingRight: "5px",
|
||||
transition: "translate 300ms ease-in-out",
|
||||
borderRadius: "4px",
|
||||
":hover": {
|
||||
opacity: 1,
|
||||
},
|
||||
});
|
||||
|
||||
export const gallerySliderMediaPreviewActive = style({
|
||||
opacity: 1,
|
||||
});
|
||||
|
||||
export const gallerySliderButton = style({
|
||||
all: "unset",
|
||||
display: "block",
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
padding: "1rem",
|
||||
cursor: "pointer",
|
||||
transition: "background-color 100ms ease-in-out",
|
||||
":hover": {
|
||||
backgroundColor: "rgb(0, 0, 0, 0.2)",
|
||||
},
|
||||
});
|
||||
|
||||
export const gallerySliderIcons = style({
|
||||
fill: vars.color.muted,
|
||||
width: "2rem",
|
||||
height: "2rem",
|
||||
});
|
||||
|
||||
export const contentSidebar = style({
|
||||
borderLeft: `solid 1px ${vars.color.border};`,
|
||||
width: "100%",
|
||||
|
@ -257,6 +257,13 @@ export function GameDetails() {
|
||||
}}
|
||||
className={styles.description}
|
||||
/>
|
||||
|
||||
<small>
|
||||
All screenshots and movies displayed on this page are the
|
||||
property of Steam and/or their respective owners. We do not
|
||||
claim ownership of any content unless otherwise stated. All
|
||||
content is used for informational and promotional purposes only.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div className={styles.contentSidebar}>
|
||||
|
@ -24,3 +24,8 @@ export const downloadsPathField = style({
|
||||
display: "flex",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
});
|
||||
|
||||
export const settingsCategories = style({
|
||||
display: "flex",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
});
|
||||
|
@ -5,7 +5,11 @@ import * as styles from "./settings.css";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { UserPreferences } from "@types";
|
||||
|
||||
const categories = ["general", "behavior", "real_debrid"];
|
||||
|
||||
export function Settings() {
|
||||
const [currentCategory, setCurrentCategory] = useState(categories.at(0)!);
|
||||
|
||||
const [form, setForm] = useState({
|
||||
downloadsPath: "",
|
||||
downloadNotificationsEnabled: false,
|
||||
@ -61,62 +65,80 @@ export function Settings() {
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section className={styles.container}>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.downloadsPathField}>
|
||||
<TextField
|
||||
label={t("downloads_path")}
|
||||
value={form.downloadsPath}
|
||||
readOnly
|
||||
disabled
|
||||
const renderCategory = () => {
|
||||
if (currentCategory === "general") {
|
||||
return (
|
||||
<>
|
||||
<div className={styles.downloadsPathField}>
|
||||
<TextField
|
||||
label={t("downloads_path")}
|
||||
value={form.downloadsPath}
|
||||
readOnly
|
||||
disabled
|
||||
/>
|
||||
|
||||
<Button
|
||||
style={{ alignSelf: "flex-end" }}
|
||||
theme="outline"
|
||||
onClick={handleChooseDownloadsPath}
|
||||
>
|
||||
{t("change")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<h3>{t("notifications")}</h3>
|
||||
|
||||
<CheckboxField
|
||||
label={t("enable_download_notifications")}
|
||||
checked={form.downloadNotificationsEnabled}
|
||||
onChange={() =>
|
||||
updateUserPreferences(
|
||||
"downloadNotificationsEnabled",
|
||||
!form.downloadNotificationsEnabled
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<Button
|
||||
style={{ alignSelf: "flex-end" }}
|
||||
theme="outline"
|
||||
onClick={handleChooseDownloadsPath}
|
||||
>
|
||||
{t("change")}
|
||||
</Button>
|
||||
</div>
|
||||
<CheckboxField
|
||||
label={t("enable_repack_list_notifications")}
|
||||
checked={form.repackUpdatesNotificationsEnabled}
|
||||
onChange={() =>
|
||||
updateUserPreferences(
|
||||
"repackUpdatesNotificationsEnabled",
|
||||
!form.repackUpdatesNotificationsEnabled
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<h3>{t("notifications")}</h3>
|
||||
<h3>{t("telemetry")}</h3>
|
||||
|
||||
<CheckboxField
|
||||
label={t("enable_download_notifications")}
|
||||
checked={form.downloadNotificationsEnabled}
|
||||
onChange={() =>
|
||||
updateUserPreferences(
|
||||
"downloadNotificationsEnabled",
|
||||
!form.downloadNotificationsEnabled
|
||||
)
|
||||
}
|
||||
<CheckboxField
|
||||
label={t("telemetry_description")}
|
||||
checked={form.telemetryEnabled}
|
||||
onChange={() =>
|
||||
updateUserPreferences("telemetryEnabled", !form.telemetryEnabled)
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (currentCategory === "real_debrid") {
|
||||
return (
|
||||
<TextField
|
||||
label={t("real_debrid_api_token_description")}
|
||||
value={form.realDebridApiToken ?? ""}
|
||||
type="password"
|
||||
onChange={(event) => {
|
||||
updateUserPreferences("realDebridApiToken", event.target.value);
|
||||
}}
|
||||
placeholder="API Token"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
<CheckboxField
|
||||
label={t("enable_repack_list_notifications")}
|
||||
checked={form.repackUpdatesNotificationsEnabled}
|
||||
onChange={() =>
|
||||
updateUserPreferences(
|
||||
"repackUpdatesNotificationsEnabled",
|
||||
!form.repackUpdatesNotificationsEnabled
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<h3>{t("telemetry")}</h3>
|
||||
|
||||
<CheckboxField
|
||||
label={t("telemetry_description")}
|
||||
checked={form.telemetryEnabled}
|
||||
onChange={() =>
|
||||
updateUserPreferences("telemetryEnabled", !form.telemetryEnabled)
|
||||
}
|
||||
/>
|
||||
|
||||
<h3>{t("behavior")}</h3>
|
||||
|
||||
return (
|
||||
<>
|
||||
<CheckboxField
|
||||
label={t("quit_app_instead_hiding")}
|
||||
checked={form.preferQuitInsteadOfHiding}
|
||||
@ -128,15 +150,6 @@ export function Settings() {
|
||||
}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
label={t("real_debrid_api_token_description")}
|
||||
value={form.realDebridApiToken ?? ""}
|
||||
type="password"
|
||||
onChange={(event) => {
|
||||
updateUserPreferences("realDebridApiToken", event.target.value);
|
||||
}}
|
||||
/>
|
||||
|
||||
<CheckboxField
|
||||
label={t("launch_with_system")}
|
||||
onChange={() => {
|
||||
@ -145,6 +158,27 @@ export function Settings() {
|
||||
}}
|
||||
checked={form.runAtStartup}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<section className={styles.container}>
|
||||
<div className={styles.content}>
|
||||
<section className={styles.settingsCategories}>
|
||||
{categories.map((category) => (
|
||||
<Button
|
||||
key={category}
|
||||
theme={currentCategory === category ? "primary" : "outline"}
|
||||
onClick={() => setCurrentCategory(category)}
|
||||
>
|
||||
{t(category)}
|
||||
</Button>
|
||||
))}
|
||||
</section>
|
||||
|
||||
<h3>{t(currentCategory)}</h3>
|
||||
{renderCategory()}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
@ -5,11 +5,12 @@ export enum GameStatus {
|
||||
CheckingFiles = "checking_files",
|
||||
DownloadingMetadata = "downloading_metadata",
|
||||
Cancelled = "cancelled",
|
||||
Decompressing = "decompressing",
|
||||
Finished = "finished",
|
||||
}
|
||||
|
||||
export enum Downloader {
|
||||
Http,
|
||||
RealDebrid,
|
||||
Torrent,
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ export interface SteamScreenshot {
|
||||
|
||||
export interface SteamVideoSource {
|
||||
max: string;
|
||||
'480': string;
|
||||
"480": string;
|
||||
}
|
||||
|
||||
export interface SteamMovies {
|
||||
@ -35,7 +35,7 @@ export interface SteamAppDetails {
|
||||
short_description: string;
|
||||
publishers: string[];
|
||||
genres: SteamGenre[];
|
||||
movies: SteamMovies[];
|
||||
movies?: SteamMovies[];
|
||||
screenshots: SteamScreenshot[];
|
||||
pc_requirements: {
|
||||
minimum: string;
|
||||
|
67
yarn.lock
67
yarn.lock
@ -1440,7 +1440,7 @@
|
||||
dependencies:
|
||||
undici-types "~5.26.4"
|
||||
|
||||
"@types/node@^18.11.18":
|
||||
"@types/node@^18.11.18", "@types/node@^18.7.13":
|
||||
version "18.19.31"
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-18.19.31.tgz"
|
||||
integrity sha512-ArgCD39YpyyrtFKIqMDvjz79jto5fcI/SVUs2HwB+f0dAzq68yqOdyaSivLiLugSziTpNXLQrVb7RZFmdZzbhA==
|
||||
@ -1523,6 +1523,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/verror/-/verror-1.10.10.tgz#d5a4b56abac169bfbc8b23d291363a682e6fa087"
|
||||
integrity sha512-l4MM0Jppn18hb9xmM6wwD1uTdShpf9Pn80aXTStnK1C94gtPvJcV2FrDmbOQUAQfJ1cKZHktkQUDwEqaAKXMMg==
|
||||
|
||||
"@types/when@^2.4.34":
|
||||
version "2.4.41"
|
||||
resolved "https://registry.yarnpkg.com/@types/when/-/when-2.4.41.tgz#e16e685aa739c696a582b10afc5f1306964846a2"
|
||||
integrity sha512-o/j5X9Bnv6mMG4ZcNJur8UaU17Rl0mLbTZvWcODVVy+Xdh8LEc7s6I0CvbEuTP786LTa0OyJby5P4hI7C+ZJNg==
|
||||
|
||||
"@types/yauzl@^2.9.1":
|
||||
version "2.10.3"
|
||||
resolved "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz"
|
||||
@ -4375,7 +4380,12 @@ minimatch@^8.0.2:
|
||||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6:
|
||||
minimist@1.2.6:
|
||||
version "1.2.6"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
||||
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
||||
|
||||
minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6, minimist@^1.2.8:
|
||||
version "1.2.8"
|
||||
resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz"
|
||||
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
|
||||
@ -4484,6 +4494,20 @@ no-case@^3.0.4:
|
||||
lower-case "^2.0.2"
|
||||
tslib "^2.0.3"
|
||||
|
||||
node-7z-archive@^1.1.7:
|
||||
version "1.1.7"
|
||||
resolved "https://registry.yarnpkg.com/node-7z-archive/-/node-7z-archive-1.1.7.tgz#0b037701e016a651d6040b63d8781b2e7102facd"
|
||||
integrity sha512-gtpWpajFyzeObGiYI9RDq76x5ULnxInvZ1OfA0/MD+2VezcMmMQMK6ITqkvsGEqVy4w/psvmIyowVDoSURAJHg==
|
||||
dependencies:
|
||||
fs-extra "^10.1.0"
|
||||
minimist "^1.2.8"
|
||||
node-sys "^1.2.2"
|
||||
node-unar "^1.0.8"
|
||||
node-wget-fetch "^1.1.3"
|
||||
when "^3.7.8"
|
||||
optionalDependencies:
|
||||
"@types/when" "^2.4.34"
|
||||
|
||||
node-abi@^3.3.0:
|
||||
version "3.62.0"
|
||||
resolved "https://registry.npmjs.org/node-abi/-/node-abi-3.62.0.tgz"
|
||||
@ -4522,6 +4546,40 @@ node-releases@^2.0.14:
|
||||
resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz"
|
||||
integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==
|
||||
|
||||
node-stream-zip@^1.12.0:
|
||||
version "1.15.0"
|
||||
resolved "https://registry.yarnpkg.com/node-stream-zip/-/node-stream-zip-1.15.0.tgz#158adb88ed8004c6c49a396b50a6a5de3bca33ea"
|
||||
integrity sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==
|
||||
|
||||
node-sys@^1.1.7, node-sys@^1.2.2:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/node-sys/-/node-sys-1.2.4.tgz#db9c50fd93c8fc62bc4eafe93eae0fd3696c8028"
|
||||
integrity sha512-71sIz+zgaHfSmP1vHTHXUVb77PqncIB1MBij+Q43fQSz7ceSLrrO5RTTBlnYWDU/M0fEFTZw3Zui/lVeJvoeag==
|
||||
dependencies:
|
||||
minimist "1.2.6"
|
||||
which "^2.0.2"
|
||||
optionalDependencies:
|
||||
"@types/node" "^18.7.13"
|
||||
|
||||
node-unar@^1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/node-unar/-/node-unar-1.0.8.tgz#fbf5b05da2ac24278b6160f3b46231d56a73a673"
|
||||
integrity sha512-AnEdWmV8/Dx1qMB5O2VcemoBmNzW1mhibYNl3YDUI7cVohVuobuIZwxrtRedItO05A6PiLp/HNw1ryg7M17H5g==
|
||||
dependencies:
|
||||
node-sys "^1.1.7"
|
||||
when "^3.7.8"
|
||||
optionalDependencies:
|
||||
fs-extra "^9.0.1"
|
||||
node-stream-zip "^1.12.0"
|
||||
node-wget-fetch "^1.1.2"
|
||||
|
||||
node-wget-fetch@^1.1.2, node-wget-fetch@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/node-wget-fetch/-/node-wget-fetch-1.1.3.tgz#1e4aea2d7093393a961bb9c07cf5c5e33913c437"
|
||||
integrity sha512-TmjZeeL/zAcB4fpok2iJ6FLbjVzSsjKi7rdk0womqvUY2ouitsEN0kGekndshaB7ENnXocrcgUudpvB4Jo3+LA==
|
||||
dependencies:
|
||||
node-fetch "^2.6.7"
|
||||
|
||||
normalize-path@^3.0.0, normalize-path@~3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz"
|
||||
@ -5964,6 +6022,11 @@ whatwg-url@^5.0.0:
|
||||
tr46 "~0.0.3"
|
||||
webidl-conversions "^3.0.0"
|
||||
|
||||
when@^3.7.8:
|
||||
version "3.7.8"
|
||||
resolved "https://registry.yarnpkg.com/when/-/when-3.7.8.tgz#c7130b6a7ea04693e842cdc9e7a1f2aa39a39f82"
|
||||
integrity sha512-5cZ7mecD3eYcMiCH4wtRPA5iFJZ50BJYDfckI5RRpQiktMiYTcn0ccLTZOvcbBume+1304fQztxeNzNS9Gvrnw==
|
||||
|
||||
which-boxed-primitive@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz"
|
||||
|
Loading…
Reference in New Issue
Block a user