Merge pull request #1302 from hydralauncher/feat/hidden-achievement-description
Some checks are pending
Release / build (ubuntu-latest) (push) Waiting to run
Release / build (windows-latest) (push) Waiting to run

feat: add description option for hidden achievements
This commit is contained in:
Zamitto 2024-12-17 15:45:32 -03:00 committed by GitHub
commit ce2cf89fd6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 79 additions and 138 deletions

View File

@ -258,7 +258,8 @@
"user_unblocked": "User has been unblocked", "user_unblocked": "User has been unblocked",
"enable_achievement_notifications": "When an achievement is unlocked", "enable_achievement_notifications": "When an achievement is unlocked",
"launch_minimized": "Launch Hydra minimized", "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": { "notifications": {
"download_complete": "Download complete", "download_complete": "Download complete",

View File

@ -254,7 +254,8 @@
"user_unblocked": "Usuário desbloqueado", "user_unblocked": "Usuário desbloqueado",
"enable_achievement_notifications": "Quando uma conquista é desbloqueada", "enable_achievement_notifications": "Quando uma conquista é desbloqueada",
"launch_minimized": "Iniciar o Hydra minimizado", "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": { "notifications": {
"download_complete": "Download concluído", "download_complete": "Download concluído",

View File

@ -41,6 +41,9 @@ export class UserPreferences {
@Column("boolean", { default: false }) @Column("boolean", { default: false })
disableNsfwAlert: boolean; disableNsfwAlert: boolean;
@Column("boolean", { default: false })
showHiddenAchievementsDescription: boolean;
@CreateDateColumn() @CreateDateColumn()
createdAt: Date; createdAt: Date;

View File

@ -1,6 +1,6 @@
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import type { StartGameDownloadPayload } from "@types"; import type { StartGameDownloadPayload } from "@types";
import { DownloadManager, HydraApi, logger } from "@main/services"; import { DownloadManager, HydraApi } from "@main/services";
import { Not } from "typeorm"; import { Not } from "typeorm";
import { steamGamesWorker } from "@main/workers"; 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.cancelDownload(updatedGame!.id);
await DownloadManager.startDownload(updatedGame!); await DownloadManager.startDownload(updatedGame!);
await downloadQueueRepository.delete({ game: { id: updatedGame!.id } }); await downloadQueueRepository.delete({ game: { id: updatedGame!.id } });
await downloadQueueRepository.insert({ 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(() => {}),
]);
}); });
}; };

View File

@ -1,6 +1,9 @@
import type { GameShop, UnlockedAchievement, UserAchievement } from "@types"; import type { GameShop, UnlockedAchievement, UserAchievement } from "@types";
import { registerEvent } from "../register-event"; 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"; import { getGameAchievementData } from "@main/services/achievements/get-game-achievement-data";
export const getUnlockedAchievements = async ( export const getUnlockedAchievements = async (
@ -12,10 +15,17 @@ export const getUnlockedAchievements = async (
where: { objectId, shop }, where: { objectId, shop },
}); });
const userPreferences = await userPreferencesRepository.findOne({
where: { id: 1 },
});
const showHiddenAchievementsDescription =
userPreferences?.showHiddenAchievementsDescription || false;
const achievementsData = await getGameAchievementData( const achievementsData = await getGameAchievementData(
objectId, objectId,
shop, shop,
useCachedData useCachedData ? cachedAchievements : null
); );
const unlockedAchievements = JSON.parse( const unlockedAchievements = JSON.parse(
@ -50,6 +60,10 @@ export const getUnlockedAchievements = async (
unlocked: false, unlocked: false,
unlockTime: null, unlockTime: null,
icongray: icongray, icongray: icongray,
description:
!achievementData.hidden || showHiddenAchievementsDescription
? achievementData.description
: undefined,
} as UserAchievement; } as UserAchievement;
}) })
.sort((a, b) => { .sort((a, b) => {

View File

@ -13,6 +13,8 @@ import { AddBackgroundImageUrl } from "./migrations/20241016100249_add_backgroun
import { AddWinePrefixToGame } from "./migrations/20241019081648_add_wine_prefix_to_game"; import { AddWinePrefixToGame } from "./migrations/20241019081648_add_wine_prefix_to_game";
import { AddStartMinimizedColumn } from "./migrations/20241030171454_add_start_minimized_column"; import { AddStartMinimizedColumn } from "./migrations/20241030171454_add_start_minimized_column";
import { AddDisableNsfwAlertColumn } from "./migrations/20241106053733_add_disable_nsfw_alert_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 }; export type HydraMigration = Knex.Migration & { name: string };
class MigrationSource implements Knex.MigrationSource<HydraMigration> { class MigrationSource implements Knex.MigrationSource<HydraMigration> {
@ -30,6 +32,7 @@ class MigrationSource implements Knex.MigrationSource<HydraMigration> {
AddWinePrefixToGame, AddWinePrefixToGame,
AddStartMinimizedColumn, AddStartMinimizedColumn,
AddDisableNsfwAlertColumn, AddDisableNsfwAlertColumn,
AddHiddenAchievementDescriptionColumn,
]); ]);
} }
getMigrationName(migration: HydraMigration): string { getMigrationName(migration: HydraMigration): string {

View File

@ -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");
});
},
};

View File

@ -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");
}

View File

@ -6,20 +6,15 @@ import { HydraApi } from "../hydra-api";
import type { AchievementData, GameShop } from "@types"; import type { AchievementData, GameShop } from "@types";
import { UserNotLoggedInError } from "@shared"; import { UserNotLoggedInError } from "@shared";
import { logger } from "../logger"; import { logger } from "../logger";
import { GameAchievement } from "@main/entity";
export const getGameAchievementData = async ( export const getGameAchievementData = async (
objectId: string, objectId: string,
shop: GameShop, shop: GameShop,
useCachedData: boolean cachedAchievements: GameAchievement | null
) => { ) => {
if (useCachedData) { if (cachedAchievements && cachedAchievements.achievements) {
const cachedAchievements = await gameAchievementRepository.findOne({ return JSON.parse(cachedAchievements.achievements) as AchievementData[];
where: { objectId, shop },
});
if (cachedAchievements && cachedAchievements.achievements) {
return JSON.parse(cachedAchievements.achievements) as AchievementData[];
}
} }
const userPreferences = await userPreferencesRepository.findOne({ const userPreferences = await userPreferencesRepository.findOne({

View File

@ -1,7 +1,6 @@
export * from "./logger"; export * from "./logger";
export * from "./steam"; export * from "./steam";
export * from "./steam-250"; export * from "./steam-250";
export * from "./steam-grid";
export * from "./window-manager"; export * from "./window-manager";
export * from "./download"; export * from "./download";
export * from "./process-watcher"; export * from "./process-watcher";

View File

@ -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<string, string> = {}
): Promise<SteamGridResponse> => {
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<SteamGridGameResponse> => {
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;
};

View File

@ -11,10 +11,10 @@ import type {
GameRunning, GameRunning,
FriendRequestAction, FriendRequestAction,
UpdateProfileRequest, UpdateProfileRequest,
GameAchievement,
} from "@types"; } from "@types";
import type { CatalogueCategory } from "@shared"; import type { CatalogueCategory } from "@shared";
import type { AxiosProgressEvent } from "axios"; import type { AxiosProgressEvent } from "axios";
import { GameAchievement } from "@main/entity";
contextBridge.exposeInMainWorld("electron", { contextBridge.exposeInMainWorld("electron", {
/* Torrenting */ /* Torrenting */

View File

@ -19,6 +19,7 @@ export function SettingsBehavior() {
runAtStartup: false, runAtStartup: false,
startMinimized: false, startMinimized: false,
disableNsfwAlert: false, disableNsfwAlert: false,
showHiddenAchievementsDescription: false,
}); });
const { t } = useTranslation("settings"); const { t } = useTranslation("settings");
@ -30,6 +31,8 @@ export function SettingsBehavior() {
runAtStartup: userPreferences.runAtStartup, runAtStartup: userPreferences.runAtStartup,
startMinimized: userPreferences.startMinimized, startMinimized: userPreferences.startMinimized,
disableNsfwAlert: userPreferences.disableNsfwAlert, disableNsfwAlert: userPreferences.disableNsfwAlert,
showHiddenAchievementsDescription:
userPreferences.showHiddenAchievementsDescription,
}); });
} }
}, [userPreferences]); }, [userPreferences]);
@ -96,6 +99,17 @@ export function SettingsBehavior() {
handleChange({ disableNsfwAlert: !form.disableNsfwAlert }) handleChange({ disableNsfwAlert: !form.disableNsfwAlert })
} }
/> />
<CheckboxField
label={t("show_hidden_achievement_description")}
checked={form.showHiddenAchievementsDescription}
onChange={() =>
handleChange({
showHiddenAchievementsDescription:
!form.showHiddenAchievementsDescription,
})
}
/>
</> </>
); );
} }

View File

@ -162,6 +162,7 @@ export interface UserPreferences {
runAtStartup: boolean; runAtStartup: boolean;
startMinimized: boolean; startMinimized: boolean;
disableNsfwAlert: boolean; disableNsfwAlert: boolean;
showHiddenAchievementsDescription: boolean;
} }
export interface Steam250Game { export interface Steam250Game {