From 1ea98613fbc630573d963cbc4a1d4f69d9d910f0 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Mon, 16 Dec 2024 14:57:02 -0300 Subject: [PATCH 1/2] feat: add hidden achievement description option --- src/locales/en/translation.json | 3 +- src/locales/pt-BR/translation.json | 3 +- src/main/entity/user-preferences.entity.ts | 3 + .../events/user/get-unlocked-achievements.ts | 18 ++++- src/main/knex-client.ts | 3 + ..._hidden_achievement_description_column .ts | 20 ++++++ src/main/scripts/get-games-icon-hash.ts | 40 ----------- .../achievements/get-game-achievement-data.ts | 13 ++-- src/main/services/index.ts | 1 - src/main/services/steam-grid.ts | 69 ------------------- .../src/pages/settings/settings-behavior.tsx | 14 ++++ src/types/index.ts | 1 + 12 files changed, 65 insertions(+), 123 deletions(-) create mode 100644 src/main/migrations/20241216140633_add_hidden_achievement_description_column .ts delete mode 100644 src/main/scripts/get-games-icon-hash.ts delete mode 100644 src/main/services/steam-grid.ts diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 940e3185..b3e1caed 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -258,7 +258,8 @@ "user_unblocked": "User has been unblocked", "enable_achievement_notifications": "When an achievement is unlocked", "launch_minimized": "Launch Hydra minimized", - "disable_nsfw_alert": "Disable NSFW alert" + "disable_nsfw_alert": "Disable NSFW alert", + "show_hidden_achievement_description": "Show hidden achievements description before unlocking them" }, "notifications": { "download_complete": "Download complete", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index e724cdc3..a1197c78 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -254,7 +254,8 @@ "user_unblocked": "Usuário desbloqueado", "enable_achievement_notifications": "Quando uma conquista é desbloqueada", "launch_minimized": "Iniciar o Hydra minimizado", - "disable_nsfw_alert": "Desativar alerta de conteúdo inapropriado" + "disable_nsfw_alert": "Desativar alerta de conteúdo inapropriado", + "show_hidden_achievement_description": "Mostrar descrição de conquistas ocultas antes de debloqueá-las" }, "notifications": { "download_complete": "Download concluído", diff --git a/src/main/entity/user-preferences.entity.ts b/src/main/entity/user-preferences.entity.ts index 357dfb50..6fe7ab77 100644 --- a/src/main/entity/user-preferences.entity.ts +++ b/src/main/entity/user-preferences.entity.ts @@ -41,6 +41,9 @@ export class UserPreferences { @Column("boolean", { default: false }) disableNsfwAlert: boolean; + @Column("boolean", { default: false }) + showHiddenAchievementsDescription: boolean; + @CreateDateColumn() createdAt: Date; diff --git a/src/main/events/user/get-unlocked-achievements.ts b/src/main/events/user/get-unlocked-achievements.ts index a831bc50..ffa25399 100644 --- a/src/main/events/user/get-unlocked-achievements.ts +++ b/src/main/events/user/get-unlocked-achievements.ts @@ -1,6 +1,9 @@ import type { GameShop, UnlockedAchievement, UserAchievement } from "@types"; import { registerEvent } from "../register-event"; -import { gameAchievementRepository } from "@main/repository"; +import { + gameAchievementRepository, + userPreferencesRepository, +} from "@main/repository"; import { getGameAchievementData } from "@main/services/achievements/get-game-achievement-data"; export const getUnlockedAchievements = async ( @@ -12,10 +15,17 @@ export const getUnlockedAchievements = async ( where: { objectId, shop }, }); + const userPreferences = await userPreferencesRepository.findOne({ + where: { id: 1 }, + }); + + const showHiddenAchievementsDescription = + userPreferences?.showHiddenAchievementsDescription || false; + const achievementsData = await getGameAchievementData( objectId, shop, - useCachedData + useCachedData ? cachedAchievements : null ); const unlockedAchievements = JSON.parse( @@ -50,6 +60,10 @@ export const getUnlockedAchievements = async ( unlocked: false, unlockTime: null, icongray: icongray, + description: + !achievementData.hidden || showHiddenAchievementsDescription + ? achievementData.description + : undefined, } as UserAchievement; }) .sort((a, b) => { diff --git a/src/main/knex-client.ts b/src/main/knex-client.ts index 988d42da..bfb4a430 100644 --- a/src/main/knex-client.ts +++ b/src/main/knex-client.ts @@ -13,6 +13,8 @@ import { AddBackgroundImageUrl } from "./migrations/20241016100249_add_backgroun import { AddWinePrefixToGame } from "./migrations/20241019081648_add_wine_prefix_to_game"; import { AddStartMinimizedColumn } from "./migrations/20241030171454_add_start_minimized_column"; import { AddDisableNsfwAlertColumn } from "./migrations/20241106053733_add_disable_nsfw_alert_column"; +import { AddHiddenAchievementDescriptionColumn } from "./migrations/20241216140633_add_hidden_achievement_description_column "; + export type HydraMigration = Knex.Migration & { name: string }; class MigrationSource implements Knex.MigrationSource { @@ -30,6 +32,7 @@ class MigrationSource implements Knex.MigrationSource { AddWinePrefixToGame, AddStartMinimizedColumn, AddDisableNsfwAlertColumn, + AddHiddenAchievementDescriptionColumn, ]); } getMigrationName(migration: HydraMigration): string { diff --git a/src/main/migrations/20241216140633_add_hidden_achievement_description_column .ts b/src/main/migrations/20241216140633_add_hidden_achievement_description_column .ts new file mode 100644 index 00000000..36771c43 --- /dev/null +++ b/src/main/migrations/20241216140633_add_hidden_achievement_description_column .ts @@ -0,0 +1,20 @@ +import type { HydraMigration } from "@main/knex-client"; +import type { Knex } from "knex"; + +export const AddHiddenAchievementDescriptionColumn: HydraMigration = { + name: "AddHiddenAchievementDescriptionColumn", + up: (knex: Knex) => { + return knex.schema.alterTable("user_preferences", (table) => { + return table + .boolean("showHiddenAchievementsDescription") + .notNullable() + .defaultTo(0); + }); + }, + + down: async (knex: Knex) => { + return knex.schema.alterTable("user_preferences", (table) => { + return table.dropColumn("showHiddenAchievementsDescription"); + }); + }, +}; diff --git a/src/main/scripts/get-games-icon-hash.ts b/src/main/scripts/get-games-icon-hash.ts deleted file mode 100644 index 36ecd35e..00000000 --- a/src/main/scripts/get-games-icon-hash.ts +++ /dev/null @@ -1,40 +0,0 @@ -import path from "node:path"; -import fs from "node:fs"; - -import { getSteamGameClientIcon, logger } from "@main/services"; -import { chunk } from "lodash-es"; -import { seedsPath } from "@main/constants"; - -import type { SteamGame } from "@types"; - -const steamGamesPath = path.join(seedsPath, "steam-games.json"); - -const steamGames = JSON.parse( - fs.readFileSync(steamGamesPath, "utf-8") -) as SteamGame[]; - -const chunks = chunk(steamGames, 1500); - -for (const chunk of chunks) { - await Promise.all( - chunk.map(async (steamGame) => { - if (steamGame.clientIcon) return; - - const index = steamGames.findIndex((game) => game.id === steamGame.id); - - try { - const clientIcon = await getSteamGameClientIcon(String(steamGame.id)); - - steamGames[index].clientIcon = clientIcon; - - logger.log("info", `Set ${steamGame.name} client icon`); - } catch (err) { - steamGames[index].clientIcon = null; - logger.log("info", `Could not set icon for ${steamGame.name}`); - } - }) - ); - - fs.writeFileSync(steamGamesPath, JSON.stringify(steamGames)); - logger.log("info", "Updated steam games"); -} diff --git a/src/main/services/achievements/get-game-achievement-data.ts b/src/main/services/achievements/get-game-achievement-data.ts index 02019dab..daac7e11 100644 --- a/src/main/services/achievements/get-game-achievement-data.ts +++ b/src/main/services/achievements/get-game-achievement-data.ts @@ -6,20 +6,15 @@ import { HydraApi } from "../hydra-api"; import type { AchievementData, GameShop } from "@types"; import { UserNotLoggedInError } from "@shared"; import { logger } from "../logger"; +import { GameAchievement } from "@main/entity"; export const getGameAchievementData = async ( objectId: string, shop: GameShop, - useCachedData: boolean + cachedAchievements: GameAchievement | null ) => { - if (useCachedData) { - const cachedAchievements = await gameAchievementRepository.findOne({ - where: { objectId, shop }, - }); - - if (cachedAchievements && cachedAchievements.achievements) { - return JSON.parse(cachedAchievements.achievements) as AchievementData[]; - } + if (cachedAchievements && cachedAchievements.achievements) { + return JSON.parse(cachedAchievements.achievements) as AchievementData[]; } const userPreferences = await userPreferencesRepository.findOne({ diff --git a/src/main/services/index.ts b/src/main/services/index.ts index 498159c9..5aaf5322 100644 --- a/src/main/services/index.ts +++ b/src/main/services/index.ts @@ -1,7 +1,6 @@ export * from "./logger"; export * from "./steam"; export * from "./steam-250"; -export * from "./steam-grid"; export * from "./window-manager"; export * from "./download"; export * from "./process-watcher"; diff --git a/src/main/services/steam-grid.ts b/src/main/services/steam-grid.ts deleted file mode 100644 index 540e5857..00000000 --- a/src/main/services/steam-grid.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { GameShop } from "@types"; -import axios from "axios"; - -export interface SteamGridResponse { - success: boolean; - data: { - id: number; - }; -} - -export interface SteamGridGameResponse { - data: { - platforms: { - steam: { - metadata: { - clienticon: string; - }; - }; - }; - }; -} - -export const getSteamGridData = async ( - objectId: string, - path: string, - shop: GameShop, - params: Record = {} -): Promise => { - const searchParams = new URLSearchParams(params); - - if (!import.meta.env.MAIN_VITE_STEAMGRIDDB_API_KEY) { - throw new Error("MAIN_VITE_STEAMGRIDDB_API_KEY is not set"); - } - - const response = await axios.get( - `https://www.steamgriddb.com/api/v2/${path}/${shop}/${objectId}?${searchParams.toString()}`, - { - headers: { - Authorization: `Bearer ${import.meta.env.MAIN_VITE_STEAMGRIDDB_API_KEY}`, - }, - } - ); - - return response.data; -}; - -export const getSteamGridGameById = async ( - id: number -): Promise => { - const response = await axios.get( - `https://www.steamgriddb.com/api/public/game/${id}`, - { - headers: { - Referer: "https://www.steamgriddb.com/", - }, - } - ); - - return response.data; -}; - -export const getSteamGameClientIcon = async (objectId: string) => { - const { - data: { id: steamGridGameId }, - } = await getSteamGridData(objectId, "games", "steam"); - - const steamGridGame = await getSteamGridGameById(steamGridGameId); - return steamGridGame.data.platforms.steam.metadata.clienticon; -}; diff --git a/src/renderer/src/pages/settings/settings-behavior.tsx b/src/renderer/src/pages/settings/settings-behavior.tsx index b4b91dd2..478cdffc 100644 --- a/src/renderer/src/pages/settings/settings-behavior.tsx +++ b/src/renderer/src/pages/settings/settings-behavior.tsx @@ -19,6 +19,7 @@ export function SettingsBehavior() { runAtStartup: false, startMinimized: false, disableNsfwAlert: false, + showHiddenAchievementsDescription: false, }); const { t } = useTranslation("settings"); @@ -30,6 +31,8 @@ export function SettingsBehavior() { runAtStartup: userPreferences.runAtStartup, startMinimized: userPreferences.startMinimized, disableNsfwAlert: userPreferences.disableNsfwAlert, + showHiddenAchievementsDescription: + userPreferences.showHiddenAchievementsDescription, }); } }, [userPreferences]); @@ -96,6 +99,17 @@ export function SettingsBehavior() { handleChange({ disableNsfwAlert: !form.disableNsfwAlert }) } /> + + + handleChange({ + showHiddenAchievementsDescription: + !form.showHiddenAchievementsDescription, + }) + } + /> ); } diff --git a/src/types/index.ts b/src/types/index.ts index 434a15e7..bd9293e8 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -162,6 +162,7 @@ export interface UserPreferences { runAtStartup: boolean; startMinimized: boolean; disableNsfwAlert: boolean; + showHiddenAchievementsDescription: boolean; } export interface Steam250Game { From 3a964dcf1721dcdec31b6c69e1f379313368a875 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Tue, 17 Dec 2024 13:23:56 -0300 Subject: [PATCH 2/2] feat: create game --- .../events/torrenting/start-game-download.ts | 27 +++++++++---------- src/preload/index.ts | 2 +- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/main/events/torrenting/start-game-download.ts b/src/main/events/torrenting/start-game-download.ts index ce16e97b..de10b07d 100644 --- a/src/main/events/torrenting/start-game-download.ts +++ b/src/main/events/torrenting/start-game-download.ts @@ -1,6 +1,6 @@ import { registerEvent } from "../register-event"; import type { StartGameDownloadPayload } from "@types"; -import { DownloadManager, HydraApi, logger } from "@main/services"; +import { DownloadManager, HydraApi } from "@main/services"; import { Not } from "typeorm"; import { steamGamesWorker } from "@main/workers"; @@ -76,24 +76,23 @@ const startGameDownload = async ( }, }); - createGame(updatedGame!).catch(() => {}); - - HydraApi.post( - "/games/download", - { - objectId: updatedGame!.objectID, - shop: updatedGame!.shop, - }, - { needsAuth: false } - ).catch((err) => { - logger.error("Failed to create game download", err); - }); - await DownloadManager.cancelDownload(updatedGame!.id); await DownloadManager.startDownload(updatedGame!); await downloadQueueRepository.delete({ game: { id: updatedGame!.id } }); await downloadQueueRepository.insert({ game: { id: updatedGame!.id } }); + + await Promise.all([ + createGame(updatedGame!).catch(() => {}), + HydraApi.post( + "/games/download", + { + objectId: updatedGame!.objectID, + shop: updatedGame!.shop, + }, + { needsAuth: false } + ).catch(() => {}), + ]); }); }; diff --git a/src/preload/index.ts b/src/preload/index.ts index f9d19644..d2a6a676 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -11,10 +11,10 @@ import type { GameRunning, FriendRequestAction, UpdateProfileRequest, + GameAchievement, } from "@types"; import type { CatalogueCategory } from "@shared"; import type { AxiosProgressEvent } from "axios"; -import { GameAchievement } from "@main/entity"; contextBridge.exposeInMainWorld("electron", { /* Torrenting */