diff --git a/README.md b/README.md index 206ff62..9917aeb 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,10 @@ Click the function names to open their complete docs on the docs site. - [`getAchievementDistribution()`](https://api-docs.retroachievements.org/v1/get-achievement-distribution.html) - Get how many players have unlocked how many achievements for a game. - [`getGameRankAndScore()`](https://api-docs.retroachievements.org/v1/get-game-rank-and-score.html) - Get a list of either the latest masters or highest hardcore points earners for a game. +### Leaderboard + +- [`getLeaderboardEntries()`](https://api-docs.retroachievements.org/v1/get-leaderboard-entries.html) - Get a given leaderboard's entries. + ### System - [`getConsoleIds()`](https://api-docs.retroachievements.org/v1/get-console-ids.html) - Get the complete list of console ID and name pairs on the site. diff --git a/src/index.ts b/src/index.ts index 82bbcfb..66318eb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ export * from "./comment"; export * from "./console"; export * from "./feed"; export * from "./game"; +export * from "./leaderboard"; export * from "./ticket"; export * from "./user"; export * from "./utils/public"; diff --git a/src/leaderboard/getLeaderboardEntries.test.ts b/src/leaderboard/getLeaderboardEntries.test.ts new file mode 100644 index 0000000..023b22b --- /dev/null +++ b/src/leaderboard/getLeaderboardEntries.test.ts @@ -0,0 +1,73 @@ +/* eslint-disable sonarjs/no-duplicate-string */ + +import { http, HttpResponse } from "msw"; +import { setupServer } from "msw/node"; + +import { apiBaseUrl } from "../utils/internal"; +import { buildAuthorization } from "../utils/public"; +import { getLeaderboardEntries } from "./getLeaderboardEntries"; +import type { GetLeaderboardEntriesResponse } from "./models"; + +const server = setupServer(); + +describe("Function: getLeaderboardEntries", () => { + // MSW Setup + beforeAll(() => server.listen()); + afterEach(() => server.resetHandlers()); + afterAll(() => server.close()); + + it("is defined #sanity", () => { + // ASSERT + expect(getLeaderboardEntries).toBeDefined(); + }); + + it("given a leaderboard ID, retrieves the entries in that leaderboard", async () => { + // ARRANGE + const authorization = buildAuthorization({ + username: "mockUserName", + webApiKey: "mockWebApiKey", + }); + + const mockResponse: GetLeaderboardEntriesResponse = { + Count: 100, + Total: 1287, + Results: [ + { + Rank: 1, + User: "vani11a", + ULID: "00003EMFWR7XB8SDPEHB3K56ZQ", + Score: 390_490, + FormattedScore: "390,490", + DateSubmitted: "2024-07-25T15:51:00+00:00", + }, + ], + }; + + server.use( + http.get(`${apiBaseUrl}/API_GetLeaderboardEntries.php`, () => + HttpResponse.json(mockResponse) + ) + ); + + // ACT + const response = await getLeaderboardEntries(authorization, { + leaderboardId: 104_370, + }); + + // ASSERT + expect(response).toEqual({ + count: 100, + total: 1287, + results: [ + { + rank: 1, + user: "vani11a", + ulid: "00003EMFWR7XB8SDPEHB3K56ZQ", + score: 390_490, + formattedScore: "390,490", + dateSubmitted: "2024-07-25T15:51:00+00:00", + }, + ], + }); + }); +}); diff --git a/src/leaderboard/getLeaderboardEntries.ts b/src/leaderboard/getLeaderboardEntries.ts new file mode 100644 index 0000000..a7793f8 --- /dev/null +++ b/src/leaderboard/getLeaderboardEntries.ts @@ -0,0 +1,75 @@ +import type { ID } from "../utils/internal"; +import { + apiBaseUrl, + buildRequestUrl, + call, + serializeProperties, +} from "../utils/internal"; +import type { AuthObject } from "../utils/public"; +import type { + GetLeaderboardEntriesResponse, + LeaderboardEntries, +} from "./models"; + +/** + * A call to this endpoint will retrieve a given leaderboard's entries, targeted by its ID. + * + * @param authorization An object containing your username and webApiKey. + * This can be constructed with `buildAuthorization()`. + * + * @param payload.leaderboardId The target leaderboard ID. + * + * @param payload.offset Defaults to 0. The number of entries to skip. + * + * @param payload.count Defaults to 100, has a max of 500. + * + * @example + * ``` + * const leaderboardEntries = await getLeaderboardEntries( + * authorization, + * { leaderboardId: 14402 } + * ); + * ``` + * + * @returns An object containing a leaderboard's entries. + * ```json + * { + * "count": 100, + * "total": 1287, + * "results": [ + * { + * "rank": 1, + * "user": "vani11a", + * "ulid": "00003EMFWR7XB8SDPEHB3K56ZQ", + * "score": 390490, + * "formattedScore": "390,490", + * "dateSubmitted": "2024-07-25T15:51:00+00:00" + * } + * ] + * } + * ``` + */ +export const getLeaderboardEntries = async ( + authorization: AuthObject, + payload: { leaderboardId: ID; offset?: number; count?: number } +): Promise => { + const queryParams: Record = {}; + queryParams.i = payload.leaderboardId; + if (payload?.offset) { + queryParams.o = payload.offset; + } + if (payload?.count) { + queryParams.c = payload.count; + } + + const url = buildRequestUrl( + apiBaseUrl, + "/API_GetLeaderboardEntries.php", + authorization, + queryParams + ); + + const rawResponse = await call({ url }); + + return serializeProperties(rawResponse); +}; diff --git a/src/leaderboard/index.ts b/src/leaderboard/index.ts new file mode 100644 index 0000000..ce52679 --- /dev/null +++ b/src/leaderboard/index.ts @@ -0,0 +1,2 @@ +export * from "./getLeaderboardEntries"; +export * from "./models"; diff --git a/src/leaderboard/models/get-leaderboard-entries-response.model.ts b/src/leaderboard/models/get-leaderboard-entries-response.model.ts new file mode 100644 index 0000000..f62ee5d --- /dev/null +++ b/src/leaderboard/models/get-leaderboard-entries-response.model.ts @@ -0,0 +1,12 @@ +export interface GetLeaderboardEntriesResponse { + Count: number; + Total: number; + Results: Array<{ + Rank: number; + User: string; + ULID: string; + Score: number; + FormattedScore: string; + DateSubmitted: string; + }>; +} diff --git a/src/leaderboard/models/index.ts b/src/leaderboard/models/index.ts new file mode 100644 index 0000000..2d99e66 --- /dev/null +++ b/src/leaderboard/models/index.ts @@ -0,0 +1,2 @@ +export * from "./get-leaderboard-entries-response.model"; +export * from "./leaderboard-entries.model"; diff --git a/src/leaderboard/models/leaderboard-entries.model.ts b/src/leaderboard/models/leaderboard-entries.model.ts new file mode 100644 index 0000000..5d4df68 --- /dev/null +++ b/src/leaderboard/models/leaderboard-entries.model.ts @@ -0,0 +1,12 @@ +export interface LeaderboardEntries { + count: number; + total: number; + results: Array<{ + rank: number; + user: string; + ulid: string; + score: number; + formattedScore: string; + dateSubmitted: string; + }>; +}