aboutsummaryrefslogtreecommitdiff
path: root/src/lib/extensions
diff options
context:
space:
mode:
authorIRONM00N <64110067+IRONM00N@users.noreply.github.com>2021-10-12 20:27:37 -0400
committerIRONM00N <64110067+IRONM00N@users.noreply.github.com>2021-10-12 20:27:37 -0400
commitba2d7b7db0a627234ed08de9d6bec8cb675404a7 (patch)
tree9ade9ed549b52eac3f2966a5cee5478267eca7c4 /src/lib/extensions
parentcac6abf3efd563b83f8f0ce70ce4bcfa5ada1a27 (diff)
downloadtanzanite-ba2d7b7db0a627234ed08de9d6bec8cb675404a7.tar.gz
tanzanite-ba2d7b7db0a627234ed08de9d6bec8cb675404a7.tar.bz2
tanzanite-ba2d7b7db0a627234ed08de9d6bec8cb675404a7.zip
revamp automod, refactoring, fixes
Diffstat (limited to 'src/lib/extensions')
-rw-r--r--src/lib/extensions/discord-akairo/BushClientUtil.ts361
-rw-r--r--src/lib/extensions/discord.js/BushGuild.ts22
-rw-r--r--src/lib/extensions/discord.js/BushGuildMember.ts25
3 files changed, 123 insertions, 285 deletions
diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts
index a50cd61..448eaf3 100644
--- a/src/lib/extensions/discord-akairo/BushClientUtil.ts
+++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts
@@ -3,16 +3,10 @@ import {
BushCache,
BushClient,
BushConstants,
- BushGuildMember,
- BushGuildMemberResolvable,
- BushGuildResolvable,
BushMessage,
BushSlashMessage,
BushUser,
Global,
- Guild,
- ModLog,
- ModLogType,
Pronoun,
PronounCode
} from '@lib';
@@ -54,7 +48,6 @@ import _ from 'lodash';
import moment from 'moment';
import { inspect, InspectOptions, promisify } from 'util';
import CommandErrorListener from '../../../listeners/commands/commandError';
-import { ActivePunishment, ActivePunishmentType } from '../../models/ActivePunishment';
import { BushNewsChannel } from '../discord.js/BushNewsChannel';
import { BushTextChannel } from '../discord.js/BushTextChannel';
import { BushSlashEditMessageType, BushSlashSendMessageType, BushUserResolvable } from './BushClient';
@@ -1079,174 +1072,6 @@ export class BushClientUtil extends ClientUtil {
return { duration, contentWithoutTime };
}
- /**
- * Checks if a moderator can perform a moderation action on another user.
- * @param moderator - The person trying to perform the action.
- * @param victim - The person getting punished.
- * @param type - The type of punishment - used to format the response.
- * @param checkModerator - Whether or not to check if the victim is a moderator.
- */
- public async moderationPermissionCheck(
- moderator: BushGuildMember,
- victim: BushGuildMember,
- type: 'mute' | 'unmute' | 'warn' | 'kick' | 'ban' | 'unban' | 'add a punishment role to' | 'remove a punishment role from',
- checkModerator = true,
- force = false
- ): Promise<true | string> {
- if (force) return true;
-
- // If the victim is not in the guild anymore it will be undefined
- if ((!victim || !victim.guild) && !['ban', 'unban'].includes(type)) return true;
-
- if (moderator.guild.id !== victim.guild.id) {
- throw new Error('moderator and victim not in same guild');
- }
-
- const isOwner = moderator.guild.ownerId === moderator.id;
- if (moderator.id === victim.id && !type.startsWith('un')) {
- return `${util.emojis.error} You cannot ${type} yourself.`;
- }
- if (
- moderator.roles.highest.position <= victim.roles.highest.position &&
- !isOwner &&
- !(type.startsWith('un') && moderator.id === victim.id)
- ) {
- return `${util.emojis.error} You cannot ${type} **${victim.user.tag}** because they have higher or equal role hierarchy as you do.`;
- }
- if (
- victim.roles.highest.position >= victim.guild.me!.roles.highest.position &&
- !(type.startsWith('un') && moderator.id === victim.id)
- ) {
- return `${util.emojis.error} You cannot ${type} **${victim.user.tag}** because they have higher or equal role hierarchy as I do.`;
- }
- if (checkModerator && victim.permissions.has('MANAGE_MESSAGES') && !(type.startsWith('un') && moderator.id === victim.id)) {
- if (await moderator.guild.hasFeature('modsCanPunishMods')) {
- return true;
- } else {
- return `${util.emojis.error} You cannot ${type} **${victim.user.tag}** because they are a moderator.`;
- }
- }
- return true;
- }
-
- public async createModLogEntry(
- options: {
- type: ModLogType;
- user: BushGuildMemberResolvable;
- moderator: BushGuildMemberResolvable;
- reason: string | undefined | null;
- duration?: number;
- guild: BushGuildResolvable;
- pseudo?: boolean;
- },
- getCaseNumber = false
- ): Promise<{ log: ModLog | null; caseNum: number | null }> {
- const user = (await util.resolveNonCachedUser(options.user))!.id;
- const moderator = (await util.resolveNonCachedUser(options.moderator))!.id;
- const guild = client.guilds.resolveId(options.guild)!;
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- const duration = options.duration || undefined;
-
- // If guild does not exist create it so the modlog can reference a guild.
- await Guild.findOrCreate({
- where: {
- id: guild
- },
- defaults: {
- id: guild
- }
- });
-
- const modLogEntry = ModLog.build({
- type: options.type,
- user,
- moderator,
- reason: options.reason,
- duration: duration,
- guild,
- pseudo: options.pseudo ?? false
- });
- const saveResult: ModLog | null = await modLogEntry.save().catch(async (e) => {
- await util.handleError('createModLogEntry', e);
- return null;
- });
-
- if (!getCaseNumber) return { log: saveResult, caseNum: null };
-
- const caseNum = (await ModLog.findAll({ where: { type: options.type, user: user, guild: guild, hidden: 'false' } }))
- ?.length;
- return { log: saveResult, caseNum };
- }
-
- public async createPunishmentEntry(options: {
- type: 'mute' | 'ban' | 'role' | 'block';
- user: BushGuildMemberResolvable;
- duration: number | undefined;
- guild: BushGuildResolvable;
- modlog: string;
- extraInfo?: Snowflake;
- }): Promise<ActivePunishment | null> {
- const expires = options.duration ? new Date(+new Date() + options.duration ?? 0) : undefined;
- const user = (await util.resolveNonCachedUser(options.user))!.id;
- const guild = client.guilds.resolveId(options.guild)!;
- const type = this.#findTypeEnum(options.type)!;
-
- const entry = ActivePunishment.build(
- options.extraInfo
- ? { user, type, guild, expires, modlog: options.modlog, extraInfo: options.extraInfo }
- : { user, type, guild, expires, modlog: options.modlog }
- );
- return await entry.save().catch(async (e) => {
- await util.handleError('createPunishmentEntry', e);
- return null;
- });
- }
-
- public async removePunishmentEntry(options: {
- type: 'mute' | 'ban' | 'role' | 'block';
- user: BushGuildMemberResolvable;
- guild: BushGuildResolvable;
- extraInfo?: Snowflake;
- }): Promise<boolean> {
- const user = await util.resolveNonCachedUser(options.user);
- const guild = client.guilds.resolveId(options.guild);
- const type = this.#findTypeEnum(options.type);
-
- if (!user || !guild) return false;
-
- let success = true;
-
- const entries = await ActivePunishment.findAll({
- // finding all cases of a certain type incase there were duplicates or something
- where: options.extraInfo
- ? { user: user.id, guild: guild, type, extraInfo: options.extraInfo }
- : { user: user.id, guild: guild, type }
- }).catch(async (e) => {
- await util.handleError('removePunishmentEntry', e);
- success = false;
- });
- if (entries) {
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
- entries.forEach(async (entry) => {
- await entry.destroy().catch(async (e) => {
- await util.handleError('removePunishmentEntry', e);
- });
- success = false;
- });
- }
- return success;
- }
-
- #findTypeEnum(type: 'mute' | 'ban' | 'role' | 'block') {
- const typeMap = {
- ['mute']: ActivePunishmentType.MUTE,
- ['ban']: ActivePunishmentType.BAN,
- ['role']: ActivePunishmentType.ROLE,
- ['block']: ActivePunishmentType.BLOCK
- };
- return typeMap[type];
- }
-
public humanizeDuration(duration: number, largest?: number): string {
if (largest) return humanizeDuration(duration, { language: 'en', maxDecimalPoints: 2, largest });
else return humanizeDuration(duration, { language: 'en', maxDecimalPoints: 2 });
@@ -1315,6 +1140,99 @@ export class BushClientUtil extends ClientUtil {
return string.charAt(0)?.toUpperCase() + string.slice(1);
}
+ /**
+ * Wait an amount in seconds.
+ */
+ public async sleep(s: number): Promise<unknown> {
+ return new Promise((resolve) => setTimeout(resolve, s * 1000));
+ }
+
+ public async handleError(context: string, error: Error) {
+ await client.console.error(_.camelCase(context), `An error occurred:\n${error?.stack ?? (error as any)}`, false);
+ await client.console.channelError({
+ embeds: [await CommandErrorListener.generateErrorEmbed({ type: 'unhandledRejection', error: error, context })]
+ });
+ }
+
+ public async resolveNonCachedUser(user: UserResolvable | undefined | null): Promise<BushUser | undefined> {
+ if (!user) return undefined;
+ const id =
+ user instanceof User || user instanceof GuildMember || user instanceof ThreadMember
+ ? user.id
+ : user instanceof Message
+ ? user.author.id
+ : typeof user === 'string'
+ ? user
+ : undefined;
+ if (!id) return undefined;
+ else return await client.users.fetch(id).catch(() => undefined);
+ }
+
+ public async getPronounsOf(user: User | Snowflake): Promise<Pronoun | undefined> {
+ const _user = await this.resolveNonCachedUser(user);
+ if (!_user) throw new Error(`Cannot find user ${user}`);
+ const apiRes = (await got
+ .get(`https://pronoundb.org/api/v1/lookup?platform=discord&id=${_user.id}`)
+ .json()
+ .catch(() => undefined)) as { pronouns: PronounCode } | undefined;
+
+ if (!apiRes) return undefined;
+ if (!apiRes.pronouns) throw new Error('apiRes.pronouns is undefined');
+
+ return client.constants.pronounMapping[apiRes.pronouns];
+ }
+
+ // modified from https://stackoverflow.com/questions/31054910/get-functions-methods-of-a-class
+ // answer by Bruno Grieder
+ public getMethods(_obj: any): string {
+ let props: string[] = [];
+ let obj: any = new Object(_obj);
+
+ do {
+ const l = Object.getOwnPropertyNames(obj)
+ .concat(Object.getOwnPropertySymbols(obj).map((s) => s.toString()))
+ .sort()
+ .filter(
+ (p, i, arr) =>
+ typeof Object.getOwnPropertyDescriptor(obj, p)?.['get'] !== 'function' && // ignore getters
+ typeof Object.getOwnPropertyDescriptor(obj, p)?.['set'] !== 'function' && // ignore setters
+ typeof obj[p] === 'function' && //only the methods
+ p !== 'constructor' && //not the constructor
+ (i == 0 || p !== arr[i - 1]) && //not overriding in this prototype
+ props.indexOf(p) === -1 //not overridden in a child
+ );
+
+ const reg = /\(([\s\S]*?)\)/;
+ props = props.concat(
+ l.map(
+ (p) =>
+ `${obj[p] && obj[p][Symbol.toStringTag] === 'AsyncFunction' ? 'async ' : ''}function ${p}(${
+ reg.exec(obj[p].toString())?.[1]
+ ? reg
+ .exec(obj[p].toString())?.[1]
+ .split(', ')
+ .map((arg) => arg.split('=')[0].trim())
+ .join(', ')
+ : ''
+ });`
+ )
+ );
+ } while (
+ (obj = Object.getPrototypeOf(obj)) && //walk-up the prototype chain
+ Object.getPrototypeOf(obj) //not the the Object prototype methods (hasOwnProperty, etc...)
+ );
+
+ return props.join('\n');
+ }
+
+ /**
+ * Removes all characters in a string that are either control characters or change the direction of text etc.
+ */
+ public sanitizeWtlAndControl(str: string) {
+ // eslint-disable-next-line no-control-regex
+ return str.replace(/[\u0000-\u001F\u007F-\u009F\u200B]/g, '');
+ }
+
get arg() {
return class Arg {
/**
@@ -1435,99 +1353,6 @@ export class BushClientUtil extends ClientUtil {
}
/**
- * Wait an amount in seconds.
- */
- public async sleep(s: number): Promise<unknown> {
- return new Promise((resolve) => setTimeout(resolve, s * 1000));
- }
-
- public async handleError(context: string, error: Error) {
- await client.console.error(_.camelCase(context), `An error occurred:\n${error?.stack ?? (error as any)}`, false);
- await client.console.channelError({
- embeds: [await CommandErrorListener.generateErrorEmbed({ type: 'unhandledRejection', error: error, context })]
- });
- }
-
- public async resolveNonCachedUser(user: UserResolvable | undefined | null): Promise<BushUser | undefined> {
- if (!user) return undefined;
- const id =
- user instanceof User || user instanceof GuildMember || user instanceof ThreadMember
- ? user.id
- : user instanceof Message
- ? user.author.id
- : typeof user === 'string'
- ? user
- : undefined;
- if (!id) return undefined;
- else return await client.users.fetch(id).catch(() => undefined);
- }
-
- public async getPronounsOf(user: User | Snowflake): Promise<Pronoun | undefined> {
- const _user = await this.resolveNonCachedUser(user);
- if (!_user) throw new Error(`Cannot find user ${user}`);
- const apiRes = (await got
- .get(`https://pronoundb.org/api/v1/lookup?platform=discord&id=${_user.id}`)
- .json()
- .catch(() => undefined)) as { pronouns: PronounCode } | undefined;
-
- if (!apiRes) return undefined;
- if (!apiRes.pronouns) throw new Error('apiRes.pronouns is undefined');
-
- return client.constants.pronounMapping[apiRes.pronouns];
- }
-
- // modified from https://stackoverflow.com/questions/31054910/get-functions-methods-of-a-class
- // answer by Bruno Grieder
- public getMethods(_obj: any): string {
- let props: string[] = [];
- let obj: any = new Object(_obj);
-
- do {
- const l = Object.getOwnPropertyNames(obj)
- .concat(Object.getOwnPropertySymbols(obj).map((s) => s.toString()))
- .sort()
- .filter(
- (p, i, arr) =>
- typeof Object.getOwnPropertyDescriptor(obj, p)?.['get'] !== 'function' && // ignore getters
- typeof Object.getOwnPropertyDescriptor(obj, p)?.['set'] !== 'function' && // ignore setters
- typeof obj[p] === 'function' && //only the methods
- p !== 'constructor' && //not the constructor
- (i == 0 || p !== arr[i - 1]) && //not overriding in this prototype
- props.indexOf(p) === -1 //not overridden in a child
- );
-
- const reg = /\(([\s\S]*?)\)/;
- props = props.concat(
- l.map(
- (p) =>
- `${obj[p] && obj[p][Symbol.toStringTag] === 'AsyncFunction' ? 'async ' : ''}function ${p}(${
- reg.exec(obj[p].toString())?.[1]
- ? reg
- .exec(obj[p].toString())?.[1]
- .split(', ')
- .map((arg) => arg.split('=')[0].trim())
- .join(', ')
- : ''
- });`
- )
- );
- } while (
- (obj = Object.getPrototypeOf(obj)) && //walk-up the prototype chain
- Object.getPrototypeOf(obj) //not the the Object prototype methods (hasOwnProperty, etc...)
- );
-
- return props.join('\n');
- }
-
- /**
- * Removes all characters in a string that are either control characters or change the direction of text etc.
- */
- public sanitizeWtlAndControl(str: string) {
- // eslint-disable-next-line no-control-regex
- return str.replace(/[\u0000-\u001F\u007F-\u009F\u200B]/g, '');
- }
-
- /**
* Discord.js's Util class
*/
get discord() {
diff --git a/src/lib/extensions/discord.js/BushGuild.ts b/src/lib/extensions/discord.js/BushGuild.ts
index 256b9dc..3a2ae51 100644
--- a/src/lib/extensions/discord.js/BushGuild.ts
+++ b/src/lib/extensions/discord.js/BushGuild.ts
@@ -1,5 +1,6 @@
-import { Guild, UserResolvable } from 'discord.js';
+import { Guild, MessageOptions, UserResolvable } from 'discord.js';
import { RawGuildData } from 'discord.js/typings/rawDataTypes';
+import { Moderation } from '../../common/moderation';
import { Guild as GuildDB, GuildFeatures, GuildLogType, GuildModel } from '../../models/Guild';
import { ModLogType } from '../../models/ModLog';
import { BushClient, BushUserResolvable } from '../discord-akairo/BushClient';
@@ -96,7 +97,7 @@ export class BushGuild extends Guild {
if (!banSuccess) return 'error banning';
// add modlog entry
- const { log: modlog } = await util.createModLogEntry({
+ const { log: modlog } = await Moderation.createModLogEntry({
type: options.duration ? ModLogType.TEMP_BAN : ModLogType.PERM_BAN,
user: user,
moderator: moderator.id,
@@ -108,7 +109,7 @@ export class BushGuild extends Guild {
caseID = modlog.id;
// add punishment entry so they can be unbanned later
- const punishmentEntrySuccess = await util.createPunishmentEntry({
+ const punishmentEntrySuccess = await Moderation.createPunishmentEntry({
type: 'ban',
user: user,
guild: this,
@@ -161,7 +162,7 @@ export class BushGuild extends Guild {
if (!unbanSuccess) return 'error unbanning';
// add modlog entry
- const { log: modlog } = await util.createModLogEntry({
+ const { log: modlog } = await Moderation.createModLogEntry({
type: ModLogType.UNBAN,
user: user.id,
moderator: moderator.id,
@@ -172,7 +173,7 @@ export class BushGuild extends Guild {
caseID = modlog.id;
// remove punishment entry
- const removePunishmentEntrySuccess = await util.removePunishmentEntry({
+ const removePunishmentEntrySuccess = await Moderation.removePunishmentEntry({
type: 'ban',
user: user.id,
guild: this
@@ -192,4 +193,15 @@ export class BushGuild extends Guild {
client.emit('bushUnban', user, moderator, this, options.reason ?? undefined, caseID!, dmSuccessEvent!);
return ret;
}
+
+ /**
+ * Sends a message to the guild's specified logging channel.
+ */
+ public async sendLogChannel(logType: GuildLogType, message: MessageOptions) {
+ const logChannel = await this.getLogChannel(logType);
+ if (!logChannel || logChannel.type !== 'GUILD_TEXT') return;
+ if (!logChannel.permissionsFor(this.me!.id)?.has(['VIEW_CHANNEL', 'SEND_MESSAGES', 'EMBED_LINKS'])) return;
+
+ return await logChannel.send(message).catch(() => null);
+ }
}
diff --git a/src/lib/extensions/discord.js/BushGuildMember.ts b/src/lib/extensions/discord.js/BushGuildMember.ts
index 8e855f7..b4c136c 100644
--- a/src/lib/extensions/discord.js/BushGuildMember.ts
+++ b/src/lib/extensions/discord.js/BushGuildMember.ts
@@ -1,5 +1,6 @@
import { GuildMember, MessageEmbed, Partialize, Role } from 'discord.js';
import { RawGuildMemberData } from 'discord.js/typings/rawDataTypes';
+import { Moderation } from '../../common/moderation';
import { ModLogType } from '../../models/ModLog';
import { BushClient } from '../discord-akairo/BushClient';
import { BushGuild } from './BushGuild';
@@ -111,7 +112,7 @@ export class BushGuildMember extends GuildMember {
const ret = await (async () => {
// add modlog entry
- const result = await util.createModLogEntry(
+ const result = await Moderation.createModLogEntry(
{
type: ModLogType.WARN,
user: this,
@@ -145,7 +146,7 @@ export class BushGuildMember extends GuildMember {
const ret = await (async () => {
if (options.addToModlog || options.duration) {
- const { log: modlog } = await util.createModLogEntry({
+ const { log: modlog } = await Moderation.createModLogEntry({
type: options.duration ? ModLogType.TEMP_PUNISHMENT_ROLE : ModLogType.PERM_PUNISHMENT_ROLE,
guild: this.guild,
moderator: moderator.id,
@@ -158,7 +159,7 @@ export class BushGuildMember extends GuildMember {
caseID = modlog.id;
if (options.addToModlog || options.duration) {
- const punishmentEntrySuccess = await util.createPunishmentEntry({
+ const punishmentEntrySuccess = await Moderation.createPunishmentEntry({
type: 'role',
user: this,
guild: this.guild,
@@ -198,7 +199,7 @@ export class BushGuildMember extends GuildMember {
const ret = await (async () => {
if (options.addToModlog) {
- const { log: modlog } = await util.createModLogEntry({
+ const { log: modlog } = await Moderation.createModLogEntry({
type: ModLogType.REMOVE_PUNISHMENT_ROLE,
guild: this.guild,
moderator: moderator.id,
@@ -209,7 +210,7 @@ export class BushGuildMember extends GuildMember {
if (!modlog) return 'error creating modlog entry';
caseID = modlog.id;
- const punishmentEntrySuccess = await util.removePunishmentEntry({
+ const punishmentEntrySuccess = await Moderation.removePunishmentEntry({
type: 'role',
user: this,
guild: this.guild,
@@ -281,7 +282,7 @@ export class BushGuildMember extends GuildMember {
if (!muteSuccess) return 'error giving mute role';
// add modlog entry
- const { log: modlog } = await util.createModLogEntry({
+ const { log: modlog } = await Moderation.createModLogEntry({
type: options.duration ? ModLogType.TEMP_MUTE : ModLogType.PERM_MUTE,
user: this,
moderator: moderator.id,
@@ -294,7 +295,7 @@ export class BushGuildMember extends GuildMember {
caseID = modlog.id;
// add punishment entry so they can be unmuted later
- const punishmentEntrySuccess = await util.createPunishmentEntry({
+ const punishmentEntrySuccess = await Moderation.createPunishmentEntry({
type: 'mute',
user: this,
guild: this.guild,
@@ -351,7 +352,7 @@ export class BushGuildMember extends GuildMember {
if (!muteSuccess) return 'error removing mute role';
//remove modlog entry
- const { log: modlog } = await util.createModLogEntry({
+ const { log: modlog } = await Moderation.createModLogEntry({
type: ModLogType.UNMUTE,
user: this,
moderator: moderator.id,
@@ -363,7 +364,7 @@ export class BushGuildMember extends GuildMember {
caseID = modlog.id;
// remove mute entry
- const removePunishmentEntrySuccess = await util.removePunishmentEntry({
+ const removePunishmentEntrySuccess = await Moderation.removePunishmentEntry({
type: 'mute',
user: this,
guild: this.guild
@@ -402,7 +403,7 @@ export class BushGuildMember extends GuildMember {
if (!kickSuccess) return 'error kicking';
// add modlog entry
- const { log: modlog } = await util.createModLogEntry({
+ const { log: modlog } = await Moderation.createModLogEntry({
type: ModLogType.KICK,
user: this,
moderator: moderator.id,
@@ -439,7 +440,7 @@ export class BushGuildMember extends GuildMember {
if (!banSuccess) return 'error banning';
// add modlog entry
- const { log: modlog } = await util.createModLogEntry({
+ const { log: modlog } = await Moderation.createModLogEntry({
type: options.duration ? ModLogType.TEMP_BAN : ModLogType.PERM_BAN,
user: this,
moderator: moderator.id,
@@ -451,7 +452,7 @@ export class BushGuildMember extends GuildMember {
caseID = modlog.id;
// add punishment entry so they can be unbanned later
- const punishmentEntrySuccess = await util.createPunishmentEntry({
+ const punishmentEntrySuccess = await Moderation.createPunishmentEntry({
type: 'ban',
user: this,
guild: this.guild,