From c4c1d9ffeb179e208792c88dd099caea5030581b Mon Sep 17 00:00:00 2001 From: IRONM00N <64110067+IRONM00N@users.noreply.github.com> Date: Sun, 5 Sep 2021 20:24:50 -0400 Subject: add moderation logging, fixes, hide modlog, jank --- src/commands/_fake-command/ironmoon.ts | 2 +- src/commands/config/log.ts | 2 +- src/commands/info/help.ts | 4 +- src/commands/moderation/ban.ts | 2 +- src/commands/moderation/hideCase.ts | 50 +++ src/commands/moderation/modlog.ts | 45 +- src/commands/moderation/purge.ts | 9 +- src/commands/moderation/unban.ts | 2 +- src/commands/moulberry-bush/report.ts | 11 +- src/lib/extensions/discord-akairo/BushClient.ts | 46 +++ .../extensions/discord-akairo/BushClientUtil.ts | 9 +- src/lib/extensions/discord-akairo/BushCommand.ts | 8 +- .../extensions/discord.js/BushClientEvents.d.ts | 139 ++++++- src/lib/extensions/discord.js/BushGuild.ts | 171 ++++---- src/lib/extensions/discord.js/BushGuildMember.ts | 454 +++++++++++++-------- src/lib/models/Guild.ts | 2 +- src/lib/models/ModLog.ts | 48 ++- src/lib/utils/BushConstants.ts | 6 +- src/listeners/custom/bushBan.ts | 34 ++ src/listeners/custom/bushKick.ts | 33 ++ src/listeners/custom/bushMute.ts | 34 ++ src/listeners/custom/bushPunishRole.ts | 33 ++ src/listeners/custom/bushPunishRoleRemove.ts | 34 ++ src/listeners/custom/bushPurge.ts | 43 ++ src/listeners/custom/bushUnban.ts | 33 ++ src/listeners/custom/bushUnmute.ts | 33 ++ src/listeners/custom/bushWarn.ts | 33 ++ src/listeners/message/automodCreate.ts | 11 +- src/listeners/other/promiseRejection.ts | 7 +- src/tasks/removeExpiredPunishements.ts | 2 +- 30 files changed, 1024 insertions(+), 316 deletions(-) create mode 100644 src/commands/moderation/hideCase.ts create mode 100644 src/listeners/custom/bushBan.ts create mode 100644 src/listeners/custom/bushKick.ts create mode 100644 src/listeners/custom/bushMute.ts create mode 100644 src/listeners/custom/bushPunishRole.ts create mode 100644 src/listeners/custom/bushPunishRoleRemove.ts create mode 100644 src/listeners/custom/bushPurge.ts create mode 100644 src/listeners/custom/bushUnban.ts create mode 100644 src/listeners/custom/bushUnmute.ts create mode 100644 src/listeners/custom/bushWarn.ts (limited to 'src') diff --git a/src/commands/_fake-command/ironmoon.ts b/src/commands/_fake-command/ironmoon.ts index 8ca1f5b..ddc6ced 100644 --- a/src/commands/_fake-command/ironmoon.ts +++ b/src/commands/_fake-command/ironmoon.ts @@ -5,7 +5,7 @@ export default class IronmoonCommand extends BushCommand { super('ironmoon', { category: 'fake-commands', description: { content: '', examples: '', usage: '' }, - completelyHide: true + pseudo: true }); } public override condition(message: BushMessage): boolean { diff --git a/src/commands/config/log.ts b/src/commands/config/log.ts index 592f700..0bc2189 100644 --- a/src/commands/config/log.ts +++ b/src/commands/config/log.ts @@ -79,7 +79,7 @@ export default class LogCommand extends BushCommand { ? `${util.emojis.success} Successfully ${oldChannel ? 'changed' : 'set'}` : `${util.emojis.error} Unable to ${oldChannel ? 'change' : 'set'}` } ${ - oldChannel ? ` the \`${args.log_type}\` log channel from <#${oldChannel}>` : ` the \`${args.log_type}\` log channel` + oldChannel ? ` the **${args.log_type}** log channel from <#${oldChannel}>` : ` the \`${args.log_type}\` log channel` } to ${args.channel ? `<#${args.channel.id}>` : '`disabled`'}` ); } diff --git a/src/commands/info/help.ts b/src/commands/info/help.ts index ad4e00f..1338f8a 100644 --- a/src/commands/info/help.ts +++ b/src/commands/info/help.ts @@ -90,14 +90,14 @@ export default class HelpCommand extends BushCommand { : args.command : null; if (!isOwner) args.showHidden = false; - if (!command || command.completelyHide) { + if (!command || command.pseudo) { const embed = new MessageEmbed().setColor(util.colors.default).setTimestamp(); if (message.guild) { embed.setFooter(`For more information about a command use ${prefix}help `); } for (const [, category] of this.handler.categories) { const categoryFilter = category.filter((command) => { - if (command.completelyHide) return false; + if (command.pseudo) return false; if (command.hidden && !args.showHidden) return false; if (command.channel == 'guild' && !message.guild && !args.showHidden) return false; if (command.ownerOnly && !isOwner) return false; diff --git a/src/commands/moderation/ban.ts b/src/commands/moderation/ban.ts index 5a1b5d9..812d7ca 100644 --- a/src/commands/moderation/ban.ts +++ b/src/commands/moderation/ban.ts @@ -126,7 +126,7 @@ export default class BanCommand extends BushCommand { duration: time! ?? 0, deleteDays: days ?? 0 }) - : await message.guild.ban({ + : await message.guild.bushBan({ user, reason: parsedReason, moderator: message.author, diff --git a/src/commands/moderation/hideCase.ts b/src/commands/moderation/hideCase.ts new file mode 100644 index 0000000..1d8dea6 --- /dev/null +++ b/src/commands/moderation/hideCase.ts @@ -0,0 +1,50 @@ +import { BushCommand, BushMessage, BushSlashMessage, ModLog } from '@lib'; + +export default class HideCaseCommand extends BushCommand { + public constructor() { + super('hideCase', { + aliases: ['hidecase', 'hide_case', 'showcase', 'show_case', 'coverupmodabuse', 'cover_up_mod_abuse'], + category: 'moderation', + description: { + content: 'Hide a particular modlog case from the modlog command unless the `--hidden` flag is specified', + usage: 'hideCase ', + examples: ['hideCase 9210b1ea-91f5-4ea2-801b-02b394469c77'] + }, + args: [ + { + id: 'case', + type: 'string', + prompt: { + start: 'What modlog case would you like to hide?', + retry: '{error} Choose a valid case id.' + } + } + ], + userPermissions: ['MANAGE_MESSAGES'], + slash: true, + slashOptions: [ + { + name: 'case', + description: 'What modlog case would you like to hide?', + type: 'STRING', + required: true + } + ], + channel: 'guild' + }); + } + + public override async exec(message: BushMessage | BushSlashMessage, { case: caseID }: { case: string }): Promise { + if (message.author.id === '496409778822709251') + return await message.util.reply(`${util.emojis.error} This command is Bestower proof.`); + const entry = await ModLog.findByPk(caseID); + if (!entry || entry.pseudo) return message.util.send(`${util.emojis.error} Invalid entry.`); + if (entry.guild !== message.guild!.id) + return message.util.reply(`${util.emojis.error} This modlog is from another server.`); + const action = entry.hidden ? 'now hidden' : 'no longer hidden'; + entry.hidden = !entry.hidden; + await entry.save(); + + return await message.util.reply(`${util.emojis.success} CaseID \`${caseID}\` is ${action}.`); + } +} diff --git a/src/commands/moderation/modlog.ts b/src/commands/moderation/modlog.ts index fd53ea7..0be6971 100644 --- a/src/commands/moderation/modlog.ts +++ b/src/commands/moderation/modlog.ts @@ -1,5 +1,5 @@ import { BushCommand, BushMessage, BushSlashMessage, BushUser, ModLog } from '@lib'; -import { MessageEmbed, User } from 'discord.js'; +import { User } from 'discord.js'; export default class ModlogCommand extends BushCommand { public constructor() { @@ -8,7 +8,7 @@ export default class ModlogCommand extends BushCommand { category: 'moderation', description: { content: "View a user's modlogs, or view a specific case.", - usage: 'modlogs ', + usage: 'modlogs [--hidden]', examples: ['modlogs @Tyman'] }, args: [ @@ -19,6 +19,12 @@ export default class ModlogCommand extends BushCommand { start: 'What case id or user would you like to see?', retry: '{error} Choose a valid case id or user.' } + }, + { + id: 'hidden', + match: 'flag', + flags: ['--hidden', '-h'], + default: false } ], userPermissions: ['MANAGE_MESSAGES'], @@ -29,6 +35,12 @@ export default class ModlogCommand extends BushCommand { description: 'What case id or user would you like to see?', type: 'STRING', required: true + }, + { + name: 'hidden', + description: 'Would you like to see hidden modlogs?', + type: 'BOOLEAN', + required: false } ] }); @@ -50,7 +62,7 @@ export default class ModlogCommand extends BushCommand { public override async exec( message: BushMessage | BushSlashMessage, - { search }: { search: BushUser | string } + { search, hidden }: { search: BushUser | string; hidden: boolean } ): Promise { const foundUser = search instanceof User ? search : await util.resolveUserAsync(search); if (foundUser) { @@ -62,28 +74,25 @@ export default class ModlogCommand extends BushCommand { order: [['createdAt', 'ASC']] }); if (!logs.length) return message.util.reply(`${util.emojis.error} **${foundUser.tag}** does not have any modlogs.`); - const niceLogs: string[] = []; - for (const log of logs) { - niceLogs.push(this.#generateModlogInfo(log)); - } + const niceLogs = logs.filter((log) => !log.pseudo && !log.hidden && !hidden).map((log) => this.#generateModlogInfo(log)); const chunked: string[][] = util.chunk(niceLogs, 3); - const embedPages = chunked.map( - (chunk) => - new MessageEmbed({ - title: `${foundUser.tag}'s Mod Logs`, - description: chunk.join('\n━━━━━━━━━━━━━━━\n'), - color: util.colors.default - }) - ); + const embedPages = chunked.map((chunk) => ({ + title: `${foundUser.tag}'s Mod Logs`, + description: chunk.join('\n━━━━━━━━━━━━━━━\n'), + color: util.colors.default + })); return await util.buttonPaginate(message, embedPages, undefined, true); } else if (search) { const entry = await ModLog.findByPk(search as string); - if (!entry) return message.util.send(`${util.emojis.error} That modlog does not exist.`); - const embed = new MessageEmbed({ + if (!entry || entry.pseudo || (entry.hidden && !hidden)) + return message.util.send(`${util.emojis.error} That modlog does not exist.`); + if (entry.guild !== message.guild!.id) + return message.util.reply(`${util.emojis.error} This modlog is from another server.`); + const embed = { title: `Case ${entry.id}`, description: this.#generateModlogInfo(entry), color: util.colors.default - }); + }; return await util.buttonPaginate(message, [embed]); } } diff --git a/src/commands/moderation/purge.ts b/src/commands/moderation/purge.ts index b391ff6..4ed1ee7 100644 --- a/src/commands/moderation/purge.ts +++ b/src/commands/moderation/purge.ts @@ -47,18 +47,19 @@ export default class PurgeCommand extends BushCommand { if (message.channel.type === 'DM') return message.util.reply(`${util.emojis.error} You cannot run this command in dms.`); if (args.amount > 100 || args.amount < 1) return message.util.reply(`${util.emojis.error} `); - const messages = (await message.channel.messages.fetch({ limit: args.amount })).filter((message) => filter(message)); - const filter = (filterMessage: BushMessage): boolean => { + const messageFilter = (filterMessage: BushMessage): boolean => { const shouldFilter = new Array(); if (args.bot) { shouldFilter.push(filterMessage.author.bot); } return shouldFilter.filter((bool) => bool === false).length === 0; }; + const messages = (await message.channel.messages.fetch({ limit: args.amount })).filter((message) => messageFilter(message)); - const purged = await message.channel.bulkDelete(messages, true).catch(() => {}); - if (!purged) return message.util.reply(`${util.emojis.error} Failed to purge messages.`).catch(() => {}); + const purged = await message.channel.bulkDelete(messages, true).catch(() => null); + if (!purged) return message.util.reply(`${util.emojis.error} Failed to purge messages.`).catch(() => null); else { + client.emit('bushPurge', message.author, message.guild!, message.channel, messages); await message.util .send(`${util.emojis.success} Successfully purged **${purged.size}** messages.`) .then(async (purgeMessage) => { diff --git a/src/commands/moderation/unban.ts b/src/commands/moderation/unban.ts index 3436da6..5025ede 100644 --- a/src/commands/moderation/unban.ts +++ b/src/commands/moderation/unban.ts @@ -59,7 +59,7 @@ export default class UnbanCommand extends BushCommand { user = util.resolveUser(user, client.users.cache) as BushUser; } - const responseCode = await message.guild!.unban({ + const responseCode = await message.guild!.bushUnban({ user, moderator: message.author, reason diff --git a/src/commands/moulberry-bush/report.ts b/src/commands/moulberry-bush/report.ts index e387e7d..a5c4cb2 100644 --- a/src/commands/moulberry-bush/report.ts +++ b/src/commands/moulberry-bush/report.ts @@ -1,6 +1,6 @@ import { GuildMember, MessageEmbed } from 'discord.js'; import moment from 'moment'; -import { AllowedMentions, BushCommand, BushMessage, BushTextChannel } from '../../lib'; +import { AllowedMentions, BushCommand, BushMessage } from '../../lib'; export default class ReportCommand extends BushCommand { public constructor() { @@ -71,9 +71,11 @@ export default class ReportCommand extends BushCommand { if (member.user.bot) return await message.util.reply(`${util.emojis.error} You cannot report a bot <:WeirdChamp:756283321301860382>.`); - const reportChannelId = (await message.guild.getSetting('logChannels')).report; - if (!reportChannelId) - return await message.util.reply(`${util.emojis.error} This server has not setup a report logging channel.`); + const reportChannel = await message.guild.getLogChannel('report'); + if (!reportChannel) + return await message.util.reply( + `${util.emojis.error} This server has not setup a report logging channel or the channel no longer exists.` + ); //The formatting of the report is mostly copied from carl since it is pretty good when it actually works const reportEmbed = new MessageEmbed() @@ -109,7 +111,6 @@ export default class ReportCommand extends BushCommand { reportEmbed.addField('Attachment', message.attachments.first()!.url); } } - const reportChannel = client.channels.cache.get(reportChannelId) as unknown as BushTextChannel; await reportChannel.send({ embeds: [reportEmbed] }).then(async (ReportMessage) => { try { await ReportMessage.react(util.emojis.check); diff --git a/src/lib/extensions/discord-akairo/BushClient.ts b/src/lib/extensions/discord-akairo/BushClient.ts index 5c1cb35..59c4df8 100644 --- a/src/lib/extensions/discord-akairo/BushClient.ts +++ b/src/lib/extensions/discord-akairo/BushClient.ts @@ -1,6 +1,7 @@ import chalk from 'chalk'; import { AkairoClient, ContextMenuCommandHandler } from 'discord-akairo'; import { + Awaited, Collection, Intents, InteractionReplyOptions, @@ -48,6 +49,7 @@ import { BushButtonInteraction } from '../discord.js/BushButtonInteraction'; import { BushCategoryChannel } from '../discord.js/BushCategoryChannel'; import { BushChannel } from '../discord.js/BushChannel'; import { BushChannelManager } from '../discord.js/BushChannelManager'; +import { BushClientEvents } from '../discord.js/BushClientEvents'; import { BushClientUser } from '../discord.js/BushClientUser'; import { BushCommandInteraction } from '../discord.js/BushCommandInteraction'; import { BushDMChannel } from '../discord.js/BushDMChannel'; @@ -159,6 +161,50 @@ export class BushClient extends AkairoClient( + event: K, + listener: (...args: BushClientEvents[K]) => Awaited + ): this; + public override on( + event: Exclude, + listener: (...args: any[]) => Awaited + ): this { + return super.on(event as any, listener); + } + + public override once( + event: K, + listener: (...args: BushClientEvents[K]) => Awaited + ): this; + public override once( + event: Exclude, + listener: (...args: any[]) => Awaited + ): this { + return super.once(event as any, listener); + } + + public override emit(event: K, ...args: BushClientEvents[K]): boolean; + public override emit(event: Exclude, ...args: unknown[]): boolean { + return super.emit(event as any, ...args); + } + + public override off( + event: K, + listener: (...args: BushClientEvents[K]) => Awaited + ): this; + public override off( + event: Exclude, + listener: (...args: any[]) => Awaited + ): this { + return super.off(event as any, listener); + } + + public override removeAllListeners(event?: K): this; + public override removeAllListeners(event?: Exclude): this { + return super.removeAllListeners(event as any); + } + public constructor(config: Config) { super({ ownerID: config.owners, diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts index 3f9e0b6..fec0174 100644 --- a/src/lib/extensions/discord-akairo/BushClientUtil.ts +++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts @@ -8,6 +8,7 @@ import { BushGuildResolvable, BushMessage, BushSlashMessage, + BushUser, Global, Guild, ModLog, @@ -576,7 +577,7 @@ export class BushClientUtil extends ClientUtil { if (content.length > 400_000 && !substr) { void this.handleError('haste', new Error(`content over 400,000 characters (${content.length.toLocaleString()})`)); return { error: 'content too long' }; - } else { + } else if (content.length > 400_000) { content = content.substr(0, 400_000); isSubstr = true; } @@ -876,7 +877,7 @@ export class BushClientUtil extends ClientUtil { const haste = await this.haste(code, substr); hasteOut = `Too large to display. ${ haste.url - ? `Hastebin: ${haste.url}${haste.error ? `(${haste.error})` : ''}` + ? `Hastebin: ${haste.url}${haste.error ? ` - ${haste.error}` : ''}` : `${this.emojis.error} Hastebin: ${haste.error}` }`; } @@ -969,7 +970,7 @@ export class BushClientUtil extends ClientUtil { public async inspectCleanRedactHaste(input: any, inspectOptions?: BushInspectOptions) { input = typeof input !== 'string' ? this.inspect(input, inspectOptions ?? undefined) : input; input = this.redact(input); - return this.haste(input); + return this.haste(input, true); } public inspectAndRedact(input: any, inspectOptions?: BushInspectOptions) { @@ -1413,7 +1414,7 @@ export class BushClientUtil extends ClientUtil { }); } - public async resolveNonCachedUser(user: UserResolvable | undefined | null): Promise { + public async resolveNonCachedUser(user: UserResolvable | undefined | null): Promise { if (!user) return undefined; const id = user instanceof User || user instanceof GuildMember || user instanceof ThreadMember diff --git a/src/lib/extensions/discord-akairo/BushCommand.ts b/src/lib/extensions/discord-akairo/BushCommand.ts index 495a454..1c8ea5b 100644 --- a/src/lib/extensions/discord-akairo/BushCommand.ts +++ b/src/lib/extensions/discord-akairo/BushCommand.ts @@ -147,7 +147,7 @@ export interface BushCommandOptions extends CommandOptions { }; args?: BushArgumentOptions[] & CustomBushArgumentOptions[]; category: string; - completelyHide?: boolean; + pseudo?: boolean; } export class BushCommand extends Command { @@ -166,8 +166,8 @@ export class BushCommand extends Command { /** Whether the command is hidden from the help command. */ public hidden: boolean; - /** Completely hide this command from the help command. */ - public completelyHide: boolean; + /** A fake command, completely hidden from the help command. */ + public pseudo: boolean; public constructor(id: string, options: BushCommandOptions) { if (options.args && typeof options.args !== 'function') { @@ -184,7 +184,7 @@ export class BushCommand extends Command { this.hidden = options.hidden ?? false; this.restrictedChannels = options.restrictedChannels!; this.restrictedGuilds = options.restrictedGuilds!; - this.completelyHide = options.completelyHide!; + this.pseudo = options.pseudo!; } public override exec(message: BushMessage, args: any): any; diff --git a/src/lib/extensions/discord.js/BushClientEvents.d.ts b/src/lib/extensions/discord.js/BushClientEvents.d.ts index ae9b186..8695e7a 100644 --- a/src/lib/extensions/discord.js/BushClientEvents.d.ts +++ b/src/lib/extensions/discord.js/BushClientEvents.d.ts @@ -9,7 +9,10 @@ import { Sticker, Typing } from 'discord.js'; -import { BushClient, BushTextBasedChannels } from '../discord-akairo/BushClient'; +import { + BushClient, + BushTextBasedChannels +} from '../discord-akairo/BushClient'; import { BushApplicationCommand } from './BushApplicationCommand'; import { BushDMChannel } from './BushDMChannel'; import { BushGuild } from './BushGuild'; @@ -18,7 +21,11 @@ import { BushGuildChannel } from './BushGuildChannel'; import { BushGuildEmoji } from './BushGuildEmoji'; import { BushGuildMember, PartialBushGuildMember } from './BushGuildMember'; import { BushMessage, PartialBushMessage } from './BushMessage'; -import { BushMessageReaction, PartialBushMessageReaction } from './BushMessageReaction'; +import { + BushMessageReaction, + PartialBushMessageReaction +} from './BushMessageReaction'; +import { BushNewsChannel } from './BushNewsChannel'; import { BushPresence } from './BushPresence'; import { BushRole } from './BushRole'; import { BushStageInstance } from './BushStageInstance'; @@ -31,11 +38,17 @@ import { BushVoiceState } from './BushVoiceState'; export interface BushClientEvents extends ClientEvents { applicationCommandCreate: [command: BushApplicationCommand]; applicationCommandDelete: [command: BushApplicationCommand]; - applicationCommandUpdate: [oldCommand: BushApplicationCommand | null, newCommand: BushApplicationCommand]; + applicationCommandUpdate: [ + oldCommand: BushApplicationCommand | null, + newCommand: BushApplicationCommand + ]; channelCreate: [channel: BushGuildChannel]; channelDelete: [channel: BushDMChannel | BushGuildChannel]; channelPinsUpdate: [channel: BushTextBasedChannels, date: Date]; - channelUpdate: [oldChannel: BushDMChannel | BushGuildChannel, newChannel: BushDMChannel | BushGuildChannel]; + channelUpdate: [ + oldChannel: BushDMChannel | BushGuildChannel, + newChannel: BushDMChannel | BushGuildChannel + ]; debug: [message: string]; warn: [message: string]; emojiCreate: [emoji: BushGuildEmoji]; @@ -54,20 +67,40 @@ export interface BushClientEvents extends ClientEvents { guildMembersChunk: [ members: Collection, guild: BushGuild, - data: { count: number; index: number; nonce: string | undefined } + data: { + count: number; + index: number; + nonce: string | undefined; + } + ]; + guildMemberUpdate: [ + oldMember: BushGuildMember | PartialBushGuildMember, + newMember: BushGuildMember ]; - guildMemberUpdate: [oldMember: BushGuildMember | PartialBushGuildMember, newMember: BushGuildMember]; guildUpdate: [oldGuild: BushGuild, newGuild: BushGuild]; inviteCreate: [invite: Invite]; inviteDelete: [invite: Invite]; messageCreate: [message: BushMessage]; messageDelete: [message: BushMessage | PartialBushMessage]; messageReactionRemoveAll: [message: BushMessage | PartialBushMessage]; - messageReactionRemoveEmoji: [reaction: BushMessageReaction | PartialBushMessageReaction]; - messageDeleteBulk: [messages: Collection]; - messageReactionAdd: [reaction: BushMessageReaction | PartialBushMessageReaction, user: BushUser | PartialBushUser]; - messageReactionRemove: [reaction: BushMessageReaction | PartialBushMessageReaction, user: BushUser | PartialBushUser]; - messageUpdate: [oldMessage: BushMessage | PartialBushMessage, newMessage: BushMessage | PartialBushMessage]; + messageReactionRemoveEmoji: [ + reaction: BushMessageReaction | PartialBushMessageReaction + ]; + messageDeleteBulk: [ + messages: Collection + ]; + messageReactionAdd: [ + reaction: BushMessageReaction | PartialBushMessageReaction, + user: BushUser | PartialBushUser + ]; + messageReactionRemove: [ + reaction: BushMessageReaction | PartialBushMessageReaction, + user: BushUser | PartialBushUser + ]; + messageUpdate: [ + oldMessage: BushMessage | PartialBushMessage, + newMessage: BushMessage | PartialBushMessage + ]; presenceUpdate: [oldPresence: BushPresence | null, newPresence: BushPresence]; rateLimit: [rateLimitData: RateLimitData]; invalidRequestWarning: [invalidRequestWarningData: InvalidRequestWarningData]; @@ -79,7 +112,10 @@ export interface BushClientEvents extends ClientEvents { threadCreate: [thread: BushThreadChannel]; threadDelete: [thread: BushThreadChannel]; threadListSync: [threads: Collection]; - threadMemberUpdate: [oldMember: BushThreadMember, newMember: BushThreadMember]; + threadMemberUpdate: [ + oldMember: BushThreadMember, + newMember: BushThreadMember + ]; threadMembersUpdate: [ oldMembers: Collection, mewMembers: Collection @@ -95,9 +131,86 @@ export interface BushClientEvents extends ClientEvents { shardReconnecting: [shardId: number]; shardResume: [shardId: number, replayedEvents: number]; stageInstanceCreate: [stageInstance: BushStageInstance]; - stageInstanceUpdate: [oldStageInstance: BushStageInstance | null, newStageInstance: BushStageInstance]; + stageInstanceUpdate: [ + oldStageInstance: BushStageInstance | null, + newStageInstance: BushStageInstance + ]; stageInstanceDelete: [stageInstance: BushStageInstance]; stickerCreate: [sticker: Sticker]; stickerDelete: [sticker: Sticker]; stickerUpdate: [oldSticker: Sticker, newSticker: Sticker]; + /* Custom */ + bushBan: [ + victim: BushGuildMember | BushUser, + moderator: BushUser, + guild: BushGuild, + reason: string | undefined, + caseID: string, + duration: number, + dmSuccess?: boolean + ]; + bushKick: [ + victim: BushGuildMember, + moderator: BushUser, + guild: BushGuild, + reason: string | undefined, + caseID: string, + dmSuccess: boolean + ]; + bushMute: [ + victim: BushGuildMember, + moderator: BushUser, + guild: BushGuild, + reason: string | undefined, + caseID: string, + duration: number, + dmSuccess: boolean + ]; + bushPunishRole: [ + victim: BushGuildMember, + moderator: BushUser, + guild: BushGuild, + reason: string | undefined, + caseID: string, + duration: number, + role: BushRole + ]; + bushPunishRoleRemove: [ + victim: BushGuildMember, + moderator: BushUser, + guild: BushGuild, + caseID: string, + reason: string | undefined, + role: BushRole + ]; + bushPurge: [ + moderator: BushUser, + guild: BushGuild, + channel: BushTextChannel | BushNewsChannel | BushThreadChannel, + messages: Collection + ]; + bushUnban: [ + victim: BushUser, + moderator: BushUser, + guild: BushGuild, + reason: string | undefined, + caseID: string, + dmSuccess: boolean + ]; + bushUnmute: [ + victim: BushGuildMember, + moderator: BushUser, + guild: BushGuild, + reason: string | undefined, + caseID: string, + dmSuccess: boolean + ]; + bushWarn: [ + victim: BushGuildMember, + moderator: BushUser, + guild: BushGuild, + reason: string | undefined, + caseID: string, + dmSuccess: boolean + ]; } diff --git a/src/lib/extensions/discord.js/BushGuild.ts b/src/lib/extensions/discord.js/BushGuild.ts index efecdcd..18f6542 100644 --- a/src/lib/extensions/discord.js/BushGuild.ts +++ b/src/lib/extensions/discord.js/BushGuild.ts @@ -1,10 +1,11 @@ import { Guild, UserResolvable } from 'discord.js'; import { RawGuildData } from 'discord.js/typings/rawDataTypes'; -import { Guild as GuildDB, GuildFeatures, GuildModel } from '../../models/Guild'; +import { Guild as GuildDB, GuildFeatures, GuildLogType, GuildModel } from '../../models/Guild'; import { ModLogType } from '../../models/ModLog'; import { BushClient, BushUserResolvable } from '../discord-akairo/BushClient'; import { BushGuildMember } from './BushGuildMember'; import { BushGuildMemberManager } from './BushGuildMemberManager'; +import { BushTextChannel } from './BushTextChannel'; import { BushUser } from './BushUser'; export class BushGuild extends Guild { @@ -50,7 +51,17 @@ export class BushGuild extends Guild { return await row.save(); } - public async ban(options: { + public async getLogChannel(logType: GuildLogType): Promise { + const channelId = (await this.getSetting('logChannels'))[logType]; + if (!channelId) return undefined; + return ( + (this.channels.cache.get(channelId) as BushTextChannel | undefined) ?? + ((await this.channels.fetch(channelId)) as BushTextChannel | null) ?? + undefined + ); + } + + public async bushBan(options: { user: BushUserResolvable | UserResolvable; reason?: string | null; moderator?: BushUserResolvable; @@ -62,42 +73,51 @@ export class BushGuild extends Guild { // checks if (!this.me!.permissions.has('BAN_MEMBERS')) return 'missing permissions'; + let caseID: string | undefined = undefined; + const user = (await util.resolveNonCachedUser(options.user))!; const moderator = (await util.resolveNonCachedUser(options.moderator!)) ?? client.user!; - // ban - const banSuccess = await this.bans - .create(options.user, { - reason: `${moderator.tag} | ${options.reason ?? 'No reason provided.'}`, - days: options.deleteDays - }) - .catch(() => false); - if (!banSuccess) return 'error banning'; - - // add modlog entry - const { log: modlog } = await util.createModLogEntry({ - type: options.duration ? ModLogType.TEMP_BAN : ModLogType.PERM_BAN, - user: options.user as BushUserResolvable, - moderator: moderator.id, - reason: options.reason, - duration: options.duration, - guild: this - }); - if (!modlog) return 'error creating modlog entry'; - - // add punishment entry so they can be unbanned later - const punishmentEntrySuccess = await util.createPunishmentEntry({ - type: 'ban', - user: options.user as BushUserResolvable, - guild: this, - duration: options.duration, - modlog: modlog.id - }); - if (!punishmentEntrySuccess) return 'error creating ban entry'; - - return 'success'; + const ret = await (async () => { + // ban + const banSuccess = await this.bans + .create(user?.id ?? options.user, { + reason: `${moderator.tag} | ${options.reason ?? 'No reason provided.'}`, + days: options.deleteDays + }) + .catch(() => false); + if (!banSuccess) return 'error banning'; + + // add modlog entry + const { log: modlog } = await util.createModLogEntry({ + type: options.duration ? ModLogType.TEMP_BAN : ModLogType.PERM_BAN, + user: user, + moderator: moderator.id, + reason: options.reason, + duration: options.duration, + guild: this + }); + if (!modlog) return 'error creating modlog entry'; + caseID = modlog.id; + + // add punishment entry so they can be unbanned later + const punishmentEntrySuccess = await util.createPunishmentEntry({ + type: 'ban', + user: user, + guild: this, + duration: options.duration, + modlog: modlog.id + }); + if (!punishmentEntrySuccess) return 'error creating ban entry'; + + return 'success'; + })(); + + if (!['error banning', 'error creating modlog entry', 'error creating ban entry'].includes(ret)) + client.emit('bushBan', user, moderator, this, options.reason ?? undefined, caseID!, options.duration ?? 0); + return ret; } - public async unban(options: { + public async bushUnban(options: { user: BushUserResolvable | BushUser; reason?: string | null; moderator?: BushUserResolvable; @@ -109,48 +129,59 @@ export class BushGuild extends Guild { | 'error creating modlog entry' | 'error removing ban entry' > { + let caseID: string | undefined = undefined; + let dmSuccessEvent: boolean | undefined = undefined; const user = (await util.resolveNonCachedUser(options.user))!; const moderator = (await util.resolveNonCachedUser(options.moderator ?? this.me))!; - const bans = await this.bans.fetch(); - - let notBanned = false; - if (!bans.has(user.id)) notBanned = true; - - const unbanSuccess = await this.bans - .remove(user, `${moderator.tag} | ${options.reason ?? 'No reason provided.'}`) - .catch((e) => { - if (e?.code === 'UNKNOWN_BAN') { - notBanned = true; - return true; - } else return false; + const ret = await (async () => { + const bans = await this.bans.fetch(); + + let notBanned = false; + if (!bans.has(user.id)) notBanned = true; + + const unbanSuccess = await this.bans + .remove(user, `${moderator.tag} | ${options.reason ?? 'No reason provided.'}`) + .catch((e) => { + if (e?.code === 'UNKNOWN_BAN') { + notBanned = true; + return true; + } else return false; + }); + + if (notBanned) return 'user not banned'; + if (!unbanSuccess) return 'error unbanning'; + + // add modlog entry + const { log: modlog } = await util.createModLogEntry({ + type: ModLogType.UNBAN, + user: user.id, + moderator: moderator.id, + reason: options.reason, + guild: this }); + if (!modlog) return 'error creating modlog entry'; + caseID = modlog.id; + + // remove punishment entry + const removePunishmentEntrySuccess = await util.removePunishmentEntry({ + type: 'ban', + user: user.id, + guild: this + }); + if (!removePunishmentEntrySuccess) return 'error removing ban entry'; - if (!unbanSuccess) return 'error unbanning'; - - // add modlog entry - const modlog = await util.createModLogEntry({ - type: ModLogType.UNBAN, - user: user.id, - moderator: moderator.id, - reason: options.reason, - guild: this - }); - if (!modlog) return 'error creating modlog entry'; - - // remove punishment entry - const removePunishmentEntrySuccess = await util.removePunishmentEntry({ - type: 'ban', - user: user.id, - guild: this - }); - if (!removePunishmentEntrySuccess) return 'error removing ban entry'; - - const userObject = client.users.cache.get(user.id); + const userObject = client.users.cache.get(user.id); - userObject?.send(`You have been unbanned from **${this}** for **${options.reason ?? 'No reason provided'}**.`); + const dmSuccess = await userObject + ?.send(`You have been unbanned from **${this}** for **${options.reason ?? 'No reason provided'}**.`) + .catch(() => false); + dmSuccessEvent = !!dmSuccess; - if (notBanned) return 'user not banned'; - return 'success'; + return 'success'; + })(); + if (!['error unbanning', 'error creating modlog entry', 'error removing ban entry'].includes(ret)) + client.emit('bushUnban', user, moderator, this, options.reason ?? undefined, caseID!, dmSuccessEvent!); + return ret; } } diff --git a/src/lib/extensions/discord.js/BushGuildMember.ts b/src/lib/extensions/discord.js/BushGuildMember.ts index ab4eee4..4dd1a5d 100644 --- a/src/lib/extensions/discord.js/BushGuildMember.ts +++ b/src/lib/extensions/discord.js/BushGuildMember.ts @@ -105,96 +105,140 @@ export class BushGuildMember extends GuildMember { } public async warn(options: BushPunishmentOptions): Promise<{ result: WarnResponse | null; caseNum: number | null }> { + let caseID: string | undefined = undefined; + let dmSuccessEvent: boolean | undefined = undefined; const moderator = (await util.resolveNonCachedUser(options.moderator ?? this.guild.me))!; - // add modlog entry - const result = await util.createModLogEntry( - { - type: ModLogType.WARN, - user: this, - moderator: moderator.id, - reason: options.reason, - guild: this.guild - }, - true - ); - if (!result || !result.log) return { result: 'error creating modlog entry', caseNum: null }; - - // dm user - const dmSuccess = await this.punishDM('warned', options.reason); - if (!dmSuccess) return { result: 'failed to dm', caseNum: result.caseNum }; - - return { result: 'success', caseNum: result.caseNum }; + const ret = await (async () => { + // add modlog entry + const result = await util.createModLogEntry( + { + type: ModLogType.WARN, + user: this, + moderator: moderator.id, + reason: options.reason, + guild: this.guild + }, + true + ); + caseID = result.log?.id; + if (!result || !result.log) return { result: 'error creating modlog entry', caseNum: null }; + + // dm user + const dmSuccess = await this.punishDM('warned', options.reason); + dmSuccessEvent = dmSuccess; + if (!dmSuccess) return { result: 'failed to dm', caseNum: result.caseNum }; + + return { result: 'success', caseNum: result.caseNum }; + })(); + if (!['error creating modlog entry'].includes(ret.result)) + client.emit('bushWarn', this, moderator, this.guild, options.reason ?? undefined, caseID!, dmSuccessEvent!); + return ret as { result: WarnResponse | null; caseNum: number | null }; } public async addRole(options: AddRoleOptions): Promise { const ifShouldAddRole = this.#checkIfShouldAddRole(options.role); if (ifShouldAddRole !== true) return ifShouldAddRole; + let caseID: string | undefined = undefined; const moderator = (await util.resolveNonCachedUser(options.moderator ?? this.guild.me))!; - if (options.addToModlog || options.duration) { - const { log: modlog } = options.addToModlog - ? await util.createModLogEntry({ - type: options.duration ? ModLogType.TEMP_PUNISHMENT_ROLE : ModLogType.PERM_PUNISHMENT_ROLE, - guild: this.guild, - moderator: moderator.id, - user: this, - reason: 'N/A' - }) - : { log: null }; - - if (!modlog && options.addToModlog) return 'error creating modlog entry'; - + const ret = await (async () => { if (options.addToModlog || options.duration) { - const punishmentEntrySuccess = await util.createPunishmentEntry({ - type: 'role', - user: this, - guild: this.guild, - modlog: modlog?.id ?? undefined, - duration: options.duration, - extraInfo: options.role.id - }); - if (!punishmentEntrySuccess) return 'error creating role entry'; + const { log: modlog } = options.addToModlog + ? await util.createModLogEntry({ + type: options.duration ? ModLogType.TEMP_PUNISHMENT_ROLE : ModLogType.PERM_PUNISHMENT_ROLE, + guild: this.guild, + moderator: moderator.id, + user: this, + reason: 'N/A' + }) + : { log: null }; + caseID = modlog?.id; + + if (!modlog && options.addToModlog) return 'error creating modlog entry'; + + if (options.addToModlog || options.duration) { + const punishmentEntrySuccess = await util.createPunishmentEntry({ + type: 'role', + user: this, + guild: this.guild, + modlog: modlog?.id ?? undefined, + duration: options.duration, + extraInfo: options.role.id + }); + if (!punishmentEntrySuccess) return 'error creating role entry'; + } } - } - - const removeRoleSuccess = await this.roles.add(options.role, `${moderator.tag}`); - if (!removeRoleSuccess) return 'error adding role'; - return 'success'; + const removeRoleSuccess = await this.roles.add(options.role, `${moderator.tag}`); + if (!removeRoleSuccess) return 'error adding role'; + + return 'success'; + })(); + if (!['error adding role', 'error creating modlog entry', 'error creating role entry'].includes(ret) && options.addToModlog) + client.emit( + 'bushPunishRole', + this, + moderator, + this.guild, + options.reason ?? undefined, + caseID!, + options.duration ?? 0, + options.role as BushRole + ); + return ret; } public async removeRole(options: RemoveRoleOptions): Promise { const ifShouldAddRole = this.#checkIfShouldAddRole(options.role); if (ifShouldAddRole !== true) return ifShouldAddRole; + let caseID: string | undefined = undefined; const moderator = (await util.resolveNonCachedUser(options.moderator ?? this.guild.me))!; - if (options.addToModlog) { - const { log: modlog } = await util.createModLogEntry({ - type: ModLogType.PERM_PUNISHMENT_ROLE, - guild: this.guild, - moderator: moderator.id, - user: this, - reason: 'N/A' - }); - - if (!modlog) return 'error creating modlog entry'; + const ret = await (async () => { + if (options.addToModlog) { + const { log: modlog } = await util.createModLogEntry({ + type: ModLogType.PERM_PUNISHMENT_ROLE, + guild: this.guild, + moderator: moderator.id, + user: this, + reason: 'N/A' + }); - const punishmentEntrySuccess = await util.removePunishmentEntry({ - type: 'role', - user: this, - guild: this.guild - }); + if (!modlog) return 'error creating modlog entry'; + caseID = modlog.id; - if (!punishmentEntrySuccess) return 'error removing role entry'; - } + const punishmentEntrySuccess = await util.removePunishmentEntry({ + type: 'role', + user: this, + guild: this.guild + }); - const removeRoleSuccess = await this.roles.remove(options.role, `${moderator.tag}`); - if (!removeRoleSuccess) return 'error removing role'; + if (!punishmentEntrySuccess) return 'error removing role entry'; + } - return 'success'; + const removeRoleSuccess = await this.roles.remove(options.role, `${moderator.tag}`); + if (!removeRoleSuccess) return 'error removing role'; + + return 'success'; + })(); + + if ( + !['error removing role', 'error creating modlog entry', 'error removing role entry'].includes(ret) && + options.addToModlog + ) + client.emit( + 'bushPunishRoleRemove', + this, + moderator, + this.guild, + caseID!, + options.reason ?? undefined, + options.role as BushRole + ); + return ret; } #checkIfShouldAddRole(role: BushRole | Role): true | 'user hierarchy' | 'role managed' | 'client hierarchy' { @@ -217,47 +261,66 @@ export class BushGuildMember extends GuildMember { if (!muteRole) return 'invalid mute role'; if (muteRole.position >= this.guild.me!.roles.highest.position || muteRole.managed) return 'mute role not manageable'; + let caseID: string | undefined = undefined; + let dmSuccessEvent: boolean | undefined = undefined; const moderator = (await util.resolveNonCachedUser(options.moderator ?? this.guild.me))!; - // add role - const muteSuccess = await this.roles - .add(muteRole, `[Mute] ${moderator.tag} | ${options.reason ?? 'No reason provided.'}`) - .catch(async (e) => { - await client.console.warn('muteRoleAddError', e); - client.console.debug(e); - return false; - }); - if (!muteSuccess) return 'error giving mute role'; - - // add modlog entry - const { log: modlog } = await util.createModLogEntry({ - type: options.duration ? ModLogType.TEMP_MUTE : ModLogType.PERM_MUTE, - user: this, - moderator: moderator.id, - reason: options.reason, - duration: options.duration, - guild: this.guild - }); - - if (!modlog) return 'error creating modlog entry'; - - // add punishment entry so they can be unmuted later - const punishmentEntrySuccess = await util.createPunishmentEntry({ - type: 'mute', - user: this, - guild: this.guild, - duration: options.duration, - modlog: modlog.id - }); + const ret = await (async () => { + // add role + const muteSuccess = await this.roles + .add(muteRole, `[Mute] ${moderator.tag} | ${options.reason ?? 'No reason provided.'}`) + .catch(async (e) => { + await client.console.warn('muteRoleAddError', e); + client.console.debug(e); + return false; + }); + if (!muteSuccess) return 'error giving mute role'; - if (!punishmentEntrySuccess) return 'error creating mute entry'; + // add modlog entry + const { log: modlog } = await util.createModLogEntry({ + type: options.duration ? ModLogType.TEMP_MUTE : ModLogType.PERM_MUTE, + user: this, + moderator: moderator.id, + reason: options.reason, + duration: options.duration, + guild: this.guild + }); - // dm user - const dmSuccess = await this.punishDM('muted', options.reason, options.duration ?? 0); + if (!modlog) return 'error creating modlog entry'; + caseID = modlog.id; - if (!dmSuccess) return 'failed to dm'; + // add punishment entry so they can be unmuted later + const punishmentEntrySuccess = await util.createPunishmentEntry({ + type: 'mute', + user: this, + guild: this.guild, + duration: options.duration, + modlog: modlog.id + }); - return 'success'; + if (!punishmentEntrySuccess) return 'error creating mute entry'; + + // dm user + const dmSuccess = await this.punishDM('muted', options.reason, options.duration ?? 0); + dmSuccessEvent = dmSuccess; + + if (!dmSuccess) return 'failed to dm'; + + return 'success'; + })(); + + if (!['error giving mute role', 'error creating modlog entry', 'error creating mute entry'].includes(ret)) + client.emit( + 'bushMute', + this, + moderator, + this.guild, + options.reason ?? undefined, + caseID!, + options.duration ?? 0, + dmSuccessEvent! + ); + return ret; } public async unmute(options: BushPunishmentOptions): Promise { @@ -269,110 +332,145 @@ export class BushGuildMember extends GuildMember { if (!muteRole) return 'invalid mute role'; if (muteRole.position >= this.guild.me!.roles.highest.position || muteRole.managed) return 'mute role not manageable'; + let caseID: string | undefined = undefined; + let dmSuccessEvent: boolean | undefined = undefined; const moderator = (await util.resolveNonCachedUser(options.moderator ?? this.guild.me))!; - //remove role - const muteSuccess = await this.roles - .remove(muteRole, `[Unmute] ${moderator.tag} | ${options.reason ?? 'No reason provided.'}`) - .catch(async (e) => { - await client.console.warn('muteRoleAddError', e?.stack || e); - return false; + const ret = await (async () => { + //remove role + const muteSuccess = await this.roles + .remove(muteRole, `[Unmute] ${moderator.tag} | ${options.reason ?? 'No reason provided.'}`) + .catch(async (e) => { + await client.console.warn('muteRoleAddError', e?.stack || e); + return false; + }); + if (!muteSuccess) return 'error removing mute role'; + + //remove modlog entry + const { log: modlog } = await util.createModLogEntry({ + type: ModLogType.UNMUTE, + user: this, + moderator: moderator.id, + reason: options.reason, + guild: this.guild }); - if (!muteSuccess) return 'error removing mute role'; - //remove modlog entry - const { log: modlog } = await util.createModLogEntry({ - type: ModLogType.UNMUTE, - user: this, - moderator: moderator.id, - reason: options.reason, - guild: this.guild - }); + if (!modlog) return 'error creating modlog entry'; + caseID = modlog.id; - if (!modlog) return 'error creating modlog entry'; + // remove mute entry + const removePunishmentEntrySuccess = await util.removePunishmentEntry({ + type: 'mute', + user: this, + guild: this.guild + }); - // remove mute entry - const removePunishmentEntrySuccess = await util.removePunishmentEntry({ - type: 'mute', - user: this, - guild: this.guild - }); + if (!removePunishmentEntrySuccess) return 'error removing mute entry'; - if (!removePunishmentEntrySuccess) return 'error removing mute entry'; + //dm user + const dmSuccess = await this.punishDM('unmuted', options.reason, undefined, false); + dmSuccessEvent = dmSuccess; - //dm user - const dmSuccess = await this.punishDM('unmuted', options.reason, undefined, false); + if (!dmSuccess) return 'failed to dm'; - if (!dmSuccess) return 'failed to dm'; + return 'success'; + })(); - return 'success'; + if (!['error removing mute role', 'error creating modlog entry', 'error removing mute entry'].includes(ret)) + client.emit('bushUnmute', this, moderator, this.guild, options.reason ?? undefined, caseID!, dmSuccessEvent!); + return ret; } public async bushKick(options: BushPunishmentOptions): Promise { // checks if (!this.guild.me?.permissions.has('KICK_MEMBERS') || !this.kickable) return 'missing permissions'; + let caseID: string | undefined = undefined; + let dmSuccessEvent: boolean | undefined = undefined; const moderator = (await util.resolveNonCachedUser(options.moderator ?? this.guild.me))!; + const ret = await (async () => { + // dm user + const dmSuccess = await this.punishDM('kicked', options.reason); + dmSuccessEvent = dmSuccess; - // dm user - const dmSuccess = await this.punishDM('kicked', options.reason); - - // kick - const kickSuccess = await this.kick(`${moderator?.tag} | ${options.reason ?? 'No reason provided.'}`).catch(() => false); - if (!kickSuccess) return 'error kicking'; - - // add modlog entry - const { log: modlog } = await util.createModLogEntry({ - type: ModLogType.KICK, - user: this, - moderator: moderator.id, - reason: options.reason, - guild: this.guild - }); - if (!modlog) return 'error creating modlog entry'; - if (!dmSuccess) return 'failed to dm'; - return 'success'; + // kick + const kickSuccess = await this.kick(`${moderator?.tag} | ${options.reason ?? 'No reason provided.'}`).catch(() => false); + if (!kickSuccess) return 'error kicking'; + + // add modlog entry + const { log: modlog } = await util.createModLogEntry({ + type: ModLogType.KICK, + user: this, + moderator: moderator.id, + reason: options.reason, + guild: this.guild + }); + if (!modlog) return 'error creating modlog entry'; + caseID = modlog.id; + if (!dmSuccess) return 'failed to dm'; + return 'success'; + })(); + if (!['error kicking', 'error creating modlog entry'].includes(ret)) + client.emit('bushKick', this, moderator, this.guild, options.reason ?? undefined, caseID!, dmSuccessEvent!); + return ret; } public async bushBan(options: BushBanOptions): Promise { // checks if (!this.guild.me!.permissions.has('BAN_MEMBERS') || !this.bannable) return 'missing permissions'; + let caseID: string | undefined = undefined; + let dmSuccessEvent: boolean | undefined = undefined; const moderator = (await util.resolveNonCachedUser(options.moderator ?? this.guild.me))!; + const ret = await (async () => { + // dm user + const dmSuccess = await this.punishDM('banned', options.reason, options.duration ?? 0); + dmSuccessEvent = dmSuccess; + + // ban + const banSuccess = await this.ban({ + reason: `${moderator.tag} | ${options.reason ?? 'No reason provided.'}`, + days: options.deleteDays + }).catch(() => false); + if (!banSuccess) return 'error banning'; + + // add modlog entry + const { log: modlog } = await util.createModLogEntry({ + type: options.duration ? ModLogType.TEMP_BAN : ModLogType.PERM_BAN, + user: this, + moderator: moderator.id, + reason: options.reason, + duration: options.duration, + guild: this.guild + }); + if (!modlog) return 'error creating modlog entry'; + caseID = modlog.id; - // dm user - const dmSuccess = await this.punishDM('banned', options.reason, options.duration ?? 0); - - // ban - const banSuccess = await this.ban({ - reason: `${moderator.tag} | ${options.reason ?? 'No reason provided.'}`, - days: options.deleteDays - }).catch(() => false); - if (!banSuccess) return 'error banning'; - - // add modlog entry - const { log: modlog } = await util.createModLogEntry({ - type: options.duration ? ModLogType.TEMP_BAN : ModLogType.PERM_BAN, - user: this, - moderator: moderator.id, - reason: options.reason, - duration: options.duration, - guild: this.guild - }); - if (!modlog) return 'error creating modlog entry'; - - // add punishment entry so they can be unbanned later - const punishmentEntrySuccess = await util.createPunishmentEntry({ - type: 'ban', - user: this, - guild: this.guild, - duration: options.duration, - modlog: modlog.id - }); - if (!punishmentEntrySuccess) return 'error creating ban entry'; - - if (!dmSuccess) return 'failed to dm'; - return 'success'; + // add punishment entry so they can be unbanned later + const punishmentEntrySuccess = await util.createPunishmentEntry({ + type: 'ban', + user: this, + guild: this.guild, + duration: options.duration, + modlog: modlog.id + }); + if (!punishmentEntrySuccess) return 'error creating ban entry'; + + if (!dmSuccess) return 'failed to dm'; + return 'success'; + })(); + if (!['error banning', 'error creating modlog entry', 'error creating ban entry'].includes(ret)) + client.emit( + 'bushBan', + this, + moderator, + this.guild, + options.reason ?? undefined, + caseID!, + options.duration ?? 0, + dmSuccessEvent! + ); + return ret; } public get isOwner(): boolean { diff --git a/src/lib/models/Guild.ts b/src/lib/models/Guild.ts index 6933794..a4780fd 100644 --- a/src/lib/models/Guild.ts +++ b/src/lib/models/Guild.ts @@ -99,7 +99,7 @@ export const guildLogsObj = { }, moderation: { description: 'Sends a message in this channel every time a moderation action is performed.', - configurable: false + configurable: true }, report: { description: 'Logs user reports.', diff --git a/src/lib/models/ModLog.ts b/src/lib/models/ModLog.ts index 0be1ea7..5c87331 100644 --- a/src/lib/models/ModLog.ts +++ b/src/lib/models/ModLog.ts @@ -2,7 +2,7 @@ import { Snowflake } from 'discord.js'; import { DataTypes, Sequelize } from 'sequelize'; import { v4 as uuidv4 } from 'uuid'; import { BaseModel } from './BaseModel'; -import { NEVER_USED } from './__helpers'; +import { jsonParseGet, jsonParseSet, NEVER_USED } from './__helpers'; export enum ModLogType { PERM_BAN = 'PERM_BAN', @@ -30,6 +30,8 @@ export interface ModLogModel { duration: number | null; guild: Snowflake; evidence: string; + pseudo: boolean; + hidden: boolean; } export interface ModLogModelCreationAttributes { @@ -41,6 +43,8 @@ export interface ModLogModelCreationAttributes { duration?: number; guild: Snowflake; evidence?: string; + pseudo?: boolean; + hidden?: boolean; } export class ModLog extends BaseModel implements ModLogModel { @@ -124,6 +128,26 @@ export class ModLog extends BaseModel