Skip to content

Commit 80ffdb5

Browse files
authored
Merge pull request #209 from Real-Dev-Squad/develop
Dev to main sync
2 parents f4c056c + 5a7bcab commit 80ffdb5

File tree

13 files changed

+352
-17
lines changed

13 files changed

+352
-17
lines changed

src/constants/commands.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ export const MENTION_EACH = {
2525
type: 3,
2626
require: false,
2727
},
28+
{
29+
name: "dev",
30+
description: "want to tag them individually?",
31+
type: 5,
32+
require: false,
33+
},
2834
],
2935
};
3036

src/controllers/baseHandler.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,12 @@ import {
3939
REMOVED_LISTENING_MESSAGE,
4040
RETRY_COMMAND,
4141
} from "../constants/responses";
42+
import { DevFlag } from "../typeDefinitions/filterUsersByRole";
4243

4344
export async function baseHandler(
4445
message: discordMessageRequest,
45-
env: env
46+
env: env,
47+
ctx: ExecutionContext
4648
): Promise<JSONResponse> {
4749
const command = lowerCaseMessageCommands(message);
4850

@@ -65,9 +67,11 @@ export async function baseHandler(
6567
// data[1] is message obj
6668
const transformedArgument = {
6769
roleToBeTaggedObj: data[0],
68-
displayMessageObj: data[1] ?? {},
70+
displayMessageObj: data.find((item) => item.name === "message"),
71+
channelId: message.channel_id,
72+
dev: data.find((item) => item.name === "dev") as unknown as DevFlag,
6973
};
70-
return await mentionEachUser(transformedArgument, env);
74+
return await mentionEachUser(transformedArgument, env, ctx);
7175
}
7276

7377
case getCommandName(LISTENING): {

src/controllers/mentionEachUser.ts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,53 @@ import { env } from "../typeDefinitions/default.types";
66
import {
77
UserArray,
88
MentionEachUserOptions,
9+
DevFlag,
910
} from "../typeDefinitions/filterUsersByRole";
11+
import { mentionEachUserInMessage } from "../utils/guildRole";
1012
import { checkDisplayType } from "../utils/checkDisplayType";
1113

1214
export async function mentionEachUser(
1315
transformedArgument: {
1416
roleToBeTaggedObj: MentionEachUserOptions;
1517
displayMessageObj?: MentionEachUserOptions;
18+
channelId: number;
19+
dev?: DevFlag;
1620
},
17-
env: env
21+
env: env,
22+
ctx: ExecutionContext
1823
) {
1924
const getMembersInServerResponse = await getMembersInServer(env);
2025
const roleId = transformedArgument.roleToBeTaggedObj.value;
2126
const msgToBeSent = transformedArgument?.displayMessageObj?.value;
27+
const dev = transformedArgument?.dev?.value || false;
2228
// optional chaining here only because display message obj is optional argument
2329
const usersWithMatchingRole = filterUserByRoles(
2430
getMembersInServerResponse as UserArray[],
2531
roleId
2632
);
27-
const responseData = checkDisplayType({
33+
const payload = {
34+
channelId: transformedArgument.channelId,
35+
roleId: roleId,
36+
message: msgToBeSent,
2837
usersWithMatchingRole,
29-
msgToBeSent,
30-
});
31-
return discordTextResponse(responseData);
38+
};
39+
if (!dev || usersWithMatchingRole.length === 0) {
40+
const responseData = checkDisplayType({
41+
usersWithMatchingRole,
42+
msgToBeSent,
43+
});
44+
return discordTextResponse(responseData);
45+
} else {
46+
ctx.waitUntil(
47+
mentionEachUserInMessage({
48+
message: payload.message,
49+
userIds: payload.usersWithMatchingRole,
50+
channelId: payload.channelId,
51+
env,
52+
})
53+
);
54+
return discordTextResponse(
55+
`Found ${usersWithMatchingRole.length} users with matched role, mentioning them shortly...`
56+
);
57+
}
3258
}

src/index.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ router.delete("/roles", removeGuildRoleHandler);
5757

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

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

6363
if (message.type === InteractionType.PING) {
@@ -66,7 +66,7 @@ router.post("/", async (request, env) => {
6666
});
6767
}
6868
if (message.type === InteractionType.APPLICATION_COMMAND) {
69-
return baseHandler(message, env);
69+
return baseHandler(message, env, ctx);
7070
}
7171
return new JSONResponse(response.UNKNOWN_INTERACTION, { status: 400 });
7272
});
@@ -78,7 +78,11 @@ router.all("*", async () => {
7878
});
7979

8080
export default {
81-
async fetch(request: Request, env: env): Promise<Response> {
81+
async fetch(
82+
request: Request,
83+
env: env,
84+
ctx: ExecutionContext
85+
): Promise<Response> {
8286
const apiUrls = ["/invite", "/roles", "/profile/blocked"];
8387
const url = new URL(request.url);
8488
if (request.method === "POST" && !apiUrls.includes(url.pathname)) {
@@ -87,7 +91,7 @@ export default {
8791
return new JSONResponse(response.BAD_SIGNATURE, { status: 401 });
8892
}
8993
}
90-
return router.handle(request, env);
94+
return router.handle(request, env, ctx);
9195
},
9296

9397
async scheduled(req: Request, env: env, ctx: ExecutionContext) {

src/typeDefinitions/discordMessage.types.d.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,59 @@ export interface discordMessageRequest {
33
data: messageRequestData;
44
member: messageRequestMember;
55
guild_id: number;
6+
channel_id: number;
7+
}
8+
9+
export interface DiscordMessageResponse {
10+
id: string;
11+
type: number;
12+
content: string;
13+
channel_id: string;
14+
author: {
15+
id: string;
16+
username: string;
17+
avatar: string | null;
18+
discriminator: string;
19+
public_flags: number;
20+
premium_type: number;
21+
flags: number;
22+
bot: boolean;
23+
banner: string | null;
24+
accent_color: string | null;
25+
global_name: string | null;
26+
avatar_decoration_data: string | null;
27+
banner_color: string | null;
28+
};
29+
attachments: Array<string>;
30+
embeds: Array<string>;
31+
mentions: {
32+
id: string;
33+
username: string;
34+
avatar: string | null;
35+
discriminator: string;
36+
public_flags: number;
37+
premium_type: number;
38+
flags: number;
39+
banner: string | null;
40+
accent_color: string | null;
41+
global_name: string | null;
42+
avatar_decoration_data: string | null;
43+
banner_color: string | null;
44+
}[];
45+
mention_roles: Array<string>;
46+
pinned: boolean;
47+
mention_everyone: boolean;
48+
tts: boolean;
49+
timestamp: string;
50+
edited_timestamp: string | null;
51+
flags: number;
52+
components: Array<string>;
53+
referenced_message: Arra<string> | null;
54+
}
55+
56+
export interface discordMessageError {
57+
code: number;
58+
message: string;
659
}
760

861
export interface messageRequestData {

src/typeDefinitions/filterUsersByRole.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,8 @@ export type MentionEachUserOptions = {
1010
type: number;
1111
value: string;
1212
};
13+
export type DevFlag = {
14+
name: string;
15+
type: number;
16+
value: boolean;
17+
};

src/utils/batchDiscordRequests.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@ interface ResponseDetails {
1919
data: RequestDetails;
2020
}
2121

22-
const parseRateLimitRemaining = (response: Response) => {
22+
export const parseRateLimitRemaining = (response: Response) => {
2323
let rateLimitRemaining = Number.parseInt(
2424
response.headers.get(DISCORD_HEADERS.RATE_LIMIT_REMAINING) || "0"
2525
);
2626
rateLimitRemaining = Math.floor(rateLimitRemaining * (1 - LIMIT_BUFFER));
2727
return rateLimitRemaining;
2828
};
2929

30-
const parseResetAfter = (response: Response) => {
30+
export const parseResetAfter = (response: Response) => {
3131
let resetAfter = Number.parseFloat(
3232
response.headers.get(DISCORD_HEADERS.RATE_LIMIT_RESET_AFTER) || "0"
3333
);

src/utils/guildRole.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,22 @@ import {
66
ROLE_REMOVED,
77
} from "../constants/responses";
88
import { DISCORD_BASE_URL } from "../constants/urls";
9+
import {
10+
parseRateLimitRemaining,
11+
parseResetAfter,
12+
} from "../utils/batchDiscordRequests";
13+
914
import { env } from "../typeDefinitions/default.types";
1015
import {
1116
createNewRole,
17+
discordMessageError,
18+
discordMessageRequest,
1219
guildRoleResponse,
1320
memberGroupRole,
1421
} from "../typeDefinitions/discordMessage.types";
1522
import { GuildRole, Role } from "../typeDefinitions/role.types";
1623
import createDiscordHeaders from "./createDiscordHeaders";
24+
import { sleep } from "./sleep";
1725

1826
export async function createGuildRole(
1927
body: createNewRole,
@@ -135,3 +143,68 @@ export async function getGuildRoleByName(
135143
const roles = await getGuildRoles(env);
136144
return roles?.find((role) => role.name === roleName);
137145
}
146+
147+
export async function mentionEachUserInMessage({
148+
message,
149+
userIds,
150+
channelId,
151+
env,
152+
}: {
153+
message?: string;
154+
userIds: string[];
155+
channelId: number;
156+
env: env;
157+
}) {
158+
const batchSize = 5;
159+
let waitTillNextAPICall = 0;
160+
try {
161+
const failedUsers: Array<string> = [];
162+
for (let i = 0; i < userIds.length; i += batchSize) {
163+
const batchwiseUserIds = userIds.slice(i, i + batchSize);
164+
const messageRequest = batchwiseUserIds.map((userId) => {
165+
return fetch(`${DISCORD_BASE_URL}/channels/${channelId}/messages`, {
166+
method: "POST",
167+
headers: {
168+
"Content-Type": "application/json",
169+
Authorization: `Bot ${env.DISCORD_TOKEN}`,
170+
},
171+
body: JSON.stringify({
172+
content: `${message ? message + " " : ""} ${userId}`,
173+
}),
174+
}).then((response) => {
175+
const rateLimitRemaining = parseRateLimitRemaining(response);
176+
if (rateLimitRemaining === 0) {
177+
waitTillNextAPICall = Math.max(
178+
parseResetAfter(response),
179+
waitTillNextAPICall
180+
);
181+
}
182+
return response.json();
183+
}) as Promise<discordMessageRequest | discordMessageError>;
184+
});
185+
const responses = await Promise.all(messageRequest);
186+
responses.forEach((response, i) => {
187+
if (response && "message" in response) {
188+
failedUsers.push(batchwiseUserIds[i]);
189+
console.error(`Failed to mention a user`);
190+
}
191+
});
192+
await sleep(waitTillNextAPICall * 1000);
193+
waitTillNextAPICall = 0;
194+
}
195+
if (failedUsers.length > 0) {
196+
await fetch(`${DISCORD_BASE_URL}/channels/${channelId}/messages`, {
197+
method: "POST",
198+
headers: {
199+
"Content-Type": "application/json",
200+
Authorization: `Bot ${env.DISCORD_TOKEN}`,
201+
},
202+
body: JSON.stringify({
203+
content: `Failed to tag ${failedUsers} individually.`,
204+
}),
205+
});
206+
}
207+
} catch (error) {
208+
console.log("Error occured while running mentionEachUserInMessage", error);
209+
}
210+
}

src/utils/sleep.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export function sleep(delay = 1000) {
2+
return new Promise((resolve) => {
3+
setTimeout(resolve, delay);
4+
});
5+
}

0 commit comments

Comments
 (0)