Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ Click the function names to open their complete docs on the docs site.
- [`getRecentGameAwards()`](https://api-docs.retroachievements.org/v1/get-recent-game-awards.html) - Get all recent mastery, completion, and beaten awards earned on the site.
- [`getTopTenUsers()`](https://api-docs.retroachievements.org/v1/get-top-ten-users.html) - Get the list of top ten points earners.

### Comment

- [`getComments()`](https://api-docs.retroachievements.org/v1/get-comments.html) - Get the comments left an achievement, game, or user wall.

### Event

- [`getAchievementOfTheWeek()`](https://api-docs.retroachievements.org/v1/get-achievement-of-the-week.html) - Get comprehensive metadata about the current Achievement of the Week.
Expand Down
277 changes: 277 additions & 0 deletions src/comment/getComments.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
/* 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 { getComments } from "./getComments";
import type { CommentsResponse, GetCommentsResponse } from "./models";

const server = setupServer();

describe("Function: getComments", () => {
// MSW Setup
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

it("is defined #sanity", () => {
// ASSERT
expect(getComments).toBeDefined();
});

it("retrieves the comments on a user's wall", async () => {
// ARRANGE
const authorization = buildAuthorization({
username: "mockUserName",
webApiKey: "mockWebApiKey",
});

const mockResponse: GetCommentsResponse = {
Count: 2,
Total: 2,
Results: [
{
User: "PlayTester",
Submitted: "2024-07-31T11:22:23.000000Z",
CommentText: "Comment 1",
},
{
User: "PlayTester",
Submitted: "2024-07-31T11:22:23.000000Z",
CommentText: "Comment 2",
},
],
};

server.use(
http.get(`${apiBaseUrl}/API_GetComments.php`, () =>
HttpResponse.json(mockResponse)
)
);

// ACT
const response = await getComments(authorization, {
identifier: "xelnia",
});

// ASSERT
const expectedResponse: CommentsResponse = {
count: 2,
total: 2,
results: [
{
user: "PlayTester",
submitted: "2024-07-31T11:22:23.000000Z",
commentText: "Comment 1",
},
{
user: "PlayTester",
submitted: "2024-07-31T11:22:23.000000Z",
commentText: "Comment 2",
},
],
};

expect(response).toEqual(expectedResponse);
});

it("retrieves the comments on an game", async () => {
// ARRANGE
const authorization = buildAuthorization({
username: "mockUserName",
webApiKey: "mockWebApiKey",
});

const mockResponse: GetCommentsResponse = {
Count: 2,
Total: 2,
Results: [
{
User: "PlayTester",
Submitted: "2024-07-31T11:22:23.000000Z",
CommentText: "Comment 1",
},
{
User: "PlayTester",
Submitted: "2024-07-31T11:22:23.000000Z",
CommentText: "Comment 2",
},
],
};

server.use(
http.get(`${apiBaseUrl}/API_GetComments.php`, () =>
HttpResponse.json(mockResponse)
)
);

// ACT
const response = await getComments(authorization, {
identifier: 321_865,
kind: "game",
});

// ASSERT
const expectedResponse: CommentsResponse = {
count: 2,
total: 2,
results: [
{
user: "PlayTester",
submitted: "2024-07-31T11:22:23.000000Z",
commentText: "Comment 1",
},
{
user: "PlayTester",
submitted: "2024-07-31T11:22:23.000000Z",
commentText: "Comment 2",
},
],
};

expect(response).toEqual(expectedResponse);
});

it("retrieves the comments on an achievement, respecting count", async () => {
// ARRANGE
const authorization = buildAuthorization({
username: "mockUserName",
webApiKey: "mockWebApiKey",
});

const mockResponse: GetCommentsResponse = {
Count: 2,
Total: 4,
Results: [
{
User: "PlayTester",
Submitted: "2024-07-31T11:22:23.000000Z",
CommentText: "Comment 1",
},
{
User: "PlayTester",
Submitted: "2024-07-31T11:22:23.000000Z",
CommentText: "Comment 2",
},
],
};

server.use(
http.get(`${apiBaseUrl}/API_GetComments.php`, () =>
HttpResponse.json(mockResponse)
)
);

// ACT
const response = await getComments(authorization, {
identifier: 321_865,
count: 2,
kind: "achievement",
});

// ASSERT
const expectedResponse: CommentsResponse = {
count: 2,
total: 4,
results: [
{
user: "PlayTester",
submitted: "2024-07-31T11:22:23.000000Z",
commentText: "Comment 1",
},
{
user: "PlayTester",
submitted: "2024-07-31T11:22:23.000000Z",
commentText: "Comment 2",
},
],
};

expect(response).toEqual(expectedResponse);
});

it("retrieves the comments on an game, respecting offset", async () => {
// ARRANGE
const authorization = buildAuthorization({
username: "mockUserName",
webApiKey: "mockWebApiKey",
});

const mockResponse: GetCommentsResponse = {
Count: 1,
Total: 2,
Results: [
{
User: "PlayTester",
Submitted: "2024-07-31T11:22:23.000000Z",
CommentText: "Comment 2",
},
],
};

server.use(
http.get(`${apiBaseUrl}/API_GetComments.php`, () =>
HttpResponse.json(mockResponse)
)
);

// ACT
const response = await getComments(authorization, {
identifier: 321_865,
offset: 1,
kind: "game",
});

// ASSERT
const expectedResponse: CommentsResponse = {
count: 1,
total: 2,
results: [
{
user: "PlayTester",
submitted: "2024-07-31T11:22:23.000000Z",
commentText: "Comment 2",
},
],
};

expect(response).toEqual(expectedResponse);
});

it("warns the developer when they don't specify kind for achievements/games", async () => {
// ARRANGE
const authorization = buildAuthorization({
username: "mockUserName",
webApiKey: "mockWebApiKey",
});

const mockResponse: GetCommentsResponse = {
Count: 1,
Total: 2,
Results: [
{
User: "PlayTester",
Submitted: "2024-07-31T11:22:23.000000Z",
CommentText: "Comment 2",
},
],
};

server.use(
http.get(`${apiBaseUrl}/API_GetComments.php`, () =>
HttpResponse.json(mockResponse)
)
);

// ACT
const response = getComments(authorization, {
identifier: 321_865,
offset: 1,
});

// ASSERT
await expect(response).rejects.toThrow(TypeError);
});
});
107 changes: 107 additions & 0 deletions src/comment/getComments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import type { ID } from "../utils/internal";
import {
apiBaseUrl,
buildRequestUrl,
call,
serializeProperties,
} from "../utils/internal";
import type { AuthObject } from "../utils/public";
import type { CommentsResponse, GetCommentsResponse } from "./models";

const kindMap: Record<"game" | "achievement" | "user", number> = {
game: 1,
achievement: 2,
user: 3,
};

/**
* A call to this function will retrieve a list of comments on a particular target.
*
* @param authorization An object containing your username and webApiKey.
* This can be constructed with `buildAuthorization()`.
*
* @param payload.identifier The identifier to retrieve. For user walls, this will
* be a string (the username), and for game and achievement walls, this will be a
* the ID of the object in question.
*
* @param payload.kind What kind of identifier was used. This can be "game",
* "achievement", or "user".
*
* @param payload.offset Defaults to 0. The number of entries to skip.
*
* @param payload.count Defaults to 50, has a max of 500.
*
* @example
* ```
* // Retrieving game/achievement comments
* const gameWallComments = await getComments(
* authorization,
* { identifier: 20294, kind: 'game', count: 4, offset: 0 },
* );
*
* // Retrieving comments on a user's wall
* const userWallComments = await getComments(
* authorization,
* { identifier: "xelnia", count: 4, offset: 0 },
* );
* ```
*
* @returns An object containing the amount of comments retrieved,
* the total comments, and an array of the comments themselves.
* ```
* {
* count: 4,
* total: 4,
* results: [
* {
* user: "PlayTester",
* submitted: "2024-07-31T11:22:23.000000Z",
* commentText: "Comment 1"
* },
* // ...
* ]
* }
* ```
*/
export const getComments = async (
authorization: AuthObject,
payload: {
identifier: ID;
kind?: "game" | "achievement" | "user";
offset?: number;
count?: number;
}
): Promise<CommentsResponse> => {
const { identifier, kind, offset, count } = payload;

const queryParams: Record<string, number | string> = { i: identifier };

if (kind) {
queryParams.t = kindMap[kind];
} else if (typeof identifier === "number") {
throw new TypeError(
"'kind' must be specified when looking up an achievement or game."
);
}

if (offset) {
queryParams.o = offset;
}

if (count) {
queryParams.c = count;
}

const url = buildRequestUrl(
apiBaseUrl,
"/API_GetComments.php",
authorization,
queryParams
);

const rawResponse = await call<GetCommentsResponse>({ url });

return serializeProperties(rawResponse, {
shouldCastToNumbers: ["Count", "Total"],
});
};
2 changes: 2 additions & 0 deletions src/comment/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./getComments";
export * from "./models";
Loading