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

added feature to remove tagged users of a particular role #216

Closed
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
13 changes: 13 additions & 0 deletions src/constants/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ export const MENTION_EACH = {
],
};

export const KICK = {
name: "kick",
description: "Kick a user from the server",
options: [
{
name: "role",
description: "The role to kick",
type: 8, // User type
required: false,
},
],
};

export const LISTENING = {
name: "listening",
description: "mark user as listening",
Expand Down
12 changes: 12 additions & 0 deletions src/controllers/baseHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
NOTIFY_ONBOARDING,
OOO,
USER,
KICK,
} from "../constants/commands";
import { updateNickName } from "../utils/updateNickname";
import { discordEphemeralResponse } from "../utils/discordEphemeralResponse";
Expand All @@ -40,6 +41,7 @@ import {
RETRY_COMMAND,
} from "../constants/responses";
import { DevFlag } from "../typeDefinitions/filterUsersByRole";
import { kickEachUser } from "./kickEachUser";

export async function baseHandler(
message: discordMessageRequest,
Expand All @@ -48,6 +50,8 @@ export async function baseHandler(
): Promise<JSONResponse> {
const command = lowerCaseMessageCommands(message);

console.log("Message: ", JSON.stringify(message.data));
console.log("Envior:", env);
switch (command) {
case getCommandName(HELLO): {
return helloCommand(message.member.user.id);
Expand Down Expand Up @@ -75,6 +79,14 @@ export async function baseHandler(
return await mentionEachUser(transformedArgument, env, ctx);
}

case getCommandName(KICK): {
const data = message.data?.options as Array<messageRequestDataOptions>;
const transformedArgument = {
roleToBeRemovedObj: data[0],
};
return await kickEachUser(transformedArgument, env, ctx);
}

case getCommandName(LISTENING): {
const data = message.data?.options;
const setter = data ? data[0].value : false;
Expand Down
34 changes: 34 additions & 0 deletions src/controllers/kickEachUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {
MentionEachUserOptions,
UserArray,
} from "../typeDefinitions/filterUsersByRole";
import { env } from "../typeDefinitions/default.types";
import { getMembersInServer } from "../utils/getMembersInServer";
import { filterUserByRoles } from "../utils/filterUsersByRole";
import { discordTextResponse } from "../utils/discordResponse";
import { removeUsers } from "../utils/removeUsers";

export async function kickEachUser(
transformedArgument: {
roleToBeRemovedObj: MentionEachUserOptions;
},
env: env,
ctx: ExecutionContext
) {
const getMembersInServerResponse = await getMembersInServer(env);
const roleId = transformedArgument.roleToBeRemovedObj.value;

const usersWithMatchingRole = filterUserByRoles(
getMembersInServerResponse as UserArray[],
roleId
);

if (usersWithMatchingRole.length === 0) {
return discordTextResponse(`Found no users with the matched role.`);
} else {
ctx.waitUntil(removeUsers(env, usersWithMatchingRole));
return discordTextResponse(
`Found ${usersWithMatchingRole.length} users with the matched role, removing them shortly...`
);
}
}
2 changes: 2 additions & 0 deletions src/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
NOTIFY_ONBOARDING,
OOO,
USER,
KICK,
} from "./constants/commands";
import { config } from "dotenv";
import { DISCORD_BASE_URL } from "./constants/urls";
Expand Down Expand Up @@ -37,6 +38,7 @@ async function registerGuildCommands(
USER,
NOTIFY_OVERDUE,
NOTIFY_ONBOARDING,
KICK,
];

try {
Expand Down
27 changes: 27 additions & 0 deletions src/utils/removeUsers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { env } from "../typeDefinitions/default.types";
import { DISCORD_BASE_URL } from "../constants/urls";

export const removeUsers = async (
env: env,
usersWithMatchingRole: string[]
) => {
const baseUrl = `${DISCORD_BASE_URL}/guilds/${env.DISCORD_GUILD_ID}/members`;
// Method : DELETE /guilds/{guild.id}/members/{user.id}

for (const mention of usersWithMatchingRole) {
// Remove <@ and > symbols from the mention
const userId = mention.replace(/<@!*/g, "").replace(/>/g, "");
const url = `${baseUrl}/${userId}`;

const headers = {
"Content-Type": "application/json",
Authorization: `Bot ${env.DISCORD_TOKEN}`,
};

try {
await fetch(url, { method: "DELETE", headers });
} catch (error) {
console.error(`Error removing user with ID ${userId}:`, error);
}
}
};
64 changes: 64 additions & 0 deletions tests/unit/handlers/kickEachUser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { kickEachUser } from "../../../src/controllers/kickEachUser";
import { discordTextResponse } from "../../../src/utils/discordResponse";
import { removeUsers } from "../../../src/utils/removeUsers";
import { transformedArgument, ctx } from "../../fixtures/fixture";

describe("kickEachUser", () => {
it("should run when found no users with Matched Role", async () => {
const env = {
BOT_PUBLIC_KEY: "xyz",
DISCORD_GUILD_ID: "123",
DISCORD_TOKEN: "abc",
};

const { roleToBeTaggedObj } = transformedArgument; // Extracting roleToBeTaggedObj
const response = kickEachUser(
{ roleToBeRemovedObj: roleToBeTaggedObj },
env,
ctx
);

expect(response).toBeInstanceOf(Promise);

const textMessage: { data: { content: string } } = await response.then(
(res) => res.json()
);
expect(textMessage.data.content).toBe(
"Found no users with the matched role."
);
});

it("should run when found users with Matched Role", async () => {
const env = {
BOT_PUBLIC_KEY: "xyz",
DISCORD_GUILD_ID: "123",
DISCORD_TOKEN: "abc",
};

const usersWithMatchingRole = [
"<@282859044593598464>",
"<@725745030706364447>",
] as string[];

const { roleToBeTaggedObj } = transformedArgument; // Extracting roleToBeTaggedObj
const response = kickEachUser(
{ roleToBeRemovedObj: roleToBeTaggedObj },
env,
ctx
);

expect(response).toEqual(
expect.objectContaining({
data: {
content:
"Found 2 users with the matched role, removing them shortly...",
},
})
); // Ensure correct response message

// Check the arguments passed to removeUsers
expect(removeUsers).toHaveBeenCalledWith(env, usersWithMatchingRole);

expect(response).toBeInstanceOf(Promise);
});
});
70 changes: 70 additions & 0 deletions tests/unit/utils/removeUsers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { DISCORD_BASE_URL } from "../../../src/constants/urls";
import JSONResponse from "../../../src/utils/JsonResponse";
import { removeUsers } from "../../../src/utils/removeUsers";

describe("removeUsers", () => {
const mockEnv = {
BOT_PUBLIC_KEY: "xyz",
DISCORD_GUILD_ID: "123",
DISCORD_TOKEN: "abc",
};

test("removes users successfully", async () => {
const usersWithMatchingRole = ["<@userId1>", "<@userId2>"];

jest
.spyOn(global, "fetch")
.mockImplementation(() =>
Promise.resolve(new Response(null, { status: 204 }))
);
await removeUsers(mockEnv, usersWithMatchingRole);

expect(fetch).toHaveBeenCalledTimes(2);
expect(fetch).toHaveBeenCalledWith(
`${DISCORD_BASE_URL}/guilds/${mockEnv.DISCORD_GUILD_ID}/members/userId1`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
Authorization: `Bot ${mockEnv.DISCORD_TOKEN}`,
},
}
);
expect(fetch).toHaveBeenCalledWith(
`${DISCORD_BASE_URL}/guilds/${mockEnv.DISCORD_GUILD_ID}/members/userId2`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
Authorization: `Bot ${mockEnv.DISCORD_TOKEN}`,
},
}
);
});
test("handles errors", async () => {
const usersWithMatchingRole = ["<@userId1>"];

// Mocking the fetch function to simulate a rejected promise with a 404 error response
jest
.spyOn(global, "fetch")
.mockImplementation(() =>
Promise.reject(new Response(null, { status: 404 }))
);

// Calling the function under test
await removeUsers(mockEnv, usersWithMatchingRole);

// Expectations
expect(fetch).toHaveBeenCalledTimes(3);
expect(fetch).toHaveBeenCalledWith(
`${DISCORD_BASE_URL}/guilds/${mockEnv.DISCORD_GUILD_ID}/members/userId1`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
Authorization: `Bot ${mockEnv.DISCORD_TOKEN}`,
},
}
);
});
});
Loading