Table of contents

Prerequisites

First and foremost, here’s the docs:

https://steamcommunity.com/dev

Now that we have that out of the way, there are two things that you need:

  1. A SteamId
  2. A Steam API Key

See below for instruction for each of these

How do I find my SteamId?

Login to Steam on the web or in the desktop app.

Go to the upper-right corner and click on

[Your Username] -> Account Details

In the header, under “[Username]‘s Account”, you’ll find your 17 Digit SteamId that looks like this:

SteamdID: XXXXXXXXXXXXXXXXX

How do I find my Steam API Key?

you can visit the Steam API Key Dashboard to register a new API key.

Here’s the full URL:

https://steamcommunity.com/dev/apikey

Steam API Reference

https://developer.valvesoftware.com/wiki/Steam_Web_API#GetPlayerSummaries_.28v0002.29

GetPlayerSummary - http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=XXXXXXXXXXXXXXXXXXXXXXX&steamids=76561197960435530

GetOwnedGames - http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?key=XXXXXXXXXXXXXXXXX&steamid=76561197960434622&format=json

GetPlayerAchievements - http://api.steampowered.com/ISteamUserStats/GetPlayerAchievements/v0001/?appid=440&key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&steamid=76561197972495328

GetUserStatsForGame - http://api.steampowered.com/ISteamUserStats/GetUserStatsForGame/v0002/?appid=440&key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&steamid=76561197972495328

export const STEAM_ID = import.meta.env.STEAM_ID ?? process.env.STEAM_ID;
export const STEAM_IDS = import.meta.env.STEAM_IDS ?? process.env.STEAM_IDS;
export const STEAM_API_KEY =
  import.meta.env.STEAM_API_KEY ?? process.env.STEAM_API_KEY;
 
const BASE_URL = "https://api.steampowered.com";
const BASE_PATHS = {
  GetPlayerSummaries: "/ISteamUser/GetPlayerSummaries/v0002",
  GetOwnedGames: "/IPlayerService/GetOwnedGames/v0001",
  GetFriendList: "/ISteamUser/GetFriendList/v0001",
  GetNewsForApp: "/ISteamNews/GetNewsForApp/v0002",
  GetPlayerAchievements: "/ISteamUserStats/GetPlayerAchievements/v0001",
  GetUserStatsForGame: "/ISteamUserStats/GetUserStatsForGame/v0002",
};
 
export interface SteamApiResponse<T> {
  response: T;
}
 
export type GetPlayerSummariesResponse = SteamApiResponse<{
  players: Array<{
    steamid: string;
    communityvisibilitystate: number;
    profilestate: number;
    personaname: string;
    profileurl: string;
    avatar: string;
    avatarmedium: string;
    avatarfull: string;
    avatarhash: string;
    lastlogoff: number;
    personastate: number;
    realname: string;
    primaryclanid: string;
    timecreated: number;
    personastateflags: number;
    gameextrainfo: string;
    gameid: string;
  }>;
}>;
 
export type GetOwnedGamesResponse = SteamApiResponse<{
  games_count: number;
  games: Array<
    {
      appid: number;
      playtime_forever: number;
      playtime_windows_forever: number;
      playtime_mac_forever: number;
      playtime_linux_forever: number;
      playtime_deck_forever: number;
      rtime_last_played: number;
      playtime_disconnected: number;
 
      // when include_appinfo is true
      name: string;
      has_community_visibile_stats: true;
      content_descriptorids: Array<number>;
      img_icon_url: string;
    } & (
      | { enhanced?: false }
      | {
          enhanced: true;
 
          achievements: GetPlayerAchievementsResponse["playerstats"]["achievements"];
          achievementsCountTotal: number;
          achievementsCountAchieved: number;
          achievementsCountPercent: string;
 
          stats: GetUserStatsResponse["playerstats"]["stats"];
          statsCount: number;
 
          news: GetNewsForAppResponse["appnews"]["newsitems"];
          newsCount: number;
        }
    )
  >;
}>;
 
export type GetPlayerAchievementsResponse = {
  playerstats: {
    success: boolean;
    steamID: string;
    gameName: string;
    achievements: Array<{
      apiname: string;
      achieved: number; // 0 or 1
      unlocktime: number;
    }>;
  };
};
 
export type GetUserStatsResponse = {
  playerstats: {
    success: boolean;
    steamID: string;
    gameName: string;
    stats: Array<{
      name: string;
      value: number;
    }>;
  };
};
 
export type GetFriendsListResponse = Array<{
  steamid: string;
  relationiship: string;
  friends_since: number;
}>;
 
export type GetNewsForAppResponse = {
  appnews: {
    appid: number;
    count: number;
    newsitems: Array<{
      gid: string;
      appid: number;
      title: string;
      url: string;
      is_external_url: boolean;
      author: string;
      contents: string;
      feedlabel: string;
      date: number;
      feed_type: number;
    }>;
  };
};
 
interface SteamAPI {
  GetPlayerSummaries: (
    steamIds: Array<string>,
  ) => Promise<GetPlayerSummariesResponse>;
  GetPlayerAchievements: (
    steamId: string,
    appId: number,
  ) => Promise<GetPlayerAchievementsResponse>;
  GetUserStatsForGame: (
    steamId: string,
    appId: number,
  ) => Promise<GetUserStatsResponse>;
  GetOwnedGames: (steamId: string) => Promise<GetOwnedGamesResponse>;
  GetFriendList: (
    steamId: string,
    relationship?: "all" | "friend",
  ) => Promise<GetFriendsListResponse>;
  GetNewsForApp: (
    appId: number,
    count?: number,
    maxlength?: number,
  ) => Promise<GetNewsForAppResponse>;
}
 
export function makeSteamAPI(
  baseUrl: string = BASE_URL,
  apiKey: string = STEAM_API_KEY,
): SteamAPI {
  let authUrl: URL = new URL(baseUrl);
  authUrl.searchParams.set("key", apiKey);
 
  const self: SteamAPI = {
    GetPlayerSummaries: async (steamIds) => {
      const url = new URL(authUrl);
      url.pathname = BASE_PATHS.GetPlayerSummaries;
      url.searchParams.set("steamids", steamIds.join(","));
      url.searchParams.set("format", "json");
 
      return handleFetch<GetPlayerSummariesResponse>(url.href).catch(
        (reason) => {
          console.error(
            "[Steam] :: Failed to fetch player summaries ::",
            reason,
          );
          return { response: { players: [] } };
        },
      );
    },
 
    GetOwnedGames: async (steamId) => {
      const url = new URL(authUrl);
      url.pathname = BASE_PATHS.GetOwnedGames;
      url.searchParams.set("steamid", steamId);
      url.searchParams.set("include_appinfo", "true");
      url.searchParams.set("include_played_free_games", "true");
      url.searchParams.set("format", "json");
 
      return handleFetch<GetOwnedGamesResponse>(url.href).catch((reason) => {
        console.error("[Steam] :: Failed to fetch owned games ::", reason);
        return { response: { games: [], games_count: 0 } };
      });
    },
 
    GetNewsForApp: async (appId, count = 5, maxlength = 1000) => {
      const url = new URL(authUrl);
      url.pathname = BASE_PATHS.GetNewsForApp;
      url.searchParams.set("appid", appId.toString());
      url.searchParams.set("count", count.toString());
      url.searchParams.set("maxlength", maxlength.toString());
      url.searchParams.set("format", "json");
 
      return handleFetch<GetNewsForAppResponse>(url.href).catch((reason) => {
        console.error("[Steam] :: Failed to fetch news ::", reason);
        return { appnews: { count: 0, newsitems: [], appid: appId } };
      });
    },
 
    GetFriendList: async (steamId, relationship = "friend") => {
      const url = new URL(authUrl);
      url.pathname = BASE_PATHS.GetFriendList;
      url.searchParams.set("steamid", steamId);
      url.searchParams.set("relationship", relationship);
      url.searchParams.set("format", "json");
 
      return handleFetch<GetFriendsListResponse>(url.href).catch((reason) => {
        console.error("[Steam] :: Failed to fetch friend list ::", reason);
        return [];
      });
    },
 
    GetPlayerAchievements: async (steamId, appId) => {
      const url = new URL(authUrl);
      url.pathname = BASE_PATHS.GetPlayerAchievements;
      url.searchParams.set("appid", appId.toString());
      url.searchParams.set("steamid", steamId);
 
      return handleFetch<GetPlayerAchievementsResponse>(url.href).catch(
        (reason) => {
          console.error(
            "[Steam] :: Failed to fetch player achievements ::",
            reason,
          );
          return {
            playerstats: {
              achievements: [],
              gameName: "N/A",
              steamID: "N/A",
              success: false,
            },
          };
        },
      );
    },
 
    GetUserStatsForGame: async (steamId, appId) => {
      const url = new URL(authUrl);
      url.pathname = BASE_PATHS.GetUserStatsForGame;
      url.searchParams.set("appId", appId.toString());
      url.searchParams.set("steamid", steamId);
 
      return handleFetch<GetUserStatsResponse>(url.href).catch((reason) => {
        console.error("[Steam] :: Failed to fetch player stats ::", reason);
        return {
          playerstats: {
            gameName: "N/A",
            stats: [],
            success: false,
            steamID: "N/A",
          },
        };
      });
    },
  };
 
  return self;
}
 
async function enhanceGame(
  steamId: string,
  game: GetOwnedGamesResponse["response"]["games"][number],
  srv: { steam: SteamAPI },
) {
  const { steam } = srv;
}
 
export async function getProfilesFromSteamIds(
  steamIds: Array<string>,
  srv: { steam: SteamAPI },
) {
  const { steam } = srv;
 
  const playerSummariesResponse = await steam.GetPlayerSummaries(steamIds),
    playerSummaries = playerSummariesResponse.response.players;
 
  const getProfileGames = async (
    profile: GetPlayerSummariesResponse["response"]["players"][number],
  ) => {
    const ownedGamesResponse = await steam.GetOwnedGames(
        profile?.steamid ?? "",
      ),
      ownedGames = ownedGamesResponse.response.games;
 
    ownedGames.sort((a, b) =>
      a.playtime_forever < b.playtime_forever ? 1 : -1,
    ); // sortbyplaytime
 
    // ownedGames.sort((a, b) =>
    //   a.rtime_last_played < b.rtime_last_played ? 1 : -1
    // ) // sortbylastplayed
 
    const enhancementLimit = import.meta.env.PROD ? 25 : 5; // limit the number of games to enhance, useful for larger libraries
    const enhancedGamesPromises = ownedGames
      .slice(0, enhancementLimit >= 0 ? enhancementLimit : undefined)
      .map(
        async (game) => {
          if (!profile) {
            return Promise.resolve(game);
          }
 
          const steamId = profile.steamid,
            appId = game.appid;
 
          const statResults = await steam.GetUserStatsForGame(steamId, appId);
          const aResults = await steam.GetPlayerAchievements(steamId, appId);
          const stats = statResults.playerstats.stats;
          const statsCount = stats.length;
 
          const achievements = aResults.playerstats.achievements;
          achievements.sort((a, b) => (a.unlocktime < b.unlocktime ? 1 : -1));
 
          const totalCount = achievements.length;
          const achievedCount = achievements.filter(
            (a) => a.achieved != 0,
          ).length;
          const achievedPercentNum = (achievedCount / totalCount) * 100;
          const achievedPercent = isNaN(achievedPercentNum)
            ? "0"
            : achievedPercentNum.toFixed(2);
 
          const newsResults = await steam.GetNewsForApp(appId);
          const news = newsResults["appnews"]["newsitems"];
          const newsCount = news.length;
 
          return {
            ...game,
            enhanced: true,
 
            stats: stats,
            statsCount: statsCount,
 
            news: news,
            newsCount,
 
            achievements: achievements,
            achievementsCountTotal: totalCount,
            achievementsCountAchieved: achievedCount,
            achievementsCountPercent: achievedPercent,
          };
        },
        // profile ? enhanceGame(profile.steamid, g, srv) : Promise.resolve(g)
      );
 
    const enhancedGames = await Promise.allSettled(enhancedGamesPromises).then(
      (results) =>
        results.filter((g) => g.status === "fulfilled").map((g) => g.value),
    );
 
    return {
      profile,
      games: [
        ...enhancedGames,
        ...ownedGames.slice(
          enhancementLimit >= 0 ? enhancementLimit : undefined,
        ),
      ],
    };
  };
 
  const steamProfilePromises = playerSummaries.map(getProfileGames),
    steamProfiles = await Promise.allSettled(steamProfilePromises).then(
      (results) =>
        results.filter((g) => g.status === "fulfilled").map((g) => g.value),
    );
 
  return steamProfiles;
}
 
export function printUnixDate(t: unknown): string {
  if (t && typeof t === "number" && t > 0) {
    const date = new Date(t * 1000);
    return date.toLocaleDateString();
  }
 
  return "N/A";
}
 
async function handleFetch<T = unknown>(url: string): Promise<T> {
  console.debug(`[Steam] :: Fetching ${url.split("?")[0]}...`);
 
  const response = await fetch(url, { cache: "default" });
 
  if (!response.ok || response.status < 200 || response.status > 299) {
    throw new Error("Failed to load data.");
  }
 
  const data = (await response.json()) as T;
  return data;
}