Skip to content

Commit

Permalink
Automod System
Browse files Browse the repository at this point in the history
  • Loading branch information
Kathund committed Aug 1, 2024
1 parent 7fecbc3 commit 4850518
Show file tree
Hide file tree
Showing 14 changed files with 234 additions and 26 deletions.
52 changes: 52 additions & 0 deletions a.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const urlRegex =
/https?:\/\/(www\.)?([-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b)([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/;

const allowedDomains: string[] = [
'hypixel.net',
'*.hypixel.net',
'discord.com',
'*.discord.com',
'kath.lol',
'*.kathund.wtf',
'kathund.wtf',
'hypixel-api-reborn.github.io'
];

function matchWildcard(domain: string, pattern: string): boolean {
const regexPattern = pattern.replace(/[-\/\\^$+?.()|[\]{}]/g, '\\$&').replace(/\*/g, '.*');
const regex = new RegExp(`^${regexPattern}$`);
return regex.test(domain);
}

function isUrlAllowed(url: string): boolean {
const isValidUrl = urlRegex.test(url);
if (!isValidUrl) {
return false;
}

const match = url.match(urlRegex);
const domain = match ? match[2] : null;
if (!domain) {
return false;
}

return allowedDomains.some((pattern) => matchWildcard(domain, pattern));
}

const testUrls = [
'https://example.com',
'https://sub.example.com',
'https://blocked.com',
'http://sub.allowed.com',
'http://another-allowed-url.org',
'https://not-allowed-url.net',
'https://sub.allow.net',
'https://another.sub.allow.net',
'https://kathund.wtf',
'https://test.kathund.wtf'
];

testUrls.forEach((url) => {
const result = isUrlAllowed(url);
console.log(`Is URL allowed: ${url} -> ${result}`);
});
6 changes: 3 additions & 3 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Client, Events, GatewayIntentBits } from 'discord.js';
import DeployCommands from './src/functions/DeployCommands';
import { execute } from './src/events/ready';
import DeployCommands from './src/utils/DeployCommands';
import ready from './src/events/ready';
import { token } from './config.json';

const client: Client = new Client({
Expand All @@ -15,7 +15,7 @@ const client: Client = new Client({
DeployCommands(client);

client.on(Events.ClientReady, () => {
execute(client);
ready(client);
});

client.login(token);
2 changes: 1 addition & 1 deletion src/commands/tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
EmbedBuilder,
ChannelType
} from 'discord.js';
import { deleteTag, getTag, getTagNames } from '../functions/mongo';
import { deleteTag, getTag, getTagNames } from '../utils/mongo';
import { supportCategory } from '../../config.json';

export const data = new SlashCommandBuilder()
Expand Down
15 changes: 9 additions & 6 deletions src/commands/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
EmbedBuilder,
ButtonStyle
} from 'discord.js';
import Infraction, { getUserInfractions } from '../functions/Infraction';
import Infraction, { getUserInfractions } from '../utils/Infraction';
import ms from 'ms';

export const data = new SlashCommandBuilder()
Expand Down Expand Up @@ -58,7 +58,6 @@ export const data = new SlashCommandBuilder()
.setName('unmute')
.setDescription('unmute a user')
.addUserOption((option) => option.setName('user').setDescription('The user to mute').setRequired(true))
.addStringOption((option) => option.setName('time').setDescription('How long to mute').setRequired(true))
.addStringOption((option) =>
option.setName('reason').setDescription('The reason for the mute').setRequired(false)
)
Expand Down Expand Up @@ -136,7 +135,8 @@ export async function execute(interaction: ChatInputCommandInteraction): Promise
long: null,
user: { id: commandUser.id, staff: false, bot: commandUser.bot },
staff: { id: interaction.user.id, staff: true, bot: interaction.user.bot },
timestamp: Date.now()
timestamp: Date.now(),
extraInfo: ''
})
.log()
.save();
Expand All @@ -152,7 +152,8 @@ export async function execute(interaction: ChatInputCommandInteraction): Promise
long: null,
user: { id: commandUser.id, staff: false, bot: commandUser.bot },
staff: { id: interaction.user.id, staff: true, bot: interaction.user.bot },
timestamp: Date.now()
timestamp: Date.now(),
extraInfo: ''
})
.log()
.save();
Expand All @@ -179,7 +180,8 @@ export async function execute(interaction: ChatInputCommandInteraction): Promise
long,
user: { id: commandUser.id, staff: false, bot: commandUser.bot },
staff: { id: interaction.user.id, staff: true, bot: interaction.user.bot },
timestamp: Date.now()
timestamp: Date.now(),
extraInfo: ''
})
.log()
.save();
Expand All @@ -196,7 +198,8 @@ export async function execute(interaction: ChatInputCommandInteraction): Promise
long: null,
user: { id: commandUser.id, staff: false, bot: commandUser.bot },
staff: { id: interaction.user.id, staff: true, bot: interaction.user.bot },
timestamp: Date.now()
timestamp: Date.now(),
extraInfo: ''
})
.log()
.save();
Expand Down
10 changes: 5 additions & 5 deletions src/events/interactionCreate.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Interaction, Events, InteractionType, GuildMemberRoleManager } from 'discord.js';
import { Interaction, InteractionType, GuildMemberRoleManager } from 'discord.js';
import { teamRole, devRole } from '../../config.json';
import { Tag, modifyTag } from '../functions/mongo';
import { eventMessage } from '../functions/logger';
export const name = Events.InteractionCreate;
export async function execute(interaction: Interaction): Promise<void> {
import { Tag, modifyTag } from '../utils/mongo';
import { eventMessage } from '../utils/logger';

export default async function (interaction: Interaction): Promise<void> {
try {
if (!interaction.member || !interaction.channel || !interaction.guild) return;
const memberRoles = (interaction.member.roles as GuildMemberRoleManager).cache.map((role) => role.id);
Expand Down
117 changes: 117 additions & 0 deletions src/events/messageCreate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { ChannelType, GuildMemberRoleManager, Message, TextChannel, Webhook, WebhookType } from 'discord.js';
import { HypixelAPIKeyRegex, IPAddressPattern, URLRegex, DiscordInviteRegex } from '../utils/regex';
import { autoModBypassRole } from '../../config.json';
import Infraction from '../utils/Infraction';

const allowedDomains: string[] = [
'hypixel.net',
'*.hypixel.net',
'discord.com',
'*.discord.com',
'kath.lol',
'*.kathund.wtf',
'kathund.wtf',
'hypixel-api-reborn.github.io'
];

function matchWildcard(domain: string, pattern: string): boolean {
const regexPattern = pattern.replace(/[-\/\\^$+?.()|[\]{}]/g, '\\$&').replace(/\*/g, '.*');
const regex = new RegExp(`^${regexPattern}$`);
return regex.test(domain);
}

function isUrlAllowed(url: string): boolean {
const isValidUrl = URLRegex.test(url);
if (!isValidUrl) return false;
const match = url.match(URLRegex);
const domain = match ? match[2] : null;
if (!domain) return false;
return !allowedDomains.some((pattern) => matchWildcard(domain, pattern));
}

async function getWebhook(
channel: TextChannel
): Promise<Webhook<WebhookType.ChannelFollower | WebhookType.Incoming> | undefined> {
const webhooks = await channel.fetchWebhooks();

if (0 === webhooks.size) {
channel.createWebhook({
name: channel.client.user.globalName ?? channel.client.user.username,
avatar: channel.client.user.avatarURL()
});

await getWebhook(channel);
}

return webhooks.first();
}

export default async function (message: Message): Promise<void> {
try {
if (
message.channel.type !== ChannelType.GuildText ||
message.author.bot ||
!message.channel ||
!message.member ||
!message.guild
) {
return;
}
const memberRoles = (message.member.roles as GuildMemberRoleManager).cache.map((role) => role.id);
if (memberRoles.includes(autoModBypassRole)) return;
const hypixelKeyTest = HypixelAPIKeyRegex.test(message.content);
const ipTest = IPAddressPattern.test(message.content);
const urlTest = isUrlAllowed(message.content);
const discordTest = DiscordInviteRegex.test(message.content);
const webhook = await getWebhook(message.channel);
if (!webhook) return;
if (hypixelKeyTest) {
const filteredContent = message.content.replace(HypixelAPIKeyRegex, '[API Key Removed]');
webhook.send({
username: message.member.nickname ?? message.author.globalName ?? message.author.username,
avatarURL: message.member.avatarURL() ?? message.author.avatarURL() ?? undefined,
content: filteredContent
});
const alert = await message.reply({ content: 'Hey thats your Hypixel API Key. Please dont post that' });
message.delete();
new Infraction({
automatic: true,
reason: 'Automod Pickup',
long: null,
type: 'AutoMod',
user: { id: message.author.id, staff: false, bot: message.author.bot },
staff: { id: message.client.user.id, staff: true, bot: message.client.user.bot },
timestamp: Date.now(),
extraInfo: `Message: ${message.content}\[Jump to Message](${message.url})`
})
.log()
.save();
setTimeout(() => alert.delete(), 10000);
} else if (ipTest || urlTest || discordTest) {
const filteredContent = message.content
.replace(IPAddressPattern, '[Content Removed]')
.replace(URLRegex, '[Content Removed]')
.replace(DiscordInviteRegex, '[Content Removed]');
webhook.send({
username: message.member.nickname ?? message.author.globalName ?? message.author.username,
avatarURL: message.member.avatarURL() ?? message.author.avatarURL() ?? undefined,
content: filteredContent
});
message.delete();
new Infraction({
automatic: true,
reason: 'Automod Pickup',
long: null,
type: 'AutoMod',
user: { id: message.author.id, staff: false, bot: message.author.bot },
staff: { id: message.client.user.id, staff: true, bot: message.client.user.bot },
timestamp: Date.now(),
extraInfo: `**Message:**\n\`\`\`\n${message.content}\n\`\`\`\n[Jump to Message](${message.url})`
})
.log()
.save();
}
} catch (error) {
console.log(error);
}
}
11 changes: 6 additions & 5 deletions src/events/ready.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import CheckPermits from '../functions/CheckPermits';
import DeployEvents from '../functions/DeployEvents';
import { eventMessage } from '../functions/logger';
import { connectDB } from '../functions/mongo';
import CheckPermits from '../utils/CheckPermits';
import DeployEvents from '../utils/DeployEvents';
import { eventMessage } from '../utils/logger';
import { connectDB } from '../utils/mongo';
import { serverId } from '../../config.json';
import { Client } from 'discord.js';
import cron from 'node-cron';
export async function execute(client: Client): Promise<void> {

export default async function (client: Client): Promise<void> {
try {
eventMessage(`Logged in as ${client.user?.username} (${client.user?.id})!`);
DeployEvents(client);
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default async function (client: Client): Promise<void> {
}
const event = await import(`../events/${file}`);
const name = file.split('.')[0];
client.on(name, event.execute.bind(null));
client.on(name, event.default.bind(null));
eventMessage(`Successfully loaded ${name}`);
}
eventMessage(`Successfully loaded ${count} event(s).`);
Expand Down
40 changes: 35 additions & 5 deletions src/functions/Infraction.ts → src/utils/Infraction.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { infractionLogchannel } from '../../config.json';
import { ChannelType } from 'discord.js';
import { ChannelType, EmbedBuilder } from 'discord.js';
import { model, Schema } from 'mongoose';
import ms from 'ms';

export interface InfractionUser {
id: string;
staff: boolean;
bot: boolean;
}

export type InfractionType = 'EVENT' | 'WARN' | 'KICK' | 'BAN' | 'MUTE' | 'UNMUTE';
export type InfractionType = 'AutoMod' | 'WARN' | 'KICK' | 'BAN' | 'MUTE' | 'UNMUTE';

export interface InfractionInfomation {
automatic: boolean;
Expand All @@ -18,6 +19,7 @@ export interface InfractionInfomation {
user: InfractionUser;
staff: InfractionUser;
timestamp: number;
extraInfo: string;
}

const InteractionUserSchema = new Schema({ id: String, staff: Boolean, bot: Boolean });
Expand All @@ -31,7 +33,8 @@ const InfractionSchema = new Schema({
type: String,
user: InteractionUserSchema,
staff: InteractionUserSchema,
timestamp: Number
timestamp: Number,
extraInfo: String
});
const InfractionModel = model('infraction', InfractionSchema);

Expand All @@ -49,7 +52,8 @@ class Infraction {
type: this.infraction.type,
user: this.infraction.user,
staff: this.infraction.staff,
timestamp: this.infraction.timestamp
timestamp: this.infraction.timestamp,
extraInfo: this.infraction.extraInfo
}).save();
}

Expand Down Expand Up @@ -88,6 +92,11 @@ class Infraction {
return this;
}

public setExtraInfo(extraInfo: string): this {
this.infraction.extraInfo = extraInfo;
return this;
}

public getAutomatic(): boolean {
return this.infraction.automatic;
}
Expand Down Expand Up @@ -116,6 +125,10 @@ class Infraction {
return this.infraction.timestamp;
}

public getExtraInfo(): string {
return this.infraction.extraInfo;
}

public toString(): string {
return `Infraction: ${this.infraction.reason}\nType: ${this.infraction.type}\nLong: ${
this.infraction.long
Expand All @@ -129,7 +142,24 @@ class Infraction {
public log(): this {
const channel = guild.channels.cache.get(infractionLogchannel);
if (!channel || channel.type !== ChannelType.GuildText) return this;
channel.send(this.toString());
const embed = new EmbedBuilder()
.setColor('#FF8C00')
.setTitle(`Infraction | ${this.infraction.type}`)
.addFields(
{ name: 'User', value: `<@${this.infraction.user.id}>` },
{ name: 'Reason', value: this.infraction.reason },
{ name: 'Staff', value: `<@${this.infraction.staff.id}>` },
{ name: 'Automatic', value: this.infraction.automatic ? 'Yes' : 'No' },
{
name: 'Timestamp',
value: `<t:${Math.floor(this.infraction.timestamp / 1000)}:t> (<t:${Math.floor(
this.infraction.timestamp / 1000
)}:R>)`
}
);
if (0 < this.infraction.extraInfo.length) embed.addFields({ name: 'Info', value: this.infraction.extraInfo });
if (this.infraction.long) embed.addFields({ name: 'How long', value: `${ms(86400000, { long: true })}` });
channel.send({ embeds: [embed] });
return this;
}
}
Expand Down
File renamed without changes.
File renamed without changes.
5 changes: 5 additions & 0 deletions src/utils/regex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const HypixelAPIKeyRegex = /[a-zA-Z0-9]{8}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{12}/g;
export const IPAddressPattern = /((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}/g;
export const URLRegex =
/https?:\/\/(www\.)?([-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b)([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/;
export const DiscordInviteRegex = /(?:https:\/\/)?discord\.gg\/[a-zA-Z0-9]+/g;

0 comments on commit 4850518

Please sign in to comment.