aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/commands/config/blacklist.ts2
-rw-r--r--src/commands/config/config.ts38
-rw-r--r--src/commands/config/disable.ts2
-rw-r--r--src/commands/config/features.ts3
-rw-r--r--src/commands/config/log.ts3
-rw-r--r--src/commands/info/botInfo.ts2
-rw-r--r--src/commands/moderation/evidence.ts92
-rw-r--r--src/commands/moderation/hideCase.ts14
-rw-r--r--src/lib/extensions/discord-akairo/BushClient.ts4
-rw-r--r--src/lib/extensions/discord-akairo/BushClientUtil.ts3
-rw-r--r--src/lib/extensions/discord.js/BushClientEvents.d.ts72
-rw-r--r--src/lib/extensions/discord.js/BushGuild.ts22
-rw-r--r--src/lib/models/Guild.ts12
-rw-r--r--src/listeners/client/ready.ts2
-rw-r--r--src/listeners/custom/bushUpdateModlog.ts33
-rw-r--r--src/listeners/custom/bushUpdateSettings.ts36
-rw-r--r--src/listeners/guild/guildMemberRemove.ts5
17 files changed, 258 insertions, 87 deletions
diff --git a/src/commands/config/blacklist.ts b/src/commands/config/blacklist.ts
index ff34567..5dea36a 100644
--- a/src/commands/config/blacklist.ts
+++ b/src/commands/config/blacklist.ts
@@ -115,7 +115,7 @@ export default class BlacklistCommand extends BushCommand {
targetID
);
const success = await message.guild
- .setSetting(target instanceof User ? 'blacklistedUsers' : 'blacklistedChannels', newValue)
+ .setSetting(target instanceof User ? 'blacklistedUsers' : 'blacklistedChannels', newValue, message.member!)
.catch(() => false);
if (!success)
return await message.util.reply({
diff --git a/src/commands/config/config.ts b/src/commands/config/config.ts
index 0c466e6..cba4468 100644
--- a/src/commands/config/config.ts
+++ b/src/commands/config/config.ts
@@ -49,7 +49,11 @@ export default class SettingsCommand extends BushCommand {
description: `What would you like to add to the server's ${guildSettingsObj[
setting
].name.toLowerCase()}?'`,
- type: guildSettingsObj[setting].type.replace('-array', '').toUpperCase() as 'ROLE' | 'STRING' | 'CHANNEL',
+ type: guildSettingsObj[setting].type.replace('-array', '').toUpperCase() as
+ | 'ROLE'
+ | 'STRING'
+ | 'CHANNEL'
+ | 'USER',
required: true
}
]
@@ -64,7 +68,11 @@ export default class SettingsCommand extends BushCommand {
description: `What would you like to remove from the server's ${guildSettingsObj[
setting
].name.toLowerCase()}?'`,
- type: guildSettingsObj[setting].type.replace('-array', '').toUpperCase() as 'ROLE' | 'STRING' | 'CHANNEL',
+ type: guildSettingsObj[setting].type.replace('-array', '').toUpperCase() as
+ | 'ROLE'
+ | 'STRING'
+ | 'CHANNEL'
+ | 'USER',
required: true
}
]
@@ -86,7 +94,7 @@ export default class SettingsCommand extends BushCommand {
description: `What would you like to set the server's ${guildSettingsObj[
setting
].name.toLowerCase()} to?'`,
- type: guildSettingsObj[setting].type.toUpperCase() as 'ROLE' | 'STRING' | 'CHANNEL',
+ type: guildSettingsObj[setting].type.toUpperCase() as 'ROLE' | 'STRING' | 'CHANNEL' | 'USER',
required: true
}
]
@@ -183,7 +191,7 @@ export default class SettingsCommand extends BushCommand {
if (!message.guild) return await message.util.reply(`${util.emojis.error} This command can only be used in servers.`);
if (!message.member?.permissions.has('MANAGE_GUILD'))
return await message.util.reply(
- `${util.emojis.error} You must have the **MANAGE_GUILD** permissions to run this command.`
+ `${util.emojis.error} You must have the **MANAGE_GUILD** permission to run this command.`
);
const setting = message.util.isSlash ? (_.camelCase(args.subcommandGroup)! as GuildSettings) : args.setting!;
const action = message.util.isSlash ? args.subcommand! : args.action!;
@@ -217,13 +225,13 @@ export default class SettingsCommand extends BushCommand {
case 'remove': {
const existing = (await message.guild.getSetting(setting)) as string[];
const updated = util.addOrRemoveFromArray('add', existing, parseVal(value));
- await message.guild.setSetting(setting, updated);
+ await message.guild.setSetting(setting, updated, message.member);
const messageOptions = await this.generateMessageOptions(message, setting);
msg = (await message.util.reply(messageOptions)) as Message;
break;
}
case 'set': {
- await message.guild.setSetting(setting, parseVal(value));
+ await message.guild.setSetting(setting, parseVal(value), message.member);
const messageOptions = await this.generateMessageOptions(message, setting);
msg = (await message.util.reply(messageOptions)) as Message;
break;
@@ -289,11 +297,11 @@ export default class SettingsCommand extends BushCommand {
} else {
settingsEmbed.setTitle(guildSettingsObj[setting].name);
const generateCurrentValue = async (
- type: 'string' | 'channel' | 'channel-array' | 'role' | 'role-array'
+ type: 'string' | 'channel' | 'channel-array' | 'role' | 'role-array' | 'user' | 'user-array' | 'custom'
): Promise<string> => {
const feat = await message.guild!.getSetting(setting);
- switch (type.replace('-array', '') as 'string' | 'channel' | 'role') {
+ switch (type.replace('-array', '') as 'string' | 'channel' | 'role' | 'user' | 'custom') {
case 'string': {
return Array.isArray(feat)
? feat.length
@@ -308,14 +316,24 @@ export default class SettingsCommand extends BushCommand {
? feat.length
? feat.map((feat) => `<#${feat}>`).join('\n')
: '[Empty Array]'
- : `<#${feat}>`;
+ : `<#${feat as string}>`;
}
case 'role': {
return Array.isArray(feat)
? feat.length
? feat.map((feat) => `<@&${feat}>`).join('\n')
: '[Empty Array]'
- : `<@&${feat}>`;
+ : `<@&${feat as string}>`;
+ }
+ case 'user': {
+ return Array.isArray(feat)
+ ? feat.length
+ ? feat.map((feat) => `<@${feat}>`).join('\n')
+ : '[Empty Array]'
+ : `<@${feat as string}>`;
+ }
+ case 'custom': {
+ return util.inspectAndRedact(feat);
}
}
};
diff --git a/src/commands/config/disable.ts b/src/commands/config/disable.ts
index bc6ed47..db4909a 100644
--- a/src/commands/config/disable.ts
+++ b/src/commands/config/disable.ts
@@ -103,7 +103,7 @@ export default class DisableCommand extends BushCommand {
action = disabledCommands.includes(commandID) ? 'disable' : 'enable';
}
const newValue = util.addOrRemoveFromArray(action === 'disable' ? 'remove' : 'add', disabledCommands, commandID);
- const success = await message.guild!.setSetting('disabledCommands', newValue).catch(() => false);
+ const success = await message.guild!.setSetting('disabledCommands', newValue, message.member!).catch(() => false);
if (!success)
return await message.util.reply({
content: `${util.emojis.error} There was an error **${action.substr(
diff --git a/src/commands/config/features.ts b/src/commands/config/features.ts
index 3c607c7..2169177 100644
--- a/src/commands/config/features.ts
+++ b/src/commands/config/features.ts
@@ -42,12 +42,11 @@ export default class FeaturesCommand extends BushCommand {
if (!guildFeaturesArr.includes(selected)) throw new Error('Invalid guild feature selected');
- const newEnabledFeatures = await message.guild.toggleFeature(selected);
+ const newEnabledFeatures = await message.guild.toggleFeature(selected, message.member!);
this.generateDescription(guildFeaturesArr, newEnabledFeatures, featureEmbed);
await interaction.update({ embeds: [featureEmbed] }).catch(() => undefined);
-
return;
} else {
return await interaction?.deferUpdate().catch(() => undefined);
diff --git a/src/commands/config/log.ts b/src/commands/config/log.ts
index 0bc2189..49db6f8 100644
--- a/src/commands/config/log.ts
+++ b/src/commands/config/log.ts
@@ -72,7 +72,8 @@ export default class LogCommand extends BushCommand {
action ? (currentLogs[args.log_type] = args.channel.id) : delete currentLogs[args.log_type];
- const success = await message.guild.setSetting('logChannels', currentLogs);
+ const success = await message.guild.setSetting('logChannels', currentLogs, message.member!);
+
return await message.util.reply(
`${
success
diff --git a/src/commands/info/botInfo.ts b/src/commands/info/botInfo.ts
index 45c3dd8..30bfeb4 100644
--- a/src/commands/info/botInfo.ts
+++ b/src/commands/info/botInfo.ts
@@ -39,7 +39,7 @@ export default class BotInfoCommand extends BushCommand {
repoUrl = repoUrl.substring(0, repoUrl.length - 4);
const embed = new MessageEmbed()
.setTitle('Bot Info:')
- .addField('**Uptime**', util.humanizeDuration(client.uptime!), true)
+ .addField('**Uptime**', util.humanizeDuration(client.uptime!, 2), true)
.addField(
'**Memory Usage**',
`System: ${prettyBytes(os.totalmem() - os.freemem(), { binary: true })}/${prettyBytes(os.totalmem(), {
diff --git a/src/commands/moderation/evidence.ts b/src/commands/moderation/evidence.ts
index ae0a128..71a52b2 100644
--- a/src/commands/moderation/evidence.ts
+++ b/src/commands/moderation/evidence.ts
@@ -1,4 +1,5 @@
-import { BushCommand, BushMessage, BushSlashMessage } from '@lib';
+import { BushCommand, BushMessage, BushSlashMessage, ModLog } from '@lib';
+import { ArgumentOptions, Flag } from 'discord-akairo';
export default class EvidenceCommand extends BushCommand {
public constructor() {
@@ -7,54 +8,81 @@ export default class EvidenceCommand extends BushCommand {
category: 'moderation',
description: {
content: 'Add evidence to a modlog case.',
- usage: 'evidence <caseID> <evidence>',
+ usage: 'evidence <case_id> <evidence>',
examples: ['evidence ']
},
- args: [
- {
- id: 'case',
- type: 'string',
- prompt: {
- start: 'What would you like to set your first argument to be?',
- retry: '{error} Pick a valid argument.',
- optional: false
- }
- },
- {
- id: 'evidence',
- type: 'string',
- prompt: {
- start: 'What would you like to set your second argument to be?',
- retry: '{error} Pick a valid argument.',
- optional: true
- }
- }
- ],
slash: true,
slashOptions: [
{
- name: 'case',
- description: 'What would you like to set your first argument to be?',
+ name: 'case_id',
+ description: 'What case would you like to modify the evidence of?',
type: 'STRING',
required: true
},
{
name: 'evidence',
- description: 'What would you like to set your second argument to be?',
+ description: 'What would you like to modify the evidence to?',
type: 'STRING',
- required: false
+ required: true
}
],
- superUserOnly: true,
- ownerOnly: true,
channel: 'guild',
- hidden: true,
clientPermissions: ['SEND_MESSAGES'],
- userPermissions: ['SEND_MESSAGES']
+ userPermissions: ['SEND_MESSAGES', 'MANAGE_MESSAGES']
});
}
- public override async exec(message: BushMessage | BushSlashMessage): Promise<unknown> {
- return await message.util.reply(`${util.emojis.error} Soon:tm:.`);
+ *args(message: BushMessage): IterableIterator<ArgumentOptions | Flag> {
+ const case_id = yield {
+ id: 'case_id',
+ type: 'string',
+ prompt: {
+ start: 'What case would you like to modify the evidence of?',
+ retry: '{error} Pick a valid case to modify the evidence of.',
+ optional: false
+ }
+ };
+
+ const evidence = yield {
+ id: 'evidence',
+ type: 'string',
+ match: 'restContent',
+ prompt: {
+ start: 'What would you like to modify the evidence to?',
+ retry: '{error} Pick a valid argument.',
+ optional: !!message.attachments.some((attachment) => !!attachment.contentType?.includes('image'))
+ }
+ };
+
+ return { case_id, evidence };
+ }
+
+ public override async exec(
+ message: BushMessage | BushSlashMessage,
+ { case_id: caseID, evidence }: { case_id: string; evidence?: string }
+ ): Promise<unknown> {
+ const entry = await ModLog.findByPk(caseID);
+ if (!entry || entry.pseudo) return message.util.send(`${util.emojis.error} Invalid modlog entry.`);
+ if (entry.guild !== message.guild!.id)
+ return message.util.reply(`${util.emojis.error} This modlog is from another server.`);
+
+ if (evidence && (message as BushMessage).attachments?.size)
+ return message.util.reply(`${util.emojis.error} Please either attach an image or a reason not both.`);
+
+ const _evidence = evidence
+ ? evidence
+ : !message.util.isSlash
+ ? (message as BushMessage).attachments.first()?.url
+ : undefined;
+ if (!_evidence) return message.util.reply(`${util.emojis.error} You must provide evidence for this modlog.`);
+
+ const oldEntry = entry.evidence;
+
+ entry.evidence = _evidence.trim();
+ await entry.save();
+
+ client.emit('bushUpdateModlog', message.member!, entry.id, 'evidence', oldEntry, entry.evidence);
+
+ return message.util.reply(`${util.emojis.success} Successfully updated the evidence for case \`${caseID}\`.`);
}
}
diff --git a/src/commands/moderation/hideCase.ts b/src/commands/moderation/hideCase.ts
index 6cd8e31..2529531 100644
--- a/src/commands/moderation/hideCase.ts
+++ b/src/commands/moderation/hideCase.ts
@@ -7,12 +7,12 @@ export default class HideCaseCommand extends BushCommand {
category: 'moderation',
description: {
content: 'Hide a particular modlog case from the modlog command unless the `--hidden` flag is specified',
- usage: 'hideCase <caseID>',
+ usage: 'hideCase <case_id>',
examples: ['hideCase 9210b1ea-91f5-4ea2-801b-02b394469c77']
},
args: [
{
- id: 'case',
+ id: 'case_id',
type: 'string',
prompt: {
start: 'What modlog case would you like to hide?',
@@ -24,7 +24,7 @@ export default class HideCaseCommand extends BushCommand {
slash: true,
slashOptions: [
{
- name: 'case',
+ name: 'case_id',
description: 'What modlog case would you like to hide?',
type: 'STRING',
required: true
@@ -34,7 +34,10 @@ export default class HideCaseCommand extends BushCommand {
});
}
- public override async exec(message: BushMessage | BushSlashMessage, { case: caseID }: { case: string }): Promise<unknown> {
+ public override async exec(
+ message: BushMessage | BushSlashMessage,
+ { case_id: caseID }: { case_id: 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);
@@ -42,9 +45,12 @@ export default class HideCaseCommand extends BushCommand {
if (entry.guild !== message.guild!.id)
return message.util.reply(`${util.emojis.error} This modlog is from another server.`);
const action = entry.hidden ? 'no longer hidden' : 'now hidden';
+ const oldEntry = entry.hidden;
entry.hidden = !entry.hidden;
await entry.save();
+ client.emit('bushUpdateModlog', message.member!, entry.id, 'hidden', oldEntry, entry.hidden);
+
return await message.util.reply(`${util.emojis.success} CaseID \`${caseID}\` is ${action}.`);
}
}
diff --git a/src/lib/extensions/discord-akairo/BushClient.ts b/src/lib/extensions/discord-akairo/BushClient.ts
index 59c4df8..d488525 100644
--- a/src/lib/extensions/discord-akairo/BushClient.ts
+++ b/src/lib/extensions/discord-akairo/BushClient.ts
@@ -9,6 +9,7 @@ import {
MessageEditOptions,
MessageOptions,
MessagePayload,
+ Options,
PartialDMChannel,
ReplyMessageOptions,
Snowflake,
@@ -219,7 +220,8 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re
status: 'online'
},
http: { api: 'https://canary.discord.com/api' },
- allowedMentions: AllowedMentions.users() // No everyone or role mentions by default
+ allowedMentions: AllowedMentions.users(), // No everyone or role mentions by default
+ makeCache: Options.cacheWithLimits({})
});
this.token = config.token;
diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts
index 4028517..38e1a06 100644
--- a/src/lib/extensions/discord-akairo/BushClientUtil.ts
+++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts
@@ -1169,7 +1169,8 @@ export class BushClientUtil extends ClientUtil {
if (!getCaseNumber) return { log: saveResult, caseNum: null };
- const caseNum = (await ModLog.findAll({ where: { type: options.type, user: user, guild: guild, hidden: false } }))?.length;
+ const caseNum = (await ModLog.findAll({ where: { type: options.type, user: user, guild: guild, hidden: 'false' } }))
+ ?.length;
return { log: saveResult, caseNum };
}
diff --git a/src/lib/extensions/discord.js/BushClientEvents.d.ts b/src/lib/extensions/discord.js/BushClientEvents.d.ts
index b797698..0406015 100644
--- a/src/lib/extensions/discord.js/BushClientEvents.d.ts
+++ b/src/lib/extensions/discord.js/BushClientEvents.d.ts
@@ -1,4 +1,31 @@
import {
+ BushApplicationCommand,
+ BushClient,
+ BushDMChannel,
+ BushGuild,
+ BushGuildChannel,
+ BushGuildEmoji,
+ BushGuildMember,
+ BushMessage,
+ BushMessageReaction,
+ BushNewsChannel,
+ BushPresence,
+ BushRole,
+ BushStageInstance,
+ BushTextBasedChannels,
+ BushTextChannel,
+ BushThreadChannel,
+ BushThreadMember,
+ BushUser,
+ BushVoiceState,
+ Guild,
+ GuildSettings,
+ PartialBushGuildMember,
+ PartialBushMessage,
+ PartialBushMessageReaction,
+ PartialBushUser
+} from '@lib';
+import {
ClientEvents,
Collection,
Interaction,
@@ -9,31 +36,7 @@ import {
Sticker,
Typing
} from 'discord.js';
-import {
- BushClient,
- BushTextBasedChannels
-} from '../discord-akairo/BushClient';
-import { BushApplicationCommand } from './BushApplicationCommand';
-import { BushDMChannel } from './BushDMChannel';
-import { BushGuild } from './BushGuild';
import { BushGuildBan } from './BushGuildBan';
-import { BushGuildChannel } from './BushGuildChannel';
-import { BushGuildEmoji } from './BushGuildEmoji';
-import { BushGuildMember, PartialBushGuildMember } from './BushGuildMember';
-import { BushMessage, PartialBushMessage } from './BushMessage';
-import {
- BushMessageReaction,
- PartialBushMessageReaction
-} from './BushMessageReaction';
-import { BushNewsChannel } from './BushNewsChannel';
-import { BushPresence } from './BushPresence';
-import { BushRole } from './BushRole';
-import { BushStageInstance } from './BushStageInstance';
-import { BushTextChannel } from './BushTextChannel';
-import { BushThreadChannel } from './BushThreadChannel';
-import { BushThreadMember } from './BushThreadMember';
-import { BushUser, PartialBushUser } from './BushUser';
-import { BushVoiceState } from './BushVoiceState';
export interface BushClientEvents extends ClientEvents {
applicationCommandCreate: [command: BushApplicationCommand];
@@ -205,6 +208,20 @@ export interface BushClientEvents extends ClientEvents {
caseID: string,
dmSuccess: boolean
];
+ bushUpdateModlog: [
+ moderator: BushGuildMember,
+ modlogID: string,
+ key: 'evidence' | 'hidden',
+ oldModlog: string | boolean,
+ newModlog: string | boolean
+ ];
+ bushUpdateSettings: [
+ setting: Setting,
+ guild: BushGuild,
+ oldValue: Guild[Setting],
+ newValue: Guild[Setting],
+ moderator?: BushGuildMember
+ ];
bushWarn: [
victim: BushGuildMember,
moderator: BushUser,
@@ -214,3 +231,10 @@ export interface BushClientEvents extends ClientEvents {
dmSuccess: boolean
];
}
+
+type Setting =
+ | GuildSettings
+ | 'enabledFeatures'
+ | 'blacklistedChannels'
+ | 'blacklistedUsers'
+ | 'disabledCommands';
diff --git a/src/lib/extensions/discord.js/BushGuild.ts b/src/lib/extensions/discord.js/BushGuild.ts
index 18f6542..256b9dc 100644
--- a/src/lib/extensions/discord.js/BushGuild.ts
+++ b/src/lib/extensions/discord.js/BushGuild.ts
@@ -21,20 +21,22 @@ export class BushGuild extends Guild {
return features.includes(feature);
}
- public async addFeature(feature: GuildFeatures): Promise<GuildModel['enabledFeatures']> {
+ public async addFeature(feature: GuildFeatures, moderator?: BushGuildMember): Promise<GuildModel['enabledFeatures']> {
const features = await this.getSetting('enabledFeatures');
const newFeatures = util.addOrRemoveFromArray('add', features, feature);
- return (await this.setSetting('enabledFeatures', newFeatures)).enabledFeatures;
+ return (await this.setSetting('enabledFeatures', newFeatures, moderator)).enabledFeatures;
}
- public async removeFeature(feature: GuildFeatures): Promise<GuildModel['enabledFeatures']> {
+ public async removeFeature(feature: GuildFeatures, moderator?: BushGuildMember): Promise<GuildModel['enabledFeatures']> {
const features = await this.getSetting('enabledFeatures');
const newFeatures = util.addOrRemoveFromArray('remove', features, feature);
- return (await this.setSetting('enabledFeatures', newFeatures)).enabledFeatures;
+ return (await this.setSetting('enabledFeatures', newFeatures, moderator)).enabledFeatures;
}
- public async toggleFeature(feature: GuildFeatures): Promise<GuildModel['enabledFeatures']> {
- return (await this.hasFeature(feature)) ? await this.removeFeature(feature) : await this.addFeature(feature);
+ public async toggleFeature(feature: GuildFeatures, moderator?: BushGuildMember): Promise<GuildModel['enabledFeatures']> {
+ return (await this.hasFeature(feature))
+ ? await this.removeFeature(feature, moderator)
+ : await this.addFeature(feature, moderator);
}
public async getSetting<K extends keyof GuildModel>(setting: K): Promise<GuildModel[K]> {
@@ -44,10 +46,16 @@ export class BushGuild extends Guild {
);
}
- public async setSetting<K extends keyof GuildModel>(setting: K, value: GuildDB[K]): Promise<GuildDB> {
+ public async setSetting<K extends Exclude<keyof GuildModel, 'id'>>(
+ setting: K,
+ value: GuildDB[K],
+ moderator?: BushGuildMember
+ ): Promise<GuildDB> {
const row = (await GuildDB.findByPk(this.id)) ?? GuildDB.build({ id: this.id });
+ const oldValue = row[setting] as GuildDB[K];
row[setting] = value;
client.cache.guilds.set(this.id, row.toJSON() as GuildDB);
+ client.emit('bushUpdateSettings', setting, this, oldValue, row[setting], moderator);
return await row.save();
}
diff --git a/src/lib/models/Guild.ts b/src/lib/models/Guild.ts
index b155330..9b283ab 100644
--- a/src/lib/models/Guild.ts
+++ b/src/lib/models/Guild.ts
@@ -52,6 +52,18 @@ export const guildSettingsObj = {
description: 'These users will be able to use commands in channels blacklisted.',
type: 'user-array',
configurable: true
+ },
+ logChannels: {
+ name: 'Log Channels',
+ description: 'The channel were logs are sent.',
+ type: 'custom',
+ configurable: false
+ },
+ autoModPhases: {
+ name: 'Automod Phases',
+ description: 'Custom phrases to be detected by automod.',
+ type: 'custom',
+ configurable: false
}
};
export type GuildSettings = keyof typeof guildSettingsObj;
diff --git a/src/listeners/client/ready.ts b/src/listeners/client/ready.ts
index fc71bb9..386d132 100644
--- a/src/listeners/client/ready.ts
+++ b/src/listeners/client/ready.ts
@@ -6,7 +6,7 @@ export default class ReadyListener extends BushListener {
super('ready', {
emitter: 'client',
event: 'ready',
- type: 'once'
+ type: 'on'
});
}
diff --git a/src/listeners/custom/bushUpdateModlog.ts b/src/listeners/custom/bushUpdateModlog.ts
new file mode 100644
index 0000000..16e0dbf
--- /dev/null
+++ b/src/listeners/custom/bushUpdateModlog.ts
@@ -0,0 +1,33 @@
+import { BushListener } from '@lib';
+import { MessageEmbed } from 'discord.js';
+import { BushClientEvents } from '../../lib/extensions/discord.js/BushClientEvents';
+
+export default class BushUpdateModlogListener extends BushListener {
+ public constructor() {
+ super('bushUpdateModlog', {
+ emitter: 'client',
+ event: 'bushUpdateModlog',
+ category: 'custom'
+ });
+ }
+
+ public override async exec(
+ ...[moderator, modlogID, key, oldModlog, newModlog]: BushClientEvents['bushUpdateModlog']
+ ): Promise<unknown> {
+ const logChannel = await moderator.guild.getLogChannel('moderation');
+ if (!logChannel) return;
+
+ const logEmbed = new MessageEmbed()
+ .setColor(util.colors.discord.BLURPLE)
+ .setTimestamp()
+ .setAuthor(moderator.user.tag, moderator.user.avatarURL({ dynamic: true, format: 'png', size: 4096 }) ?? undefined)
+ .addField('**Action**', `${'Update Modlog'}`)
+ .addField('**Moderator**', `${moderator} (${moderator.user.tag})`)
+ .addField('**ModLog Changed**', modlogID)
+ .addField('**Value Changed**', key)
+ .addField('**Old Value**', await util.inspectCleanRedactCodeblock(oldModlog, undefined, undefined, 1024))
+ .addField('**New Value**', await util.inspectCleanRedactCodeblock(newModlog, undefined, undefined, 1024));
+
+ return await logChannel.send({ embeds: [logEmbed] });
+ }
+}
diff --git a/src/listeners/custom/bushUpdateSettings.ts b/src/listeners/custom/bushUpdateSettings.ts
new file mode 100644
index 0000000..eae3062
--- /dev/null
+++ b/src/listeners/custom/bushUpdateSettings.ts
@@ -0,0 +1,36 @@
+import { BushListener } from '@lib';
+import { MessageEmbed } from 'discord.js';
+import { BushClientEvents } from '../../lib/extensions/discord.js/BushClientEvents';
+
+export default class BushUpdateSettingsListener extends BushListener {
+ public constructor() {
+ super('bushUpdateSettings', {
+ emitter: 'client',
+ event: 'bushUpdateSettings',
+ category: 'custom'
+ });
+ }
+
+ public override async exec(
+ ...[setting, guild, oldSettings, newSettings, moderator]: BushClientEvents['bushUpdateSettings']
+ ): Promise<unknown> {
+ const logChannel = await guild.getLogChannel('moderation');
+ if (!logChannel) return;
+
+ const logEmbed = new MessageEmbed().setColor(util.colors.discord.BLURPLE).setTimestamp();
+
+ if (moderator)
+ logEmbed.setAuthor(
+ moderator.user.tag,
+ moderator.user.avatarURL({ dynamic: true, format: 'png', size: 4096 }) ?? undefined
+ );
+ logEmbed.addField('**Action**', `${'Update Settings'}`);
+ if (moderator) logEmbed.addField('**Moderator**', `${moderator} (${moderator.user.tag})`);
+ logEmbed
+ .addField('**Setting Changed**', setting)
+ .addField('**Old Value**', await util.inspectCleanRedactCodeblock(oldSettings, 'js', undefined, 1024))
+ .addField('**New Value**', await util.inspectCleanRedactCodeblock(newSettings, 'js', undefined, 1024));
+
+ return await logChannel.send({ embeds: [logEmbed] });
+ }
+}
diff --git a/src/listeners/guild/guildMemberRemove.ts b/src/listeners/guild/guildMemberRemove.ts
index 74404a3..90634d6 100644
--- a/src/listeners/guild/guildMemberRemove.ts
+++ b/src/listeners/guild/guildMemberRemove.ts
@@ -42,7 +42,10 @@ export default class GuildMemberRemoveListener extends BushListener {
public async stickyRoles(member: BushGuildMember | PartialBushGuildMember): Promise<void> {
if (!(await member.guild.hasFeature('stickyRoles'))) return;
- if (member.partial) throw new Error('Partial member, cannot save sticky roles.');
+ if (member.partial) {
+ await member.guild.members.fetch(); // try to prevent in the future
+ throw new Error(`${member.id} is a partial member, cannot save sticky roles.`);
+ }
const rolesArray = member.roles.cache.filter((role) => role.name !== '@everyone').map((role) => role.id);
const nickname = member.nickname;
if (rolesArray) {