diff options
author | IRONM00N <64110067+IRONM00N@users.noreply.github.com> | 2021-07-04 15:25:32 -0400 |
---|---|---|
committer | IRONM00N <64110067+IRONM00N@users.noreply.github.com> | 2021-07-04 15:25:32 -0400 |
commit | cf564dbb6435886f97e2e9870363144386af368d (patch) | |
tree | d535cd73f24a145ca9d7ce1a0e1174cba0d38b31 /src | |
parent | 34f0d1d3ff3e2a90193c9a4d4de29d8335160d6a (diff) | |
download | tanzanite-cf564dbb6435886f97e2e9870363144386af368d.tar.gz tanzanite-cf564dbb6435886f97e2e9870363144386af368d.tar.bz2 tanzanite-cf564dbb6435886f97e2e9870363144386af368d.zip |
mute command
Diffstat (limited to 'src')
-rw-r--r-- | src/commands/dev/eval.ts | 2 | ||||
-rw-r--r-- | src/commands/dev/reload.ts | 6 | ||||
-rw-r--r-- | src/commands/dev/testDuration.ts | 54 | ||||
-rw-r--r-- | src/commands/moderation/kick.ts | 2 | ||||
-rw-r--r-- | src/commands/moderation/mute.ts | 171 | ||||
-rw-r--r-- | src/lib/extensions/discord-akairo/BushClient.ts | 14 | ||||
-rw-r--r-- | src/lib/extensions/discord-akairo/BushClientUtil.ts | 106 | ||||
-rw-r--r-- | src/lib/extensions/discord.js/BushGuildMember.ts | 62 | ||||
-rw-r--r-- | src/lib/models/Ban.ts | 10 | ||||
-rw-r--r-- | src/lib/models/Guild.ts | 8 | ||||
-rw-r--r-- | src/lib/models/ModLog.ts | 11 | ||||
-rw-r--r-- | src/lib/models/Mute.ts | 10 | ||||
-rw-r--r-- | src/lib/models/PunishmentRole.ts | 10 | ||||
-rw-r--r-- | src/lib/utils/BushConstants.ts | 24 | ||||
-rw-r--r-- | src/listeners/commands/commandError.ts | 17 | ||||
-rw-r--r-- | src/listeners/commands/commandMissingPermissions.ts | 12 | ||||
-rw-r--r-- | src/listeners/commands/slashCommandError.ts | 17 |
17 files changed, 338 insertions, 198 deletions
diff --git a/src/commands/dev/eval.ts b/src/commands/dev/eval.ts index 4896945..ebf055b 100644 --- a/src/commands/dev/eval.ts +++ b/src/commands/dev/eval.ts @@ -259,7 +259,7 @@ export default class EvalCommand extends BushCommand { } else { embed.addField('📥 Input', await this.client.util.codeblock(inputJS, 1024, 'js')); } - embed.addField('📤 Output', await this.client.util.codeblock(e?.stack, 1024, 'js')); + embed.addField('📤 Output', await this.client.util.codeblock(typeof e === 'object' ? e?.stack : e, 1024, 'js')); } if (!args.silent && !message.util.isSlash) { await message.util.reply({ embeds: [embed], ephemeral: args.silent }); diff --git a/src/commands/dev/reload.ts b/src/commands/dev/reload.ts index d932816..4a69098 100644 --- a/src/commands/dev/reload.ts +++ b/src/commands/dev/reload.ts @@ -46,7 +46,11 @@ export default class ReloadCommand extends BushCommand { return message.util.send(`🔁 Successfully reloaded! (${new Date().getTime() - s.getTime()}ms)`); } catch (e) { return message.util.send( - `An error occurred while reloading:\n${await this.client.util.codeblock(e.stack, 2048 - 34, 'js')}` + `An error occurred while reloading:\n${await this.client.util.codeblock( + typeof e === 'object' ? e?.stack : e, + 2048 - 34, + 'js' + )}` ); } } diff --git a/src/commands/dev/testDuration.ts b/src/commands/dev/testDuration.ts new file mode 100644 index 0000000..bf30840 --- /dev/null +++ b/src/commands/dev/testDuration.ts @@ -0,0 +1,54 @@ +import { stripIndents } from 'common-tags'; +import { Message } from 'discord.js'; +import { BushCommand } from '../../lib/extensions/discord-akairo/BushCommand'; +import { BushSlashMessage } from '../../lib/extensions/discord-akairo/BushSlashMessage'; + +export default class TestDurationCommand extends BushCommand { + public constructor() { + super('testduration', { + aliases: ['testduration'], + category: 'dev', + description: { + content: 'Tests duration parsing.', + usage: 'testduration [reason]', + examples: ['testduration'] + }, + args: [ + { + id: 'reason', + type: 'contentWithDuration', + match: 'rest', + prompt: { + start: 'Enter text and a duration here.', + retry: '{error} Error parsing duration and text.', + optional: true + } + } + ], + slash: true, + slashOptions: [ + { + type: 'STRING', + name: 'reason', + description: 'Enter text and a duration here.', + required: false + } + ], + hidden: true, + ownerOnly: true + }); + } + + async exec( + message: Message | BushSlashMessage, + { reason }: { reason?: { duration: number; contentWithoutTime: string } } + ): Promise<unknown> { + const rawDuration = reason.duration; + const text = reason.contentWithoutTime; + const humanizedDuration = this.client.util.humanizeDuration(rawDuration); + return await message.util.reply(stripIndents` + **rawDuration:** ${rawDuration} + **text:** ${text} + **humanizedDuration:** ${humanizedDuration}`); + } +} diff --git a/src/commands/moderation/kick.ts b/src/commands/moderation/kick.ts index df538bc..f88819b 100644 --- a/src/commands/moderation/kick.ts +++ b/src/commands/moderation/kick.ts @@ -76,7 +76,7 @@ export default class KickCommand extends BushCommand { // }); // await modlogEnry.save(); // } catch (e) { - // this.client.console.error(`KickCommand`, `Error saving to database. ${e?.stack}`); + // this.client.console.error(`KickCommand`, `Error saving to database. ${typeof e === 'object' ? e?.stack : e}`); // yield `${this.client.util.emojis.error} Error saving to database. Please report this to a developer.`; // return; // } diff --git a/src/commands/moderation/mute.ts b/src/commands/moderation/mute.ts index ffad432..8ac77a7 100644 --- a/src/commands/moderation/mute.ts +++ b/src/commands/moderation/mute.ts @@ -53,136 +53,83 @@ export default class MuteCommand extends BushCommand { slash: true }); } - // async *genResponses( - // message: Message | CommandInteraction, - // user: User, - // reason?: string, - // time?: number - // ): AsyncIterable<string> { - // const duration = moment.duration(time); - // let modlogEnry: ModLog; - // let muteEntry: Mute; - // // Create guild entry so postgres doesn't get mad when I try and add a modlog entry - // await Guild.findOrCreate({ - // where: { - // id: message.guild.id - // }, - // defaults: { - // id: message.guild.id - // } - // }); - // try { - // const muteRole = (await Guild.findByPk(message.guild.id)).get('muteRole'); - // try { - // if (time) { - // modlogEnry = ModLog.build({ - // user: user.id, - // guild: message.guild.id, - // reason, - // type: ModLogType.TEMP_MUTE, - // duration: duration.asMilliseconds(), - // moderator: message instanceof CommandInteraction ? message.user.id : message.author.id - // }); - // muteEntry = Mute.build({ - // user: user.id, - // guild: message.guild.id, - // reason, - // expires: new Date(new Date().getTime() + duration.asMilliseconds()), - // modlog: modlogEnry.id - // }); - // } else { - // modlogEnry = ModLog.build({ - // user: user.id, - // guild: message.guild.id, - // reason, - // type: ModLogType.MUTE, - // moderator: message instanceof CommandInteraction ? message.user.id : message.author.id - // }); - // muteEntry = Mute.build({ - // user: user.id, - // guild: message.guild.id, - // reason, - // modlog: modlogEnry.id - // }); - // } - // await modlogEnry.save(); - // await muteEntry.save(); - // } catch (e) { - // this.client.console.error(`MuteCommand`, `Error saving to database. ${e?.stack}`); - // yield `${this.client.util.emojis.error} Error saving to database. Please report this to a developer.`; - // return; - // } - // try { - // await user.send( - // `You were muted in ${message.guild.name} ${time ? `for ${duration.humanize()}` : 'permanently'} with reason \`${ - // reason || 'No reason given' - // }\`` - // ); - // } catch (e) { - // yield `${this.client.util.emojis.warn} Unable to dm user`; - // } - // await ( - // await message.guild.members.fetch(user) - // ).roles.add( - // muteRole, - // `Muted by ${message instanceof CommandInteraction ? message.user.tag : message.author.tag} with ${ - // reason ? `reason ${reason}` : 'no reason' - // }` - // ); - // yield `${this.client.util.emojis.success} muted <@!${user.id}> ${ - // time ? `for ${duration.humanize()}` : 'permanently' - // } with reason \`${reason || 'No reason given'}\``; - // } catch { - // yield `${this.client.util.emojis.error} Error muting :/`; - // await muteEntry.destroy(); - // await modlogEnry.destroy(); - // return; - // } - // } async exec( message: BushMessage, { user, reason }: { user: BushUser; reason?: { duration: number; contentWithoutTime: string } } ): Promise<unknown> { - return message.util.reply(`${this.client.util.emojis.error} This command is not finished.`); - // this.client.console.debug(reason); - - // if (typeof time === 'string') { - // time = (await Argument.cast('duration', this.client.commandHandler.resolver, message, time)) as number; - // } - // for await (const response of this.genResponses(message, user, reason.join(' '), time)) { - // await message.util.sendNew(response); - // } - + const error = this.client.util.emojis.error; const member = message.guild.members.cache.get(user.id) as BushGuildMember; - if (!this.client.util.moderatorCanModerateUser(message.member, member)) { - return message.util.reply({ - content: `${this.client.util.emojis.error} You cannot mute **${member.user.tag}**.` - }); + const canModerateResponse = this.client.util.moderationPermissionCheck(message.member, member); + const victimBoldTag = `**${member.user.tag}**`; + switch (canModerateResponse) { + case 'moderator': + return message.util.reply(`${error} You cannot mute ${victimBoldTag} because they are a moderator.`); + case 'user hierarchy': + return message.util.reply( + `${error} You cannot mute ${victimBoldTag} because they have higher or equal role hierarchy as you do.` + ); + case 'client hierarchy': + return message.util.reply( + `${error} You cannot mute ${victimBoldTag} because they have higher or equal role hierarchy as I do.` + ); + case 'self': + return message.util.reply(`${error} You cannot mute yourself.`); } - const time = - typeof reason === 'string' - ? //@ts-ignore: you are unreachable bitch - await Argument.cast('duration', this.client.commandHandler.resolver, message, reason) - : reason.duration; + let time; + if (reason) { + time = + typeof reason === 'string' + ? await Argument.cast('duration', this.client.commandHandler.resolver, message, reason) + : reason.duration; + } const parsedReason = reason.contentWithoutTime; const response = await member.mute({ reason: parsedReason, moderator: message.author, - duration: time, - createModLogEntry: true + duration: time }); switch (response) { - case 'success': - return message.util.reply(`${this.client.util.emojis.success} Successfully muted **${member.user.tag}**.`); + case 'missing permissions': + return message.util.reply( + `${error} Could not mute ${victimBoldTag} because I am missing the \`Manage Roles\` permission.` + ); case 'no mute role': return message.util.reply( - `${this.client.util.emojis.error} Could not mute **${ - member.user.tag - }**, you must set a mute role with ${message.guild.getSetting('prefix')}.` + `${error} Could not mute ${victimBoldTag}, you must set a mute role with \`${message.guild.getSetting( + 'prefix' + )}muterole\`.` ); + case 'invalid mute role': + return message.util.reply( + `${error} Could not mute ${victimBoldTag} because the current mute role no longer exists. Please set a new mute role with \`${message.guild.getSetting( + 'prefix' + )}muterole\`.` + ); + case 'mute role not manageable': + return message.util.reply( + `${error} Could not mute ${victimBoldTag} because I cannot assign the current mute role, either change the role's position or set a new mute role with \`${message.guild.getSetting( + 'prefix' + )}muterole\`.` + ); + case 'error giving mute role': + return message.util.reply(`${error} Could not mute ${victimBoldTag}, there was an error assigning them the mute role.`); + case 'error creating modlog entry': + return message.util.reply( + `${error} While muting ${victimBoldTag}, there was an error creating a modlog entry, please report this to my developers.` + ); + case 'error creating mute entry': + return message.util.reply( + `${error} While muting ${victimBoldTag}, there was an error creating a mute entry, please report this to my developers.` + ); + case 'failed to dm': + return message.util.reply( + `${this.client.util.emojis.warn} Muted **${member.user.tag}** however I could not send them a dm.` + ); + case 'success': + return message.util.reply(`${this.client.util.emojis.success} Successfully muted **${member.user.tag}**.`); } } } diff --git a/src/lib/extensions/discord-akairo/BushClient.ts b/src/lib/extensions/discord-akairo/BushClient.ts index 6911573..ed5c90a 100644 --- a/src/lib/extensions/discord-akairo/BushClient.ts +++ b/src/lib/extensions/discord-akairo/BushClient.ts @@ -216,7 +216,11 @@ export class BushClient extends AkairoClient { loaders[loader].loadAll(); await this.logger.success('Startup', `Successfully loaded <<${loader}>>.`, false); } catch (e) { - await this.logger.error('Startup', `Unable to load loader <<${loader}>> with error:\n${e?.stack}`, false); + await this.logger.error( + 'Startup', + `Unable to load loader <<${loader}>> with error:\n${typeof e === 'object' ? e?.stack : e}`, + false + ); } } await this.dbPreInit(); @@ -237,8 +241,12 @@ export class BushClient extends AkairoClient { Models.StickyRole.initModel(this.db); await this.db.sync({ alter: true }); // Sync all tables to fix everything if updated await this.console.success('Startup', `Successfully connected to <<database>>.`, false); - } catch (error) { - await this.console.error('Startup', `Failed to connect to <<database>> with error:\n` + error?.stack, false); + } catch (e) { + await this.console.error( + 'Startup', + `Failed to connect to <<database>> with error:\n` + typeof e === 'object' ? e?.stack : e, + false + ); } } diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts index 9289598..5a22efc 100644 --- a/src/lib/extensions/discord-akairo/BushClientUtil.ts +++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts @@ -24,8 +24,9 @@ import { WebhookEditMessageOptions } from 'discord.js'; import got from 'got'; +import humanizeDuration from 'humanize-duration'; import { promisify } from 'util'; -import { Global, Guild, ModLog, ModLogType } from '../../models'; +import { Ban, Global, Guild, ModLog, ModLogType, Mute, PunishmentRole } from '../../models'; import { BushCache } from '../../utils/BushCache'; import { BushConstants } from '../../utils/BushConstants'; import { BushGuildResolvable } from '../discord.js/BushCommandInteraction'; @@ -302,7 +303,7 @@ export class BushClientUtil extends ClientUtil { }); const filter = (interaction: ButtonInteraction) => interaction.customID.startsWith('paginate_') && interaction.message == msg; - const collector = msg.createMessageComponentInteractionCollector({ filter, time: 300000 }); + const collector = msg.createMessageComponentCollector({ filter, time: 300000 }); collector.on('collect', async (interaction: MessageComponentInteraction) => { if (interaction.user.id == message.author.id || this.client.config.owners.includes(interaction.user.id)) { switch (interaction.customID) { @@ -391,7 +392,7 @@ export class BushClientUtil extends ClientUtil { updateOptions(); const msg = await message.util.reply(options as MessageOptions & { split?: false }); const filter = (interaction: ButtonInteraction) => interaction.customID == 'paginate__stop' && interaction.message == msg; - const collector = msg.createMessageComponentInteractionCollector({ filter, time: 300000 }); + const collector = msg.createMessageComponentCollector({ filter, time: 300000 }); collector.on('collect', async (interaction: MessageComponentInteraction) => { if (interaction.user.id == message.author.id || this.client.config.owners.includes(interaction.user.id)) { await interaction.deferUpdate().catch(() => undefined); @@ -530,23 +531,36 @@ export class BushClientUtil extends ClientUtil { return newArray; } - public parseDuration(content: string): { duration: number; contentWithoutTime: string } { + public parseDuration(content: string, remove = true): { duration: number; contentWithoutTime: string } { if (!content) return { duration: 0, contentWithoutTime: null }; - let duration = 0, - contentWithoutTime = content; + let duration = 0; + // Try to reduce false positives by requiring a space before the duration, this makes sure it still matches if it is + // in the beginning of the argument + let contentWithoutTime = ` ${content}`; - const regexString = Object.entries(BushConstants.TimeUnits) - .map(([name, { label }]) => String.raw`(?:(?<${name}>-?(?:\d+)?\.?\d+) *${label})?`) - .join('\\s*'); - const match = new RegExp(`^${regexString}$`, 'im').exec(content); - if (!match) return null; + for (const unit in BushConstants.TimeUnits) { + const regex = BushConstants.TimeUnits[unit].match; + const match = regex.exec(contentWithoutTime); + const value = Number(match?.groups?.[unit] || 0); + // this.client.console.debug(unit + ': ' + value); + duration += value * BushConstants.TimeUnits[unit].value; - for (const key in match.groups) { - contentWithoutTime = contentWithoutTime.replace(match.groups[key], ''); - const value = Number(match.groups[key] || 0); - duration += value * BushConstants.TimeUnits[key].value; + if (remove) contentWithoutTime = contentWithoutTime.replace(regex, ''); + // this.client.console.debug(contentWithoutTime); } + //^(?:(?<years>-?(?:\d+)?\.?\d+) *(?:years?|y))?\s*(?:(?<months>-?(?:\d+)?\.?\d+) *(?:months?|mon|mo?))?\s*(?:(?<weeks>-?(?:\d+)?\.?\d+) *(?:weeks?|w))?\s*(?:(?<days>-?(?:\d+)?\.?\d+) *(?:days?|d))?\s*(?:(?<hours>-?(?:\d+)?\.?\d+) *(?:hours?|hrs?|h))?\s*(?:(?<minutes>-?(?:\d+)?\\.?\\d+) *(?:minutes?|mins?))?\s*(?:(?<seconds>-?(?:\d+)?\\.?\d+) *(?:seconds?|secs?|s))?\s*(?:(?<milliseconds>-?(?:\d+)?\.?\d+) *(?:milliseconds?|msecs?|ms))?$ + // const regexString = Object.entries(BushConstants.TimeUnits) + // .map(([name, { label }]) => String.raw`(?: (?<${name}>-?(?:\d+)?\.?\d+) *${label})`) + // .join(' |'); + // const match = new RegExp(`^${regexString}$`, 'img').exec(' ' + content + ' '); + // if (!match) return null; + // console. + // const contentWithoutTime = content.replace(new RegExp(`^${regexString}$`, 'img'), ''); + // for (const key in match.groups) { + // const value = Number(match.groups[key] || 0); + // duration += value * BushConstants.TimeUnits[key].value; + // } return { duration, contentWithoutTime }; } @@ -555,11 +569,20 @@ export class BushClientUtil extends ClientUtil { * 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 checkModerator - Whether or not to check if the victim is a moderator. */ - public moderatorCanModerateUser(moderator: BushGuildMember, victim: BushGuildMember): boolean { - throw 'not implemented'; + public moderationPermissionCheck( + moderator: BushGuildMember, + victim: BushGuildMember, + checkModerator = true + ): true | 'user hierarchy' | 'client hierarchy' | 'moderator' | 'self' { if (moderator.guild.id !== victim.guild.id) throw 'wtf'; - if (moderator.guild.ownerID === moderator.id) return true; + const isOwner = moderator.guild.ownerID === moderator.id; + if (moderator.id === victim.id) return 'self'; + if (moderator.roles.highest.position <= victim.roles.highest.position && !isOwner) return 'user hierarchy'; + if (victim.roles.highest.position >= victim.guild.me.roles.highest.position) return 'client hierarchy'; + if (checkModerator && victim.permissions.has('MANAGE_MESSAGES')) return 'moderator'; + return true; } public async createModLogEntry(options: { @@ -569,10 +592,11 @@ export class BushClientUtil extends ClientUtil { reason: string; duration: number; guild: BushGuildResolvable; - }): Promise<void> { + }): Promise<ModLog> { const user = this.client.users.resolveID(options.user); const moderator = this.client.users.resolveID(options.moderator); const guild = this.client.guilds.resolveID(options.guild); + const duration = options.duration || null; // If guild does not exist create it so the modlog can reference a guild. await Guild.findOrCreate({ @@ -589,9 +613,49 @@ export class BushClientUtil extends ClientUtil { user, moderator, reason: options.reason, - duration: options.duration, + duration: duration, guild }); - await modLogEntry.save(); + return modLogEntry.save().catch((err) => { + this.client.console.error('createModLogEntry', err); + return null; + }); + } + + public async createPunishmentEntry(options: { + type: 'mute' | 'ban' | 'role'; + user: BushGuildMemberResolvable; + duration: number; + guild: BushGuildResolvable; + modlog: string; + }): Promise<Mute | Ban | PunishmentRole> { + let dbModel: typeof Mute | typeof Ban | typeof PunishmentRole; + switch (options.type) { + case 'mute': + dbModel = Mute; + break; + case 'ban': + dbModel = Ban; + break; + case 'role': + dbModel = PunishmentRole; + break; + default: + throw 'choose a valid punishment entry type'; + } + + const expires = options.duration ? new Date(new Date().getTime() + options.duration) : null; + const user = this.client.users.resolveID(options.user); + const guild = this.client.guilds.resolveID(options.guild); + + const entry = dbModel.build({ user, guild, expires, modlog: options.modlog }); + return await entry.save().catch((err) => { + this.client.console.error('createPunishmentEntry', err); + return null; + }); + } + + public humanizeDuration(duration: number): string { + return humanizeDuration(duration, { language: 'en', maxDecimalPoints: 2 }); } } diff --git a/src/lib/extensions/discord.js/BushGuildMember.ts b/src/lib/extensions/discord.js/BushGuildMember.ts index 59dc777..2fefcdd 100644 --- a/src/lib/extensions/discord.js/BushGuildMember.ts +++ b/src/lib/extensions/discord.js/BushGuildMember.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { GuildMember } from 'discord.js'; +import { ModLogType } from '../../models'; import { BushClient, BushUserResolvable } from '../discord-akairo/BushClient'; import { BushGuild } from './BushGuild'; import { BushUser } from './BushUser'; @@ -7,7 +8,6 @@ import { BushUser } from './BushUser'; interface BushPunishmentOptions { reason?: string; moderator: BushUserResolvable; - createModLogEntry?: boolean; } interface BushTimedPunishmentOptions extends BushPunishmentOptions { @@ -18,7 +18,16 @@ type PunishmentResponse = 'success'; type WarnResponse = PunishmentResponse; -type MuteResponse = PunishmentResponse | 'no mute role'; +type MuteResponse = + | PunishmentResponse + | 'missing permissions' + | 'no mute role' + | 'invalid mute role' + | 'mute role not manageable' + | 'error giving mute role' + | 'error creating modlog entry' + | 'error creating mute entry' + | 'failed to dm'; type UnmuteResponse = PunishmentResponse; @@ -44,7 +53,54 @@ export class BushGuildMember extends GuildMember { } public async mute(options: BushTimedPunishmentOptions): Promise<MuteResponse> { - throw 'not implemented'; + //checks + if (!this.guild.me.permissions.has('MANAGE_ROLES')) return 'missing permissions'; + const muteRoleID = await this.guild.getSetting('muteRole'); + if (!muteRoleID) return 'no mute role'; + const muteRole = this.guild.roles.cache.get(muteRoleID); + if (!muteRole) return 'invalid mute role'; + if (muteRole.position >= this.guild.me.roles.highest.position || muteRole.managed) return 'mute role not manageable'; + + //add role + const success = await this.roles.add(muteRole).catch(() => null); + if (!success) return 'error giving mute role'; + + //add modlog entry + const modlog = await this.client.util + .createModLogEntry({ + type: options.duration ? ModLogType.TEMP_MUTE : ModLogType.PERM_MUTE, + user: this, + moderator: options.moderator, + reason: options.reason, + duration: options.duration, + guild: this.guild + }) + .catch(() => null); + if (!modlog) return 'error creating modlog entry'; + + // add punishment entry so they can be unmuted later + const mute = await this.client.util + .createPunishmentEntry({ + type: 'mute', + user: this, + guild: this.guild, + duration: options.duration, + modlog: modlog.id + }) + .catch(() => null); + if (!mute) return 'error creating mute entry'; + + //dm user + const ending = this.guild.getSetting('punishmentEnding'); + const dmSuccess = await this.send({ + content: `You have been muted ${ + options.duration ? 'for ' + this.client.util.humanizeDuration(options.duration) : 'permanently' + } in **${this.guild}** for **${options.reason || 'No reason provided'}**.${ending ? `\n\n${ending}` : ''}` + }).catch(() => null); + + if (!dmSuccess) return 'failed to dm'; + + return 'success'; } public async unmute(options: BushPunishmentOptions): Promise<UnmuteResponse> { diff --git a/src/lib/models/Ban.ts b/src/lib/models/Ban.ts index f4463b8..54ca6ae 100644 --- a/src/lib/models/Ban.ts +++ b/src/lib/models/Ban.ts @@ -7,7 +7,6 @@ export interface BanModel { id: string; user: string; guild: string; - reason: string; expires: Date; modlog: string; } @@ -15,7 +14,6 @@ export interface BanModelCreationAttributes { id?: string; user: string; guild: string; - reason?: string; expires?: Date; modlog: string; } @@ -34,10 +32,6 @@ export class Ban extends BaseModel<BanModel, BanModelCreationAttributes> impleme */ guild: Snowflake; /** - * The reason they are banned (optional) - */ - reason: string | null; - /** * The date at which this ban expires and should be unbanned (optional) */ expires: Date | null; @@ -71,10 +65,6 @@ export class Ban extends BaseModel<BanModel, BanModelCreationAttributes> impleme type: DataTypes.DATE, allowNull: true }, - reason: { - type: DataTypes.STRING, - allowNull: true - }, modlog: { type: DataTypes.STRING, allowNull: false, diff --git a/src/lib/models/Guild.ts b/src/lib/models/Guild.ts index 303335b..0fc3413 100644 --- a/src/lib/models/Guild.ts +++ b/src/lib/models/Guild.ts @@ -10,11 +10,12 @@ export interface GuildModel { blacklistedChannels: Snowflake[]; welcomeChannel: Snowflake; muteRole: Snowflake; + punishmentEnding: string; } export type GuildModelCreationAttributes = Optional< GuildModel, - 'prefix' | 'autoPublishChannels' | 'blacklistedChannels' | 'welcomeChannel' | 'muteRole' + 'prefix' | 'autoPublishChannels' | 'blacklistedChannels' | 'welcomeChannel' | 'muteRole' | 'punishmentEnding' >; export class Guild extends BaseModel<GuildModel, GuildModelCreationAttributes> implements GuildModel { @@ -24,6 +25,7 @@ export class Guild extends BaseModel<GuildModel, GuildModelCreationAttributes> i blacklistedChannels: Snowflake[]; welcomeChannel: Snowflake; muteRole: Snowflake; + punishmentEnding: string; static initModel(sequelize: Sequelize, client: BushClient): void { Guild.init( @@ -64,6 +66,10 @@ export class Guild extends BaseModel<GuildModel, GuildModelCreationAttributes> i muteRole: { type: DataTypes.STRING, allowNull: true + }, + punishmentEnding: { + type: DataTypes.TEXT, + allowNull: true } }, { sequelize: sequelize } diff --git a/src/lib/models/ModLog.ts b/src/lib/models/ModLog.ts index 1d850d9..6261794 100644 --- a/src/lib/models/ModLog.ts +++ b/src/lib/models/ModLog.ts @@ -4,14 +4,17 @@ import { v4 as uuidv4 } from 'uuid'; import { BaseModel } from './BaseModel'; export enum ModLogType { - BAN = 'BAN', + PERM_BAN = 'PERM_BAN', TEMP_BAN = 'TEMP_BAN', + UNBAN = 'UNBAN', KICK = 'KICK', - MUTE = 'MUTE', + PERM_MUTE = 'PERM_MUTE', TEMP_MUTE = 'TEMP_MUTE', + UNMUTE = 'UNMUTE', WARN = 'WARN', - PUNISHMENT_ROLE = 'PUNISHMENT_ROLE', - TEMP_PUNISHMENT_ROLE = 'TEMP_PUNISHMENT_ROLE' + PERM_PUNISHMENT_ROLE = 'PERM_PUNISHMENT_ROLE', + TEMP_PUNISHMENT_ROLE = 'TEMP_PUNISHMENT_ROLE', + REMOVE_PUNISHMENT_ROLE = 'REMOVE_PUNISHMENT_ROLE' } export interface ModLogModel { diff --git a/src/lib/models/Mute.ts b/src/lib/models/Mute.ts index 273d5b1..71a32e3 100644 --- a/src/lib/models/Mute.ts +++ b/src/lib/models/Mute.ts @@ -7,7 +7,6 @@ export interface MuteModel { id: string; user: string; guild: string; - reason: string; expires: Date; modlog: string; } @@ -15,7 +14,6 @@ export interface MuteModelCreationAttributes { id?: string; user: string; guild: string; - reason?: string; expires?: Date; modlog: string; } @@ -34,10 +32,6 @@ export class Mute extends BaseModel<MuteModel, MuteModelCreationAttributes> impl */ guild: Snowflake; /** - * The reason they are muted (optional) - */ - reason: string | null; - /** * The date at which this Mute expires and should be unmuted (optional) */ expires: Date | null; @@ -71,10 +65,6 @@ export class Mute extends BaseModel<MuteModel, MuteModelCreationAttributes> impl type: DataTypes.DATE, allowNull: true }, - reason: { - type: DataTypes.STRING, - allowNull: true - }, modlog: { type: DataTypes.STRING, allowNull: false, diff --git a/src/lib/models/PunishmentRole.ts b/src/lib/models/PunishmentRole.ts index 3326dca..927cf28 100644 --- a/src/lib/models/PunishmentRole.ts +++ b/src/lib/models/PunishmentRole.ts @@ -7,7 +7,6 @@ export interface PunishmentRoleModel { id: string; user: string; guild: string; - reason: string; expires: Date; modlog: string; } @@ -15,7 +14,6 @@ export interface PunishmentRoleModelCreationAttributes { id?: string; user: string; guild: string; - reason?: string; expires?: Date; modlog: string; } @@ -37,10 +35,6 @@ export class PunishmentRole */ guild: Snowflake; /** - * The reason they received a role (optional) - */ - reason: string | null; - /** * The date at which this role expires and should be removed (optional) */ expires: Date | null; @@ -74,10 +68,6 @@ export class PunishmentRole type: DataTypes.DATE, allowNull: true }, - reason: { - type: DataTypes.STRING, - allowNull: true - }, modlog: { type: DataTypes.STRING, allowNull: false, diff --git a/src/lib/utils/BushConstants.ts b/src/lib/utils/BushConstants.ts index 0e3f6bb..7e6013d 100644 --- a/src/lib/utils/BushConstants.ts +++ b/src/lib/utils/BushConstants.ts @@ -1,36 +1,36 @@ export class BushConstants { - // Stolen from @Mzato0001 (pr to discord akairo that hasn't been merged yet) - public static TimeUnits = { + // Somewhat stolen from @Mzato0001 + public static TimeUnits: { [key: string]: { match: RegExp; value: number } } = { years: { - label: '(?:years?|y)', - value: 1000 * 60 * 60 * 24 * 365 + match: / (?:(?<years>-?(?:\d+)?\.?\d+) *(?:years?|y))/im, + value: 1000 * 60 * 60 * 24 * 365.25 //leap years }, months: { - label: '(?:months?|mon|mo?)', - value: 1000 * 60 * 60 * 24 * 30 + match: / (?:(?<months>-?(?:\d+)?\.?\d+) *(?:months?|mon|mo?))/im, + value: 1000 * 60 * 60 * 24 * 30.4375 // average of days in months including leap years }, weeks: { - label: '(?:weeks?|w)', + match: / (?:(?<weeks>-?(?:\d+)?\.?\d+) *(?:weeks?|w))/im, value: 1000 * 60 * 60 * 24 * 7 }, days: { - label: '(?:days?|d)', + match: / (?:(?<days>-?(?:\d+)?\.?\d+) *(?:days?|d))/im, value: 1000 * 60 * 60 * 24 }, hours: { - label: '(?:hours?|hrs?|h)', + match: / (?:(?<hours>-?(?:\d+)?\.?\d+) *(?:hours?|hrs?|h))/im, value: 1000 * 60 * 60 }, minutes: { - label: '(?:minutes?|mins?)', + match: / (?:(?<minutes>-?(?:\d+)?\.?\d+) *(?:minutes?|mins?))/im, value: 1000 * 60 }, seconds: { - label: '(?:seconds?|secs?|s)', + match: / (?:(?<seconds>-?(?:\d+)?\.?\d+) *(?:seconds?|secs?|s))/im, value: 1000 }, milliseconds: { - label: '(?:milliseconds?|msecs?|ms)', + match: / (?:(?<milliseconds>-?(?:\d+)?\.?\d+) *(?:milliseconds?|msecs?|ms))/im, value: 1 } }; diff --git a/src/listeners/commands/commandError.ts b/src/listeners/commands/commandError.ts index 3cb5046..20132dd 100644 --- a/src/listeners/commands/commandError.ts +++ b/src/listeners/commands/commandError.ts @@ -22,7 +22,7 @@ export default class CommandErrorListener extends BushListener { **Channel:** ${message.channel} (${message.channel?.id}) **Message:** [link](${message.url})` ) - .addField('Error', await this.client.util.codeblock(`${error?.stack}`, 1024, 'js')) + .addField('Error', await this.client.util.codeblock(`${typeof error === 'object' ? error?.stack : error}`, 1024, 'js')) .setColor(this.client.util.colors.error) .setTimestamp(); @@ -41,7 +41,10 @@ export default class CommandErrorListener extends BushListener { ); await message.util.send({ embeds: [errorUserEmbed] }).catch((e) => { const channel = message.channel.type === 'dm' ? message.channel.recipient.tag : message.channel.name; - this.client.console.warn('CommandError', `Failed to send user error embed in <<${channel}>>:\n` + e?.stack); + this.client.console.warn( + 'CommandError', + `Failed to send user error embed in <<${channel}>>:\n` + typeof e === 'object' ? e?.stack : e + ); }); } else { const errorDevEmbed = new MessageEmbed() @@ -51,7 +54,10 @@ export default class CommandErrorListener extends BushListener { .setDescription(await this.client.util.codeblock(`${error?.stack}`, 2048, 'js')); await message.util.send({ embeds: [errorDevEmbed] }).catch((e) => { const channel = message.channel.type === 'dm' ? message.channel.recipient.tag : message.channel.name; - this.client.console.warn('CommandError', `Failed to send owner error stack in <<${channel}>>.` + e?.stack); + this.client.console.warn( + 'CommandError', + `Failed to send owner error stack in <<${channel}>>.` + typeof e === 'object' ? e?.stack : e + ); }); } } @@ -59,7 +65,10 @@ export default class CommandErrorListener extends BushListener { this.client.console.error( 'CommandError', `an error occurred with the <<${command}>> command in <<${channel}>> triggered by <<${message?.author?.tag}>>:\n` + - error?.stack, + typeof error === + 'object' + ? error?.stack + : error, false ); } diff --git a/src/listeners/commands/commandMissingPermissions.ts b/src/listeners/commands/commandMissingPermissions.ts index e3420ae..bef8e9c 100644 --- a/src/listeners/commands/commandMissingPermissions.ts +++ b/src/listeners/commands/commandMissingPermissions.ts @@ -1,3 +1,4 @@ +import { PermissionString } from 'discord.js'; import { BushCommand } from '../../lib/extensions/discord-akairo/BushCommand'; import { BushListener } from '../../lib/extensions/discord-akairo/BushListener'; import { BushMessage } from '../../lib/extensions/discord.js/BushMessage'; @@ -15,8 +16,17 @@ export default class CommandMissingPermissionsListener extends BushListener { message: BushMessage, command: BushCommand | null | undefined, type: 'client' | 'user', - missing: Array<string> + missing: Array<PermissionString> ): Promise<void> { + this.client.console.debug(message.guild.me.permissions.toArray()); + missing.forEach((permission) => { + this.client.console.debug(message.guild.me.permissions.has(permission)); + }); + message.guild.me.permissions; + this.client.console.debug(type); + this.client.console.debug(command.clientPermissions); + this.client.console.debug(command.userPermissions); + this.client.console.debug(missing); const niceMissing = []; missing.forEach((missing) => { if (this.client.consts.mappings.permissions[missing]) { diff --git a/src/listeners/commands/slashCommandError.ts b/src/listeners/commands/slashCommandError.ts index 767bd3d..b172670 100644 --- a/src/listeners/commands/slashCommandError.ts +++ b/src/listeners/commands/slashCommandError.ts @@ -21,7 +21,7 @@ export default class SlashCommandErrorListener extends BushListener { **Channel:** ${message.channel || message.interaction.user?.tag} ${message.channel ? `(${message.channel?.id})` : ''} **Message:** [link](https://discord.com/${message.guild?.id}/${message.channel?.id}/${message.id})` ) - .addField('Error', await this.client.util.codeblock(`${error?.stack}`, 1024, 'js')) + .addField('Error', await this.client.util.codeblock(`${typeof error === 'object' ? error?.stack : error}`, 1024, 'js')) .setColor(this.client.util.colors.error) .setTimestamp(); @@ -40,7 +40,10 @@ export default class SlashCommandErrorListener extends BushListener { `Oh no! While running the command \`${command.id}\`, an error occurred. Please give the developers code \`${errorNo}\`.` ); await message.util.send({ embeds: [errorUserEmbed] }).catch((e) => { - this.client.console.warn('SlashError', `Failed to send user error embed in <<${channel}>>:\n` + e?.stack); + this.client.console.warn( + 'SlashError', + `Failed to send user error embed in <<${channel}>>:\n` + typeof e === 'object' ? e?.stack : e + ); }); } else { const errorDevEmbed = new MessageEmbed() @@ -49,7 +52,10 @@ export default class SlashCommandErrorListener extends BushListener { .setTimestamp() .setDescription(await this.client.util.codeblock(`${error?.stack}`, 2048, 'js')); await message.util.send({ embeds: [errorDevEmbed] }).catch((e) => { - this.client.console.warn('SlashError', `Failed to send owner error stack in <<${channel}>>.` + e?.stack); + this.client.console.warn( + 'SlashError', + `Failed to send owner error stack in <<${channel}>>.` + typeof e === 'object' ? e?.stack : e + ); }); } } @@ -57,7 +63,10 @@ export default class SlashCommandErrorListener extends BushListener { this.client.console.error( 'SlashError', `an error occurred with the <<${command}>> command in <<${channel}>> triggered by <<${message?.author?.tag}>>:\n` + - error?.stack, + typeof error === + 'object' + ? error?.stack + : error, false ); } |