Skip to content

Commit 3fc2f49

Browse files
authored
feat(comments): add comment retrieval functions (#117)
1 parent 2008a4d commit 3fc2f49

9 files changed

+421
-0
lines changed

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ Click the function names to open their complete docs on the docs site.
117117
- [`getRecentGameAwards()`](https://api-docs.retroachievements.org/v1/get-recent-game-awards.html) - Get all recent mastery, completion, and beaten awards earned on the site.
118118
- [`getTopTenUsers()`](https://api-docs.retroachievements.org/v1/get-top-ten-users.html) - Get the list of top ten points earners.
119119

120+
### Comment
121+
122+
- [`getComments()`](https://api-docs.retroachievements.org/v1/get-comments.html) - Get the comments left an achievement, game, or user wall.
123+
120124
### Event
121125

122126
- [`getAchievementOfTheWeek()`](https://api-docs.retroachievements.org/v1/get-achievement-of-the-week.html) - Get comprehensive metadata about the current Achievement of the Week.

src/comment/getComments.test.ts

+277
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
/* eslint-disable sonarjs/no-duplicate-string */
2+
3+
import { http, HttpResponse } from "msw";
4+
import { setupServer } from "msw/node";
5+
6+
import { apiBaseUrl } from "../utils/internal";
7+
import { buildAuthorization } from "../utils/public";
8+
import { getComments } from "./getComments";
9+
import type { CommentsResponse, GetCommentsResponse } from "./models";
10+
11+
const server = setupServer();
12+
13+
describe("Function: getComments", () => {
14+
// MSW Setup
15+
beforeAll(() => server.listen());
16+
afterEach(() => server.resetHandlers());
17+
afterAll(() => server.close());
18+
19+
it("is defined #sanity", () => {
20+
// ASSERT
21+
expect(getComments).toBeDefined();
22+
});
23+
24+
it("retrieves the comments on a user's wall", async () => {
25+
// ARRANGE
26+
const authorization = buildAuthorization({
27+
username: "mockUserName",
28+
webApiKey: "mockWebApiKey",
29+
});
30+
31+
const mockResponse: GetCommentsResponse = {
32+
Count: 2,
33+
Total: 2,
34+
Results: [
35+
{
36+
User: "PlayTester",
37+
Submitted: "2024-07-31T11:22:23.000000Z",
38+
CommentText: "Comment 1",
39+
},
40+
{
41+
User: "PlayTester",
42+
Submitted: "2024-07-31T11:22:23.000000Z",
43+
CommentText: "Comment 2",
44+
},
45+
],
46+
};
47+
48+
server.use(
49+
http.get(`${apiBaseUrl}/API_GetComments.php`, () =>
50+
HttpResponse.json(mockResponse)
51+
)
52+
);
53+
54+
// ACT
55+
const response = await getComments(authorization, {
56+
identifier: "xelnia",
57+
});
58+
59+
// ASSERT
60+
const expectedResponse: CommentsResponse = {
61+
count: 2,
62+
total: 2,
63+
results: [
64+
{
65+
user: "PlayTester",
66+
submitted: "2024-07-31T11:22:23.000000Z",
67+
commentText: "Comment 1",
68+
},
69+
{
70+
user: "PlayTester",
71+
submitted: "2024-07-31T11:22:23.000000Z",
72+
commentText: "Comment 2",
73+
},
74+
],
75+
};
76+
77+
expect(response).toEqual(expectedResponse);
78+
});
79+
80+
it("retrieves the comments on an game", async () => {
81+
// ARRANGE
82+
const authorization = buildAuthorization({
83+
username: "mockUserName",
84+
webApiKey: "mockWebApiKey",
85+
});
86+
87+
const mockResponse: GetCommentsResponse = {
88+
Count: 2,
89+
Total: 2,
90+
Results: [
91+
{
92+
User: "PlayTester",
93+
Submitted: "2024-07-31T11:22:23.000000Z",
94+
CommentText: "Comment 1",
95+
},
96+
{
97+
User: "PlayTester",
98+
Submitted: "2024-07-31T11:22:23.000000Z",
99+
CommentText: "Comment 2",
100+
},
101+
],
102+
};
103+
104+
server.use(
105+
http.get(`${apiBaseUrl}/API_GetComments.php`, () =>
106+
HttpResponse.json(mockResponse)
107+
)
108+
);
109+
110+
// ACT
111+
const response = await getComments(authorization, {
112+
identifier: 321_865,
113+
kind: "game",
114+
});
115+
116+
// ASSERT
117+
const expectedResponse: CommentsResponse = {
118+
count: 2,
119+
total: 2,
120+
results: [
121+
{
122+
user: "PlayTester",
123+
submitted: "2024-07-31T11:22:23.000000Z",
124+
commentText: "Comment 1",
125+
},
126+
{
127+
user: "PlayTester",
128+
submitted: "2024-07-31T11:22:23.000000Z",
129+
commentText: "Comment 2",
130+
},
131+
],
132+
};
133+
134+
expect(response).toEqual(expectedResponse);
135+
});
136+
137+
it("retrieves the comments on an achievement, respecting count", async () => {
138+
// ARRANGE
139+
const authorization = buildAuthorization({
140+
username: "mockUserName",
141+
webApiKey: "mockWebApiKey",
142+
});
143+
144+
const mockResponse: GetCommentsResponse = {
145+
Count: 2,
146+
Total: 4,
147+
Results: [
148+
{
149+
User: "PlayTester",
150+
Submitted: "2024-07-31T11:22:23.000000Z",
151+
CommentText: "Comment 1",
152+
},
153+
{
154+
User: "PlayTester",
155+
Submitted: "2024-07-31T11:22:23.000000Z",
156+
CommentText: "Comment 2",
157+
},
158+
],
159+
};
160+
161+
server.use(
162+
http.get(`${apiBaseUrl}/API_GetComments.php`, () =>
163+
HttpResponse.json(mockResponse)
164+
)
165+
);
166+
167+
// ACT
168+
const response = await getComments(authorization, {
169+
identifier: 321_865,
170+
count: 2,
171+
kind: "achievement",
172+
});
173+
174+
// ASSERT
175+
const expectedResponse: CommentsResponse = {
176+
count: 2,
177+
total: 4,
178+
results: [
179+
{
180+
user: "PlayTester",
181+
submitted: "2024-07-31T11:22:23.000000Z",
182+
commentText: "Comment 1",
183+
},
184+
{
185+
user: "PlayTester",
186+
submitted: "2024-07-31T11:22:23.000000Z",
187+
commentText: "Comment 2",
188+
},
189+
],
190+
};
191+
192+
expect(response).toEqual(expectedResponse);
193+
});
194+
195+
it("retrieves the comments on an game, respecting offset", async () => {
196+
// ARRANGE
197+
const authorization = buildAuthorization({
198+
username: "mockUserName",
199+
webApiKey: "mockWebApiKey",
200+
});
201+
202+
const mockResponse: GetCommentsResponse = {
203+
Count: 1,
204+
Total: 2,
205+
Results: [
206+
{
207+
User: "PlayTester",
208+
Submitted: "2024-07-31T11:22:23.000000Z",
209+
CommentText: "Comment 2",
210+
},
211+
],
212+
};
213+
214+
server.use(
215+
http.get(`${apiBaseUrl}/API_GetComments.php`, () =>
216+
HttpResponse.json(mockResponse)
217+
)
218+
);
219+
220+
// ACT
221+
const response = await getComments(authorization, {
222+
identifier: 321_865,
223+
offset: 1,
224+
kind: "game",
225+
});
226+
227+
// ASSERT
228+
const expectedResponse: CommentsResponse = {
229+
count: 1,
230+
total: 2,
231+
results: [
232+
{
233+
user: "PlayTester",
234+
submitted: "2024-07-31T11:22:23.000000Z",
235+
commentText: "Comment 2",
236+
},
237+
],
238+
};
239+
240+
expect(response).toEqual(expectedResponse);
241+
});
242+
243+
it("warns the developer when they don't specify kind for achievements/games", async () => {
244+
// ARRANGE
245+
const authorization = buildAuthorization({
246+
username: "mockUserName",
247+
webApiKey: "mockWebApiKey",
248+
});
249+
250+
const mockResponse: GetCommentsResponse = {
251+
Count: 1,
252+
Total: 2,
253+
Results: [
254+
{
255+
User: "PlayTester",
256+
Submitted: "2024-07-31T11:22:23.000000Z",
257+
CommentText: "Comment 2",
258+
},
259+
],
260+
};
261+
262+
server.use(
263+
http.get(`${apiBaseUrl}/API_GetComments.php`, () =>
264+
HttpResponse.json(mockResponse)
265+
)
266+
);
267+
268+
// ACT
269+
const response = getComments(authorization, {
270+
identifier: 321_865,
271+
offset: 1,
272+
});
273+
274+
// ASSERT
275+
await expect(response).rejects.toThrow(TypeError);
276+
});
277+
});

src/comment/getComments.ts

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import type { ID } from "../utils/internal";
2+
import {
3+
apiBaseUrl,
4+
buildRequestUrl,
5+
call,
6+
serializeProperties,
7+
} from "../utils/internal";
8+
import type { AuthObject } from "../utils/public";
9+
import type { CommentsResponse, GetCommentsResponse } from "./models";
10+
11+
const kindMap: Record<"game" | "achievement" | "user", number> = {
12+
game: 1,
13+
achievement: 2,
14+
user: 3,
15+
};
16+
17+
/**
18+
* A call to this function will retrieve a list of comments on a particular target.
19+
*
20+
* @param authorization An object containing your username and webApiKey.
21+
* This can be constructed with `buildAuthorization()`.
22+
*
23+
* @param payload.identifier The identifier to retrieve. For user walls, this will
24+
* be a string (the username), and for game and achievement walls, this will be a
25+
* the ID of the object in question.
26+
*
27+
* @param payload.kind What kind of identifier was used. This can be "game",
28+
* "achievement", or "user".
29+
*
30+
* @param payload.offset Defaults to 0. The number of entries to skip.
31+
*
32+
* @param payload.count Defaults to 50, has a max of 500.
33+
*
34+
* @example
35+
* ```
36+
* // Retrieving game/achievement comments
37+
* const gameWallComments = await getComments(
38+
* authorization,
39+
* { identifier: 20294, kind: 'game', count: 4, offset: 0 },
40+
* );
41+
*
42+
* // Retrieving comments on a user's wall
43+
* const userWallComments = await getComments(
44+
* authorization,
45+
* { identifier: "xelnia", count: 4, offset: 0 },
46+
* );
47+
* ```
48+
*
49+
* @returns An object containing the amount of comments retrieved,
50+
* the total comments, and an array of the comments themselves.
51+
* ```
52+
* {
53+
* count: 4,
54+
* total: 4,
55+
* results: [
56+
* {
57+
* user: "PlayTester",
58+
* submitted: "2024-07-31T11:22:23.000000Z",
59+
* commentText: "Comment 1"
60+
* },
61+
* // ...
62+
* ]
63+
* }
64+
* ```
65+
*/
66+
export const getComments = async (
67+
authorization: AuthObject,
68+
payload: {
69+
identifier: ID;
70+
kind?: "game" | "achievement" | "user";
71+
offset?: number;
72+
count?: number;
73+
}
74+
): Promise<CommentsResponse> => {
75+
const { identifier, kind, offset, count } = payload;
76+
77+
const queryParams: Record<string, number | string> = { i: identifier };
78+
79+
if (kind) {
80+
queryParams.t = kindMap[kind];
81+
} else if (typeof identifier === "number") {
82+
throw new TypeError(
83+
"'kind' must be specified when looking up an achievement or game."
84+
);
85+
}
86+
87+
if (offset) {
88+
queryParams.o = offset;
89+
}
90+
91+
if (count) {
92+
queryParams.c = count;
93+
}
94+
95+
const url = buildRequestUrl(
96+
apiBaseUrl,
97+
"/API_GetComments.php",
98+
authorization,
99+
queryParams
100+
);
101+
102+
const rawResponse = await call<GetCommentsResponse>({ url });
103+
104+
return serializeProperties(rawResponse, {
105+
shouldCastToNumbers: ["Count", "Total"],
106+
});
107+
};

src/comment/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./getComments";
2+
export * from "./models";

0 commit comments

Comments
 (0)