Skip to content

Commit

Permalink
Spam-detection (#16)
Browse files Browse the repository at this point in the history
Co-authored-by: IThundxr <[email protected]>
  • Loading branch information
Szedann and IThundxr committed Dec 28, 2023
1 parent 6151a3d commit dd43f77
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 3 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ SAY_LOGS_CHANNEL=
LOGS_CHANNEL=
SIMULATED_BAN_SHARE_LOGS_CHANNEL=
BAN_LOGS_CHANNEL=
MESSAGE_LOGS_CHANNEL=

# These arent needed outside of production
MAVEN_REPO=
Expand Down
181 changes: 181 additions & 0 deletions src/handlers/spam.handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import {
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
EmbedBuilder,
GuildMember,
GuildNSFWLevel,
Message,
ModalActionRowComponentBuilder,
ModalBuilder,
PermissionsBitField,
TextBasedChannel,
TextInputBuilder,
TextInputStyle,
} from 'discord.js';
import { Handler } from '..';
import { Button } from './button.handler';

const banButton = new Button(
'ban-spammer',
async (interaction, data: { userId: string }) => {
const user = await interaction.client.users.fetch(data.userId);
if (
!(interaction.member as GuildMember)?.permissions.has(
PermissionsBitField.Flags.BanMembers
)
) {
return await interaction.reply({
content: 'You do not have permission to ban this user',
ephemeral: true,
});
}

const modal = new ModalBuilder()
.setCustomId(`ban`)
.setTitle(`Ban ${user.username}`)

.addComponents(
new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents(
new TextInputBuilder()
.setCustomId('banReason')
.setLabel('Ban reason')
.setStyle(TextInputStyle.Paragraph)
.setValue('spam (autodetected)')
)
);
await interaction.showModal(modal);
interaction
.awaitModalSubmit({
filter: (interaction) =>
interaction.customId == modal.data.custom_id,
time: 300_000,
})
.then(async (modalResponse) => {
interaction.guild?.bans.create(data.userId, {
reason: modalResponse.components[0].components[0].value,
});
await modalResponse.reply({
content: `<@${data.userId}> (\`${data.userId}\`) was banned.`,
ephemeral: true,
});
await interaction.message.edit({
components: [
new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId('fakeBanButton')
.setLabel('Ban')
.setStyle(ButtonStyle.Danger)
.setDisabled(true)
),
],
});
if (interaction.guild != null) {
const channel = await interaction.guild.channels.fetch(
process.env.BAN_LOGS_CHANNEL
);
if (channel?.isTextBased()) {
channel.send({
embeds: [
new EmbedBuilder()
.setTitle('User Banned for spam')
.setDescription(
`<@!${data.userId}> was banned!`
)
.setFields([
{
name: 'Reason',
value: modalResponse.components[0]
.components[0].value,
},
])
.setAuthor({
iconURL:
interaction.user.avatar ??
undefined,
name: interaction.user.username,
}),
],
});
}
}
});
}
);

const getMessageSuspicion = async (message: Message) => {
let suspicionLevel = 0;
const links = message.content.matchAll(
/(https:\/\/www\.|http:\/\/www\.|https:\/\/|http:\/\/)?[a-zA-Z]{2,}(\.[a-zA-Z]{2,})(\.[a-zA-Z]{2,})?\/[a-zA-Z0-9]{2,}|((https:\/\/www\.|http:\/\/www\.|https:\/\/|http:\/\/)?[a-zA-Z]{2,}(\.[a-zA-Z]{2,})(\.[a-zA-Z]{2,})?)|(https:\/\/www\.|http:\/\/www\.|https:\/\/|http:\/\/)?[a-zA-Z0-9]{2,}\.[a-zA-Z0-9]{2,}\.[a-zA-Z0-9]{2,}(\.[a-zA-Z0-9]{2,})?/g
);
if (message.content.toLowerCase().includes('nitro')) suspicionLevel += 5;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for (const _ of links) suspicionLevel += 5;

const invites = message.content.matchAll(
/\s*(?:https:\/\/)?(?:discord\.gg\/|discord\.com\/invite\/)[a-zA-Z0-9]+\s*/g
);
for (const inviteLink of invites) {
const invite = await message.client.fetchInvite(inviteLink[0]);
suspicionLevel += 10;
if (invite.guild?.name.includes('18')) suspicionLevel += 20;
if (
invite.guild?.nsfwLevel == GuildNSFWLevel.Explicit ||
invite.guild?.nsfwLevel == GuildNSFWLevel.AgeRestricted
)
suspicionLevel += 20;
}
return suspicionLevel;
};

export const spamHandler: Handler = (client) => {
client.on('messageCreate', async (message: Message) => {
let suspicionLevel = await getMessageSuspicion(message);
if (suspicionLevel > 10) {
if (!message.inGuild()) return;

suspicionLevel += (
await Promise.all(
(
await Promise.all(
message.guild.channels.cache
.filter((channel) => channel.isTextBased())
.map(async (channel) => {
return (
await (
channel as TextBasedChannel
).messages.fetch({ limit: 10 })
).filter(
(msg) =>
msg.author.id == message.author.id
);
})
)
)
.reduce((a, b) => a.concat(b))
.map(getMessageSuspicion)
)
).reduce((a, b) => a + b);

const logChannel = await message.guild?.channels.fetch(
process.env.MESSAGE_LOGS_CHANNEL
);
if (logChannel?.isTextBased())
logChannel.send({
embeds: [
new EmbedBuilder({
description: `suspicion level ${suspicionLevel} for ${message.author}, from message ${message.url}`,
}),
],
components: [
new ActionRowBuilder<ButtonBuilder>().addComponents(
banButton.button(
{ label: 'Ban', style: ButtonStyle.Danger },
{ userId: message.author.id }
)
),
],
});
}
});
};
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { reloadGlobalSlashCommands } from './handlers/command.handler';
import './webserver';
import { buttonHandler } from './handlers/button.handler';
import textCommandHandler from './handlers/textCommand.handler';
import { spamHandler } from './handlers/spam.handler';

export const client = new Client({
intents: [
Expand Down Expand Up @@ -89,6 +90,7 @@ const handlers: Handler[] = [
textCommandHandler,
logHandler,
buttonHandler,
spamHandler,
];

for (const handler of handlers) {
Expand Down
1 change: 1 addition & 0 deletions src/types/environment.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ declare global {
SAY_LOGS_CHANNEL: string;
LOGS_CHANNEL: string;
BAN_LOGS_CHANNEL: string;
MESSAGE_LOGS_CHANNEL: string;
MAVEN_REPO: string;
GITHUB_STATUS_CHANNEL: string;
GITHUB_SECRET: string;
Expand Down
8 changes: 5 additions & 3 deletions src/webserver/banshare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { Button } from '../handlers/button.handler';
const banButton = new Button(
'ban',
async (interaction, data: { userId: string }) => {
const user = await interaction.client.users.fetch(data.userId)
const user = await interaction.client.users.fetch(data.userId);
if (
!(interaction.member as GuildMember)?.permissions.has(
PermissionsBitField.Flags.BanMembers
Expand All @@ -36,7 +36,7 @@ const banButton = new Button(
const modal = new ModalBuilder()
.setCustomId(`ban`)
.setTitle(`Ban ${user.username}`)

.addComponents(
new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents(
new TextInputBuilder()
Expand Down Expand Up @@ -158,7 +158,9 @@ const handleBan = async (client: Client, req: Request) => {
{
label: 'Ban',
style: ButtonStyle.Danger,
disabled: guildMember ? !guildMember.bannable : false
disabled: guildMember
? !guildMember.bannable
: false,
},
{ userId }
)
Expand Down

0 comments on commit dd43f77

Please sign in to comment.