Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat : Updated mention each flow #205

Merged
merged 3 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
10 changes: 7 additions & 3 deletions src/controllers/baseHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,12 @@ import {
REMOVED_LISTENING_MESSAGE,
RETRY_COMMAND,
} from "../constants/responses";
import { DevFlag } from "../typeDefinitions/filterUsersByRole";

export async function baseHandler(
message: discordMessageRequest,
env: env
env: env,
ctx: ExecutionContext
): Promise<JSONResponse> {
const command = lowerCaseMessageCommands(message);

Expand All @@ -65,9 +67,11 @@ 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,
prakashchoudhary07 marked this conversation as resolved.
Show resolved Hide resolved
};
return await mentionEachUser(transformedArgument, env);
return await mentionEachUser(transformedArgument, env, ctx);
}

case getCommandName(LISTENING): {
Expand Down
36 changes: 31 additions & 5 deletions src/controllers/mentionEachUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,53 @@ 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
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[],
roleId
);
const responseData = checkDisplayType({
const payload = {
channelId: transformedArgument.channelId,
roleId: roleId,
message: msgToBeSent,
usersWithMatchingRole,
msgToBeSent,
});
return discordTextResponse(responseData);
};
if (!dev || usersWithMatchingRole.length === 0) {
prakashchoudhary07 marked this conversation as resolved.
Show resolved Hide resolved
const responseData = checkDisplayType({
usersWithMatchingRole,
msgToBeSent,
});
return discordTextResponse(responseData);
} else {
ctx.waitUntil(
prakashchoudhary07 marked this conversation as resolved.
Show resolved Hide resolved
prakashchoudhary07 marked this conversation as resolved.
Show resolved Hide resolved
mentionEachUserInMessage({
message: payload.message,
userIds: payload.usersWithMatchingRole,
channelId: payload.channelId,
env,
prakashchoudhary07 marked this conversation as resolved.
Show resolved Hide resolved
})
);
return discordTextResponse(
`Found ${usersWithMatchingRole.length} users with matched role, mentioning them shortly...`
);
}
}
12 changes: 8 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ router.delete("/roles", removeGuildRoleHandler);

router.post("/profile/blocked", sendProfileBlockedMessage);

router.post("/", async (request, env) => {
router.post("/", async (request, env, ctx: ExecutionContext) => {
const message: discordMessageRequest = await request.json();

if (message.type === InteractionType.PING) {
Expand All @@ -66,7 +66,7 @@ router.post("/", async (request, env) => {
});
}
if (message.type === InteractionType.APPLICATION_COMMAND) {
return baseHandler(message, env);
return baseHandler(message, env, ctx);
}
return new JSONResponse(response.UNKNOWN_INTERACTION, { status: 400 });
});
Expand All @@ -78,7 +78,11 @@ router.all("*", async () => {
});

export default {
async fetch(request: Request, env: env): Promise<Response> {
async fetch(
request: Request,
env: env,
ctx: ExecutionContext
): Promise<Response> {
const apiUrls = ["/invite", "/roles", "/profile/blocked"];
const url = new URL(request.url);
if (request.method === "POST" && !apiUrls.includes(url.pathname)) {
Expand All @@ -87,7 +91,7 @@ export default {
return new JSONResponse(response.BAD_SIGNATURE, { status: 401 });
}
}
return router.handle(request, env);
return router.handle(request, env, ctx);
},

async scheduled(req: Request, env: env, ctx: ExecutionContext) {
Expand Down
53 changes: 53 additions & 0 deletions src/typeDefinitions/discordMessage.types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,59 @@ export interface discordMessageRequest {
data: messageRequestData;
member: messageRequestMember;
guild_id: number;
channel_id: number;
}

export interface DiscordMessageResponse {
id: string;
type: number;
content: string;
channel_id: string;
author: {
id: string;
username: string;
avatar: string | null;
discriminator: string;
public_flags: number;
premium_type: number;
flags: number;
bot: boolean;
banner: string | null;
accent_color: string | null;
global_name: string | null;
avatar_decoration_data: string | null;
banner_color: string | null;
};
attachments: Array<string>;
embeds: Array<string>;
mentions: {
id: string;
username: string;
avatar: string | null;
discriminator: string;
public_flags: number;
premium_type: number;
flags: number;
banner: string | null;
accent_color: string | null;
global_name: string | null;
avatar_decoration_data: string | null;
banner_color: string | null;
}[];
mention_roles: Array<string>;
pinned: boolean;
mention_everyone: boolean;
tts: boolean;
timestamp: string;
edited_timestamp: string | null;
flags: number;
components: Array<string>;
referenced_message: Arra<string> | null;
}

export interface discordMessageError {
code: number;
message: string;
}

export interface messageRequestData {
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
73 changes: 73 additions & 0 deletions src/utils/guildRole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,22 @@ 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 {
createNewRole,
discordMessageError,
discordMessageRequest,
guildRoleResponse,
memberGroupRole,
} from "../typeDefinitions/discordMessage.types";
import { GuildRole, Role } from "../typeDefinitions/role.types";
import createDiscordHeaders from "./createDiscordHeaders";
import { sleep } from "./sleep";

export async function createGuildRole(
body: createNewRole,
Expand Down Expand Up @@ -135,3 +143,68 @@ export async function getGuildRoleByName(
const roles = await getGuildRoles(env);
return roles?.find((role) => role.name === roleName);
}

export async function mentionEachUserInMessage({
message,
userIds,
channelId,
env,
}: {
message?: string;
userIds: string[];
channelId: number;
env: env;
}) {
const batchSize = 5;
prakashchoudhary07 marked this conversation as resolved.
Show resolved Hide resolved
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) => {
return fetch(`${DISCORD_BASE_URL}/channels/${channelId}/messages`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bot ${env.DISCORD_TOKEN}`,
},
body: JSON.stringify({
content: `${message ? message + " " : ""} ${userId}`,
}),
prakashchoudhary07 marked this conversation as resolved.
Show resolved Hide resolved
}).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);
prakashchoudhary07 marked this conversation as resolved.
Show resolved Hide resolved
responses.forEach((response, i) => {
if (response && "message" in response) {
failedUsers.push(batchwiseUserIds[i]);
console.error(`Failed to mention a user`);
}
});
await sleep(waitTillNextAPICall * 1000);
waitTillNextAPICall = 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 ${failedUsers} individually.`,
}),
});
}
prakashchoudhary07 marked this conversation as resolved.
Show resolved Hide resolved
} catch (error) {
console.log("Error occured while running mentionEachUserInMessage", error);
}
}
5 changes: 5 additions & 0 deletions src/utils/sleep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function sleep(delay = 1000) {
return new Promise((resolve) => {
setTimeout(resolve, delay);
});
}
Loading
Loading