Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,11 @@ exports[`Banning spammer in different channels Does not ban user in different ch
}
`;

exports[`Banning spammer that has left the server Reacts with the correct emoji to automod message 1`] = `""`;
exports[`Banning spammer that has left the server Reacts with the correct emoji to automod message 1`] = `""`;

exports[`Banning spammer that has left the server Sends back correct interaction reply to calling moderator 1`] = `
{
"content": "Couldn't ban <@123>. User is not on the server.",
"content": "Banned <@123> for spam but wasn't able to contact the user as they have left the server.",
"ephemeral": true,
}
`;
Expand Down
50 changes: 30 additions & 20 deletions services/spam-ban/spam-banning.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,46 @@ class SpamBanningService {
const message = interaction.options.getMessage('message');

try {
let reply;

if (message.author.bot || isAdmin(message.member)) {
reply = 'You do not have the permission to ban this user';
} else if (message.channelId !== config.channels.automodBlockChannelId) {
reply = 'This command can only be used in the automod block channel.';
} else if (!message.member) {
message.react('❌');
reply = `Couldn't ban <@${message.author.id}>. User is not on the server.`;
} else {
reply = await SpamBanningService.#banUser(message);
await SpamBanningService.#announceBan(interaction, message);
interaction.reply({
content: 'You do not have the permission to ban this user',
ephemeral: true,
});
return;
}

if (message.channelId !== config.channels.automodBlockChannelId) {
interaction.reply({
content:
'This command can only be used in the automod block channel.',
ephemeral: true,
});
return;
}
const reply = await SpamBanningService.#banUser(interaction);
interaction.reply({ content: reply, ephemeral: true });
await SpamBanningService.#announceBan(interaction, message);
} catch (error) {
console.error(error);
}
}

static async #banUser(message) {
static async #banUser(interaction) {
const message = interaction.options.getMessage('message');
const { guild } = interaction;
let reply = `Successfully banned <@${message.author.id}> for spam.`;
try {
// Make sure to send the message before banning otherwise user will not be found
await SpamBanningService.#sendMessageToUser(message.author);
} catch (error) {
reply = `Banned <@${message.author.id}> for spam but wasn't able to contact the user.`;
// Only attempt to send the message if message.author exists
if (message.member) {
try {
await SpamBanningService.#sendMessageToUser(message.author);
} catch (error) {
reply = `Banned <@${message.author.id}> for spam but wasn't able to contact the user.`;
}
} else {
reply = `Banned <@${message.author.id}> for spam but wasn't able to contact the user as they have left the server.`;
}

message.member.ban({ reason: 'Account is compromised' });
await guild.members.ban(message.author.id, {
reason: 'Account is compromised',
});
message.react('✅');
return reply;
}
Expand Down
46 changes: 21 additions & 25 deletions services/spam-ban/spam-banning.service.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function createInteractionMock(message, guild) {

getReplyArg: () => replyArg,
getAuthorSendArg: () => message.getSendArg(),
getBanArg: () => message.getBanArg(),
getBanArg: () => guild.getBanArg(),
getReactArg: () => message.getReactArg(),
getChannelSendArg: () =>
guild.channels.cache
Expand All @@ -42,7 +42,6 @@ function createInteractionMock(message, guild) {

function createMessageMock() {
let sendArg;
let banArg;
let reactArg;

return {
Expand All @@ -60,15 +59,11 @@ function createMessageMock() {
roles: {
cache: [],
},
ban: jest.fn((arg) => {
banArg = arg;
}),
},
react: jest.fn((arg) => {
reactArg = arg;
}),
getSendArg: () => sendArg,
getBanArg: () => banArg,
getReactArg: () => reactArg,
};
}
Expand All @@ -85,6 +80,7 @@ function createChannelMock(id) {
}

function createGuildMock() {
let banArg;
const channels = {
cache: [
createChannelMock(config.channels.moderationLogChannelId),
Expand All @@ -96,9 +92,16 @@ function createGuildMock() {
],
fetch: (id) => channels.cache.find((c) => c.id === id),
};
const members = {
ban: jest.fn((user, options) => {
banArg = options; // I don't know why this works. Jest just likes swapping function arguments I guess?
}),
};

return {
channels,
members,
getBanArg: () => banArg,
};
}

Expand All @@ -111,27 +114,27 @@ describe('Banning spammer in different channels', () => {
});

it('Ban user if in the automod channel', async () => {
interactionMock.message.channelId = config.channels.automodBlockChannelId;
await SpamBanningService.handleInteraction(interactionMock);
expect(interactionMock.message.member.ban).toHaveBeenCalledTimes(1);
await SpamBanningService.handleInteraction(interactionMock);
// expect(interactionMock.guild.members.ban).toHaveBeenCalledTimes(1);
expect(interactionMock.getBanArg()).toMatchSnapshot();
expect(interactionMock.getReplyArg()).toMatchSnapshot();
});

it('Does not ban user in different channels other than automod', async () => {
interactionMock.message.channelId = null;
await SpamBanningService.handleInteraction(interactionMock);
expect(interactionMock.message.member.ban).not.toHaveBeenCalled();
expect(interactionMock.guild.members.ban).not.toHaveBeenCalled();
expect(interactionMock.getReplyArg()).toMatchSnapshot();

interactionMock.message.channelId = config.channels.moderationLogChannelId;
await SpamBanningService.handleInteraction(interactionMock);
expect(interactionMock.message.member.ban).not.toHaveBeenCalled();
expect(interactionMock.guild.members.ban).not.toHaveBeenCalled();
expect(interactionMock.getReplyArg()).toMatchSnapshot();

interactionMock.message.channelId = '21304782342';
await SpamBanningService.handleInteraction(interactionMock);
expect(interactionMock.message.member.ban).not.toHaveBeenCalled();
expect(interactionMock.guild.members.ban).not.toHaveBeenCalled();
expect(interactionMock.getReplyArg()).toMatchSnapshot();
});
});
Expand All @@ -146,7 +149,7 @@ describe('Banning spammer with DM enabled', () => {

it('Discord ban api is called with the correct reason', async () => {
await SpamBanningService.handleInteraction(interactionMock);
expect(interactionMock.message.member.ban).toHaveBeenCalledTimes(1);
expect(interactionMock.guild.members.ban).toHaveBeenCalledTimes(1);
expect(interactionMock.getBanArg()).toMatchSnapshot();
expect(interactionMock.getReplyArg()).toMatchSnapshot();
});
Expand Down Expand Up @@ -194,7 +197,7 @@ describe('Banning spammer who has DM set to private', () => {

it('Discord ban api is called with the correct reason', async () => {
await SpamBanningService.handleInteraction(interactionMock);
expect(interactionMock.message.member.ban).toHaveBeenCalledTimes(1);
expect(interactionMock.guild.members.ban).toHaveBeenCalledTimes(1);
expect(interactionMock.getBanArg()).toMatchSnapshot();
});

Expand Down Expand Up @@ -247,13 +250,6 @@ describe('Banning spammer that has left the server', () => {
expect(interactionMock.getReactArg()).toMatchSnapshot();
});

it('Does not log any channel', async () => {
await SpamBanningService.handleInteraction(interactionMock);
interactionMock.guild.channels.cache.forEach((channel) => {
expect(channel.send).not.toHaveBeenCalled();
});
});

it('Sends back correct interaction reply to calling moderator', async () => {
await SpamBanningService.handleInteraction(interactionMock);
expect(interactionMock.getReplyArg()).toMatchSnapshot();
Expand All @@ -271,7 +267,7 @@ describe('Attempting to ban a bot or team member', () => {
it('Does not ban bots', async () => {
interactionMock.message.author.bot = true;
await SpamBanningService.handleInteraction(interactionMock);
expect(interactionMock.message.member.ban).not.toHaveBeenCalled();
expect(interactionMock.guild.members.ban).not.toHaveBeenCalled();
expect(interactionMock.message.author.send).not.toHaveBeenCalled();
expect(interactionMock.message.react).not.toHaveBeenCalled();
expect(interactionMock.getReplyArg()).toMatchSnapshot();
Expand All @@ -280,7 +276,7 @@ describe('Attempting to ban a bot or team member', () => {
it('Does not ban moderators', async () => {
interactionMock.message.member.roles.cache = [{ name: 'moderator' }];
await SpamBanningService.handleInteraction(interactionMock);
expect(interactionMock.message.member.ban).not.toHaveBeenCalled();
expect(interactionMock.guild.members.ban).not.toHaveBeenCalled();
expect(interactionMock.message.author.send).not.toHaveBeenCalled();
expect(interactionMock.message.react).not.toHaveBeenCalled();
expect(interactionMock.getReplyArg()).toMatchSnapshot();
Expand All @@ -289,7 +285,7 @@ describe('Attempting to ban a bot or team member', () => {
it('Does not ban maintainers', async () => {
interactionMock.message.member.roles.cache = [{ name: 'maintainer' }];
await SpamBanningService.handleInteraction(interactionMock);
expect(interactionMock.message.member.ban).not.toHaveBeenCalled();
expect(interactionMock.guild.members.ban).not.toHaveBeenCalled();
expect(interactionMock.message.author.send).not.toHaveBeenCalled();
expect(interactionMock.message.react).not.toHaveBeenCalled();
expect(interactionMock.getReplyArg()).toMatchSnapshot();
Expand All @@ -298,7 +294,7 @@ describe('Attempting to ban a bot or team member', () => {
it('Does not ban core', async () => {
interactionMock.message.member.roles.cache = [{ name: 'core' }];
await SpamBanningService.handleInteraction(interactionMock);
expect(interactionMock.message.member.ban).not.toHaveBeenCalled();
expect(interactionMock.guild.members.ban).not.toHaveBeenCalled();
expect(interactionMock.message.author.send).not.toHaveBeenCalled();
expect(interactionMock.message.react).not.toHaveBeenCalled();
expect(interactionMock.getReplyArg()).toMatchSnapshot();
Expand All @@ -307,7 +303,7 @@ describe('Attempting to ban a bot or team member', () => {
it('Does not ban admins', async () => {
interactionMock.message.member.roles.cache = [{ name: 'admin' }];
await SpamBanningService.handleInteraction(interactionMock);
expect(interactionMock.message.member.ban).not.toHaveBeenCalled();
expect(interactionMock.guild.members.ban).not.toHaveBeenCalled();
expect(interactionMock.message.author.send).not.toHaveBeenCalled();
expect(interactionMock.message.react).not.toHaveBeenCalled();
expect(interactionMock.getReplyArg()).toMatchSnapshot();
Expand Down