mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-03 00:33:49 +03:00
feat: removing sentry
This commit is contained in:
parent
59b1f2d5a5
commit
dc7591ee28
@ -1,5 +1,4 @@
|
|||||||
MAIN_VITE_STEAMGRIDDB_API_KEY=YOUR_API_KEY
|
MAIN_VITE_STEAMGRIDDB_API_KEY=YOUR_API_KEY
|
||||||
MAIN_VITE_ONLINEFIX_USERNAME=YOUR_USERNAME
|
MAIN_VITE_ONLINEFIX_USERNAME=YOUR_USERNAME
|
||||||
MAIN_VITE_ONLINEFIX_PASSWORD=YOUR_PASSWORD
|
MAIN_VITE_ONLINEFIX_PASSWORD=YOUR_PASSWORD
|
||||||
MAIN_VITE_SENTRY_DSN=YOUR_SENTRY_DSN
|
|
||||||
RENDERER_VITE_SENTRY_DSN=YOUR_SENTRY_DSN
|
|
||||||
|
14
.github/workflows/build.yml
vendored
14
.github/workflows/build.yml
vendored
@ -40,9 +40,6 @@ jobs:
|
|||||||
run: yarn build:linux
|
run: yarn build:linux
|
||||||
env:
|
env:
|
||||||
MAIN_VITE_STEAMGRIDDB_API_KEY: ${{ secrets.STEAMGRIDDB_API_KEY }}
|
MAIN_VITE_STEAMGRIDDB_API_KEY: ${{ secrets.STEAMGRIDDB_API_KEY }}
|
||||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
|
||||||
MAIN_VITE_SENTRY_DSN: ${{ vars.SENTRY_DSN }}
|
|
||||||
RENDERER_VITE_SENTRY_DSN: ${{ vars.SENTRY_DSN }}
|
|
||||||
MAIN_VITE_ONLINEFIX_USERNAME: ${{ secrets.ONLINEFIX_USERNAME }}
|
MAIN_VITE_ONLINEFIX_USERNAME: ${{ secrets.ONLINEFIX_USERNAME }}
|
||||||
MAIN_VITE_ONLINEFIX_PASSWORD: ${{ secrets.ONLINEFIX_PASSWORD }}
|
MAIN_VITE_ONLINEFIX_PASSWORD: ${{ secrets.ONLINEFIX_PASSWORD }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@ -52,9 +49,6 @@ jobs:
|
|||||||
run: yarn build:win
|
run: yarn build:win
|
||||||
env:
|
env:
|
||||||
MAIN_VITE_STEAMGRIDDB_API_KEY: ${{ secrets.STEAMGRIDDB_API_KEY }}
|
MAIN_VITE_STEAMGRIDDB_API_KEY: ${{ secrets.STEAMGRIDDB_API_KEY }}
|
||||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
|
||||||
MAIN_VITE_SENTRY_DSN: ${{ vars.SENTRY_DSN }}
|
|
||||||
RENDERER_VITE_SENTRY_DSN: ${{ vars.SENTRY_DSN }}
|
|
||||||
MAIN_VITE_ONLINEFIX_USERNAME: ${{ secrets.ONLINEFIX_USERNAME }}
|
MAIN_VITE_ONLINEFIX_USERNAME: ${{ secrets.ONLINEFIX_USERNAME }}
|
||||||
MAIN_VITE_ONLINEFIX_PASSWORD: ${{ secrets.ONLINEFIX_PASSWORD }}
|
MAIN_VITE_ONLINEFIX_PASSWORD: ${{ secrets.ONLINEFIX_PASSWORD }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@ -72,11 +66,3 @@ jobs:
|
|||||||
dist/*.tar.gz
|
dist/*.tar.gz
|
||||||
dist/*.yml
|
dist/*.yml
|
||||||
dist/*.blockmap
|
dist/*.blockmap
|
||||||
|
|
||||||
- name: VirusTotal Scan
|
|
||||||
uses: crazy-max/ghaction-virustotal@v4
|
|
||||||
if: matrix.os == 'windows-latest'
|
|
||||||
with:
|
|
||||||
vt_api_key: ${{ secrets.VT_API_KEY }}
|
|
||||||
files: |
|
|
||||||
./dist/*.exe
|
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -10,5 +10,3 @@ out
|
|||||||
.env
|
.env
|
||||||
.vite
|
.vite
|
||||||
|
|
||||||
# Sentry Config File
|
|
||||||
.env.sentry-build-plugin
|
|
||||||
|
@ -7,17 +7,10 @@ import {
|
|||||||
} from "electron-vite";
|
} from "electron-vite";
|
||||||
import react from "@vitejs/plugin-react";
|
import react from "@vitejs/plugin-react";
|
||||||
import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin";
|
import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin";
|
||||||
import { sentryVitePlugin } from "@sentry/vite-plugin";
|
|
||||||
import svgr from "vite-plugin-svgr";
|
import svgr from "vite-plugin-svgr";
|
||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig(({ mode }) => {
|
||||||
loadEnv(mode);
|
loadEnv(mode);
|
||||||
|
|
||||||
const sentryPlugin = sentryVitePlugin({
|
|
||||||
authToken: process.env.SENTRY_AUTH_TOKEN,
|
|
||||||
org: "hydra-launcher",
|
|
||||||
project: "hydra-launcher",
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
main: {
|
main: {
|
||||||
build: {
|
build: {
|
||||||
@ -34,7 +27,7 @@ export default defineConfig(({ mode }) => {
|
|||||||
"@shared": resolve("src/shared"),
|
"@shared": resolve("src/shared"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [externalizeDepsPlugin(), swcPlugin(), sentryPlugin],
|
plugins: [externalizeDepsPlugin(), swcPlugin()],
|
||||||
},
|
},
|
||||||
preload: {
|
preload: {
|
||||||
plugins: [externalizeDepsPlugin()],
|
plugins: [externalizeDepsPlugin()],
|
||||||
@ -50,7 +43,7 @@ export default defineConfig(({ mode }) => {
|
|||||||
"@shared": resolve("src/shared"),
|
"@shared": resolve("src/shared"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [svgr(), react(), vanillaExtractPlugin(), sentryPlugin],
|
plugins: [svgr(), react(), vanillaExtractPlugin()],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hydra",
|
"name": "hydra",
|
||||||
"version": "1.1.1",
|
"version": "1.2.0",
|
||||||
"description": "Hydra",
|
"description": "Hydra",
|
||||||
"main": "./out/main/index.js",
|
"main": "./out/main/index.js",
|
||||||
"author": "Los Broxas",
|
"author": "Los Broxas",
|
||||||
@ -33,9 +33,6 @@
|
|||||||
"@fontsource/fira-sans": "^5.0.20",
|
"@fontsource/fira-sans": "^5.0.20",
|
||||||
"@primer/octicons-react": "^19.9.0",
|
"@primer/octicons-react": "^19.9.0",
|
||||||
"@reduxjs/toolkit": "^2.2.3",
|
"@reduxjs/toolkit": "^2.2.3",
|
||||||
"@sentry/electron": "^4.23.0",
|
|
||||||
"@sentry/react": "^7.111.0",
|
|
||||||
"@sentry/vite-plugin": "^2.16.1",
|
|
||||||
"@vanilla-extract/css": "^1.14.2",
|
"@vanilla-extract/css": "^1.14.2",
|
||||||
"@vanilla-extract/recipes": "^0.5.2",
|
"@vanilla-extract/recipes": "^0.5.2",
|
||||||
"auto-launch": "^5.0.6",
|
"auto-launch": "^5.0.6",
|
||||||
|
@ -12,8 +12,8 @@ export const repackersOn1337x = [
|
|||||||
export const repackers = [
|
export const repackers = [
|
||||||
...repackersOn1337x,
|
...repackersOn1337x,
|
||||||
"Xatab",
|
"Xatab",
|
||||||
"CPG",
|
|
||||||
"TinyRepacks",
|
"TinyRepacks",
|
||||||
|
"CPG",
|
||||||
"GOG",
|
"GOG",
|
||||||
"onlinefix",
|
"onlinefix",
|
||||||
] as const;
|
] as const;
|
||||||
|
@ -3,9 +3,7 @@ import {
|
|||||||
Game,
|
Game,
|
||||||
GameShopCache,
|
GameShopCache,
|
||||||
Repack,
|
Repack,
|
||||||
RepackerFriendlyName,
|
|
||||||
UserPreferences,
|
UserPreferences,
|
||||||
MigrationScript,
|
|
||||||
SteamGame,
|
SteamGame,
|
||||||
} from "@main/entity";
|
} from "@main/entity";
|
||||||
import type { SqliteConnectionOptions } from "typeorm/driver/sqlite/SqliteConnectionOptions";
|
import type { SqliteConnectionOptions } from "typeorm/driver/sqlite/SqliteConnectionOptions";
|
||||||
@ -16,15 +14,7 @@ export const createDataSource = (options: Partial<SqliteConnectionOptions>) =>
|
|||||||
new DataSource({
|
new DataSource({
|
||||||
type: "better-sqlite3",
|
type: "better-sqlite3",
|
||||||
database: databasePath,
|
database: databasePath,
|
||||||
entities: [
|
entities: [Game, Repack, UserPreferences, GameShopCache, SteamGame],
|
||||||
Game,
|
|
||||||
Repack,
|
|
||||||
RepackerFriendlyName,
|
|
||||||
UserPreferences,
|
|
||||||
GameShopCache,
|
|
||||||
MigrationScript,
|
|
||||||
SteamGame,
|
|
||||||
],
|
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
export * from "./game.entity";
|
export * from "./game.entity";
|
||||||
export * from "./repack.entity";
|
export * from "./repack.entity";
|
||||||
export * from "./repacker-friendly-name.entity";
|
|
||||||
export * from "./user-preferences.entity";
|
export * from "./user-preferences.entity";
|
||||||
export * from "./game-shop-cache.entity";
|
export * from "./game-shop-cache.entity";
|
||||||
export * from "./migration-script.entity";
|
|
||||||
export * from "./steam-game.entity";
|
export * from "./steam-game.entity";
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
import {
|
|
||||||
Entity,
|
|
||||||
PrimaryGeneratedColumn,
|
|
||||||
Column,
|
|
||||||
CreateDateColumn,
|
|
||||||
UpdateDateColumn,
|
|
||||||
} from "typeorm";
|
|
||||||
|
|
||||||
@Entity("migration_script")
|
|
||||||
export class MigrationScript {
|
|
||||||
@PrimaryGeneratedColumn()
|
|
||||||
id: number;
|
|
||||||
|
|
||||||
@Column("text", { unique: true })
|
|
||||||
version: string;
|
|
||||||
|
|
||||||
@CreateDateColumn()
|
|
||||||
createdAt: Date;
|
|
||||||
|
|
||||||
@UpdateDateColumn()
|
|
||||||
updatedAt: Date;
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
import {
|
|
||||||
Entity,
|
|
||||||
PrimaryGeneratedColumn,
|
|
||||||
Column,
|
|
||||||
CreateDateColumn,
|
|
||||||
UpdateDateColumn,
|
|
||||||
} from "typeorm";
|
|
||||||
|
|
||||||
@Entity("repacker_friendly_name")
|
|
||||||
export class RepackerFriendlyName {
|
|
||||||
@PrimaryGeneratedColumn()
|
|
||||||
id: number;
|
|
||||||
|
|
||||||
@Column("text", { unique: true })
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
@Column("text")
|
|
||||||
friendlyName: string;
|
|
||||||
|
|
||||||
@CreateDateColumn()
|
|
||||||
createdAt: Date;
|
|
||||||
|
|
||||||
@UpdateDateColumn()
|
|
||||||
updatedAt: Date;
|
|
||||||
}
|
|
@ -26,9 +26,6 @@ export class UserPreferences {
|
|||||||
@Column("boolean", { default: false })
|
@Column("boolean", { default: false })
|
||||||
repackUpdatesNotificationsEnabled: boolean;
|
repackUpdatesNotificationsEnabled: boolean;
|
||||||
|
|
||||||
@Column("boolean", { default: true })
|
|
||||||
telemetryEnabled: boolean;
|
|
||||||
|
|
||||||
@Column("boolean", { default: false })
|
@Column("boolean", { default: false })
|
||||||
preferQuitInsteadOfHiding: boolean;
|
preferQuitInsteadOfHiding: boolean;
|
||||||
|
|
||||||
|
@ -92,7 +92,4 @@ const getRecentlyAddedCatalogue = async (
|
|||||||
return results.slice(0, resultSize);
|
return results.slice(0, resultSize);
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(getCatalogue, {
|
registerEvent("getCatalogue", getCatalogue);
|
||||||
name: "getCatalogue",
|
|
||||||
memoize: true,
|
|
||||||
});
|
|
||||||
|
@ -72,7 +72,4 @@ const getGameShopDetails = async (
|
|||||||
throw new Error("Not implemented");
|
throw new Error("Not implemented");
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(getGameShopDetails, {
|
registerEvent("getGameShopDetails", getGameShopDetails);
|
||||||
name: "getGameShopDetails",
|
|
||||||
memoize: true,
|
|
||||||
});
|
|
||||||
|
@ -36,7 +36,4 @@ const getGames = async (
|
|||||||
return { results, cursor: i };
|
return { results, cursor: i };
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(getGames, {
|
registerEvent("getGames", getGames);
|
||||||
name: "getGames",
|
|
||||||
memoize: true,
|
|
||||||
});
|
|
||||||
|
@ -42,7 +42,4 @@ const getHowLongToBeat = async (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(getHowLongToBeat, {
|
registerEvent("getHowLongToBeat", getHowLongToBeat);
|
||||||
name: "getHowLongToBeat",
|
|
||||||
memoize: true,
|
|
||||||
});
|
|
||||||
|
@ -36,6 +36,4 @@ const getRandomGame = async (_event: Electron.IpcMainInvokeEvent) => {
|
|||||||
return state.games[state.index];
|
return state.games[state.index];
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(getRandomGame, {
|
registerEvent("getRandomGame", getRandomGame);
|
||||||
name: "getRandomGame",
|
|
||||||
});
|
|
||||||
|
@ -8,7 +8,4 @@ const searchGameRepacks = (
|
|||||||
return searchRepacks(query);
|
return searchRepacks(query);
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(searchGameRepacks, {
|
registerEvent("searchGameRepacks", searchGameRepacks);
|
||||||
name: "searchGameRepacks",
|
|
||||||
memoize: true,
|
|
||||||
});
|
|
||||||
|
@ -9,7 +9,4 @@ const searchGamesEvent = async (
|
|||||||
return searchGames({ query, take: 12 });
|
return searchGames({ query, take: 12 });
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(searchGamesEvent, {
|
registerEvent("searchGames", searchGamesEvent);
|
||||||
name: "searchGames",
|
|
||||||
memoize: true,
|
|
||||||
});
|
|
||||||
|
@ -7,6 +7,4 @@ const getDiskFreeSpace = async (
|
|||||||
path: string
|
path: string
|
||||||
) => checkDiskSpace(path);
|
) => checkDiskSpace(path);
|
||||||
|
|
||||||
registerEvent(getDiskFreeSpace, {
|
registerEvent("getDiskFreeSpace", getDiskFreeSpace);
|
||||||
name: "getDiskFreeSpace",
|
|
||||||
});
|
|
||||||
|
@ -14,7 +14,6 @@ import "./library/close-game";
|
|||||||
import "./library/delete-game-folder";
|
import "./library/delete-game-folder";
|
||||||
import "./library/get-game-by-object-id";
|
import "./library/get-game-by-object-id";
|
||||||
import "./library/get-library";
|
import "./library/get-library";
|
||||||
import "./library/get-repackers-friendly-names";
|
|
||||||
import "./library/open-game";
|
import "./library/open-game";
|
||||||
import "./library/open-game-installer";
|
import "./library/open-game-installer";
|
||||||
import "./library/remove-game";
|
import "./library/remove-game";
|
||||||
|
@ -42,6 +42,4 @@ const addGameToLibrary = async (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(addGameToLibrary, {
|
registerEvent("addGameToLibrary", addGameToLibrary);
|
||||||
name: "addGameToLibrary",
|
|
||||||
});
|
|
||||||
|
@ -36,6 +36,4 @@ const closeGame = async (
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(closeGame, {
|
registerEvent("closeGame", closeGame);
|
||||||
name: "closeGame",
|
|
||||||
});
|
|
||||||
|
@ -47,6 +47,4 @@ const deleteGameFolder = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(deleteGameFolder, {
|
registerEvent("deleteGameFolder", deleteGameFolder);
|
||||||
name: "deleteGameFolder",
|
|
||||||
});
|
|
||||||
|
@ -16,6 +16,4 @@ const getGameByObjectID = async (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
registerEvent(getGameByObjectID, {
|
registerEvent("getGameByObjectID", getGameByObjectID);
|
||||||
name: "getGameByObjectID",
|
|
||||||
});
|
|
||||||
|
@ -28,6 +28,4 @@ const getLibrary = async () =>
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
registerEvent(getLibrary, {
|
registerEvent("getLibrary", getLibrary);
|
||||||
name: "getLibrary",
|
|
||||||
});
|
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
import { registerEvent } from "../register-event";
|
|
||||||
import { stateManager } from "@main/state-manager";
|
|
||||||
|
|
||||||
const getRepackersFriendlyNames = async () =>
|
|
||||||
stateManager.getValue("repackersFriendlyNames").reduce((prev, next) => {
|
|
||||||
return { ...prev, [next.name]: next.friendlyName };
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
registerEvent(getRepackersFriendlyNames, {
|
|
||||||
name: "getRepackersFriendlyNames",
|
|
||||||
memoize: true,
|
|
||||||
});
|
|
@ -55,6 +55,4 @@ const openGameInstaller = async (
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(openGameInstaller, {
|
registerEvent("openGameInstaller", openGameInstaller);
|
||||||
name: "openGameInstaller",
|
|
||||||
});
|
|
||||||
|
@ -13,6 +13,4 @@ const openGame = async (
|
|||||||
shell.openPath(executablePath);
|
shell.openPath(executablePath);
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(openGame, {
|
registerEvent("openGame", openGame);
|
||||||
name: "openGame",
|
|
||||||
});
|
|
||||||
|
@ -8,6 +8,4 @@ const removeGameFromLibrary = async (
|
|||||||
gameRepository.update({ id: gameId }, { isDeleted: true });
|
gameRepository.update({ id: gameId }, { isDeleted: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(removeGameFromLibrary, {
|
registerEvent("removeGameFromLibrary", removeGameFromLibrary);
|
||||||
name: "removeGameFromLibrary",
|
|
||||||
});
|
|
||||||
|
@ -20,6 +20,4 @@ const removeGame = async (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(removeGame, {
|
registerEvent("removeGame", removeGame);
|
||||||
name: "removeGame",
|
|
||||||
});
|
|
||||||
|
@ -4,6 +4,4 @@ import { registerEvent } from "../register-event";
|
|||||||
const openExternal = async (_event: Electron.IpcMainInvokeEvent, src: string) =>
|
const openExternal = async (_event: Electron.IpcMainInvokeEvent, src: string) =>
|
||||||
shell.openExternal(src);
|
shell.openExternal(src);
|
||||||
|
|
||||||
registerEvent(openExternal, {
|
registerEvent("openExternal", openExternal);
|
||||||
name: "openExternal",
|
|
||||||
});
|
|
||||||
|
@ -13,6 +13,4 @@ const showOpenDialog = async (
|
|||||||
throw new Error("Main window is not available");
|
throw new Error("Main window is not available");
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(showOpenDialog, {
|
registerEvent("showOpenDialog", showOpenDialog);
|
||||||
name: "showOpenDialog",
|
|
||||||
});
|
|
||||||
|
@ -1,37 +1,11 @@
|
|||||||
import { ipcMain } from "electron";
|
import { ipcMain } from "electron";
|
||||||
|
|
||||||
import { stateManager } from "@main/state-manager";
|
|
||||||
|
|
||||||
interface EventArgs {
|
|
||||||
name: string;
|
|
||||||
memoize?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const registerEvent = (
|
export const registerEvent = (
|
||||||
listener: (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any,
|
name: string,
|
||||||
{ name, memoize = false }: EventArgs
|
listener: (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any
|
||||||
) => {
|
) => {
|
||||||
ipcMain.handle(name, (event: Electron.IpcMainInvokeEvent, ...args) => {
|
ipcMain.handle(name, async (event: Electron.IpcMainInvokeEvent, ...args) => {
|
||||||
const eventResults = stateManager.getValue("eventResults");
|
|
||||||
const keys = Array.from(eventResults.keys());
|
|
||||||
|
|
||||||
const key = [name, args] as [string, any[]];
|
|
||||||
|
|
||||||
const memoizationKey = keys.find(([memoizedEvent, memoizedArgs]) => {
|
|
||||||
const sameEvent = name === memoizedEvent;
|
|
||||||
const sameArgs = memoizedArgs.every((arg, index) => arg === args[index]);
|
|
||||||
|
|
||||||
return sameEvent && sameArgs;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (memoizationKey) return eventResults.get(memoizationKey);
|
|
||||||
|
|
||||||
return Promise.resolve(listener(event, ...args)).then((result) => {
|
return Promise.resolve(listener(event, ...args)).then((result) => {
|
||||||
if (memoize) {
|
|
||||||
eventResults.set(key, JSON.parse(JSON.stringify(result)));
|
|
||||||
stateManager.setValue("eventResults", eventResults);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result) return result;
|
if (!result) return result;
|
||||||
return JSON.parse(JSON.stringify(result));
|
return JSON.parse(JSON.stringify(result));
|
||||||
});
|
});
|
||||||
|
@ -50,6 +50,4 @@ const cancelGameDownload = async (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(cancelGameDownload, {
|
registerEvent("cancelGameDownload", cancelGameDownload);
|
||||||
name: "cancelGameDownload",
|
|
||||||
});
|
|
||||||
|
@ -27,6 +27,4 @@ const pauseGameDownload = async (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(pauseGameDownload, {
|
registerEvent("pauseGameDownload", pauseGameDownload);
|
||||||
name: "pauseGameDownload",
|
|
||||||
});
|
|
||||||
|
@ -46,6 +46,4 @@ const resumeGameDownload = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(resumeGameDownload, {
|
registerEvent("resumeGameDownload", resumeGameDownload);
|
||||||
name: "resumeGameDownload",
|
|
||||||
});
|
|
||||||
|
@ -97,6 +97,4 @@ const startGameDownload = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(startGameDownload, {
|
registerEvent("startGameDownload", startGameDownload);
|
||||||
name: "startGameDownload",
|
|
||||||
});
|
|
||||||
|
@ -16,6 +16,4 @@ const autoLaunch = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(autoLaunch, {
|
registerEvent("autoLaunch", autoLaunch);
|
||||||
name: "autoLaunch",
|
|
||||||
});
|
|
||||||
|
@ -6,6 +6,4 @@ const getUserPreferences = async () =>
|
|||||||
where: { id: 1 },
|
where: { id: 1 },
|
||||||
});
|
});
|
||||||
|
|
||||||
registerEvent(getUserPreferences, {
|
registerEvent("getUserPreferences", getUserPreferences);
|
||||||
name: "getUserPreferences",
|
|
||||||
});
|
|
||||||
|
@ -21,6 +21,4 @@ const updateUserPreferences = async (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(updateUserPreferences, {
|
registerEvent("updateUserPreferences", updateUserPreferences);
|
||||||
name: "updateUserPreferences",
|
|
||||||
});
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { app, BrowserWindow, net, protocol } from "electron";
|
import { app, BrowserWindow, net, protocol } from "electron";
|
||||||
import { init } from "@sentry/electron/main";
|
|
||||||
import i18n from "i18next";
|
import i18n from "i18next";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { electronApp, optimizer } from "@electron-toolkit/utils";
|
import { electronApp, optimizer } from "@electron-toolkit/utils";
|
||||||
@ -11,20 +10,6 @@ import { userPreferencesRepository } from "@main/repository";
|
|||||||
const gotTheLock = app.requestSingleInstanceLock();
|
const gotTheLock = app.requestSingleInstanceLock();
|
||||||
if (!gotTheLock) app.quit();
|
if (!gotTheLock) app.quit();
|
||||||
|
|
||||||
if (import.meta.env.MAIN_VITE_SENTRY_DSN) {
|
|
||||||
init({
|
|
||||||
dsn: import.meta.env.MAIN_VITE_SENTRY_DSN,
|
|
||||||
beforeSend: async (event) => {
|
|
||||||
const userPreferences = await userPreferencesRepository.findOne({
|
|
||||||
where: { id: 1 },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (userPreferences?.telemetryEnabled) return event;
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
i18n.init({
|
i18n.init({
|
||||||
resources,
|
resources,
|
||||||
lng: "en",
|
lng: "en",
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { stateManager } from "./state-manager";
|
import { stateManager } from "./state-manager";
|
||||||
import { repackers } from "./constants";
|
import { repackersOn1337x } from "./constants";
|
||||||
import {
|
import {
|
||||||
getNewGOGGames,
|
getNewGOGGames,
|
||||||
getNewRepacksFromCPG,
|
|
||||||
getNewRepacksFromUser,
|
getNewRepacksFromUser,
|
||||||
getNewRepacksFromXatab,
|
getNewRepacksFromXatab,
|
||||||
getNewRepacksFromOnlineFix,
|
getNewRepacksFromOnlineFix,
|
||||||
@ -12,7 +11,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
gameRepository,
|
gameRepository,
|
||||||
repackRepository,
|
repackRepository,
|
||||||
repackerFriendlyNameRepository,
|
|
||||||
steamGameRepository,
|
steamGameRepository,
|
||||||
userPreferencesRepository,
|
userPreferencesRepository,
|
||||||
} from "./repository";
|
} from "./repository";
|
||||||
@ -27,7 +25,7 @@ import { RealDebridClient } from "./services/real-debrid";
|
|||||||
startProcessWatcher();
|
startProcessWatcher();
|
||||||
|
|
||||||
const track1337xUsers = async (existingRepacks: Repack[]) => {
|
const track1337xUsers = async (existingRepacks: Repack[]) => {
|
||||||
for (const repacker of repackers) {
|
for (const repacker of repackersOn1337x) {
|
||||||
await getNewRepacksFromUser(
|
await getNewRepacksFromUser(
|
||||||
repacker,
|
repacker,
|
||||||
existingRepacks.filter((repack) => repack.repacker === repacker)
|
existingRepacks.filter((repack) => repack.repacker === repacker)
|
||||||
@ -39,19 +37,16 @@ const checkForNewRepacks = async (userPreferences: UserPreferences | null) => {
|
|||||||
const existingRepacks = stateManager.getValue("repacks");
|
const existingRepacks = stateManager.getValue("repacks");
|
||||||
|
|
||||||
Promise.allSettled([
|
Promise.allSettled([
|
||||||
getNewGOGGames(
|
track1337xUsers(existingRepacks),
|
||||||
existingRepacks.filter((repack) => repack.repacker === "GOG")
|
|
||||||
),
|
|
||||||
getNewRepacksFromXatab(
|
getNewRepacksFromXatab(
|
||||||
existingRepacks.filter((repack) => repack.repacker === "Xatab")
|
existingRepacks.filter((repack) => repack.repacker === "Xatab")
|
||||||
),
|
),
|
||||||
getNewRepacksFromCPG(
|
getNewGOGGames(
|
||||||
existingRepacks.filter((repack) => repack.repacker === "CPG")
|
existingRepacks.filter((repack) => repack.repacker === "GOG")
|
||||||
),
|
),
|
||||||
getNewRepacksFromOnlineFix(
|
getNewRepacksFromOnlineFix(
|
||||||
existingRepacks.filter((repack) => repack.repacker === "onlinefix")
|
existingRepacks.filter((repack) => repack.repacker === "onlinefix")
|
||||||
),
|
),
|
||||||
track1337xUsers(existingRepacks),
|
|
||||||
]).then(() => {
|
]).then(() => {
|
||||||
repackRepository.count().then((count) => {
|
repackRepository.count().then((count) => {
|
||||||
const total = count - stateManager.getValue("repacks").length;
|
const total = count - stateManager.getValue("repacks").length;
|
||||||
@ -74,8 +69,7 @@ const checkForNewRepacks = async (userPreferences: UserPreferences | null) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const loadState = async (userPreferences: UserPreferences | null) => {
|
const loadState = async (userPreferences: UserPreferences | null) => {
|
||||||
const [friendlyNames, repacks, steamGames] = await Promise.all([
|
const [repacks, steamGames] = await Promise.all([
|
||||||
repackerFriendlyNameRepository.find(),
|
|
||||||
repackRepository.find({
|
repackRepository.find({
|
||||||
order: {
|
order: {
|
||||||
createdAt: "desc",
|
createdAt: "desc",
|
||||||
@ -88,7 +82,6 @@ const loadState = async (userPreferences: UserPreferences | null) => {
|
|||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
stateManager.setValue("repackersFriendlyNames", friendlyNames);
|
|
||||||
stateManager.setValue("repacks", repacks);
|
stateManager.setValue("repacks", repacks);
|
||||||
stateManager.setValue("steamGames", steamGames);
|
stateManager.setValue("steamGames", steamGames);
|
||||||
|
|
||||||
|
@ -3,9 +3,7 @@ import {
|
|||||||
Game,
|
Game,
|
||||||
GameShopCache,
|
GameShopCache,
|
||||||
Repack,
|
Repack,
|
||||||
RepackerFriendlyName,
|
|
||||||
UserPreferences,
|
UserPreferences,
|
||||||
MigrationScript,
|
|
||||||
SteamGame,
|
SteamGame,
|
||||||
} from "@main/entity";
|
} from "@main/entity";
|
||||||
|
|
||||||
@ -13,15 +11,9 @@ export const gameRepository = dataSource.getRepository(Game);
|
|||||||
|
|
||||||
export const repackRepository = dataSource.getRepository(Repack);
|
export const repackRepository = dataSource.getRepository(Repack);
|
||||||
|
|
||||||
export const repackerFriendlyNameRepository =
|
|
||||||
dataSource.getRepository(RepackerFriendlyName);
|
|
||||||
|
|
||||||
export const userPreferencesRepository =
|
export const userPreferencesRepository =
|
||||||
dataSource.getRepository(UserPreferences);
|
dataSource.getRepository(UserPreferences);
|
||||||
|
|
||||||
export const gameShopCacheRepository = dataSource.getRepository(GameShopCache);
|
export const gameShopCacheRepository = dataSource.getRepository(GameShopCache);
|
||||||
|
|
||||||
export const migrationScriptRepository =
|
|
||||||
dataSource.getRepository(MigrationScript);
|
|
||||||
|
|
||||||
export const steamGameRepository = dataSource.getRepository(SteamGame);
|
export const steamGameRepository = dataSource.getRepository(SteamGame);
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import cp from "node:child_process";
|
import cp from "node:child_process";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import * as Sentry from "@sentry/electron/main";
|
|
||||||
import { app, dialog } from "electron";
|
import { app, dialog } from "electron";
|
||||||
import type { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
|
import type { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
|
||||||
|
|
||||||
@ -87,8 +86,6 @@ export class TorrentDownloader extends Downloader {
|
|||||||
downloadSpeed: payload.downloadSpeed,
|
downloadSpeed: payload.downloadSpeed,
|
||||||
timeRemaining: payload.timeRemaining,
|
timeRemaining: payload.timeRemaining,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
Sentry.captureException(err);
|
|
||||||
} finally {
|
} finally {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
}
|
}
|
||||||
|
@ -1,64 +0,0 @@
|
|||||||
import { JSDOM } from "jsdom";
|
|
||||||
|
|
||||||
import { Repack } from "@main/entity";
|
|
||||||
|
|
||||||
import { requestWebPage, savePage } from "./helpers";
|
|
||||||
import { logger } from "../logger";
|
|
||||||
import type { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
|
|
||||||
|
|
||||||
export const getNewRepacksFromCPG = async (
|
|
||||||
existingRepacks: Repack[] = [],
|
|
||||||
page = 1
|
|
||||||
): Promise<void> => {
|
|
||||||
const data = await requestWebPage(`https://cpgrepacks.site/page/${page}`);
|
|
||||||
|
|
||||||
const { window } = new JSDOM(data);
|
|
||||||
|
|
||||||
const repacks: QueryDeepPartialEntity<Repack>[] = [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
Array.from(window.document.querySelectorAll(".post")).forEach(($post) => {
|
|
||||||
const $title = $post.querySelector(".entry-title")!;
|
|
||||||
const uploadDate = $post.querySelector("time")?.getAttribute("datetime");
|
|
||||||
|
|
||||||
const $downloadInfo = Array.from(
|
|
||||||
$post.querySelectorAll(".wp-block-heading")
|
|
||||||
).find(($heading) => $heading.textContent?.startsWith("Download"));
|
|
||||||
|
|
||||||
/* Side note: CPG often misspells "Magnet" as "Magent" */
|
|
||||||
const $magnet = Array.from($post.querySelectorAll("a")).find(
|
|
||||||
($a) =>
|
|
||||||
$a.textContent?.startsWith("Magnet") ||
|
|
||||||
$a.textContent?.startsWith("Magent")
|
|
||||||
);
|
|
||||||
|
|
||||||
const fileSize = ($downloadInfo?.textContent ?? "")
|
|
||||||
.split("Download link => ")
|
|
||||||
.at(1);
|
|
||||||
|
|
||||||
repacks.push({
|
|
||||||
title: $title.textContent!,
|
|
||||||
fileSize: fileSize ?? "N/A",
|
|
||||||
magnet: $magnet!.href,
|
|
||||||
repacker: "CPG",
|
|
||||||
page,
|
|
||||||
uploadDate: uploadDate ? new Date(uploadDate) : new Date(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} catch (err: unknown) {
|
|
||||||
logger.error((err as Error).message, { method: "getNewRepacksFromCPG" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const newRepacks = repacks.filter(
|
|
||||||
(repack) =>
|
|
||||||
!existingRepacks.some(
|
|
||||||
(existingRepack) => existingRepack.title === repack.title
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!newRepacks.length) return;
|
|
||||||
|
|
||||||
await savePage(newRepacks);
|
|
||||||
|
|
||||||
return getNewRepacksFromCPG(existingRepacks, page + 1);
|
|
||||||
};
|
|
@ -6,32 +6,57 @@ import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity
|
|||||||
|
|
||||||
const virtualConsole = new VirtualConsole();
|
const virtualConsole = new VirtualConsole();
|
||||||
|
|
||||||
|
const getUploadDate = (document: Document) => {
|
||||||
|
const $modifiedTime = document.querySelector(
|
||||||
|
'[property="article:modified_time"]'
|
||||||
|
) as HTMLMetaElement;
|
||||||
|
if ($modifiedTime) return $modifiedTime.content;
|
||||||
|
|
||||||
|
const $publishedTime = document.querySelector(
|
||||||
|
'[property="article:published_time"]'
|
||||||
|
) as HTMLMetaElement;
|
||||||
|
return $publishedTime.content;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDownloadLink = (document: Document) => {
|
||||||
|
const $latestDownloadButton = document.querySelector(
|
||||||
|
".download-btn:not(.lightweight-accordion *)"
|
||||||
|
) as HTMLAnchorElement;
|
||||||
|
if ($latestDownloadButton) return $latestDownloadButton.href;
|
||||||
|
|
||||||
|
const $downloadButton = document.querySelector(
|
||||||
|
".download-btn"
|
||||||
|
) as HTMLAnchorElement;
|
||||||
|
if (!$downloadButton) return null;
|
||||||
|
|
||||||
|
return $downloadButton.href;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMagnet = (downloadLink: string) => {
|
||||||
|
if (downloadLink.startsWith("http")) {
|
||||||
|
const { searchParams } = new URL(downloadLink);
|
||||||
|
return Buffer.from(searchParams.get("url")!, "base64").toString("utf-8");
|
||||||
|
}
|
||||||
|
|
||||||
|
return downloadLink;
|
||||||
|
};
|
||||||
|
|
||||||
const getGOGGame = async (url: string) => {
|
const getGOGGame = async (url: string) => {
|
||||||
const data = await requestWebPage(url);
|
const data = await requestWebPage(url);
|
||||||
const { window } = new JSDOM(data, { virtualConsole });
|
const { window } = new JSDOM(data, { virtualConsole });
|
||||||
|
|
||||||
const $modifiedTime = window.document.querySelector(
|
const downloadLink = getDownloadLink(window.document);
|
||||||
'[property="article:modified_time"]'
|
if (!downloadLink) return null;
|
||||||
) as HTMLMetaElement;
|
|
||||||
|
|
||||||
const $em = window.document.querySelector(
|
const $em = window.document.querySelector("p em");
|
||||||
"p:not(.lightweight-accordion *) em"
|
if (!$em) return null;
|
||||||
)!;
|
|
||||||
const fileSize = $em.textContent!.split("Size: ").at(1);
|
const fileSize = $em.textContent!.split("Size: ").at(1);
|
||||||
const $downloadButton = window.document.querySelector(
|
|
||||||
".download-btn:not(.lightweight-accordion *)"
|
|
||||||
) as HTMLAnchorElement;
|
|
||||||
|
|
||||||
const { searchParams } = new URL($downloadButton.href);
|
|
||||||
const magnet = Buffer.from(searchParams.get("url")!, "base64").toString(
|
|
||||||
"utf-8"
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fileSize: fileSize ?? "N/A",
|
fileSize: fileSize ?? "N/A",
|
||||||
uploadDate: new Date($modifiedTime.content),
|
uploadDate: new Date(getUploadDate(window.document)),
|
||||||
repacker: "GOG",
|
repacker: "GOG",
|
||||||
magnet,
|
magnet: getMagnet(downloadLink),
|
||||||
page: 1,
|
page: 1,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -62,7 +87,7 @@ export const getNewGOGGames = async (existingRepacks: Repack[] = []) => {
|
|||||||
if (!gameExists) {
|
if (!gameExists) {
|
||||||
const game = await getGOGGame(href);
|
const game = await getGOGGame(href);
|
||||||
|
|
||||||
repacks.push({ ...game, title });
|
if (game) repacks.push({ ...game, title });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import axios from "axios";
|
||||||
import UserAgent from "user-agents";
|
import UserAgent from "user-agents";
|
||||||
|
|
||||||
import type { Repack } from "@main/entity";
|
import type { Repack } from "@main/entity";
|
||||||
@ -13,12 +14,13 @@ export const savePage = async (repacks: QueryDeepPartialEntity<Repack>[]) =>
|
|||||||
export const requestWebPage = async (url: string) => {
|
export const requestWebPage = async (url: string) => {
|
||||||
const userAgent = new UserAgent();
|
const userAgent = new UserAgent();
|
||||||
|
|
||||||
return fetch(url, {
|
return axios
|
||||||
method: "GET",
|
.get(url, {
|
||||||
headers: {
|
headers: {
|
||||||
"User-Agent": userAgent.toString(),
|
"User-Agent": userAgent.toString(),
|
||||||
},
|
},
|
||||||
}).then((response) => response.text());
|
})
|
||||||
|
.then((response) => response.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const decodeNonUtf8Response = async (res: Response) => {
|
export const decodeNonUtf8Response = async (res: Response) => {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
export * from "./1337x";
|
export * from "./1337x";
|
||||||
export * from "./xatab";
|
export * from "./xatab";
|
||||||
export * from "./cpg-repacks";
|
|
||||||
export * from "./gog";
|
export * from "./gog";
|
||||||
export * from "./online-fix";
|
export * from "./online-fix";
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import { Repack } from "@main/entity";
|
import { Repack } from "@main/entity";
|
||||||
import { decodeNonUtf8Response, savePage } from "./helpers";
|
import { decodeNonUtf8Response, savePage } from "./helpers";
|
||||||
import { logger } from "../logger";
|
import { logger } from "../logger";
|
||||||
import parseTorrent, {
|
|
||||||
toMagnetURI,
|
|
||||||
Instance as TorrentInstance,
|
|
||||||
} from "parse-torrent";
|
|
||||||
import { JSDOM } from "jsdom";
|
import { JSDOM } from "jsdom";
|
||||||
|
|
||||||
import { format, parse, sub } from "date-fns";
|
import { format, parse, sub } from "date-fns";
|
||||||
import { ru } from "date-fns/locale";
|
import { ru } from "date-fns/locale";
|
||||||
|
|
||||||
|
import createWorker from "@main/workers/torrent-parser.worker?nodeWorker";
|
||||||
|
import { toMagnetURI } from "parse-torrent";
|
||||||
|
|
||||||
|
const worker = createWorker({});
|
||||||
|
|
||||||
import { onlinefixFormatter } from "@main/helpers";
|
import { onlinefixFormatter } from "@main/helpers";
|
||||||
import makeFetchCookie from "fetch-cookie";
|
import makeFetchCookie from "fetch-cookie";
|
||||||
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
|
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
|
||||||
@ -139,27 +140,23 @@ export const getNewRepacksFromOnlineFix = async (
|
|||||||
?.getAttribute("href");
|
?.getAttribute("href");
|
||||||
|
|
||||||
const torrentFile = Buffer.from(
|
const torrentFile = Buffer.from(
|
||||||
await http(`${torrentPrePage}/${torrentLink}`).then((res) =>
|
await http(`${torrentPrePage}${torrentLink}`).then((res) =>
|
||||||
res.arrayBuffer()
|
res.arrayBuffer()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const torrent = parseTorrent(torrentFile) as TorrentInstance;
|
worker.once("message", (torrent) => {
|
||||||
const magnetLink = toMagnetURI({
|
|
||||||
infoHash: torrent.infoHash,
|
|
||||||
});
|
|
||||||
|
|
||||||
const torrentSizeInBytes = torrent.length;
|
|
||||||
if (!torrentSizeInBytes) return;
|
|
||||||
|
|
||||||
repacks.push({
|
repacks.push({
|
||||||
fileSize: formatBytes(torrentSizeInBytes),
|
fileSize: formatBytes(torrent.length ?? 0),
|
||||||
magnet: magnetLink,
|
magnet: toMagnetURI(torrent),
|
||||||
page: 1,
|
page: 1,
|
||||||
repacker: "onlinefix",
|
repacker: "onlinefix",
|
||||||
title: gameName,
|
title: gameName,
|
||||||
uploadDate: uploadDate,
|
uploadDate: uploadDate,
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
worker.postMessage(torrentFile);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
|
@ -9,6 +9,7 @@ import { toMagnetURI } from "parse-torrent";
|
|||||||
import type { Instance } from "parse-torrent";
|
import type { Instance } from "parse-torrent";
|
||||||
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
|
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
|
||||||
import { formatBytes } from "@shared";
|
import { formatBytes } from "@shared";
|
||||||
|
import { getFileBuffer } from "@main/helpers";
|
||||||
|
|
||||||
const worker = createWorker({});
|
const worker = createWorker({});
|
||||||
|
|
||||||
@ -27,7 +28,7 @@ const formatXatabDate = (str: string) => {
|
|||||||
|
|
||||||
const getXatabRepack = (
|
const getXatabRepack = (
|
||||||
url: string
|
url: string
|
||||||
): Promise<{ fileSize: string; magnet: string; uploadDate: Date }> => {
|
): Promise<{ fileSize: string; magnet: string; uploadDate: Date } | null> => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const data = await requestWebPage(url);
|
const data = await requestWebPage(url);
|
||||||
@ -40,15 +41,20 @@ const getXatabRepack = (
|
|||||||
".download-torrent"
|
".download-torrent"
|
||||||
) as HTMLAnchorElement;
|
) as HTMLAnchorElement;
|
||||||
|
|
||||||
if (!$downloadButton) throw new Error("Download button not found");
|
if (!$downloadButton) return resolve(null);
|
||||||
|
|
||||||
|
worker.once("message", (torrent: Instance | null) => {
|
||||||
|
if (!torrent) return resolve(null);
|
||||||
|
|
||||||
worker.once("message", (torrent: Instance) => {
|
|
||||||
resolve({
|
resolve({
|
||||||
fileSize: formatBytes(torrent.length ?? 0),
|
fileSize: formatBytes(torrent.length ?? 0),
|
||||||
magnet: toMagnetURI(torrent),
|
magnet: toMagnetURI(torrent),
|
||||||
uploadDate: formatXatabDate($uploadDate!.textContent!),
|
uploadDate: formatXatabDate($uploadDate!.textContent!),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const buffer = await getFileBuffer($downloadButton.href);
|
||||||
|
worker.postMessage(buffer);
|
||||||
})();
|
})();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -69,12 +75,14 @@ export const getNewRepacksFromXatab = async (
|
|||||||
try {
|
try {
|
||||||
const repack = await getXatabRepack(($a as HTMLAnchorElement).href);
|
const repack = await getXatabRepack(($a as HTMLAnchorElement).href);
|
||||||
|
|
||||||
|
if (repack) {
|
||||||
repacks.push({
|
repacks.push({
|
||||||
title: $a.textContent!,
|
title: $a.textContent!,
|
||||||
repacker: "Xatab",
|
repacker: "Xatab",
|
||||||
...repack,
|
...repack,
|
||||||
page,
|
page,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
logger.error((err as Error).message, {
|
logger.error((err as Error).message, {
|
||||||
method: "getNewRepacksFromXatab",
|
method: "getNewRepacksFromXatab",
|
||||||
|
@ -3,107 +3,9 @@ import { app } from "electron";
|
|||||||
|
|
||||||
import { chunk } from "lodash-es";
|
import { chunk } from "lodash-es";
|
||||||
|
|
||||||
import { createDataSource, dataSource } from "@main/data-source";
|
import { createDataSource } from "@main/data-source";
|
||||||
import { Repack, RepackerFriendlyName, SteamGame } from "@main/entity";
|
import { Repack, SteamGame } from "@main/entity";
|
||||||
import {
|
import { repackRepository, steamGameRepository } from "@main/repository";
|
||||||
migrationScriptRepository,
|
|
||||||
repackRepository,
|
|
||||||
repackerFriendlyNameRepository,
|
|
||||||
steamGameRepository,
|
|
||||||
} from "@main/repository";
|
|
||||||
import { MigrationScript } from "@main/entity/migration-script.entity";
|
|
||||||
import { Like } from "typeorm";
|
|
||||||
|
|
||||||
const migrationScripts = {
|
|
||||||
/*
|
|
||||||
0.0.6 -> 0.0.7
|
|
||||||
Xatab repacks were previously created with an incorrect upload date.
|
|
||||||
This migration script will update the upload date of all Xatab repacks.
|
|
||||||
*/
|
|
||||||
"0.0.7": async (updateRepacks: Repack[]) => {
|
|
||||||
const VERSION = "0.0.7";
|
|
||||||
|
|
||||||
const migrationScript = await migrationScriptRepository.findOne({
|
|
||||||
where: {
|
|
||||||
version: VERSION,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!migrationScript) {
|
|
||||||
const xatabRepacks = updateRepacks.filter(
|
|
||||||
(repack) => repack.repacker === "Xatab"
|
|
||||||
);
|
|
||||||
|
|
||||||
await dataSource.transaction(async (transactionalEntityManager) => {
|
|
||||||
await Promise.all(
|
|
||||||
xatabRepacks.map((repack) =>
|
|
||||||
transactionalEntityManager.getRepository(Repack).update(
|
|
||||||
{
|
|
||||||
title: repack.title,
|
|
||||||
repacker: repack.repacker,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uploadDate: repack.uploadDate,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
await transactionalEntityManager.getRepository(MigrationScript).insert({
|
|
||||||
version: VERSION,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/*
|
|
||||||
1.0.1 -> 1.1.0
|
|
||||||
A few torrents scraped from 1337x were previously created with an incorrect upload date.
|
|
||||||
*/
|
|
||||||
"1.1.0": async () => {
|
|
||||||
const VERSION = "1.1.0";
|
|
||||||
|
|
||||||
const migrationScript = await migrationScriptRepository.findOne({
|
|
||||||
where: {
|
|
||||||
version: VERSION,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!migrationScript) {
|
|
||||||
await dataSource.transaction(async (transactionalEntityManager) => {
|
|
||||||
const repacks = await transactionalEntityManager
|
|
||||||
.getRepository(Repack)
|
|
||||||
.find({
|
|
||||||
where: {
|
|
||||||
uploadDate: Like("1%"),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
repacks.map(async (repack) => {
|
|
||||||
return transactionalEntityManager
|
|
||||||
.getRepository(Repack)
|
|
||||||
.update(
|
|
||||||
{ id: repack.id },
|
|
||||||
{ uploadDate: new Date(repack.uploadDate) }
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
await transactionalEntityManager.getRepository(MigrationScript).insert({
|
|
||||||
version: VERSION,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const runMigrationScripts = async (updateRepacks: Repack[]) => {
|
|
||||||
return Promise.all(
|
|
||||||
Object.values(migrationScripts).map((migrationScript) => {
|
|
||||||
return migrationScript(updateRepacks);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const resolveDatabaseUpdates = async () => {
|
export const resolveDatabaseUpdates = async () => {
|
||||||
const updateDataSource = createDataSource({
|
const updateDataSource = createDataSource({
|
||||||
@ -114,26 +16,13 @@ export const resolveDatabaseUpdates = async () => {
|
|||||||
|
|
||||||
return updateDataSource.initialize().then(async () => {
|
return updateDataSource.initialize().then(async () => {
|
||||||
const updateRepackRepository = updateDataSource.getRepository(Repack);
|
const updateRepackRepository = updateDataSource.getRepository(Repack);
|
||||||
const updateRepackerFriendlyNameRepository =
|
|
||||||
updateDataSource.getRepository(RepackerFriendlyName);
|
|
||||||
const updateSteamGameRepository = updateDataSource.getRepository(SteamGame);
|
const updateSteamGameRepository = updateDataSource.getRepository(SteamGame);
|
||||||
|
|
||||||
const [updateRepacks, updateSteamGames, updateRepackerFriendlyNames] =
|
const [updateRepacks, updateSteamGames] = await Promise.all([
|
||||||
await Promise.all([
|
|
||||||
updateRepackRepository.find(),
|
updateRepackRepository.find(),
|
||||||
updateSteamGameRepository.find(),
|
updateSteamGameRepository.find(),
|
||||||
updateRepackerFriendlyNameRepository.find(),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await runMigrationScripts(updateRepacks);
|
|
||||||
|
|
||||||
await repackerFriendlyNameRepository
|
|
||||||
.createQueryBuilder()
|
|
||||||
.insert()
|
|
||||||
.values(updateRepackerFriendlyNames)
|
|
||||||
.orIgnore()
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
const updateRepacksChunks = chunk(updateRepacks, 800);
|
const updateRepacksChunks = chunk(updateRepacks, 800);
|
||||||
|
|
||||||
for (const chunk of updateRepacksChunks) {
|
for (const chunk of updateRepacksChunks) {
|
||||||
|
@ -1,17 +1,13 @@
|
|||||||
import type { Repack, RepackerFriendlyName, SteamGame } from "@main/entity";
|
import type { Repack, SteamGame } from "@main/entity";
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
repacks: Repack[];
|
repacks: Repack[];
|
||||||
repackersFriendlyNames: RepackerFriendlyName[];
|
|
||||||
steamGames: SteamGame[];
|
steamGames: SteamGame[];
|
||||||
eventResults: Map<[string, any[]], any>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: State = {
|
const initialState: State = {
|
||||||
repacks: [],
|
repacks: [],
|
||||||
repackersFriendlyNames: [],
|
|
||||||
steamGames: [],
|
steamGames: [],
|
||||||
eventResults: new Map(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export class StateManager {
|
export class StateManager {
|
||||||
|
1
src/main/vite-env.d.ts
vendored
1
src/main/vite-env.d.ts
vendored
@ -4,7 +4,6 @@ interface ImportMetaEnv {
|
|||||||
readonly MAIN_VITE_STEAMGRIDDB_API_KEY: string;
|
readonly MAIN_VITE_STEAMGRIDDB_API_KEY: string;
|
||||||
readonly MAIN_VITE_ONLINEFIX_USERNAME: string;
|
readonly MAIN_VITE_ONLINEFIX_USERNAME: string;
|
||||||
readonly MAIN_VITE_ONLINEFIX_PASSWORD: string;
|
readonly MAIN_VITE_ONLINEFIX_PASSWORD: string;
|
||||||
readonly MAIN_VITE_SENTRY_DSN: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
|
@ -4,13 +4,11 @@ import parseTorrent from "parse-torrent";
|
|||||||
const port = parentPort;
|
const port = parentPort;
|
||||||
if (!port) throw new Error("IllegalState");
|
if (!port) throw new Error("IllegalState");
|
||||||
|
|
||||||
export const getFileBuffer = async (url: string) =>
|
port.on("message", async (buffer: Buffer) => {
|
||||||
fetch(url, { method: "GET" }).then((response) =>
|
try {
|
||||||
response.arrayBuffer().then((buffer) => Buffer.from(buffer))
|
|
||||||
);
|
|
||||||
|
|
||||||
port.on("message", async (url: string) => {
|
|
||||||
const buffer = await getFileBuffer(url);
|
|
||||||
const torrent = await parseTorrent(buffer);
|
const torrent = await parseTorrent(buffer);
|
||||||
port.postMessage(torrent);
|
port.postMessage(torrent);
|
||||||
|
} catch (err) {
|
||||||
|
port.postMessage(null);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
@ -76,8 +76,6 @@ contextBridge.exposeInMainWorld("electron", {
|
|||||||
executablePath
|
executablePath
|
||||||
),
|
),
|
||||||
getLibrary: () => ipcRenderer.invoke("getLibrary"),
|
getLibrary: () => ipcRenderer.invoke("getLibrary"),
|
||||||
getRepackersFriendlyNames: () =>
|
|
||||||
ipcRenderer.invoke("getRepackersFriendlyNames"),
|
|
||||||
openGameInstaller: (gameId: number) =>
|
openGameInstaller: (gameId: number) =>
|
||||||
ipcRenderer.invoke("openGameInstaller", gameId),
|
ipcRenderer.invoke("openGameInstaller", gameId),
|
||||||
openGame: (gameId: number, executablePath: string) =>
|
openGame: (gameId: number, executablePath: string) =>
|
||||||
|
@ -17,7 +17,6 @@ import {
|
|||||||
setSearch,
|
setSearch,
|
||||||
clearSearch,
|
clearSearch,
|
||||||
setUserPreferences,
|
setUserPreferences,
|
||||||
setRepackersFriendlyNames,
|
|
||||||
toggleDraggingDisabled,
|
toggleDraggingDisabled,
|
||||||
} from "@renderer/features";
|
} from "@renderer/features";
|
||||||
import { GameStatusHelper } from "@shared";
|
import { GameStatusHelper } from "@shared";
|
||||||
@ -45,14 +44,11 @@ export function App({ children }: AppProps) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Promise.all([
|
Promise.all([window.electron.getUserPreferences(), updateLibrary()]).then(
|
||||||
window.electron.getUserPreferences(),
|
([preferences]) => {
|
||||||
window.electron.getRepackersFriendlyNames(),
|
|
||||||
updateLibrary(),
|
|
||||||
]).then(([preferences, repackersFriendlyNames]) => {
|
|
||||||
dispatch(setUserPreferences(preferences));
|
dispatch(setUserPreferences(preferences));
|
||||||
dispatch(setRepackersFriendlyNames(repackersFriendlyNames));
|
}
|
||||||
});
|
);
|
||||||
}, [navigate, location.pathname, dispatch, updateLibrary]);
|
}, [navigate, location.pathname, dispatch, updateLibrary]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -5,7 +5,6 @@ import SteamLogo from "@renderer/assets/steam-logo.svg?react";
|
|||||||
import EpicGamesLogo from "@renderer/assets/epic-games-logo.svg?react";
|
import EpicGamesLogo from "@renderer/assets/epic-games-logo.svg?react";
|
||||||
|
|
||||||
import * as styles from "./game-card.css";
|
import * as styles from "./game-card.css";
|
||||||
import { useAppSelector } from "@renderer/hooks";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export interface GameCardProps
|
export interface GameCardProps
|
||||||
@ -24,10 +23,6 @@ const shopIcon = {
|
|||||||
export function GameCard({ game, ...props }: GameCardProps) {
|
export function GameCard({ game, ...props }: GameCardProps) {
|
||||||
const { t } = useTranslation("game_card");
|
const { t } = useTranslation("game_card");
|
||||||
|
|
||||||
const repackersFriendlyNames = useAppSelector(
|
|
||||||
(state) => state.repackersFriendlyNames.value
|
|
||||||
);
|
|
||||||
|
|
||||||
const uniqueRepackers = Array.from(
|
const uniqueRepackers = Array.from(
|
||||||
new Set(game.repacks.map(({ repacker }) => repacker))
|
new Set(game.repacks.map(({ repacker }) => repacker))
|
||||||
);
|
);
|
||||||
@ -47,7 +42,7 @@ export function GameCard({ game, ...props }: GameCardProps) {
|
|||||||
<ul className={styles.downloadOptions}>
|
<ul className={styles.downloadOptions}>
|
||||||
{uniqueRepackers.map((repacker) => (
|
{uniqueRepackers.map((repacker) => (
|
||||||
<li key={repacker} className={styles.downloadOption}>
|
<li key={repacker} className={styles.downloadOption}>
|
||||||
<span>{repackersFriendlyNames[repacker]}</span>
|
<span>{repacker}</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -122,36 +122,3 @@ export const section = style({
|
|||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
paddingBottom: `${SPACING_UNIT}px`,
|
paddingBottom: `${SPACING_UNIT}px`,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const sidebarFooter = style({
|
|
||||||
marginTop: "auto",
|
|
||||||
padding: `${SPACING_UNIT * 2}px`,
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const footerSocialsContainer = style({
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: `${SPACING_UNIT * 1.5}px`,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const footerSocialsItem = style({
|
|
||||||
color: vars.color.bodyText,
|
|
||||||
backgroundColor: vars.color.darkBackground,
|
|
||||||
width: "16px",
|
|
||||||
height: "16px",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
transition: "all ease 0.2s",
|
|
||||||
cursor: "pointer",
|
|
||||||
":hover": {
|
|
||||||
opacity: "0.75",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const footerText = style({
|
|
||||||
color: vars.color.bodyText,
|
|
||||||
fontSize: "12px",
|
|
||||||
});
|
|
||||||
|
@ -9,10 +9,6 @@ import { useDownload, useLibrary } from "@renderer/hooks";
|
|||||||
|
|
||||||
import { routes } from "./routes";
|
import { routes } from "./routes";
|
||||||
|
|
||||||
import { MarkGithubIcon } from "@primer/octicons-react";
|
|
||||||
import TelegramLogo from "@renderer/assets/telegram-icon.svg?react";
|
|
||||||
import XLogo from "@renderer/assets/x-icon.svg?react";
|
|
||||||
|
|
||||||
import * as styles from "./sidebar.css";
|
import * as styles from "./sidebar.css";
|
||||||
import { GameStatus, GameStatusHelper } from "@shared";
|
import { GameStatus, GameStatusHelper } from "@shared";
|
||||||
import { buildGameDetailsPath } from "@renderer/helpers";
|
import { buildGameDetailsPath } from "@renderer/helpers";
|
||||||
@ -35,24 +31,6 @@ export function Sidebar() {
|
|||||||
initialSidebarWidth ? Number(initialSidebarWidth) : SIDEBAR_INITIAL_WIDTH
|
initialSidebarWidth ? Number(initialSidebarWidth) : SIDEBAR_INITIAL_WIDTH
|
||||||
);
|
);
|
||||||
|
|
||||||
const socials = [
|
|
||||||
{
|
|
||||||
url: "https://t.me/hydralauncher",
|
|
||||||
icon: <TelegramLogo />,
|
|
||||||
label: t("telegram"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: "https://twitter.com/hydralauncher",
|
|
||||||
icon: <XLogo />,
|
|
||||||
label: t("x"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: "https://github.com/hydralauncher/hydra",
|
|
||||||
icon: <MarkGithubIcon size={16} />,
|
|
||||||
label: t("github"),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
const { game: gameDownloading, progress } = useDownload();
|
const { game: gameDownloading, progress } = useDownload();
|
||||||
@ -233,26 +211,6 @@ export function Sidebar() {
|
|||||||
className={styles.handle}
|
className={styles.handle}
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<footer className={styles.sidebarFooter}>
|
|
||||||
<div className={styles.footerText}>{t("follow_us")}</div>
|
|
||||||
|
|
||||||
<span className={styles.footerSocialsContainer}>
|
|
||||||
{socials.map((item) => {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={item.url}
|
|
||||||
className={styles.footerSocialsItem}
|
|
||||||
onClick={() => window.electron.openExternal(item.url)}
|
|
||||||
title={item.label}
|
|
||||||
aria-label={item.label}
|
|
||||||
>
|
|
||||||
{item.icon}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
</footer>
|
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
1
src/renderer/src/declaration.d.ts
vendored
1
src/renderer/src/declaration.d.ts
vendored
@ -62,7 +62,6 @@ declare global {
|
|||||||
executablePath: string | null
|
executablePath: string | null
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
getLibrary: () => Promise<Game[]>;
|
getLibrary: () => Promise<Game[]>;
|
||||||
getRepackersFriendlyNames: () => Promise<Record<string, string>>;
|
|
||||||
openGameInstaller: (gameId: number) => Promise<boolean>;
|
openGameInstaller: (gameId: number) => Promise<boolean>;
|
||||||
openGame: (gameId: number, executablePath: string) => Promise<void>;
|
openGame: (gameId: number, executablePath: string) => Promise<void>;
|
||||||
closeGame: (gameId: number) => Promise<boolean>;
|
closeGame: (gameId: number) => Promise<boolean>;
|
||||||
|
@ -2,7 +2,7 @@ import { createSlice } from "@reduxjs/toolkit";
|
|||||||
import type { PayloadAction } from "@reduxjs/toolkit";
|
import type { PayloadAction } from "@reduxjs/toolkit";
|
||||||
import type { TorrentProgress } from "@types";
|
import type { TorrentProgress } from "@types";
|
||||||
|
|
||||||
interface DownloadState {
|
export interface DownloadState {
|
||||||
lastPacket: TorrentProgress | null;
|
lastPacket: TorrentProgress | null;
|
||||||
gameId: number | null;
|
gameId: number | null;
|
||||||
gamesWithDeletionInProgress: number[];
|
gamesWithDeletionInProgress: number[];
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
export * from "./search-slice";
|
export * from "./search-slice";
|
||||||
export * from "./repackers-friendly-names-slice";
|
|
||||||
export * from "./library-slice";
|
export * from "./library-slice";
|
||||||
export * from "./use-preferences-slice";
|
export * from "./use-preferences-slice";
|
||||||
export * from "./download-slice";
|
export * from "./download-slice";
|
||||||
|
@ -3,7 +3,7 @@ import type { PayloadAction } from "@reduxjs/toolkit";
|
|||||||
|
|
||||||
import type { Game } from "@types";
|
import type { Game } from "@types";
|
||||||
|
|
||||||
interface LibraryState {
|
export interface LibraryState {
|
||||||
value: Game[];
|
value: Game[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
import { createSlice } from "@reduxjs/toolkit";
|
|
||||||
import type { PayloadAction } from "@reduxjs/toolkit";
|
|
||||||
|
|
||||||
interface RepackersFriendlyNamesState {
|
|
||||||
value: Record<string, string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialState: RepackersFriendlyNamesState = {
|
|
||||||
value: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const repackersFriendlyNamesSlice = createSlice({
|
|
||||||
name: "repackersFriendlyNames",
|
|
||||||
initialState,
|
|
||||||
reducers: {
|
|
||||||
setRepackersFriendlyNames: (
|
|
||||||
state,
|
|
||||||
action: PayloadAction<RepackersFriendlyNamesState["value"]>
|
|
||||||
) => {
|
|
||||||
state.value = action.payload;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const { setRepackersFriendlyNames } =
|
|
||||||
repackersFriendlyNamesSlice.actions;
|
|
@ -1,7 +1,7 @@
|
|||||||
import { createSlice } from "@reduxjs/toolkit";
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
import type { PayloadAction } from "@reduxjs/toolkit";
|
import type { PayloadAction } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
interface SearchState {
|
export interface SearchState {
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import { createSlice } from "@reduxjs/toolkit";
|
|||||||
import type { PayloadAction } from "@reduxjs/toolkit";
|
import type { PayloadAction } from "@reduxjs/toolkit";
|
||||||
import type { UserPreferences } from "@types";
|
import type { UserPreferences } from "@types";
|
||||||
|
|
||||||
interface UserPreferencesState {
|
export interface UserPreferencesState {
|
||||||
value: UserPreferences | null;
|
value: UserPreferences | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { createSlice } from "@reduxjs/toolkit";
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
import type { PayloadAction } from "@reduxjs/toolkit";
|
import type { PayloadAction } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
interface WindowState {
|
export interface WindowState {
|
||||||
draggingDisabled: boolean;
|
draggingDisabled: boolean;
|
||||||
headerTitle: string;
|
headerTitle: string;
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,6 @@ import { Provider } from "react-redux";
|
|||||||
import LanguageDetector from "i18next-browser-languagedetector";
|
import LanguageDetector from "i18next-browser-languagedetector";
|
||||||
import { HashRouter, Route, Routes } from "react-router-dom";
|
import { HashRouter, Route, Routes } from "react-router-dom";
|
||||||
|
|
||||||
import { init } from "@sentry/electron/renderer";
|
|
||||||
import { init as reactInit } from "@sentry/react";
|
|
||||||
|
|
||||||
import "@fontsource/fira-mono/400.css";
|
import "@fontsource/fira-mono/400.css";
|
||||||
import "@fontsource/fira-mono/500.css";
|
import "@fontsource/fira-mono/500.css";
|
||||||
import "@fontsource/fira-mono/700.css";
|
import "@fontsource/fira-mono/700.css";
|
||||||
@ -31,21 +28,6 @@ import { store } from "./store";
|
|||||||
|
|
||||||
import * as resources from "@locales";
|
import * as resources from "@locales";
|
||||||
|
|
||||||
if (import.meta.env.RENDERER_VITE_SENTRY_DSN) {
|
|
||||||
init(
|
|
||||||
{
|
|
||||||
dsn: import.meta.env.RENDERER_VITE_SENTRY_DSN,
|
|
||||||
beforeSend: async (event) => {
|
|
||||||
const userPreferences = await window.electron.getUserPreferences();
|
|
||||||
|
|
||||||
if (userPreferences?.telemetryEnabled) return event;
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
reactInit
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
i18n
|
i18n
|
||||||
.use(LanguageDetector)
|
.use(LanguageDetector)
|
||||||
.use(initReactI18next)
|
.use(initReactI18next)
|
||||||
|
@ -1,60 +1,15 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
import { ShareAndroidIcon } from "@primer/octicons-react";
|
|
||||||
|
|
||||||
import { Button } from "@renderer/components";
|
|
||||||
import type { ShopDetails } from "@types";
|
import type { ShopDetails } from "@types";
|
||||||
|
|
||||||
import * as styles from "./game-details.css";
|
import * as styles from "./game-details.css";
|
||||||
|
|
||||||
const OPEN_HYDRA_URL = "https://open.hydralauncher.site";
|
|
||||||
|
|
||||||
export interface DescriptionHeaderProps {
|
export interface DescriptionHeaderProps {
|
||||||
gameDetails: ShopDetails;
|
gameDetails: ShopDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DescriptionHeader({ gameDetails }: DescriptionHeaderProps) {
|
export function DescriptionHeader({ gameDetails }: DescriptionHeaderProps) {
|
||||||
const [clipboardLocked, setClipboardLocked] = useState(false);
|
const { t } = useTranslation("game_details");
|
||||||
const { t, i18n } = useTranslation("game_details");
|
|
||||||
|
|
||||||
const { objectID, shop } = useParams();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!gameDetails) return setClipboardLocked(true);
|
|
||||||
setClipboardLocked(false);
|
|
||||||
}, [gameDetails]);
|
|
||||||
|
|
||||||
const handleCopyToClipboard = () => {
|
|
||||||
if (gameDetails) {
|
|
||||||
setClipboardLocked(true);
|
|
||||||
|
|
||||||
const searchParams = new URLSearchParams({
|
|
||||||
p: btoa(
|
|
||||||
JSON.stringify([
|
|
||||||
objectID,
|
|
||||||
shop,
|
|
||||||
encodeURIComponent(gameDetails.name),
|
|
||||||
i18n.language,
|
|
||||||
])
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
navigator.clipboard.writeText(
|
|
||||||
OPEN_HYDRA_URL + `/?${searchParams.toString()}`
|
|
||||||
);
|
|
||||||
|
|
||||||
const zero = performance.now();
|
|
||||||
|
|
||||||
requestAnimationFrame(function holdLock(time) {
|
|
||||||
if (time - zero <= 3000) {
|
|
||||||
requestAnimationFrame(holdLock);
|
|
||||||
} else {
|
|
||||||
setClipboardLocked(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.descriptionHeader}>
|
<div className={styles.descriptionHeader}>
|
||||||
@ -66,21 +21,6 @@ export function DescriptionHeader({ gameDetails }: DescriptionHeaderProps) {
|
|||||||
</p>
|
</p>
|
||||||
<p>{t("publisher", { publisher: gameDetails.publishers[0] })}</p>
|
<p>{t("publisher", { publisher: gameDetails.publishers[0] })}</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<Button
|
|
||||||
theme="outline"
|
|
||||||
onClick={handleCopyToClipboard}
|
|
||||||
disabled={clipboardLocked || !gameDetails}
|
|
||||||
>
|
|
||||||
{clipboardLocked ? (
|
|
||||||
t("copied_link_to_clipboard")
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<ShareAndroidIcon />
|
|
||||||
{t("copy_link_to_clipboard")}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -47,10 +47,19 @@ export function HeroPanelActions({
|
|||||||
|
|
||||||
const { t } = useTranslation("game_details");
|
const { t } = useTranslation("game_details");
|
||||||
|
|
||||||
|
const getDownloadsPath = async () => {
|
||||||
|
const userPreferences = await window.electron.getUserPreferences();
|
||||||
|
if (userPreferences?.downloadsPath) return userPreferences.downloadsPath;
|
||||||
|
return window.electron.getDefaultDownloadsPath();
|
||||||
|
};
|
||||||
|
|
||||||
const selectGameExecutable = async () => {
|
const selectGameExecutable = async () => {
|
||||||
|
const downloadsPath = await getDownloadsPath();
|
||||||
|
|
||||||
return window.electron
|
return window.electron
|
||||||
.showOpenDialog({
|
.showOpenDialog({
|
||||||
properties: ["openFile"],
|
properties: ["openFile"],
|
||||||
|
defaultPath: downloadsPath,
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
name: "Game executable",
|
name: "Game executable",
|
||||||
|
@ -6,7 +6,6 @@ import type { GameRepack } from "@types";
|
|||||||
|
|
||||||
import * as styles from "./repacks-modal.css";
|
import * as styles from "./repacks-modal.css";
|
||||||
|
|
||||||
import { useAppSelector } from "@renderer/hooks";
|
|
||||||
import { SPACING_UNIT } from "../../theme.css";
|
import { SPACING_UNIT } from "../../theme.css";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { SelectFolderModal } from "./select-folder-modal";
|
import { SelectFolderModal } from "./select-folder-modal";
|
||||||
@ -28,10 +27,6 @@ export function RepacksModal({
|
|||||||
const [repack, setRepack] = useState<GameRepack | null>(null);
|
const [repack, setRepack] = useState<GameRepack | null>(null);
|
||||||
const [showSelectFolderModal, setShowSelectFolderModal] = useState(false);
|
const [showSelectFolderModal, setShowSelectFolderModal] = useState(false);
|
||||||
|
|
||||||
const repackersFriendlyNames = useAppSelector(
|
|
||||||
(state) => state.repackersFriendlyNames.value
|
|
||||||
);
|
|
||||||
|
|
||||||
const { t } = useTranslation("game_details");
|
const { t } = useTranslation("game_details");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -87,7 +82,7 @@ export function RepacksModal({
|
|||||||
>
|
>
|
||||||
<p style={{ color: "#DADBE1" }}>{repack.title}</p>
|
<p style={{ color: "#DADBE1" }}>{repack.title}</p>
|
||||||
<p style={{ fontSize: "12px" }}>
|
<p style={{ fontSize: "12px" }}>
|
||||||
{repack.fileSize} - {repackersFriendlyNames[repack.repacker]} -{" "}
|
{repack.fileSize} - {repack.repacker} -{" "}
|
||||||
{repack.uploadDate
|
{repack.uploadDate
|
||||||
? format(repack.uploadDate, "dd/MM/yyyy")
|
? format(repack.uploadDate, "dd/MM/yyyy")
|
||||||
: ""}
|
: ""}
|
||||||
|
@ -19,7 +19,6 @@ export function SettingsGeneral({
|
|||||||
downloadsPath: "",
|
downloadsPath: "",
|
||||||
downloadNotificationsEnabled: false,
|
downloadNotificationsEnabled: false,
|
||||||
repackUpdatesNotificationsEnabled: false,
|
repackUpdatesNotificationsEnabled: false,
|
||||||
telemetryEnabled: false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -28,7 +27,6 @@ export function SettingsGeneral({
|
|||||||
downloadsPath,
|
downloadsPath,
|
||||||
downloadNotificationsEnabled,
|
downloadNotificationsEnabled,
|
||||||
repackUpdatesNotificationsEnabled,
|
repackUpdatesNotificationsEnabled,
|
||||||
telemetryEnabled,
|
|
||||||
} = userPreferences;
|
} = userPreferences;
|
||||||
|
|
||||||
window.electron.getDefaultDownloadsPath().then((defaultDownloadsPath) => {
|
window.electron.getDefaultDownloadsPath().then((defaultDownloadsPath) => {
|
||||||
@ -37,7 +35,6 @@ export function SettingsGeneral({
|
|||||||
downloadsPath: downloadsPath ?? defaultDownloadsPath,
|
downloadsPath: downloadsPath ?? defaultDownloadsPath,
|
||||||
downloadNotificationsEnabled,
|
downloadNotificationsEnabled,
|
||||||
repackUpdatesNotificationsEnabled,
|
repackUpdatesNotificationsEnabled,
|
||||||
telemetryEnabled,
|
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -104,18 +101,6 @@ export function SettingsGeneral({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<h3>{t("telemetry")}</h3>
|
|
||||||
|
|
||||||
<CheckboxField
|
|
||||||
label={t("telemetry_description")}
|
|
||||||
checked={form.telemetryEnabled}
|
|
||||||
onChange={() =>
|
|
||||||
handleChange({
|
|
||||||
telemetryEnabled: !form.telemetryEnabled,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ import {
|
|||||||
downloadSlice,
|
downloadSlice,
|
||||||
windowSlice,
|
windowSlice,
|
||||||
librarySlice,
|
librarySlice,
|
||||||
repackersFriendlyNamesSlice,
|
|
||||||
searchSlice,
|
searchSlice,
|
||||||
userPreferencesSlice,
|
userPreferencesSlice,
|
||||||
} from "@renderer/features";
|
} from "@renderer/features";
|
||||||
@ -11,7 +10,6 @@ import {
|
|||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
search: searchSlice.reducer,
|
search: searchSlice.reducer,
|
||||||
repackersFriendlyNames: repackersFriendlyNamesSlice.reducer,
|
|
||||||
window: windowSlice.reducer,
|
window: windowSlice.reducer,
|
||||||
library: librarySlice.reducer,
|
library: librarySlice.reducer,
|
||||||
userPreferences: userPreferencesSlice.reducer,
|
userPreferences: userPreferencesSlice.reducer,
|
||||||
|
4
src/renderer/src/vite-env.d.ts
vendored
4
src/renderer/src/vite-env.d.ts
vendored
@ -1,10 +1,6 @@
|
|||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
/// <reference types="vite-plugin-svgr/client" />
|
/// <reference types="vite-plugin-svgr/client" />
|
||||||
|
|
||||||
interface ImportMetaEnv {
|
|
||||||
readonly RENDERER_VITE_SENTRY_DSN: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
readonly env: ImportMetaEnv;
|
readonly env: ImportMetaEnv;
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,6 @@ export interface UserPreferences {
|
|||||||
language: string;
|
language: string;
|
||||||
downloadNotificationsEnabled: boolean;
|
downloadNotificationsEnabled: boolean;
|
||||||
repackUpdatesNotificationsEnabled: boolean;
|
repackUpdatesNotificationsEnabled: boolean;
|
||||||
telemetryEnabled: boolean;
|
|
||||||
realDebridApiToken: string | null;
|
realDebridApiToken: string | null;
|
||||||
preferQuitInsteadOfHiding: boolean;
|
preferQuitInsteadOfHiding: boolean;
|
||||||
runAtStartup: boolean;
|
runAtStartup: boolean;
|
||||||
|
Loading…
Reference in New Issue
Block a user