Skip to content

Commit

Permalink
Feat : Handled discord messaging failure use case
Browse files Browse the repository at this point in the history
  • Loading branch information
joyguptaa committed Mar 9, 2024
1 parent 49ee62c commit 46c8021
Show file tree
Hide file tree
Showing 9 changed files with 86 additions and 25 deletions.
6 changes: 6 additions & 0 deletions src/constants/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ export const MENTION_EACH = {
type: 3,
require: false,
},
{
name: "dev",
description: "want to tag them individually?",
type: 5,
require: false,
},
],
};

Expand Down
4 changes: 3 additions & 1 deletion src/controllers/baseHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
REMOVED_LISTENING_MESSAGE,
RETRY_COMMAND,
} from "../constants/responses";
import { DevFlag } from "../typeDefinitions/filterUsersByRole";

export async function baseHandler(
message: discordMessageRequest,
Expand Down Expand Up @@ -66,8 +67,9 @@ export async function baseHandler(
// data[1] is message obj
const transformedArgument = {
roleToBeTaggedObj: data[0],
displayMessageObj: data[1] ?? {},
displayMessageObj: data.find((item) => item.name === "message"),
channelId: message.channel_id,
dev: data.find((item) => item.name === "dev") as unknown as DevFlag,
};
return await mentionEachUser(transformedArgument, env, ctx);
}
Expand Down
12 changes: 10 additions & 2 deletions src/controllers/mentionEachUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,25 @@ import { env } from "../typeDefinitions/default.types";
import {
UserArray,
MentionEachUserOptions,
DevFlag,
} from "../typeDefinitions/filterUsersByRole";
import { mentionEachUserInMessage } from "../utils/guildRole";
import { checkDisplayType } from "../utils/checkDisplayType";

export async function mentionEachUser(
transformedArgument: {
roleToBeTaggedObj: MentionEachUserOptions;
displayMessageObj?: MentionEachUserOptions;
channelId: number;
dev?: DevFlag;
},
env: env,
ctx: ExecutionContext
) {
const getMembersInServerResponse = await getMembersInServer(env);
const roleId = transformedArgument.roleToBeTaggedObj.value;
const msgToBeSent = transformedArgument?.displayMessageObj?.value;
const dev = transformedArgument?.dev?.value || false;
// optional chaining here only because display message obj is optional argument
const usersWithMatchingRole = filterUserByRoles(
getMembersInServerResponse as UserArray[],
Expand All @@ -32,8 +36,12 @@ export async function mentionEachUser(
message: msgToBeSent,
usersWithMatchingRole,
};
if (usersWithMatchingRole.length === 0) {
return discordTextResponse("Sorry no user found under this role.");
if (!dev || usersWithMatchingRole.length === 0) {
const responseData = checkDisplayType({
usersWithMatchingRole,
msgToBeSent,
});
return discordTextResponse(responseData);
} else {
ctx.waitUntil(
mentionEachUserInMessage({
Expand Down
5 changes: 5 additions & 0 deletions src/typeDefinitions/filterUsersByRole.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,8 @@ export type MentionEachUserOptions = {
type: number;
value: string;
};
export type DevFlag = {
name: string;
type: number;
value: boolean;
};
4 changes: 2 additions & 2 deletions src/utils/batchDiscordRequests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ interface ResponseDetails {
data: RequestDetails;
}

const parseRateLimitRemaining = (response: Response) => {
export const parseRateLimitRemaining = (response: Response) => {
let rateLimitRemaining = Number.parseInt(
response.headers.get(DISCORD_HEADERS.RATE_LIMIT_REMAINING) || "0"
);
rateLimitRemaining = Math.floor(rateLimitRemaining * (1 - LIMIT_BUFFER));
return rateLimitRemaining;
};

const parseResetAfter = (response: Response) => {
export const parseResetAfter = (response: Response) => {
let resetAfter = Number.parseFloat(
response.headers.get(DISCORD_HEADERS.RATE_LIMIT_RESET_AFTER) || "0"
);
Expand Down
43 changes: 27 additions & 16 deletions src/utils/guildRole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ import {
ROLE_REMOVED,
} from "../constants/responses";
import { DISCORD_BASE_URL } from "../constants/urls";
import {
parseRateLimitRemaining,
parseResetAfter,
} from "../utils/batchDiscordRequests";

import { env } from "../typeDefinitions/default.types";
import {
DiscordMessageResponse,
createNewRole,
discordMessageError,
discordMessageRequest,
Expand Down Expand Up @@ -150,9 +154,10 @@ export async function mentionEachUserInMessage({
channelId: number;
env: env;
}) {
const batchSize = 10;
let failedAPICalls = 0;
const batchSize = 5;
let waitTillNextAPICall = 0;
try {
const failedUsers: Array<string> = [];
for (let i = 0; i < userIds.length; i += batchSize) {
const batchwiseUserIds = userIds.slice(i, i + batchSize);
const messageRequest = batchwiseUserIds.map((userId) => {
Expand All @@ -165,32 +170,38 @@ export async function mentionEachUserInMessage({
body: JSON.stringify({
content: `${message ? message + " " : ""} ${userId}`,
}),
}).then((response) => response.json()) as Promise<
discordMessageRequest | discordMessageError
>;
}).then((response) => {
const rateLimitRemaining = parseRateLimitRemaining(response);
if (rateLimitRemaining === 0) {
waitTillNextAPICall = Math.max(
parseResetAfter(response),
waitTillNextAPICall
);
}
return response.json();
}) as Promise<discordMessageRequest | discordMessageError>;
});
const responses = await Promise.all(messageRequest);
responses.forEach((response) => {
if (
response &&
"message" in response &&
response.message === "404: Not Found"
) {
failedAPICalls += 1;
responses.forEach((response, i) => {
if (response && "message" in response) {
failedUsers.push(batchwiseUserIds[i]);
console.error(`Failed to mention a user`);
}
});
await new Promise((resolve) => setTimeout(resolve, 1000));
await new Promise((resolve) =>
setTimeout(resolve, waitTillNextAPICall * 1000)
);
waitTillNextAPICall = 0;
}
if (failedAPICalls > 0) {
if (failedUsers.length > 0) {
await fetch(`${DISCORD_BASE_URL}/channels/${channelId}/messages`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bot ${env.DISCORD_TOKEN}`,
},
body: JSON.stringify({
content: `Failed to tag ${failedAPICalls} users`,
content: `Failed to tag ${failedUsers} individually.`,
}),
});
}
Expand Down
5 changes: 5 additions & 0 deletions tests/fixtures/fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ export const onlyRoleToBeTagged = {
value: "1118201414078976192",
},
channelId: 1244,
dev: {
name: "dev",
type: 4,
value: false,
},
};

export const ctx = {
Expand Down
27 changes: 27 additions & 0 deletions tests/unit/handlers/mentionEachUser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,33 @@ describe("Test mention each function", () => {
expect(response).toBeInstanceOf(Promise);
});

it("should run without displayMessageObj argument in dev mode", async () => {
const env = {
BOT_PUBLIC_KEY: "xyz",
DISCORD_GUILD_ID: "123",
DISCORD_TOKEN: "abc",
};
const response = mentionEachUser(
{
...onlyRoleToBeTagged,
dev: {
name: "dev",
type: 4,
value: true,
},
},
env,
ctx
);
expect(response).toBeInstanceOf(Promise);
const textMessage: { data: { content: string } } = await response.then(
(res) => res.json()
);
expect(textMessage.data.content).toBe(
"Sorry no user found under this role."
);
});

it("should run without displayMessageObj argument", async () => {
const env = {
BOT_PUBLIC_KEY: "xyz",
Expand Down
5 changes: 1 addition & 4 deletions tests/unit/utils/guildRole.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@ import {
mockMessageResponse,
rolesMock,
} from "../../fixtures/fixture";
import {
DiscordMessageResponse,
discordMessageRequest,
} from "../../../src/typeDefinitions/discordMessage.types";
import { DiscordMessageResponse } from "../../../src/typeDefinitions/discordMessage.types";

describe("createGuildRole", () => {
it("should pass the reason to discord as a X-Audit-Log-Reason header if provided", async () => {
Expand Down

0 comments on commit 46c8021

Please sign in to comment.