aboutsummaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
authorIRONM00N <64110067+IRONM00N@users.noreply.github.com>2021-12-13 19:19:06 -0500
committerIRONM00N <64110067+IRONM00N@users.noreply.github.com>2021-12-13 19:19:06 -0500
commit8a27a3428dc3946a538f6471e480a34e41ceb7e6 (patch)
treeca45e5d9a94b32aa410624ae8a02d53f11cb4a33 /src/lib
parent64d8476209d33c06547988c27874b986814db232 (diff)
downloadtanzanite-8a27a3428dc3946a538f6471e480a34e41ceb7e6.tar.gz
tanzanite-8a27a3428dc3946a538f6471e480a34e41ceb7e6.tar.bz2
tanzanite-8a27a3428dc3946a538f6471e480a34e41ceb7e6.zip
added new automod thing, fixed some errors
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/common/AutoMod.ts175
-rw-r--r--src/lib/common/ButtonPaginator.ts13
-rw-r--r--src/lib/extensions/discord.js/BushMessage.ts2
-rw-r--r--src/lib/models/Guild.ts4
4 files changed, 176 insertions, 18 deletions
diff --git a/src/lib/common/AutoMod.ts b/src/lib/common/AutoMod.ts
index 5fd5d2d..c52754a 100644
--- a/src/lib/common/AutoMod.ts
+++ b/src/lib/common/AutoMod.ts
@@ -4,18 +4,36 @@ import badLinksSecretArray from '../badlinks-secret.js'; // I cannot make this p
import badLinksArray from '../badlinks.js';
import badWords from '../badwords.js';
+/**
+ * Handles auto moderation functionality.
+ */
export class AutoMod {
+ /**
+ * The message to check for blacklisted phrases on
+ */
private message: BushMessage;
+ /**
+ * Whether or not a punishment has already been given to the user
+ */
+ private punished = false;
+
+ /**
+ * @param message The message to check and potentially perform automod actions to
+ */
public constructor(message: BushMessage) {
this.message = message;
if (message.author.id === client.user?.id) return;
void this.handle();
}
+ /**
+ * Handles the auto moderation
+ */
private async handle() {
if (this.message.channel.type === 'DM' || !this.message.guild) return;
- if (!(await this.message.guild.hasFeature('automod'))) return;
+ const hasFeature = this.message.guild.hasFeature;
+ if (!(await hasFeature('automod'))) return;
const customAutomodPhrases = (await this.message.guild.getSetting('autoModPhases')) ?? {};
const badLinks: BadWords = {};
@@ -34,8 +52,8 @@ export class AutoMod {
const result = {
...this.checkWords(customAutomodPhrases),
- ...this.checkWords((await this.message.guild.hasFeature('excludeDefaultAutomod')) ? {} : badWords),
- ...this.checkWords((await this.message.guild.hasFeature('excludeAutomodScamLinks')) ? {} : badLinks)
+ ...this.checkWords((await hasFeature('excludeDefaultAutomod')) ? {} : badWords),
+ ...this.checkWords((await hasFeature('excludeAutomodScamLinks')) ? {} : badLinks)
};
if (Object.keys(result).length === 0) return;
@@ -44,7 +62,7 @@ export class AutoMod {
.map(([key, value]) => ({ word: key, ...value }))
.sort((a, b) => b.severity - a.severity)[0];
- if (highestOffence.severity === undefined || highestOffence.severity === null)
+ if (highestOffence.severity === undefined || highestOffence.severity === null) {
void this.message.guild.sendLogChannel('error', {
embeds: [
{
@@ -54,12 +72,19 @@ export class AutoMod {
}
]
});
- else {
+ } else {
const color = this.punish(highestOffence);
void this.log(highestOffence, color, result);
}
+
+ if (!this.punished && (await hasFeature('delScamMentions'))) void this.checkScamMentions();
}
+ /**
+ * Checks if any of the words provided are in the message
+ * @param words The words to check for
+ * @returns The blacklisted words found in the message
+ */
private checkWords(words: BadWords): BadWords {
if (Object.keys(words).length === 0) return {};
@@ -79,17 +104,81 @@ export class AutoMod {
return matchedWords;
}
+ /**
+ * If the message contains '@everyone' or '@here' and it contains a common scam phrase, it will be deleted
+ * @returns
+ */
+ private async checkScamMentions() {
+ const includes = this.message.content.toLocaleLowerCase().includes;
+ if (!includes('@everyone' || !includes('@here'))) return;
+ // It would be bad if we deleted a message that actually pinged @everyone or @here
+ if (this.message.member?.permissionsIn(this.message.channelId).has('MENTION_EVERYONE') || this.message.mentions.everyone)
+ return;
+
+ if (
+ includes('steam') ||
+ includes('www.youtube.com') ||
+ includes('youtu.be') ||
+ includes('nitro') ||
+ includes('1 month') ||
+ includes('3 months') ||
+ includes('personalize your profile') ||
+ includes('even more') ||
+ includes('xbox and discord') ||
+ includes('left over') ||
+ includes('check this lol') ||
+ includes('airdrop')
+ ) {
+ const color = this.punish({ severity: Severity.TEMP_MUTE, reason: 'everyone mention and scam phrase' } as HighestOffence);
+ void this.message.guild!.sendLogChannel('automod', {
+ embeds: [
+ new MessageEmbed()
+ .setTitle(`[Severity ${Severity.TEMP_MUTE}] Mention Scam Deleted`)
+ .setDescription(
+ `**User:** ${this.message.author} (${this.message.author.tag})\n**Sent From**: <#${this.message.channel.id}> [Jump to context](${this.message.url})`
+ )
+ .addField('Message Content', `${await util.codeblock(this.message.content, 1024)}`)
+ .setColor(color)
+ .setTimestamp()
+ ],
+ components:
+ Severity.TEMP_MUTE >= 2
+ ? [
+ new MessageActionRow().addComponents(
+ new MessageButton()
+ .setStyle('DANGER')
+ .setLabel('Ban User')
+ .setCustomId(`automod;ban;${this.message.author.id};everyone mention and scam phrase`)
+ )
+ ]
+ : undefined
+ });
+ }
+ }
+
+ /**
+ * Format a string according to the word options
+ * @param string The string to format
+ * @param wordOptions The word options to format with
+ * @returns The formatted string
+ */
private format(string: string, wordOptions: BadWordDetails) {
const temp = wordOptions.ignoreCapitalization ? string.toLowerCase() : string;
return wordOptions.ignoreSpaces ? temp.replace(/ /g, '') : temp;
}
- private punish(highestOffence: BadWordDetails & { word: string }) {
+ /**
+ * Punishes the user based on the severity of the offence
+ * @param highestOffence The highest offence to punish the user for
+ * @returns The color of the embed that the log should, based on the severity of the offence
+ */
+ private punish(highestOffence: HighestOffence) {
let color;
switch (highestOffence.severity) {
case Severity.DELETE: {
color = util.colors.lightGray;
void this.message.delete().catch((e) => deleteError.bind(this, e));
+ this.punished = true;
break;
}
case Severity.WARN: {
@@ -99,6 +188,7 @@ export class AutoMod {
moderator: this.message.guild!.me!,
reason: `[AutoMod] ${highestOffence.reason}`
});
+ this.punished = true;
break;
}
case Severity.TEMP_MUTE: {
@@ -109,6 +199,7 @@ export class AutoMod {
reason: `[AutoMod] ${highestOffence.reason}`,
duration: 900_000 // 15 minutes
});
+ this.punished = true;
break;
}
case Severity.PERM_MUTE: {
@@ -119,6 +210,7 @@ export class AutoMod {
reason: `[AutoMod] ${highestOffence.reason}`,
duration: 0 // permanent
});
+ this.punished = true;
break;
}
default: {
@@ -142,7 +234,13 @@ export class AutoMod {
}
}
- private async log(highestOffence: BadWordDetails & { word: string }, color: `#${string}`, offences: BadWords) {
+ /**
+ * Log an automod infraction to the guild's specified automod log channel
+ * @param highestOffence The highest severity word found in the message
+ * @param color The color that the log embed should be (based on the severity)
+ * @param offences The other offences that were also matched in the message
+ */
+ private async log(highestOffence: HighestOffence, color: `#${string}`, offences: BadWords) {
void client.console.info(
'autoMod',
`Severity <<${highestOffence.severity}>> action performed on <<${this.message.author.tag}>> (<<${
@@ -150,7 +248,7 @@ export class AutoMod {
}>>) in <<#${(this.message.channel as TextChannel).name}>> in <<${this.message.guild!.name}>>`
);
- return await this.message.guild!.sendLogChannel('automod', {
+ await this.message.guild!.sendLogChannel('automod', {
embeds: [
new MessageEmbed()
.setTitle(`[Severity ${highestOffence.severity}] Automod Action Performed`)
@@ -179,6 +277,10 @@ export class AutoMod {
});
}
+ /**
+ * Handles the ban button in the automod log.
+ * @param interaction The button interaction.
+ */
public static async handleInteraction(interaction: BushButtonInteraction) {
if (!interaction.memberPermissions?.has('BAN_MEMBERS'))
return interaction.reply({
@@ -228,25 +330,74 @@ export class AutoMod {
}
}
+/**
+ * The severity of the blacklisted word
+ */
export const enum Severity {
- /** Delete message */
+ /**
+ * Delete message
+ */
DELETE,
- /** Delete message and warn user */
+
+ /**
+ * Delete message and warn user
+ */
WARN,
- /** Delete message and mute user for 15 minutes */
+
+ /**
+ * Delete message and mute user for 15 minutes
+ */
TEMP_MUTE,
- /** Delete message and mute user permanently */
+
+ /**
+ * Delete message and mute user permanently
+ */
PERM_MUTE
}
+/**
+ * Details about a blacklisted word
+ */
interface BadWordDetails {
+ /**
+ * The severity of the word
+ */
severity: Severity;
+
+ /**
+ * Whether or not to ignore spaces when checking for the word
+ */
ignoreSpaces: boolean;
+
+ /**
+ * Whether or not to ignore case when checking for the word
+ */
ignoreCapitalization: boolean;
+
+ /**
+ * The reason that this word is blacklisted (used for the punishment reason)
+ */
reason: string;
+
+ /**
+ * Whether or not the word is regex
+ */
regex: boolean;
}
+interface HighestOffence extends BadWordDetails {
+ /**
+ * The word that is blacklisted
+ */
+ word: string;
+}
+
+/**
+ * Blacklisted words mapped to their details
+ */
export interface BadWords {
+ /**
+ * The blacklisted word
+ */
[key: string]: BadWordDetails;
}
diff --git a/src/lib/common/ButtonPaginator.ts b/src/lib/common/ButtonPaginator.ts
index b8ae249..983eb56 100644
--- a/src/lib/common/ButtonPaginator.ts
+++ b/src/lib/common/ButtonPaginator.ts
@@ -18,11 +18,11 @@ export class ButtonPaginator {
/**
* Sends multiple embeds with controls to switch between them
- * @param message - The message to respond to
- * @param embeds - The embeds to switch between
- * @param text - The text send with the embeds (optional)
- * @param deleteOnExit - Whether to delete the message when the exit button is clicked (defaults to true)
- * @param startOn - The page to start from (**not** the index)
+ * @param message The message to respond to
+ * @param embeds The embeds to switch between
+ * @param text The text send with the embeds (optional)
+ * @param deleteOnExit Whether to delete the message when the exit button is clicked (defaults to true)
+ * @param startOn The page to start from (**not** the index)
*/
public static async send(
message: BushMessage | BushSlashMessage,
@@ -37,6 +37,9 @@ export class ButtonPaginator {
return await new ButtonPaginator(message, embeds, text, deleteOnExit, startOn).send();
}
+ /**
+ * The number of pages in the paginator
+ */
protected get numPages(): number {
return this.embeds.length;
}
diff --git a/src/lib/extensions/discord.js/BushMessage.ts b/src/lib/extensions/discord.js/BushMessage.ts
index c722f3d..9f6d422 100644
--- a/src/lib/extensions/discord.js/BushMessage.ts
+++ b/src/lib/extensions/discord.js/BushMessage.ts
@@ -18,7 +18,7 @@ export type PartialBushMessage = Partialize<
export class BushMessage<Cached extends boolean = boolean> extends Message<Cached> {
public declare readonly client: BushClient;
public declare util: BushCommandUtil<BushMessage<true>>;
- public declare readonly guild: BushGuild | null;
+ public declare readonly guild: If<Cached, BushGuild>;
public declare readonly member: BushGuildMember | null;
public declare author: BushUser;
public declare readonly channel: If<Cached, BushGuildTextBasedChannel, BushTextBasedChannels>;
diff --git a/src/lib/models/Guild.ts b/src/lib/models/Guild.ts
index 02f487b..50113bf 100644
--- a/src/lib/models/Guild.ts
+++ b/src/lib/models/Guild.ts
@@ -280,6 +280,10 @@ export const guildFeaturesObj = asGuildFeature({
name: 'Exclude Automod Scam Links',
description: 'Opt out of having automod delete scam links.'
},
+ delScamMentions: {
+ name: 'Delete Scam Mentions',
+ description: 'Deletes messages with @everyone and @here mentions that have common scam phrases.'
+ },
autoPublish: {
name: 'Auto Publish',
description: 'Publishes messages in configured announcement channels.'