Merge branch 'main' into feat/logs-python-process-errors

This commit is contained in:
Zamitto
2024-08-19 21:24:38 -03:00
committed by GitHub
76 changed files with 1532 additions and 1022 deletions

View File

@@ -6,12 +6,12 @@ import {
GameShopCache,
Repack,
UserPreferences,
UserAuth,
} from "@main/entity";
import type { BetterSqlite3ConnectionOptions } from "typeorm/driver/better-sqlite3/BetterSqlite3ConnectionOptions";
import { databasePath } from "./constants";
import migrations from "./migrations";
import { UserAuth } from "./entity/user-auth";
export const createDataSource = (
options: Partial<BetterSqlite3ConnectionOptions>

View File

@@ -1,80 +0,0 @@
declare module "aria2" {
export type Aria2Status =
| "active"
| "waiting"
| "paused"
| "error"
| "complete"
| "removed";
export interface StatusResponse {
gid: string;
status: Aria2Status;
totalLength: string;
completedLength: string;
uploadLength: string;
bitfield: string;
downloadSpeed: string;
uploadSpeed: string;
infoHash?: string;
numSeeders?: string;
seeder?: boolean;
pieceLength: string;
numPieces: string;
connections: string;
errorCode?: string;
errorMessage?: string;
followedBy?: string[];
following: string;
belongsTo: string;
dir: string;
files: {
path: string;
length: string;
completedLength: string;
selected: string;
}[];
bittorrent?: {
announceList: string[][];
comment: string;
creationDate: string;
mode: "single" | "multi";
info: {
name: string;
verifiedLength: string;
verifyIntegrityPending: string;
};
};
}
export default class Aria2 {
constructor(options: any);
open: () => Promise<void>;
call(
method: "addUri",
uris: string[],
options: { dir: string }
): Promise<string>;
call(
method: "tellStatus",
gid: string,
keys?: string[]
): Promise<StatusResponse>;
call(method: "pause", gid: string): Promise<string>;
call(method: "forcePause", gid: string): Promise<string>;
call(method: "unpause", gid: string): Promise<string>;
call(method: "remove", gid: string): Promise<string>;
call(method: "forceRemove", gid: string): Promise<string>;
call(method: "pauseAll"): Promise<string>;
call(method: "forcePauseAll"): Promise<string>;
listNotifications: () => [
"onDownloadStart",
"onDownloadPause",
"onDownloadStop",
"onDownloadComplete",
"onDownloadError",
"onBtDownloadComplete",
];
on: (event: string, callback: (params: any) => void) => void;
}
}

View File

@@ -16,11 +16,14 @@ export class Repack {
@Column("text", { unique: true })
title: string;
/**
* @deprecated Use uris instead
*/
@Column("text", { unique: true })
magnet: string;
/**
* @deprecated
* @deprecated Direct scraping capability has been removed
*/
@Column("int", { nullable: true })
page: number;
@@ -37,6 +40,9 @@ export class Repack {
@ManyToOne(() => DownloadSource, { nullable: true, onDelete: "CASCADE" })
downloadSource: DownloadSource;
@Column("text", { default: "[]" })
uris: string;
@CreateDateColumn()
createdAt: Date;

View File

@@ -26,6 +26,8 @@ const signOut = async (_event: Electron.IpcMainInvokeEvent) => {
/* Disconnects libtorrent */
PythonInstance.killTorrent();
HydraApi.handleSignOut();
await Promise.all([
databaseOperations,
HydraApi.post("/auth/logout").catch(() => {}),

View File

@@ -1,16 +1,11 @@
import { downloadSourceRepository } from "@main/repository";
import { registerEvent } from "../register-event";
const getDownloadSources = async (_event: Electron.IpcMainInvokeEvent) => {
return downloadSourceRepository
.createQueryBuilder("downloadSource")
.leftJoin("downloadSource.repacks", "repacks")
.orderBy("downloadSource.createdAt", "DESC")
.loadRelationCountAndMap(
"downloadSource.repackCount",
"downloadSource.repacks"
)
.getMany();
};
const getDownloadSources = async (_event: Electron.IpcMainInvokeEvent) =>
downloadSourceRepository.find({
order: {
createdAt: "DESC",
},
});
registerEvent("getDownloadSources", getDownloadSources);

View File

@@ -43,6 +43,7 @@ import "./auth/sign-out";
import "./auth/open-auth-window";
import "./auth/get-session-hash";
import "./user/get-user";
import "./user/get-user-blocks";
import "./user/block-user";
import "./user/unblock-user";
import "./user/get-user-friends";
@@ -52,11 +53,9 @@ import "./profile/undo-friendship";
import "./profile/update-friend-request";
import "./profile/update-profile";
import "./profile/send-friend-request";
import { isPortableVersion } from "@main/helpers";
ipcMain.handle("ping", () => "pong");
ipcMain.handle("getVersion", () => app.getVersion());
ipcMain.handle(
"isPortableVersion",
() => process.env.PORTABLE_EXECUTABLE_FILE != null
);
ipcMain.handle("isPortableVersion", () => isPortableVersion());
ipcMain.handle("getDefaultDownloadsPath", () => defaultDownloadsPath);

View File

@@ -4,33 +4,22 @@ import axios from "axios";
import fs from "node:fs";
import path from "node:path";
import { fileTypeFromFile } from "file-type";
import { UserProfile } from "@types";
import { UpdateProfileProps, UserProfile } from "@types";
const patchUserProfile = async (
displayName: string,
profileImageUrl?: string
) => {
if (profileImageUrl) {
return HydraApi.patch("/profile", {
displayName,
profileImageUrl,
});
} else {
return HydraApi.patch("/profile", {
displayName,
});
}
const patchUserProfile = async (updateProfile: UpdateProfileProps) => {
return HydraApi.patch("/profile", updateProfile);
};
const updateProfile = async (
_event: Electron.IpcMainInvokeEvent,
displayName: string,
newProfileImagePath: string | null
updateProfile: UpdateProfileProps
): Promise<UserProfile> => {
if (!newProfileImagePath) {
return patchUserProfile(displayName);
if (!updateProfile.profileImageUrl) {
return patchUserProfile(updateProfile);
}
const newProfileImagePath = updateProfile.profileImageUrl;
const stats = fs.statSync(newProfileImagePath);
const fileBuffer = fs.readFileSync(newProfileImagePath);
const fileSizeInBytes = stats.size;
@@ -53,7 +42,7 @@ const updateProfile = async (
})
.catch(() => undefined);
return patchUserProfile(displayName, profileImageUrl);
return patchUserProfile({ ...updateProfile, profileImageUrl });
};
registerEvent("updateProfile", updateProfile);

View File

@@ -18,7 +18,8 @@ const startGameDownload = async (
_event: Electron.IpcMainInvokeEvent,
payload: StartGameDownloadPayload
) => {
const { repackId, objectID, title, shop, downloadPath, downloader } = payload;
const { repackId, objectID, title, shop, downloadPath, downloader, uri } =
payload;
const [game, repack] = await Promise.all([
gameRepository.findOne({
@@ -54,7 +55,7 @@ const startGameDownload = async (
bytesDownloaded: 0,
downloadPath,
downloader,
uri: repack.magnet,
uri,
isDeleted: false,
}
);
@@ -76,7 +77,7 @@ const startGameDownload = async (
shop,
status: "active",
downloadPath,
uri: repack.magnet,
uri,
})
.then((result) => {
if (iconUrl) {
@@ -100,6 +101,7 @@ const startGameDownload = async (
await downloadQueueRepository.delete({ game: { id: updatedGame!.id } });
await downloadQueueRepository.insert({ game: { id: updatedGame!.id } });
await DownloadManager.cancelDownload(updatedGame!.id);
await DownloadManager.startDownload(updatedGame!);
};

View File

@@ -0,0 +1,13 @@
import { registerEvent } from "../register-event";
import { HydraApi } from "@main/services";
import { UserBlocks } from "@types";
export const getUserBlocks = async (
_event: Electron.IpcMainInvokeEvent,
take: number,
skip: number
): Promise<UserBlocks> => {
return HydraApi.get(`/profile/blocks`, { take, skip });
};
registerEvent("getUserBlocks", getUserBlocks);

View File

@@ -17,7 +17,8 @@ export const insertDownloadsFromSource = async (
const repacks: QueryDeepPartialEntity<Repack>[] = downloads.map(
(download) => ({
title: download.title,
magnet: download.uris[0],
uris: JSON.stringify(download.uris),
magnet: download.uris[0]!,
fileSize: download.fileSize,
repacker: downloadSource.name,
uploadDate: download.uploadDate,

View File

@@ -1,4 +1,5 @@
import axios from "axios";
import { JSDOM } from "jsdom";
import UserAgent from "user-agents";
export const getSteamAppAsset = (
@@ -48,13 +49,19 @@ export const sleep = (ms: number) =>
export const requestWebPage = async (url: string) => {
const userAgent = new UserAgent();
return axios
const data = await axios
.get(url, {
headers: {
"User-Agent": userAgent.toString(),
},
})
.then((response) => response.data);
const { window } = new JSDOM(data);
return window.document;
};
export const isPortableVersion = () =>
process.env.PORTABLE_EXECUTABLE_FILE != null;
export * from "./download-source";

View File

@@ -20,8 +20,6 @@ autoUpdater.setFeedURL({
autoUpdater.logger = logger;
logger.log("Init Hydra");
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) app.quit();
@@ -123,7 +121,6 @@ app.on("window-all-closed", () => {
app.on("before-quit", () => {
/* Disconnects libtorrent */
PythonInstance.kill();
logger.log("Quit Hydra");
});
app.on("activate", () => {

View File

@@ -22,8 +22,9 @@ const loadState = async (userPreferences: UserPreferences | null) => {
import("./events");
if (userPreferences?.realDebridApiToken)
if (userPreferences?.realDebridApiToken) {
RealDebridClient.authorize(userPreferences?.realDebridApiToken);
}
HydraApi.setupApi().then(() => {
uploadGamesBatch();

View File

@@ -1,20 +0,0 @@
import path from "node:path";
import { spawn } from "node:child_process";
import { app } from "electron";
export const startAria2 = () => {
const binaryPath = app.isPackaged
? path.join(process.resourcesPath, "aria2", "aria2c")
: path.join(__dirname, "..", "..", "aria2", "aria2c");
return spawn(
binaryPath,
[
"--enable-rpc",
"--rpc-listen-all",
"--file-allocation=none",
"--allow-overwrite=true",
],
{ stdio: "inherit", windowsHide: true }
);
};

View File

@@ -6,6 +6,8 @@ import { downloadQueueRepository, gameRepository } from "@main/repository";
import { publishDownloadCompleteNotification } from "../notifications";
import { RealDebridDownloader } from "./real-debrid-downloader";
import type { DownloadProgress } from "@types";
import { GofileApi, QiwiApi } from "../hosters";
import { GenericHttpDownloader } from "./generic-http-downloader";
export class DownloadManager {
private static currentDownloader: Downloader | null = null;
@@ -13,10 +15,12 @@ export class DownloadManager {
public static async watchDownloads() {
let status: DownloadProgress | null = null;
if (this.currentDownloader === Downloader.RealDebrid) {
if (this.currentDownloader === Downloader.Torrent) {
status = await PythonInstance.getStatus();
} else if (this.currentDownloader === Downloader.RealDebrid) {
status = await RealDebridDownloader.getStatus();
} else {
status = await PythonInstance.getStatus();
status = await GenericHttpDownloader.getStatus();
}
if (status) {
@@ -62,10 +66,12 @@ export class DownloadManager {
}
static async pauseDownload() {
if (this.currentDownloader === Downloader.RealDebrid) {
if (this.currentDownloader === Downloader.Torrent) {
await PythonInstance.pauseDownload();
} else if (this.currentDownloader === Downloader.RealDebrid) {
await RealDebridDownloader.pauseDownload();
} else {
await PythonInstance.pauseDownload();
await GenericHttpDownloader.pauseDownload();
}
WindowManager.mainWindow?.setProgressBar(-1);
@@ -73,20 +79,16 @@ export class DownloadManager {
}
static async resumeDownload(game: Game) {
if (game.downloader === Downloader.RealDebrid) {
RealDebridDownloader.startDownload(game);
this.currentDownloader = Downloader.RealDebrid;
} else {
PythonInstance.startDownload(game);
this.currentDownloader = Downloader.Torrent;
}
return this.startDownload(game);
}
static async cancelDownload(gameId: number) {
if (this.currentDownloader === Downloader.RealDebrid) {
if (this.currentDownloader === Downloader.Torrent) {
PythonInstance.cancelDownload(gameId);
} else if (this.currentDownloader === Downloader.RealDebrid) {
RealDebridDownloader.cancelDownload(gameId);
} else {
PythonInstance.cancelDownload(gameId);
GenericHttpDownloader.cancelDownload(gameId);
}
WindowManager.mainWindow?.setProgressBar(-1);
@@ -94,12 +96,40 @@ export class DownloadManager {
}
static async startDownload(game: Game) {
if (game.downloader === Downloader.RealDebrid) {
RealDebridDownloader.startDownload(game);
this.currentDownloader = Downloader.RealDebrid;
} else {
PythonInstance.startDownload(game);
this.currentDownloader = Downloader.Torrent;
switch (game.downloader) {
case Downloader.Gofile: {
const id = game!.uri!.split("/").pop();
const token = await GofileApi.authorize();
const downloadLink = await GofileApi.getDownloadLink(id!);
GenericHttpDownloader.startDownload(game, downloadLink, {
Cookie: `accountToken=${token}`,
});
break;
}
case Downloader.PixelDrain: {
const id = game!.uri!.split("/").pop();
await GenericHttpDownloader.startDownload(
game,
`https://pixeldrain.com/api/file/${id}?download`
);
break;
}
case Downloader.Qiwi: {
const downloadUrl = await QiwiApi.getDownloadUrl(game.uri!);
await GenericHttpDownloader.startDownload(game, downloadUrl);
break;
}
case Downloader.Torrent:
PythonInstance.startDownload(game);
break;
case Downloader.RealDebrid:
RealDebridDownloader.startDownload(game);
}
this.currentDownloader = game.downloader;
}
}

View File

@@ -0,0 +1,109 @@
import { Game } from "@main/entity";
import { gameRepository } from "@main/repository";
import { calculateETA } from "./helpers";
import { DownloadProgress } from "@types";
import { HttpDownload } from "./http-download";
export class GenericHttpDownloader {
public static downloads = new Map<number, HttpDownload>();
public static downloadingGame: Game | null = null;
public static async getStatus() {
if (this.downloadingGame) {
const download = this.downloads.get(this.downloadingGame.id)!;
const status = download.getStatus();
if (status) {
const progress =
Number(status.completedLength) / Number(status.totalLength);
await gameRepository.update(
{ id: this.downloadingGame!.id },
{
bytesDownloaded: Number(status.completedLength),
fileSize: Number(status.totalLength),
progress,
status: "active",
folderName: status.folderName,
}
);
const result = {
numPeers: 0,
numSeeds: 0,
downloadSpeed: status.downloadSpeed,
timeRemaining: calculateETA(
status.totalLength,
status.completedLength,
status.downloadSpeed
),
isDownloadingMetadata: false,
isCheckingFiles: false,
progress,
gameId: this.downloadingGame!.id,
} as DownloadProgress;
if (progress === 1) {
this.downloads.delete(this.downloadingGame.id);
this.downloadingGame = null;
}
return result;
}
}
return null;
}
static async pauseDownload() {
if (this.downloadingGame) {
const httpDownload = this.downloads.get(this.downloadingGame!.id!);
if (httpDownload) {
await httpDownload.pauseDownload();
}
this.downloadingGame = null;
}
}
static async startDownload(
game: Game,
downloadUrl: string,
headers?: Record<string, string>
) {
this.downloadingGame = game;
if (this.downloads.has(game.id)) {
await this.resumeDownload(game.id!);
return;
}
const httpDownload = new HttpDownload(
game.downloadPath!,
downloadUrl,
headers
);
httpDownload.startDownload();
this.downloads.set(game.id!, httpDownload);
}
static async cancelDownload(gameId: number) {
const httpDownload = this.downloads.get(gameId);
if (httpDownload) {
await httpDownload.cancelDownload();
this.downloads.delete(gameId);
}
}
static async resumeDownload(gameId: number) {
const httpDownload = this.downloads.get(gameId);
if (httpDownload) {
await httpDownload.resumeDownload();
}
}
}

View File

@@ -1,68 +1,54 @@
import type { ChildProcess } from "node:child_process";
import { logger } from "../logger";
import { sleep } from "@main/helpers";
import { startAria2 } from "../aria2c";
import Aria2 from "aria2";
import { WindowManager } from "../window-manager";
import path from "node:path";
export class HttpDownload {
private static connected = false;
private static aria2c: ChildProcess | null = null;
private downloadItem: Electron.DownloadItem;
private static aria2 = new Aria2({});
constructor(
private downloadPath: string,
private downloadUrl: string,
private headers?: Record<string, string>
) {}
private static async connect() {
this.aria2c = startAria2();
let retries = 0;
while (retries < 4 && !this.connected) {
try {
await this.aria2.open();
logger.log("Connected to aria2");
this.connected = true;
} catch (err) {
await sleep(100);
logger.log("Failed to connect to aria2, retrying...");
retries++;
}
}
}
public static getStatus(gid: string) {
if (this.connected) {
return this.aria2.call("tellStatus", gid);
}
return null;
}
public static disconnect() {
if (this.aria2c) {
this.aria2c.kill();
this.connected = false;
}
}
static async cancelDownload(gid: string) {
await this.aria2.call("forceRemove", gid);
}
static async pauseDownload(gid: string) {
await this.aria2.call("forcePause", gid);
}
static async resumeDownload(gid: string) {
await this.aria2.call("unpause", gid);
}
static async startDownload(downloadPath: string, downloadUrl: string) {
if (!this.connected) await this.connect();
const options = {
dir: downloadPath,
public getStatus() {
return {
completedLength: this.downloadItem.getReceivedBytes(),
totalLength: this.downloadItem.getTotalBytes(),
downloadSpeed: this.downloadItem.getCurrentBytesPerSecond(),
folderName: this.downloadItem.getFilename(),
};
}
return this.aria2.call("addUri", [downloadUrl], options);
async cancelDownload() {
this.downloadItem.cancel();
}
async pauseDownload() {
this.downloadItem.pause();
}
async resumeDownload() {
this.downloadItem.resume();
}
async startDownload() {
return new Promise((resolve) => {
const options = this.headers ? { headers: this.headers } : {};
WindowManager.mainWindow?.webContents.downloadURL(
this.downloadUrl,
options
);
WindowManager.mainWindow?.webContents.session.once(
"will-download",
(_event, item, _webContents) => {
this.downloadItem = item;
item.setSavePath(path.join(this.downloadPath, item.getFilename()));
resolve(null);
}
);
});
}
}

View File

@@ -1,162 +1,72 @@
import { Game } from "@main/entity";
import { RealDebridClient } from "../real-debrid";
import { gameRepository } from "@main/repository";
import { calculateETA } from "./helpers";
import { DownloadProgress } from "@types";
import { HttpDownload } from "./http-download";
import { GenericHttpDownloader } from "./generic-http-downloader";
export class RealDebridDownloader {
private static downloads = new Map<number, string>();
private static downloadingGame: Game | null = null;
export class RealDebridDownloader extends GenericHttpDownloader {
private static realDebridTorrentId: string | null = null;
private static async getRealDebridDownloadUrl() {
if (this.realDebridTorrentId) {
const torrentInfo = await RealDebridClient.getTorrentInfo(
let torrentInfo = await RealDebridClient.getTorrentInfo(
this.realDebridTorrentId
);
const { status, links } = torrentInfo;
if (status === "waiting_files_selection") {
if (torrentInfo.status === "waiting_files_selection") {
await RealDebridClient.selectAllFiles(this.realDebridTorrentId);
return null;
torrentInfo = await RealDebridClient.getTorrentInfo(
this.realDebridTorrentId
);
}
const { links, status } = torrentInfo;
if (status === "downloaded") {
const [link] = links;
const { download } = await RealDebridClient.unrestrictLink(link);
return decodeURIComponent(download);
}
return null;
}
return null;
}
public static async getStatus() {
if (this.downloadingGame) {
const gid = this.downloads.get(this.downloadingGame.id)!;
const status = await HttpDownload.getStatus(gid);
if (status) {
const progress =
Number(status.completedLength) / Number(status.totalLength);
await gameRepository.update(
{ id: this.downloadingGame!.id },
{
bytesDownloaded: Number(status.completedLength),
fileSize: Number(status.totalLength),
progress,
status: "active",
}
);
const result = {
numPeers: 0,
numSeeds: 0,
downloadSpeed: Number(status.downloadSpeed),
timeRemaining: calculateETA(
Number(status.totalLength),
Number(status.completedLength),
Number(status.downloadSpeed)
),
isDownloadingMetadata: false,
isCheckingFiles: false,
progress,
gameId: this.downloadingGame!.id,
} as DownloadProgress;
if (progress === 1) {
this.downloads.delete(this.downloadingGame.id);
this.realDebridTorrentId = null;
this.downloadingGame = null;
}
return result;
}
}
if (this.realDebridTorrentId && this.downloadingGame) {
const torrentInfo = await RealDebridClient.getTorrentInfo(
this.realDebridTorrentId
if (this.downloadingGame?.uri) {
const { download } = await RealDebridClient.unrestrictLink(
this.downloadingGame?.uri
);
const { status } = torrentInfo;
if (status === "downloaded") {
this.startDownload(this.downloadingGame);
}
const progress = torrentInfo.progress / 100;
const totalDownloaded = progress * torrentInfo.bytes;
return {
numPeers: 0,
numSeeds: torrentInfo.seeders,
downloadSpeed: torrentInfo.speed,
timeRemaining: calculateETA(
torrentInfo.bytes,
totalDownloaded,
torrentInfo.speed
),
isDownloadingMetadata: status === "magnet_conversion",
} as DownloadProgress;
return decodeURIComponent(download);
}
return null;
}
static async pauseDownload() {
const gid = this.downloads.get(this.downloadingGame!.id!);
if (gid) {
await HttpDownload.pauseDownload(gid);
}
this.realDebridTorrentId = null;
this.downloadingGame = null;
}
static async startDownload(game: Game) {
this.downloadingGame = game;
if (this.downloads.has(game.id)) {
await this.resumeDownload(game.id!);
this.downloadingGame = game;
return;
}
this.realDebridTorrentId = await RealDebridClient.getTorrentId(game!.uri!);
if (game.uri?.startsWith("magnet:")) {
this.realDebridTorrentId = await RealDebridClient.getTorrentId(
game!.uri!
);
}
this.downloadingGame = game;
const downloadUrl = await this.getRealDebridDownloadUrl();
if (downloadUrl) {
this.realDebridTorrentId = null;
const gid = await HttpDownload.startDownload(
game.downloadPath!,
downloadUrl
);
const httpDownload = new HttpDownload(game.downloadPath!, downloadUrl);
httpDownload.startDownload();
this.downloads.set(game.id!, gid);
}
}
static async cancelDownload(gameId: number) {
const gid = this.downloads.get(gameId);
if (gid) {
await HttpDownload.cancelDownload(gid);
this.downloads.delete(gameId);
}
}
static async resumeDownload(gameId: number) {
const gid = this.downloads.get(gameId);
if (gid) {
await HttpDownload.resumeDownload(gid);
this.downloads.set(game.id!, httpDownload);
}
}
}

View File

@@ -0,0 +1,63 @@
import axios from "axios";
export interface GofileAccountsReponse {
id: string;
token: string;
}
export interface GofileContentChild {
id: string;
link: string;
}
export interface GofileContentsResponse {
id: string;
type: string;
children: Record<string, GofileContentChild>;
}
export const WT = "4fd6sg89d7s6";
export class GofileApi {
private static token: string;
public static async authorize() {
const response = await axios.post<{
status: string;
data: GofileAccountsReponse;
}>("https://api.gofile.io/accounts");
if (response.data.status === "ok") {
this.token = response.data.data.token;
return this.token;
}
throw new Error("Failed to authorize");
}
public static async getDownloadLink(id: string) {
const searchParams = new URLSearchParams({
wt: WT,
});
const response = await axios.get<{
status: string;
data: GofileContentsResponse;
}>(`https://api.gofile.io/contents/${id}?${searchParams.toString()}`, {
headers: {
Authorization: `Bearer ${this.token}`,
},
});
if (response.data.status === "ok") {
if (response.data.data.type !== "folder") {
throw new Error("Only folders are supported");
}
const [firstChild] = Object.values(response.data.data.children);
return firstChild.link;
}
throw new Error("Failed to get download link");
}
}

View File

@@ -0,0 +1,2 @@
export * from "./gofile";
export * from "./qiwi";

View File

@@ -0,0 +1,15 @@
import { requestWebPage } from "@main/helpers";
export class QiwiApi {
public static async getDownloadUrl(url: string) {
const document = await requestWebPage(url);
const fileName = document.querySelector("h1")?.textContent;
const slug = url.split("/").pop();
const extension = fileName?.split(".").pop();
const downloadUrl = `https://spyderrock.com/${slug}.${extension}`;
return downloadUrl;
}
}

View File

@@ -1,5 +1,4 @@
import axios from "axios";
import { JSDOM } from "jsdom";
import { requestWebPage } from "@main/helpers";
import { HowLongToBeatCategory } from "@types";
import { formatName } from "@shared";
@@ -52,10 +51,7 @@ const parseListItems = ($lis: Element[]) => {
export const getHowLongToBeatGame = async (
id: string
): Promise<HowLongToBeatCategory[]> => {
const response = await requestWebPage(`https://howlongtobeat.com/game/${id}`);
const { window } = new JSDOM(response);
const { document } = window;
const document = await requestWebPage(`https://howlongtobeat.com/game/${id}`);
const $ul = document.querySelector(".shadow_shadow ul");
if (!$ul) return [];

View File

@@ -64,59 +64,67 @@ export class HydraApi {
}
}
static handleSignOut() {
this.userAuth = {
authToken: "",
refreshToken: "",
expirationTimestamp: 0,
};
}
static async setupApi() {
this.instance = axios.create({
baseURL: import.meta.env.MAIN_VITE_API_URL,
});
this.instance.interceptors.request.use(
(request) => {
logger.log(" ---- REQUEST -----");
logger.log(request.method, request.url, request.params, request.data);
return request;
},
(error) => {
logger.error("request error", error);
return Promise.reject(error);
}
);
// this.instance.interceptors.request.use(
// (request) => {
// logger.log(" ---- REQUEST -----");
// logger.log(request.method, request.url, request.params, request.data);
// return request;
// },
// (error) => {
// logger.error("request error", error);
// return Promise.reject(error);
// }
// );
this.instance.interceptors.response.use(
(response) => {
logger.log(" ---- RESPONSE -----");
logger.log(
response.status,
response.config.method,
response.config.url,
response.data
);
return response;
},
(error) => {
logger.error(" ---- RESPONSE ERROR -----");
// this.instance.interceptors.response.use(
// (response) => {
// logger.log(" ---- RESPONSE -----");
// logger.log(
// response.status,
// response.config.method,
// response.config.url,
// response.data
// );
// return response;
// },
// (error) => {
// logger.error(" ---- RESPONSE ERROR -----");
const { config } = error;
// const { config } = error;
logger.error(
config.method,
config.baseURL,
config.url,
config.headers,
config.data
);
// logger.error(
// config.method,
// config.baseURL,
// config.url,
// config.headers,
// config.data
// );
if (error.response) {
logger.error("Response", error.response.status, error.response.data);
} else if (error.request) {
logger.error("Request", error.request);
} else {
logger.error("Error", error.message);
}
// if (error.response) {
// logger.error("Response", error.response.status, error.response.data);
// } else if (error.request) {
// logger.error("Request", error.request);
// } else {
// logger.error("Error", error.message);
// }
logger.error(" ----- END RESPONSE ERROR -------");
return Promise.reject(error);
}
);
// logger.error(" ----- END RESPONSE ERROR -------");
// return Promise.reject(error);
// }
// );
const userAuth = await userAuthRepository.findOne({
where: { id: 1 },

View File

@@ -46,7 +46,7 @@ export class RealDebridClient {
static async selectAllFiles(id: string) {
const searchParams = new URLSearchParams({ files: "all" });
await this.instance.post(
return this.instance.post(
`/torrents/selectFiles/${id}`,
searchParams.toString()
);

View File

@@ -8,11 +8,25 @@ export class RepacksManager {
private static repacksIndex = new flexSearch.Index();
public static async updateRepacks() {
this.repacks = await repackRepository.find({
order: {
createdAt: "DESC",
},
});
this.repacks = await repackRepository
.find({
order: {
createdAt: "DESC",
},
})
.then((repacks) =>
repacks.map((repack) => {
const uris: string[] = [];
const magnet = repack?.magnet;
if (magnet) uris.push(magnet);
return {
...repack,
uris: [...uris, ...JSON.parse(repack.uris)],
};
})
);
for (let i = 0; i < this.repacks.length; i++) {
this.repacksIndex.remove(i);