creates syncSteamAchievements calls in steam_api and steamSync, adds models to support. adds palette command. need to acually inject into template

This commit is contained in:
CMorooney 2025-01-04 11:23:47 -05:00
parent f124ac08ea
commit fbb59b3ea0
4 changed files with 134 additions and 19 deletions

View file

@ -3,7 +3,14 @@
import type { Nullable } from '../main';
import { requestUrl } from 'obsidian';
import { extract } from 'fuzzball';
import { SteamResponse, OwnedSteamGames, SteamGame, SteamWishlistedGame } from '@models/steam_game.model';
import {
SteamResponse,
OwnedSteamGames,
SteamGame,
SteamWishlistedGame,
SteamAchievementInfo,
SteamUserAchievement,
} from '@models/steam_game.model';
export class SteamAPI {
constructor(private readonly key: string, private readonly steamId: string) {}
@ -89,6 +96,7 @@ export class SteamAPI {
});
const games = res?.json?.response?.games;
if (games) {
const ret: { [key: string]: { playtime_forever: Nullable<number>; playtime_2weeks: Nullable<number> } } = {};
for (const game of games) {
@ -106,4 +114,42 @@ export class SteamAPI {
throw error;
}
}
async getPlayerAchievmentsForGame(gameId: string): Promise<SteamAchievementInfo[]> {
try {
const userAchievementsUrl = new URL('http://api.steampowered.com/ISteamUserStats/GetUserStatsForGame/v0002/');
userAchievementsUrl.searchParams.append('key', this.key);
userAchievementsUrl.searchParams.append('format', 'json');
userAchievementsUrl.searchParams.append('steamid', this.steamId);
userAchievementsUrl.searchParams.append('appid', gameId);
const achievementDataUrl = new URL('https://api.steampowered.com/ISteamUserStats/GetSchemaForGame/v2');
achievementDataUrl.searchParams.append('key', this.key);
achievementDataUrl.searchParams.append('format', 'json');
achievementDataUrl.searchParams.append('steamid', this.steamId);
achievementDataUrl.searchParams.append('appid', gameId);
// this has readable content we'll need to match
const gameResult = await requestUrl({ url: achievementDataUrl.href, method: 'GET' });
// this has the user achievement data but nothing readable
const userResult = await requestUrl({ url: userAchievementsUrl.href, method: 'GET' });
const matches: SteamAchievementInfo[] = [];
if ((userResult?.json?.playerstats?.achievements?.length ?? 0) > 0) {
userResult?.json?.playerstats?.achievements
.filter(a => a.achieved)
.forEach(async (a: SteamUserAchievement) => {
const match = gameResult.json.game.availableGameStats.achievements.first(gs => gs.name === a.name);
if (match) {
matches.push(match);
}
});
}
return matches;
} catch (error) {
console.warn('[Game Search][Steam API][getPlayerAchievementsForGame]' + error);
throw error;
}
}
}

View file

@ -24,7 +24,7 @@ import {
useTemplaterPluginInFile,
executeInlineScriptsTemplates,
} from '@utils/template';
import { syncSteamWishlist, syncOwnedSteamGames, syncPlaytimes } from '@utils/steamSync';
import { syncAchievements, syncSteamWishlist, syncOwnedSteamGames, syncPlaytimes } from '@utils/steamSync';
export type Nullable<T> = T | undefined | null;
@ -78,6 +78,12 @@ export default class GameSearchPlugin extends Plugin {
callback: () => this.syncSteamPlaytime(true),
});
this.addCommand({
id: 'sync steam achievements',
name: 'Sync Steam Achievements',
callback: () => this.syncSteamAchievements(true),
});
// This adds a settings tab so the user can configure various aspects of the plugin
this.addSettingTab(new GameSearchSettingTab(this.app, this));
}
@ -239,6 +245,23 @@ export default class GameSearchPlugin extends Plugin {
}).open();
}
async syncSteamAchievements(alertUninitializedApi: boolean): Promise<void> {
// always check to see if steamApi needs to be initialized on sync,
// it's possible the user has entered API credentials at any point in time.
if (this.steamApi === undefined && this.settings.steamApiKey && this.settings.steamUserId) {
console.info('[Game Search][Steam Sync]: initializing steam api');
this.steamApi = new SteamAPI(this.settings.steamApiKey, this.settings.steamUserId);
}
if (this.steamApi !== undefined) {
new Notice('syncing steam achievements');
await syncAchievements(this.app.vault, this.app.fileManager, this.steamApi, this.settings);
new Notice('steam achievements sync complete');
} else if (alertUninitializedApi) {
console.warn('[Game Search][SteamSync]: steam api not initialized');
this.showNotice('Steam Api not initialized. Did you enter your steam API key and user Id in plugin settings?');
}
}
async syncSteamPlaytime(alertUninitializedApi: boolean): Promise<void> {
// always check to see if steamApi needs to be initialized on sync,
// it's possible the user has entered API credentials at any point in time.

View file

@ -43,10 +43,45 @@ export interface SteamWishlistedGame {
win: number;
}
interface SteamWishlistedGameSub {
export interface SteamWishlistedGameSub {
packageid: number;
bundleid?: number;
discount_block: string;
discount_pct?: string;
price: string;
}
export interface SteamUserAchievement {
name: string;
achieved: number;
}
export interface SteamAchievementInfo {
name: string;
defaultvalue: number;
displayName: string;
hidden: number;
description: string;
icon: string;
icongray: string;
}
export interface SteamGameStats {
achievements: SteamAchievementInfo[];
}
export interface SteamAchievementDataResponse {
game: {
gameName: string;
gameVersion: string;
availableGameStats: SteamGameStats;
};
}
export interface SteamUserStateForGameResponse {
playerstats: {
steamID: string;
gameName: string;
achievements: SteamUserAchievement[];
};
}

View file

@ -165,14 +165,6 @@ export async function syncOwnedSteamGames(
}
}
async function parseFileMetadata(fileManager: FileManager, file: TFile): Promise<any> {
return new Promise<any>(accept => {
fileManager.processFrontMatter(file, (data: any) => {
accept(data);
});
});
}
export async function syncPlaytimes(
vault: Vault,
fileManager: FileManager,
@ -192,14 +184,6 @@ export async function syncPlaytimes(
}
};
doForChild(async file => {
const noteMetadata = await parseFileMetadata(fileManager, file);
const steamId: Nullable<string> = noteMetadata.steamId;
if (steamId) {
ids.push(steamId);
}
});
const playerStats = await steamApi.getPlayerStatsForGames(ids);
if (playerStats) {
doForChild(async file => {
@ -213,3 +197,30 @@ export async function syncPlaytimes(
});
}
}
export async function syncAchievements(
vault: Vault,
fileManager: FileManager,
steamApi: SteamAPI,
settings: any,
): Promise<void> {
const folderPath = normalizePath(settings.folder);
const folder = vault.getFolderByPath(folderPath);
const doForChild = async (func: (file: TFile) => Promise<void>) => {
for (const f of folder.children) {
const file = f as TFile;
if (!!file && file.name.includes('.md')) {
await func(file);
}
}
};
doForChild(async file => {
fileManager.processFrontMatter(file, async data => {
if (data.steamId) {
await steamApi.getPlayerAchievmentsForGame(data.steamId);
}
});
});
}