aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIRONM00N <64110067+IRONM00N@users.noreply.github.com>2021-09-05 20:24:50 -0400
committerIRONM00N <64110067+IRONM00N@users.noreply.github.com>2021-09-05 20:24:50 -0400
commitc4c1d9ffeb179e208792c88dd099caea5030581b (patch)
treedc075bda115de5f6cec925c398f3c9547d1bad55 /src
parentc238b0279c7686ca45506b0909e376f241cf0e30 (diff)
downloadtanzanite-c4c1d9ffeb179e208792c88dd099caea5030581b.tar.gz
tanzanite-c4c1d9ffeb179e208792c88dd099caea5030581b.tar.bz2
tanzanite-c4c1d9ffeb179e208792c88dd099caea5030581b.zip
add moderation logging, fixes, hide modlog, jank
Diffstat (limited to 'src')
-rw-r--r--src/commands/_fake-command/ironmoon.ts2
-rw-r--r--src/commands/config/log.ts2
-rw-r--r--src/commands/info/help.ts4
-rw-r--r--src/commands/moderation/ban.ts2
-rw-r--r--src/commands/moderation/hideCase.ts50
-rw-r--r--src/commands/moderation/modlog.ts45
-rw-r--r--src/commands/moderation/purge.ts9
-rw-r--r--src/commands/moderation/unban.ts2
-rw-r--r--src/commands/moulberry-bush/report.ts11
-rw-r--r--src/lib/extensions/discord-akairo/BushClient.ts46
-rw-r--r--src/lib/extensions/discord-akairo/BushClientUtil.ts9
-rw-r--r--src/lib/extensions/discord-akairo/BushCommand.ts8
-rw-r--r--src/lib/extensions/discord.js/BushClientEvents.d.ts139
-rw-r--r--src/lib/extensions/discord.js/BushGuild.ts171
-rw-r--r--src/lib/extensions/discord.js/BushGuildMember.ts454
-rw-r--r--src/lib/models/Guild.ts2
-rw-r--r--src/lib/models/ModLog.ts48
-rw-r--r--src/lib/utils/BushConstants.ts6
-rw-r--r--src/listeners/custom/bushBan.ts34
-rw-r--r--src/listeners/custom/bushKick.ts33
-rw-r--r--src/listeners/custom/bushMute.ts34
-rw-r--r--src/listeners/custom/bushPunishRole.ts33
-rw-r--r--src/listeners/custom/bushPunishRoleRemove.ts34
-rw-r--r--src/listeners/custom/bushPurge.ts43
-rw-r--r--src/listeners/custom/bushUnban.ts33
-rw-r--r--src/listeners/custom/bushUnmute.ts33
-rw-r--r--src/listeners/custom/bushWarn.ts33
-rw-r--r--src/listeners/message/automodCreate.ts11
-rw-r--r--src/listeners/other/promiseRejection.ts7
-rw-r--r--src/tasks/removeExpiredPunishements.ts2
30 files changed, 1024 insertions, 316 deletions
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 <command>`);
}
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 <caseID>',
+ 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<unknown> {
+ 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 <search>',
+ usage: 'modlogs <search> [--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<unknown> {
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<boolean>();
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<Ready extends boolean = boolean> extends AkairoClient<Re
public logger = BushLogger;
public constants = BushConstants;
public cache = BushCache;
+
+ public override on<K extends keyof BushClientEvents>(
+ event: K,
+ listener: (...args: BushClientEvents[K]) => Awaited<void>
+ ): this;
+ public override on<S extends string | symbol>(
+ event: Exclude<S, keyof BushClientEvents>,
+ listener: (...args: any[]) => Awaited<void>
+ ): this {
+ return super.on(event as any, listener);
+ }
+
+ public override once<K extends keyof BushClientEvents>(
+ event: K,
+ listener: (...args: BushClientEvents[K]) => Awaited<void>
+ ): this;
+ public override once<S extends string | symbol>(
+ event: Exclude<S, keyof BushClientEvents>,
+ listener: (...args: any[]) => Awaited<void>
+ ): this {
+ return super.once(event as any, listener);
+ }
+
+ public override emit<K extends keyof BushClientEvents>(event: K, ...args: BushClientEvents[K]): boolean;
+ public override emit<S extends string | symbol>(event: Exclude<S, keyof BushClientEvents>, ...args: unknown[]): boolean {
+ return super.emit(event as any, ...args);
+ }
+
+ public override off<K extends keyof BushClientEvents>(
+ event: K,
+ listener: (...args: BushClientEvents[K]) => Awaited<void>
+ ): this;
+ public override off<S extends string | symbol>(
+ event: Exclude<S, keyof BushClientEvents>,
+ listener: (...args: any[]) => Awaited<void>
+ ): this {
+ return super.off(event as any, listener);
+ }
+
+ public override removeAllListeners<K extends keyof BushClientEvents>(event?: K): this;
+ public override removeAllListeners<S extends string | symbol>(event?: Exclude<S, keyof BushClientEvents>): 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<User | undefined> {
+ 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
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<Snowflake, BushGuildMember>,
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<Snowflake, BushMessage | PartialBushMessage>];
- 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<Snowflake, BushMessage | PartialBushMessage>
+ ];
+ 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<Snowflake, BushThreadChannel>];
- threadMemberUpdate: [oldMember: BushThreadMember, newMember: BushThreadMember];
+ threadMemberUpdate: [
+ oldMember: BushThreadMember,
+ newMember: BushThreadMember
+ ];
threadMembersUpdate: [
oldMembers: Collection<Snowflake, BushThreadMember>,
mewMembers: Collection<Snowflake, BushThreadMember>
@@ -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<Snowflake, BushMessage>
+ ];
+ 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<BushTextChannel | undefined> {
+ 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<AddRoleResponse> {
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<RemoveRoleResponse> {
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<UnmuteResponse> {
@@ -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<KickResponse> {
// 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<BanResponse> {
// 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<ModLogModel, ModLogModelCreationAttributes> implements ModLogModel {
@@ -124,6 +128,26 @@ export class ModLog extends BaseModel<ModLogModel, ModLogModelCreationAttributes
throw new Error(NEVER_USED);
}
+ /**
+ * Not an actual modlog just used so a punishment entry can be made
+ */
+ public get pseudo(): boolean {
+ throw new Error(NEVER_USED);
+ }
+ public set pseudo(_: boolean) {
+ throw new Error(NEVER_USED);
+ }
+
+ /**
+ * Hides from the modlog command unless show hidden is specified.
+ */
+ public get hidden(): boolean {
+ throw new Error(NEVER_USED);
+ }
+ public set hidden(_: boolean) {
+ throw new Error(NEVER_USED);
+ }
+
public static initModel(sequelize: Sequelize): void {
ModLog.init(
{
@@ -163,6 +187,28 @@ export class ModLog extends BaseModel<ModLogModel, ModLogModelCreationAttributes
evidence: {
type: DataTypes.TEXT,
allowNull: true
+ },
+ pseudo: {
+ type: DataTypes.STRING,
+ get: function (): boolean {
+ return jsonParseGet('pseudo', this);
+ },
+ set: function (val: boolean) {
+ return jsonParseSet('pseudo', this, val);
+ },
+ allowNull: false,
+ defaultValue: 'false'
+ },
+ hidden: {
+ type: DataTypes.STRING,
+ get: function (): boolean {
+ return jsonParseGet('hidden', this);
+ },
+ set: function (val: boolean) {
+ return jsonParseSet('hidden', this, val);
+ },
+ allowNull: false,
+ defaultValue: 'false'
}
},
{ sequelize: sequelize }
diff --git a/src/lib/utils/BushConstants.ts b/src/lib/utils/BushConstants.ts
index a8f75fa..f085d26 100644
--- a/src/lib/utils/BushConstants.ts
+++ b/src/lib/utils/BushConstants.ts
@@ -1,3 +1,5 @@
+import { Constants, ConstantsColors } from 'discord.js';
+
interface bushColors {
default: '#1FD8F1';
error: '#EF4947';
@@ -21,6 +23,7 @@ interface bushColors {
darkGray: '#7a7a7a';
black: '#000000';
orange: '#E86100';
+ discord: ConstantsColors;
}
export type PronounCode =
@@ -110,7 +113,8 @@ export class BushConstants {
lightGray: '#cfcfcf',
darkGray: '#7a7a7a',
black: '#000000',
- orange: '#E86100'
+ orange: '#E86100',
+ discord: Constants.Colors
};
// Somewhat stolen from @Mzato0001
diff --git a/src/listeners/custom/bushBan.ts b/src/listeners/custom/bushBan.ts
new file mode 100644
index 0000000..a7f30a2
--- /dev/null
+++ b/src/listeners/custom/bushBan.ts
@@ -0,0 +1,34 @@
+import { BushListener } from '@lib';
+import { GuildMember, MessageEmbed } from 'discord.js';
+import { BushClientEvents } from '../../lib/extensions/discord.js/BushClientEvents';
+
+export default class BushBanListener extends BushListener {
+ public constructor() {
+ super('bushBan', {
+ emitter: 'client',
+ event: 'bushBan',
+ category: 'custom'
+ });
+ }
+
+ public override async exec(
+ ...[victim, moderator, guild, reason, caseID, duration, dmSuccess]: BushClientEvents['bushBan']
+ ): Promise<unknown> {
+ const logChannel = await guild.getLogChannel('moderation');
+ if (!logChannel) return;
+ const user = victim instanceof GuildMember ? victim.user : victim;
+
+ const logEmbed = new MessageEmbed()
+ .setColor(util.colors.discord.RED)
+ .setTimestamp()
+ .setFooter(`CaseID: ${caseID}`)
+ .setAuthor(user.tag, user.avatarURL({ dynamic: true, format: 'png', size: 4096 }) ?? undefined)
+ .addField('**Action**', `${duration ? 'Temp Ban' : 'Perm Ban'}`, true)
+ .addField('**User**', `${user} (${user.tag})`, true)
+ .addField('**Moderator**', `${moderator} (${moderator.tag})`, true)
+ .addField('**Reason**', `${reason ?? '[No Reason Provided]'}`, true);
+ if (duration) logEmbed.addField('**Duration**', util.humanizeDuration(duration), true);
+ if (dmSuccess === false) logEmbed.addField('**Additional Info**', 'Could not dm user.');
+ return await logChannel.send({ embeds: [logEmbed] });
+ }
+}
diff --git a/src/listeners/custom/bushKick.ts b/src/listeners/custom/bushKick.ts
new file mode 100644
index 0000000..3e586f1
--- /dev/null
+++ b/src/listeners/custom/bushKick.ts
@@ -0,0 +1,33 @@
+import { BushListener } from '@lib';
+import { GuildMember, MessageEmbed } from 'discord.js';
+import { BushClientEvents } from '../../lib/extensions/discord.js/BushClientEvents';
+
+export default class BushKickListener extends BushListener {
+ public constructor() {
+ super('bushKick', {
+ emitter: 'client',
+ event: 'bushKick',
+ category: 'custom'
+ });
+ }
+
+ public override async exec(
+ ...[victim, moderator, guild, reason, caseID, dmSuccess]: BushClientEvents['bushKick']
+ ): Promise<unknown> {
+ const logChannel = await guild.getLogChannel('moderation');
+ if (!logChannel) return;
+ const user = victim instanceof GuildMember ? victim.user : victim;
+
+ const logEmbed = new MessageEmbed()
+ .setColor(util.colors.discord.RED)
+ .setTimestamp()
+ .setFooter(`CaseID: ${caseID}`)
+ .setAuthor(user.tag, user.avatarURL({ dynamic: true, format: 'png', size: 4096 }) ?? undefined)
+ .addField('**Action**', `${'Kick'}`, true)
+ .addField('**User**', `${user} (${user.tag})`, true)
+ .addField('**Moderator**', `${moderator} (${moderator.tag})`, true)
+ .addField('**Reason**', `${reason ?? '[No Reason Provided]'}`, true);
+ if (dmSuccess === false) logEmbed.addField('**Additional Info**', 'Could not dm user.');
+ return await logChannel.send({ embeds: [logEmbed] });
+ }
+}
diff --git a/src/listeners/custom/bushMute.ts b/src/listeners/custom/bushMute.ts
new file mode 100644
index 0000000..9513c20
--- /dev/null
+++ b/src/listeners/custom/bushMute.ts
@@ -0,0 +1,34 @@
+import { BushListener } from '@lib';
+import { GuildMember, MessageEmbed } from 'discord.js';
+import { BushClientEvents } from '../../lib/extensions/discord.js/BushClientEvents';
+
+export default class BushMuteListener extends BushListener {
+ public constructor() {
+ super('bushMute', {
+ emitter: 'client',
+ event: 'bushMute',
+ category: 'custom'
+ });
+ }
+
+ public override async exec(
+ ...[victim, moderator, guild, reason, caseID, duration, dmSuccess]: BushClientEvents['bushMute']
+ ): Promise<unknown> {
+ const logChannel = await guild.getLogChannel('moderation');
+ if (!logChannel) return;
+ const user = victim instanceof GuildMember ? victim.user : victim;
+
+ const logEmbed = new MessageEmbed()
+ .setColor(util.colors.discord.ORANGE)
+ .setTimestamp()
+ .setFooter(`CaseID: ${caseID}`)
+ .setAuthor(user.tag, user.avatarURL({ dynamic: true, format: 'png', size: 4096 }) ?? undefined)
+ .addField('**Action**', `${duration ? 'Temp Mute' : 'Perm Mute'}`, true)
+ .addField('**User**', `${user} (${user.tag})`, true)
+ .addField('**Moderator**', `${moderator} (${moderator.tag})`, true)
+ .addField('**Reason**', `${reason ?? '[No Reason Provided]'}`, true);
+ if (duration) logEmbed.addField('**Duration**', util.humanizeDuration(duration), true);
+ if (dmSuccess === false) logEmbed.addField('**Additional Info**', 'Could not dm user.');
+ return await logChannel.send({ embeds: [logEmbed] });
+ }
+}
diff --git a/src/listeners/custom/bushPunishRole.ts b/src/listeners/custom/bushPunishRole.ts
new file mode 100644
index 0000000..3e7e98f
--- /dev/null
+++ b/src/listeners/custom/bushPunishRole.ts
@@ -0,0 +1,33 @@
+import { BushListener } from '@lib';
+import { GuildMember, MessageEmbed } from 'discord.js';
+import { BushClientEvents } from '../../lib/extensions/discord.js/BushClientEvents';
+
+export default class BushPunishRoleListener extends BushListener {
+ public constructor() {
+ super('bushPunishRole', {
+ emitter: 'client',
+ event: 'bushPunishRole',
+ category: 'custom'
+ });
+ }
+
+ public override async exec(
+ ...[victim, moderator, guild, reason, caseID, duration]: BushClientEvents['bushPunishRole']
+ ): Promise<unknown> {
+ const logChannel = await guild.getLogChannel('moderation');
+ if (!logChannel) return;
+ const user = victim instanceof GuildMember ? victim.user : victim;
+
+ const logEmbed = new MessageEmbed()
+ .setColor(util.colors.discord.YELLOW)
+ .setTimestamp()
+ .setFooter(`CaseID: ${caseID}`)
+ .setAuthor(user.tag, user.avatarURL({ dynamic: true, format: 'png', size: 4096 }) ?? undefined)
+ .addField('**Action**', `${duration ? 'Temp Punishment Role' : 'Perm Punishment Role'}`, true)
+ .addField('**User**', `${user} (${user.tag})`, true)
+ .addField('**Moderator**', `${moderator} (${moderator.tag})`, true)
+ .addField('**Reason**', `${reason ?? '[No Reason Provided]'}`, true);
+ if (duration) logEmbed.addField('**Duration**', util.humanizeDuration(duration), true);
+ return await logChannel.send({ embeds: [logEmbed] });
+ }
+}
diff --git a/src/listeners/custom/bushPunishRoleRemove.ts b/src/listeners/custom/bushPunishRoleRemove.ts
new file mode 100644
index 0000000..04d7244
--- /dev/null
+++ b/src/listeners/custom/bushPunishRoleRemove.ts
@@ -0,0 +1,34 @@
+import { BushListener } from '@lib';
+import { GuildMember, MessageEmbed } from 'discord.js';
+import { BushClientEvents } from '../../lib/extensions/discord.js/BushClientEvents';
+
+export default class BushPunishRoleRemoveListener extends BushListener {
+ public constructor() {
+ super('bushPunishRoleRemove', {
+ emitter: 'client',
+ event: 'bushPunishRoleRemove',
+ category: 'custom'
+ });
+ }
+
+ public override async exec(
+ ...[victim, moderator, guild, reason, caseID, role]: BushClientEvents['bushPunishRoleRemove']
+ ): Promise<unknown> {
+ const logChannel = await guild.getLogChannel('moderation');
+ if (!logChannel) return;
+ const user = victim instanceof GuildMember ? victim.user : victim;
+
+ const logEmbed = new MessageEmbed()
+ .setColor(util.colors.discord.GREEN)
+ .setTimestamp()
+ .setFooter(`CaseID: ${caseID}`)
+ .setAuthor(user.tag, user.avatarURL({ dynamic: true, format: 'png', size: 4096 }) ?? undefined)
+ .addField('**Action**', `${'Remove Punishment Role'}`, true)
+ .addField('**Role**', `${role}`, true)
+ .addField('**User**', `${user} (${user.tag})`, true)
+ .addField('**Moderator**', `${moderator} (${moderator.tag})`, true)
+ .addField('**Reason**', `${reason ?? '[No Reason Provided]'}`, true);
+
+ return await logChannel.send({ embeds: [logEmbed] });
+ }
+}
diff --git a/src/listeners/custom/bushPurge.ts b/src/listeners/custom/bushPurge.ts
new file mode 100644
index 0000000..cc55fc4
--- /dev/null
+++ b/src/listeners/custom/bushPurge.ts
@@ -0,0 +1,43 @@
+import { BushListener } from '@lib';
+import { MessageEmbed } from 'discord.js';
+import { BushClientEvents } from '../../lib/extensions/discord.js/BushClientEvents';
+
+export default class BushPurgeListener extends BushListener {
+ public constructor() {
+ super('bushPurge', {
+ emitter: 'client',
+ event: 'bushPurge',
+ category: 'custom'
+ });
+ }
+
+ public override async exec(...[moderator, guild, channel, messages]: BushClientEvents['bushPurge']): Promise<unknown> {
+ const logChannel = await guild.getLogChannel('moderation');
+ if (!logChannel) return;
+
+ const mappedMessages = messages.map((m) => ({
+ id: m.id,
+ author: `${m.author.tag} (${m.id})`,
+ content: m.content,
+ embeds: m.embeds,
+ attachments: m.attachments
+ }));
+ const haste = await util.inspectCleanRedactHaste(mappedMessages);
+
+ const logEmbed = new MessageEmbed()
+ .setColor(util.colors.discord.DARK_PURPLE)
+ .setTimestamp()
+ .setFooter(`${messages.size.toLocaleString()} Messages`)
+ .addField('**Action**', `${'Purge'}`, true)
+ .addField('**Moderator**', `${moderator} (${moderator.tag})`, true)
+ .addField('**Channel**', `<#${channel.id}> (${channel.name})`, true)
+ .addField(
+ '**Messages**',
+ `${
+ haste.url ? `[haste](${haste.url})${haste.error ? `- ${haste.error}` : ''}` : `${util.emojis.error} ${haste.error}`
+ }`,
+ true
+ );
+ return await logChannel.send({ embeds: [logEmbed] });
+ }
+}
diff --git a/src/listeners/custom/bushUnban.ts b/src/listeners/custom/bushUnban.ts
new file mode 100644
index 0000000..ad82979
--- /dev/null
+++ b/src/listeners/custom/bushUnban.ts
@@ -0,0 +1,33 @@
+import { BushListener } from '@lib';
+import { GuildMember, MessageEmbed } from 'discord.js';
+import { BushClientEvents } from '../../lib/extensions/discord.js/BushClientEvents';
+
+export default class BushUnbanListener extends BushListener {
+ public constructor() {
+ super('bushUnban', {
+ emitter: 'client',
+ event: 'bushUnban',
+ category: 'custom'
+ });
+ }
+
+ public override async exec(
+ ...[victim, moderator, guild, reason, caseID, dmSuccess]: BushClientEvents['bushUnban']
+ ): Promise<unknown> {
+ const logChannel = await guild.getLogChannel('moderation');
+ if (!logChannel) return;
+ const user = victim instanceof GuildMember ? victim.user : victim;
+
+ const logEmbed = new MessageEmbed()
+ .setColor(util.colors.discord.GREEN)
+ .setTimestamp()
+ .setFooter(`CaseID: ${caseID}`)
+ .setAuthor(user.tag, user.avatarURL({ dynamic: true, format: 'png', size: 4096 }) ?? undefined)
+ .addField('**Action**', `${'Unban'}`, true)
+ .addField('**User**', `${user} (${user.tag})`, true)
+ .addField('**Moderator**', `${moderator} (${moderator.tag})`, true)
+ .addField('**Reason**', `${reason ?? '[No Reason Provided]'}`, true);
+ if (dmSuccess === false) logEmbed.addField('**Additional Info**', 'Could not dm user.');
+ return await logChannel.send({ embeds: [logEmbed] });
+ }
+}
diff --git a/src/listeners/custom/bushUnmute.ts b/src/listeners/custom/bushUnmute.ts
new file mode 100644
index 0000000..8beb27b
--- /dev/null
+++ b/src/listeners/custom/bushUnmute.ts
@@ -0,0 +1,33 @@
+import { BushListener } from '@lib';
+import { GuildMember, MessageEmbed } from 'discord.js';
+import { BushClientEvents } from '../../lib/extensions/discord.js/BushClientEvents';
+
+export default class BushUnmuteListener extends BushListener {
+ public constructor() {
+ super('bushUnmute', {
+ emitter: 'client',
+ event: 'bushUnmute',
+ category: 'custom'
+ });
+ }
+
+ public override async exec(
+ ...[victim, moderator, guild, reason, caseID, dmSuccess]: BushClientEvents['bushUnmute']
+ ): Promise<unknown> {
+ const logChannel = await guild.getLogChannel('moderation');
+ if (!logChannel) return;
+ const user = victim instanceof GuildMember ? victim.user : victim;
+
+ const logEmbed = new MessageEmbed()
+ .setColor(util.colors.discord.GREEN)
+ .setTimestamp()
+ .setFooter(`CaseID: ${caseID}`)
+ .setAuthor(user.tag, user.avatarURL({ dynamic: true, format: 'png', size: 4096 }) ?? undefined)
+ .addField('**Action**', `${'Unmute'}`, true)
+ .addField('**User**', `${user} (${user.tag})`, true)
+ .addField('**Moderator**', `${moderator} (${moderator.tag})`, true)
+ .addField('**Reason**', `${reason ?? '[No Reason Provided]'}`, true);
+ if (dmSuccess === false) logEmbed.addField('**Additional Info**', 'Could not dm user.');
+ return await logChannel.send({ embeds: [logEmbed] });
+ }
+}
diff --git a/src/listeners/custom/bushWarn.ts b/src/listeners/custom/bushWarn.ts
new file mode 100644
index 0000000..40c477c
--- /dev/null
+++ b/src/listeners/custom/bushWarn.ts
@@ -0,0 +1,33 @@
+import { BushListener } from '@lib';
+import { GuildMember, MessageEmbed } from 'discord.js';
+import { BushClientEvents } from '../../lib/extensions/discord.js/BushClientEvents';
+
+export default class BushWarnListener extends BushListener {
+ public constructor() {
+ super('bushWarn', {
+ emitter: 'client',
+ event: 'bushWarn',
+ category: 'custom'
+ });
+ }
+
+ public override async exec(
+ ...[victim, moderator, guild, reason, caseID, dmSuccess]: BushClientEvents['bushWarn']
+ ): Promise<unknown> {
+ const logChannel = await guild.getLogChannel('moderation');
+ if (!logChannel) return;
+ const user = victim instanceof GuildMember ? victim.user : victim;
+
+ const logEmbed = new MessageEmbed()
+ .setColor(util.colors.discord.YELLOW)
+ .setTimestamp()
+ .setFooter(`CaseID: ${caseID}`)
+ .setAuthor(user.tag, user.avatarURL({ dynamic: true, format: 'png', size: 4096 }) ?? undefined)
+ .addField('**Action**', `${'Warn'}`, true)
+ .addField('**User**', `${user} (${user.tag})`, true)
+ .addField('**Moderator**', `${moderator} (${moderator.tag})`, true)
+ .addField('**Reason**', `${reason ?? '[No Reason Provided]'}`, true);
+ if (dmSuccess === false) logEmbed.addField('**Additional Info**', 'Could not dm user.');
+ return await logChannel.send({ embeds: [logEmbed] });
+ }
+}
diff --git a/src/listeners/message/automodCreate.ts b/src/listeners/message/automodCreate.ts
index 94b73c7..ae1bd21 100644
--- a/src/listeners/message/automodCreate.ts
+++ b/src/listeners/message/automodCreate.ts
@@ -5,7 +5,7 @@ import _badLinks from '@root/lib/badlinks'; // Stolen from https://github.com/na
import _badLinksSecret from '@root/lib/badlinks-secret'; // shhhh
// @ts-expect-error: ts doesn't recognize json5
import badWords from '@root/lib/badwords';
-import { MessageEmbed, TextChannel } from 'discord.js';
+import { MessageEmbed } from 'discord.js';
import { BushClientEvents } from '../../lib/extensions/discord.js/BushClientEvents';
export default class AutomodMessageCreateListener extends BushListener {
@@ -100,14 +100,11 @@ export default class AutomodMessageCreateListener extends BushListener {
? util.colors.orange
: util.colors.red;
- const automodChannel = (await message.guild.getSetting('logChannels')).automod;
+ const automodChannel = await message.guild.getLogChannel('automod');
if (!automodChannel) return;
- const fetchedChannel = (message.guild.channels.cache.get(automodChannel) ??
- (await message.guild.channels.fetch(automodChannel).catch(() => null))) as TextChannel;
- if (!fetchedChannel) return;
- if (fetchedChannel.permissionsFor(message.guild.me!.id)?.has(['VIEW_CHANNEL', 'SEND_MESSAGES', 'EMBED_LINKS']))
- void fetchedChannel.send({
+ if (automodChannel.permissionsFor(message.guild.me!.id)?.has(['VIEW_CHANNEL', 'SEND_MESSAGES', 'EMBED_LINKS']))
+ void automodChannel.send({
embeds: [
new MessageEmbed()
.setTitle(`[Severity ${highestOffence}] Automod Action Performed`)
diff --git a/src/listeners/other/promiseRejection.ts b/src/listeners/other/promiseRejection.ts
index 8785b78..130daa3 100644
--- a/src/listeners/other/promiseRejection.ts
+++ b/src/listeners/other/promiseRejection.ts
@@ -12,8 +12,9 @@ export default class PromiseRejectionListener extends BushListener {
public override async exec(error: Error): Promise<void> {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
void client.console.error('promiseRejection', `An unhanded promise rejection occurred:\n${error.stack ?? error}`, false);
- void client.console.channelError({
- embeds: [await CommandErrorListener.generateErrorEmbed({ type: 'unhandledRejection', error: error })]
- });
+ if (!error.message.includes('reason: getaddrinfo ENOTFOUND canary.discord.com'))
+ void client.console.channelError({
+ embeds: [await CommandErrorListener.generateErrorEmbed({ type: 'unhandledRejection', error: error })]
+ });
}
}
diff --git a/src/tasks/removeExpiredPunishements.ts b/src/tasks/removeExpiredPunishements.ts
index 49267f5..0610718 100644
--- a/src/tasks/removeExpiredPunishements.ts
+++ b/src/tasks/removeExpiredPunishements.ts
@@ -33,7 +33,7 @@ export default class RemoveExpiredPunishmentsTask extends BushTask {
switch (entry.type) {
case ActivePunishmentType.BAN: {
if (!user) throw new Error(`user is undefined`);
- const result = await guild.unban({ user: user, reason: 'Punishment expired.' });
+ const result = await guild.bushUnban({ user: user, reason: 'Punishment expired.' });
if (['success', 'user not banned'].includes(result)) await entry.destroy();
else throw new Error(result);
void client.logger.verbose(`removeExpiredPunishments`, `Unbanned ${entry.user}.`);