@retroachievements/api 1.1.1 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +2 -0
  2. package/dist/api.cjs +1 -1
  3. package/dist/api.cjs.map +1 -1
  4. package/dist/api.modern.js +1 -1
  5. package/dist/api.modern.js.map +1 -1
  6. package/dist/api.module.js +1 -1
  7. package/dist/api.module.js.map +1 -1
  8. package/dist/api.umd.js +1 -1
  9. package/dist/api.umd.js.map +1 -1
  10. package/dist/user/getUserAwards.d.ts +47 -0
  11. package/dist/user/getUserAwards.test.d.ts +1 -0
  12. package/dist/user/getUserRecentAchievements.d.ts +49 -0
  13. package/dist/user/getUserRecentAchievements.test.d.ts +1 -0
  14. package/dist/user/index.d.ts +2 -0
  15. package/dist/user/models/award-type.model.d.ts +1 -0
  16. package/dist/user/models/get-user-awards-response.model.d.ts +22 -0
  17. package/dist/user/models/get-user-recent-achievements-response.model.d.ts +18 -0
  18. package/dist/user/models/index.d.ts +5 -0
  19. package/dist/user/models/user-awards.model.d.ts +22 -0
  20. package/dist/user/models/user-recent-achievement.model.d.ts +16 -0
  21. package/package.json +1 -1
  22. package/src/user/getUserAwards.test.ts +83 -0
  23. package/src/user/getUserAwards.ts +70 -0
  24. package/src/user/getUserRecentAchievements.test.ts +82 -0
  25. package/src/user/getUserRecentAchievements.ts +80 -0
  26. package/src/user/getUserSummary.test.ts +21 -0
  27. package/src/user/index.ts +2 -0
  28. package/src/user/models/award-type.model.ts +7 -0
  29. package/src/user/models/get-user-awards-response.model.ts +23 -0
  30. package/src/user/models/get-user-recent-achievements-response.model.ts +19 -0
  31. package/src/user/models/index.ts +5 -0
  32. package/src/user/models/user-awards.model.ts +23 -0
  33. package/src/user/models/user-recent-achievement.model.ts +16 -0
  34. package/src/utils/internal/call.test.ts +17 -0
  35. package/src/utils/internal/call.ts +7 -0
  36. package/src/utils/internal/serializeProperties.ts +3 -0
@@ -0,0 +1,22 @@
1
+ import type { AwardType } from "./award-type.model";
2
+ interface UserAward {
3
+ awardedAt: string;
4
+ awardType: AwardType;
5
+ awardData: number;
6
+ awardDataExtra: number;
7
+ displayOrder: number;
8
+ title: string;
9
+ consoleName: string;
10
+ flags: number | null;
11
+ imageIcon: string;
12
+ }
13
+ export interface UserAwards {
14
+ totalAwardsCount: number;
15
+ hiddenAwardsCount: number;
16
+ masteryAwardsCount: number;
17
+ completionAwardsCount: number;
18
+ eventAwardsCount: number;
19
+ siteAwardsCount: number;
20
+ visibleUserAwards: UserAward[];
21
+ }
22
+ export {};
@@ -0,0 +1,16 @@
1
+ export interface UserRecentAchievement {
2
+ date: string;
3
+ hardcoreMode: boolean;
4
+ achievementId: number;
5
+ title: string;
6
+ description: string;
7
+ badgeName: string;
8
+ points: number;
9
+ author: string;
10
+ gameTitle: string;
11
+ gameIcon: string;
12
+ gameId: number;
13
+ consoleName: string;
14
+ badgeUrl: string;
15
+ gameUrl: string;
16
+ }
package/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "raweb",
11
11
  "retro gaming"
12
12
  ],
13
- "version": "1.1.1",
13
+ "version": "1.2.1",
14
14
  "typings": "dist/index.d.ts",
15
15
  "exports": {
16
16
  ".": {
@@ -0,0 +1,83 @@
1
+ import { rest } from "msw";
2
+ import { setupServer } from "msw/node";
3
+
4
+ import { apiBaseUrl } from "../utils/internal";
5
+ import { buildAuthorization } from "../utils/public";
6
+ import { getUserAwards } from "./getUserAwards";
7
+ import type { GetUserAwardsResponse } from "./models";
8
+
9
+ const server = setupServer();
10
+
11
+ describe("Function: getUserAwards", () => {
12
+ // MSW Setup
13
+ beforeAll(() => server.listen());
14
+ afterEach(() => server.resetHandlers());
15
+ afterAll(() => server.close());
16
+
17
+ it("is defined #sanity", () => {
18
+ // ASSERT
19
+ expect(getUserAwards).toBeDefined();
20
+ });
21
+
22
+ it("retrieves a list of a target user awards", async () => {
23
+ // ARRANGE
24
+ const authorization = buildAuthorization({
25
+ userName: "mockUserName",
26
+ webApiKey: "mockWebApiKey"
27
+ });
28
+
29
+ const mockResponse: GetUserAwardsResponse = {
30
+ TotalAwardsCount: 10,
31
+ HiddenAwardsCount: 5,
32
+ MasteryAwardsCount: 5,
33
+ CompletionAwardsCount: 0,
34
+ EventAwardsCount: 0,
35
+ SiteAwardsCount: 0,
36
+ VisibleUserAwards: [
37
+ {
38
+ AwardedAt: "2022-08-26T19:34:43+00:00",
39
+ AwardType: "Mastery/Completion",
40
+ AwardData: 802,
41
+ AwardDataExtra: 1,
42
+ DisplayOrder: 114,
43
+ Title: "WarioWare, Inc.: Mega Microgames!",
44
+ ConsoleName: "Game Boy Advance",
45
+ Flags: null,
46
+ ImageIcon: "/Images/034678.png"
47
+ }
48
+ ]
49
+ };
50
+
51
+ server.use(
52
+ rest.get(`${apiBaseUrl}/API_GetUserAwards.php`, (_, res, ctx) =>
53
+ res(ctx.json(mockResponse))
54
+ )
55
+ );
56
+
57
+ // ACT
58
+ const response = await getUserAwards(authorization, { userName: "xelnia" });
59
+
60
+ // ASSERT
61
+ expect(response).toEqual({
62
+ totalAwardsCount: 10,
63
+ hiddenAwardsCount: 5,
64
+ masteryAwardsCount: 5,
65
+ completionAwardsCount: 0,
66
+ eventAwardsCount: 0,
67
+ siteAwardsCount: 0,
68
+ visibleUserAwards: [
69
+ {
70
+ awardedAt: "2022-08-26T19:34:43+00:00",
71
+ awardType: "Mastery/Completion",
72
+ awardData: 802,
73
+ awardDataExtra: 1,
74
+ displayOrder: 114,
75
+ title: "WarioWare, Inc.: Mega Microgames!",
76
+ consoleName: "Game Boy Advance",
77
+ flags: null,
78
+ imageIcon: "/Images/034678.png"
79
+ }
80
+ ]
81
+ });
82
+ });
83
+ });
@@ -0,0 +1,70 @@
1
+ import {
2
+ apiBaseUrl,
3
+ buildRequestUrl,
4
+ call,
5
+ serializeProperties
6
+ } from "../utils/internal";
7
+ import type { AuthObject } from "../utils/public";
8
+ import type { GetUserAwardsResponse, UserAwards } from "./models";
9
+
10
+ /**
11
+ * A call to this function will retrieve metadata about the target user's
12
+ * site awards, via their username.
13
+ *
14
+ * @param authorization An object containing your userName and webApiKey.
15
+ * This can be constructed with `buildAuthorization()`.
16
+ *
17
+ * @param payload.userName The user for which to retrieve the site awards for.
18
+ *
19
+ * @example
20
+ * ```
21
+ * const userAwards = await getUserAwards(
22
+ * authorization,
23
+ * { userName: "xelnia" }
24
+ * )
25
+ * ```
26
+ *
27
+ * @returns
28
+ * ```json
29
+ * {
30
+ * totalAwardsCount: 10,
31
+ * hiddenAwardsCount: 2,
32
+ * masteryAwardsCount: 6,
33
+ * completionAwardsCount: 0,
34
+ * eventAwardsCount: 0,
35
+ * siteAwardsCount: 2,
36
+ * visibleUserAwards: [
37
+ * {
38
+ * awardedAt: "2022-08-26T19:34:43+00:00",
39
+ * awardType: "Mastery/Completion",
40
+ * awardData: 802,
41
+ * awardDataExtra: 1,
42
+ * displayOrder: 114,
43
+ * title: "WarioWare, Inc.: Mega Microgames!",
44
+ * consoleName: "Game Boy Advance",
45
+ * flags: null,
46
+ * imageIcon: "/Images/034678.png"
47
+ * }
48
+ * ]
49
+ * }
50
+ * ```
51
+ */
52
+ export const getUserAwards = async (
53
+ authorization: AuthObject,
54
+ payload: { userName: string }
55
+ ): Promise<UserAwards> => {
56
+ const { userName } = payload;
57
+
58
+ const queryParams: Record<string, string> = { u: userName };
59
+
60
+ const url = buildRequestUrl(
61
+ apiBaseUrl,
62
+ "/API_GetUserAwards.php",
63
+ authorization,
64
+ queryParams
65
+ );
66
+
67
+ const rawResponse = await call<GetUserAwardsResponse>({ url });
68
+
69
+ return serializeProperties(rawResponse);
70
+ };
@@ -0,0 +1,82 @@
1
+ import { rest } from "msw";
2
+ import { setupServer } from "msw/node";
3
+
4
+ import { apiBaseUrl } from "../utils/internal";
5
+ import { buildAuthorization } from "../utils/public";
6
+ import { getUserRecentAchievements } from "./getUserRecentAchievements";
7
+ import type { GetUserRecentAchievementsResponse } from "./models";
8
+
9
+ const server = setupServer();
10
+
11
+ describe("Function: getUserRecentAchievements", () => {
12
+ // MSW Setup
13
+ beforeAll(() => server.listen());
14
+ afterEach(() => server.resetHandlers());
15
+ afterAll(() => server.close());
16
+
17
+ it("is defined #sanity", () => {
18
+ // ASSERT
19
+ expect(getUserRecentAchievements).toBeDefined();
20
+ });
21
+
22
+ it("retrieves a list of recently-earned user achievements", async () => {
23
+ // ARRANGE
24
+ const authorization = buildAuthorization({
25
+ userName: "mockUserName",
26
+ webApiKey: "mockWebApiKey"
27
+ });
28
+
29
+ const mockResponse: GetUserRecentAchievementsResponse = [
30
+ {
31
+ Date: "2023-05-23 22:32:24",
32
+ HardcoreMode: 1,
33
+ AchievementID: 51_214,
34
+ Title: "You're a special Champ!",
35
+ Description:
36
+ "Win the Tournament as [You] on Hard with 1 attribute on max. and 1 attribute on min.",
37
+ BadgeName: "121991",
38
+ Points: 25,
39
+ Author: "Som1",
40
+ GameTitle: "WWF King of the Ring",
41
+ GameIcon: "/Images/062599.png",
42
+ GameID: 6316,
43
+ ConsoleName: "Game Boy",
44
+ BadgeURL: "/Badge/121991.png",
45
+ GameURL: "/game/6316"
46
+ }
47
+ ];
48
+
49
+ server.use(
50
+ rest.get(
51
+ `${apiBaseUrl}/API_GetUserRecentAchievements.php`,
52
+ (_, res, ctx) => res(ctx.json(mockResponse))
53
+ )
54
+ );
55
+
56
+ // ACT
57
+ const response = await getUserRecentAchievements(authorization, {
58
+ userName: "xelnia"
59
+ });
60
+
61
+ // ASSERT
62
+ expect(response).toEqual([
63
+ {
64
+ date: "2023-05-23 22:32:24",
65
+ hardcoreMode: true,
66
+ achievementId: 51_214,
67
+ title: "You're a special Champ!",
68
+ description:
69
+ "Win the Tournament as [You] on Hard with 1 attribute on max. and 1 attribute on min.",
70
+ badgeName: "121991",
71
+ points: 25,
72
+ author: "Som1",
73
+ gameTitle: "WWF King of the Ring",
74
+ gameIcon: "/Images/062599.png",
75
+ gameId: 6316,
76
+ consoleName: "Game Boy",
77
+ badgeUrl: "/Badge/121991.png",
78
+ gameUrl: "/game/6316"
79
+ }
80
+ ]);
81
+ });
82
+ });
@@ -0,0 +1,80 @@
1
+ import {
2
+ apiBaseUrl,
3
+ buildRequestUrl,
4
+ call,
5
+ serializeProperties
6
+ } from "../utils/internal";
7
+ import type { AuthObject } from "../utils/public";
8
+ import type {
9
+ GetUserRecentAchievementsResponse,
10
+ UserRecentAchievement
11
+ } from "./models";
12
+
13
+ /**
14
+ * A call to this function will retrieve a list of a target user's
15
+ * recently earned achievements, via their username. By default, it
16
+ * fetches achievements earned in the last hour.
17
+ *
18
+ * @param authorization An object containing your userName and webApiKey.
19
+ * This can be constructed with `buildAuthorization()`.
20
+ *
21
+ * @param payload.userName The user for which to retrieve the recent achievements for.
22
+ *
23
+ * @param payload.recentMinutes Optional. Defaults to 60. How many minutes
24
+ * back to fetch for the given user.
25
+ *
26
+ * @example
27
+ * ```
28
+ * const userRecentAchievements = await getUserRecentAchievements(
29
+ * authorization,
30
+ * { userName: "xelnia" }
31
+ * );
32
+ * ```
33
+ *
34
+ * @returns An array containing metadata about a user's recently earned achievements.
35
+ * ```json
36
+ * [
37
+ * {
38
+ * date: '2023-05-23 22:32:24',
39
+ * hardcoreMode: true,
40
+ * achievementId: 51214,
41
+ * title: "You're a special Champ!",
42
+ * description: 'Win the Tournament as [You] on Hard with 1 attribute on max. and 1 attribute on min.',
43
+ * badgeName: '121991',
44
+ * points: 25,
45
+ * author: 'Som1',
46
+ * gameTitle: 'WWF King of the Ring',
47
+ * gameIcon: '/Images/062599.png',
48
+ * gameId: 6316,
49
+ * consoleName: 'Game Boy',
50
+ * badgeUrl: '/Badge/121991.png',
51
+ * gameUrl: '/game/6316'
52
+ * }
53
+ * ]
54
+ * ```
55
+ */
56
+ export const getUserRecentAchievements = async (
57
+ authorization: AuthObject,
58
+ payload: { userName: string; recentMinutes?: number }
59
+ ): Promise<UserRecentAchievement[]> => {
60
+ const { userName, recentMinutes } = payload;
61
+
62
+ const queryParams: Record<string, string | number> = { u: userName };
63
+
64
+ if (recentMinutes !== undefined) {
65
+ queryParams["m"] = recentMinutes;
66
+ }
67
+
68
+ const url = buildRequestUrl(
69
+ apiBaseUrl,
70
+ "/API_GetUserRecentAchievements.php",
71
+ authorization,
72
+ queryParams
73
+ );
74
+
75
+ const rawResponse = await call<GetUserRecentAchievementsResponse>({ url });
76
+
77
+ return serializeProperties(rawResponse, {
78
+ shouldMapToBooleans: ["HardcoreMode"]
79
+ });
80
+ };
@@ -44,6 +44,27 @@ describe("Function: getUserSummary", () => {
44
44
  // ASSERT
45
45
  expect(response).toEqual(mockExpectedSummaryValue);
46
46
  });
47
+
48
+ it("given the API returns a 503, throws an error", async () => {
49
+ // ARRANGE
50
+ const authorization = buildAuthorization({
51
+ userName: "mockUserName",
52
+ webApiKey: "mockWebApiKey"
53
+ });
54
+
55
+ const mockResponse = "<html><body>the api is down</body></html>";
56
+
57
+ server.use(
58
+ rest.get(`${apiBaseUrl}/API_GetUserSummary.php`, (_, res, ctx) =>
59
+ res(ctx.status(503), ctx.body(mockResponse))
60
+ )
61
+ );
62
+
63
+ // ASSERT
64
+ await expect(
65
+ getUserSummary(authorization, { userName: "WCopeland" })
66
+ ).rejects.toThrow();
67
+ });
47
68
  });
48
69
 
49
70
  const mockGetUserSummaryResponse: GetUserSummaryResponse = {
package/src/user/index.ts CHANGED
@@ -1,11 +1,13 @@
1
1
  export * from "./getAchievementsEarnedBetween";
2
2
  export * from "./getAchievementsEarnedOnDay";
3
3
  export * from "./getGameInfoAndUserProgress";
4
+ export * from "./getUserAwards";
4
5
  export * from "./getUserClaims";
5
6
  export * from "./getUserCompletedGames";
6
7
  export * from "./getUserGameRankAndScore";
7
8
  export * from "./getUserPoints";
8
9
  export * from "./getUserProgress";
10
+ export * from "./getUserRecentAchievements";
9
11
  export * from "./getUserRecentlyPlayedGames";
10
12
  export * from "./getUserSummary";
11
13
  export * from "./models";
@@ -0,0 +1,7 @@
1
+ export type AwardType =
2
+ | "Mastery/Completion"
3
+ | "Achievement Unlocks Yield"
4
+ | "Achievement Points Yield"
5
+ | "Patreon Supporter"
6
+ | "Certified Legend"
7
+ | "Invalid or deprecated award type";
@@ -0,0 +1,23 @@
1
+ import type { AwardType } from "./award-type.model";
2
+
3
+ interface GetUserAwardsEntity {
4
+ AwardedAt: string;
5
+ AwardType: AwardType;
6
+ AwardData: number;
7
+ AwardDataExtra: number;
8
+ DisplayOrder: number;
9
+ Title: string;
10
+ ConsoleName: string;
11
+ Flags: number | null;
12
+ ImageIcon: string;
13
+ }
14
+
15
+ export interface GetUserAwardsResponse {
16
+ TotalAwardsCount: number;
17
+ HiddenAwardsCount: number;
18
+ MasteryAwardsCount: number;
19
+ CompletionAwardsCount: number;
20
+ EventAwardsCount: number;
21
+ SiteAwardsCount: number;
22
+ VisibleUserAwards: GetUserAwardsEntity[];
23
+ }
@@ -0,0 +1,19 @@
1
+ interface GetUserRecentAchievementsEntity {
2
+ Date: string;
3
+ HardcoreMode: 0 | 1;
4
+ AchievementID: number;
5
+ Title: string;
6
+ Description: string;
7
+ BadgeName: string;
8
+ Points: number;
9
+ Author: string;
10
+ GameTitle: string;
11
+ GameIcon: string;
12
+ GameID: number;
13
+ ConsoleName: string;
14
+ BadgeURL: string;
15
+ GameURL: string;
16
+ }
17
+
18
+ export type GetUserRecentAchievementsResponse =
19
+ GetUserRecentAchievementsEntity[];
@@ -1,18 +1,23 @@
1
+ export * from "./award-type.model";
1
2
  export * from "./dated-user-achievement.model";
2
3
  export * from "./dated-user-achievements-response.model";
3
4
  export * from "./game-info-and-user-progress.model";
4
5
  export * from "./get-game-info-and-user-progress-response.model";
6
+ export * from "./get-user-awards-response.model";
5
7
  export * from "./get-user-completed-games-response.model";
6
8
  export * from "./get-user-game-rank-and-score-response.model";
7
9
  export * from "./get-user-points-response.model";
8
10
  export * from "./get-user-progress-response.model";
11
+ export * from "./get-user-recent-achievements-response.model";
9
12
  export * from "./get-user-recently-played-games-response.model";
10
13
  export * from "./get-user-summary-response.model";
14
+ export * from "./user-awards.model";
11
15
  export * from "./user-claims.model";
12
16
  export * from "./user-claims-response.model";
13
17
  export * from "./user-completed-games.model";
14
18
  export * from "./user-game-rank-and-score.model";
15
19
  export * from "./user-points.model";
16
20
  export * from "./user-progress.model";
21
+ export * from "./user-recent-achievement.model";
17
22
  export * from "./user-recently-played-games.model";
18
23
  export * from "./user-summary.model";
@@ -0,0 +1,23 @@
1
+ import type { AwardType } from "./award-type.model";
2
+
3
+ interface UserAward {
4
+ awardedAt: string;
5
+ awardType: AwardType;
6
+ awardData: number;
7
+ awardDataExtra: number;
8
+ displayOrder: number;
9
+ title: string;
10
+ consoleName: string;
11
+ flags: number | null;
12
+ imageIcon: string;
13
+ }
14
+
15
+ export interface UserAwards {
16
+ totalAwardsCount: number;
17
+ hiddenAwardsCount: number;
18
+ masteryAwardsCount: number;
19
+ completionAwardsCount: number;
20
+ eventAwardsCount: number;
21
+ siteAwardsCount: number;
22
+ visibleUserAwards: UserAward[];
23
+ }
@@ -0,0 +1,16 @@
1
+ export interface UserRecentAchievement {
2
+ date: string;
3
+ hardcoreMode: boolean;
4
+ achievementId: number;
5
+ title: string;
6
+ description: string;
7
+ badgeName: string;
8
+ points: number;
9
+ author: string;
10
+ gameTitle: string;
11
+ gameIcon: string;
12
+ gameId: number;
13
+ consoleName: string;
14
+ badgeUrl: string;
15
+ gameUrl: string;
16
+ }
@@ -36,4 +36,21 @@ describe("Util: call", () => {
36
36
  expect(response).toEqual({ foo: "bar" });
37
37
  expect(receivedMethod).toEqual("GET");
38
38
  });
39
+
40
+ it("given the call returns an error, throws an error", async () => {
41
+ // ARRANGE
42
+ const mockRequestUrl = "https://abc.xyz/v1/endpoint2";
43
+
44
+ server.use(
45
+ rest.get(mockRequestUrl, (req, res, ctx) => {
46
+ return res(
47
+ ctx.status(503),
48
+ ctx.text("<HTML><BODY>something bad happened</BODY></HTML>")
49
+ );
50
+ })
51
+ );
52
+
53
+ // ASSERT
54
+ await expect(call({ url: mockRequestUrl })).rejects.toThrow();
55
+ });
39
56
  });
@@ -25,5 +25,12 @@ export const call = async <
25
25
  const { url } = config;
26
26
 
27
27
  const rawResponse = await unfetch(url);
28
+
29
+ if (!rawResponse.ok) {
30
+ throw new Error(
31
+ `HTTP Error: Status ${rawResponse.status} ${rawResponse.statusText}`
32
+ );
33
+ }
34
+
28
35
  return (await rawResponse.json()) as T;
29
36
  };
@@ -71,5 +71,8 @@ const naiveCamelCase = (originalValue: string) => {
71
71
  // "rAPoints" -> "raPoints"
72
72
  camelCased = camelCased.replace(/rA/g, "ra");
73
73
 
74
+ // "visibleUserawards" -> "visibleUserAwards"
75
+ camelCased = camelCased.replace(/visibleUserawards/g, "visibleUserAwards");
76
+
74
77
  return camelCased;
75
78
  };