From 34f0d1d3ff3e2a90193c9a4d4de29d8335160d6a Mon Sep 17 00:00:00 2001
From: IRONM00N <64110067+IRONM00N@users.noreply.github.com>
Date: Fri, 2 Jul 2021 19:33:29 -0400
Subject: started restructuring moderation commands,

note: nothing currently works :flushed:
---
 src/arguments/contentWithDuration.ts               |   9 +
 src/arguments/duration.ts                          |  30 ++-
 src/commands/moderation/ban.ts                     | 224 +++++++++----------
 src/commands/moderation/kick.ts                    | 125 ++++++-----
 src/commands/moderation/modlog.ts                  | 137 ++++--------
 src/commands/moderation/mute.ts                    | 246 +++++++++++----------
 src/commands/moderation/warn.ts                    |   5 +-
 src/lib/extensions/discord-akairo/BushClient.ts    |  17 +-
 .../extensions/discord-akairo/BushClientUtil.ts    |  83 ++++++-
 src/lib/extensions/discord.js/BushGuild.ts         |   2 +
 src/lib/extensions/discord.js/BushGuildMember.ts   |  60 ++++-
 .../discord.js/BushGuildMemberManager.ts           |  11 +
 .../extensions/discord.js/BushMessageManager.ts    |   3 +-
 .../discord.js/BushThreadMemberManager.ts          |   9 +-
 src/lib/extensions/global.d.ts                     |   9 +
 src/lib/models/ModLog.ts                           |  19 +-
 16 files changed, 544 insertions(+), 445 deletions(-)
 create mode 100644 src/lib/extensions/discord.js/BushGuildMemberManager.ts
 create mode 100644 src/lib/extensions/global.d.ts

(limited to 'src')

diff --git a/src/arguments/contentWithDuration.ts b/src/arguments/contentWithDuration.ts
index e69de29..8dd7621 100644
--- a/src/arguments/contentWithDuration.ts
+++ b/src/arguments/contentWithDuration.ts
@@ -0,0 +1,9 @@
+import { BushArgumentTypeCaster } from '../lib/extensions/discord-akairo/BushArgumentTypeCaster';
+import { BushMessage } from '../lib/extensions/discord.js/BushMessage';
+
+export const contentWithDurationTypeCaster: BushArgumentTypeCaster = async (
+	_message: BushMessage,
+	phrase
+): Promise<{ duration: number; contentWithoutTime: string }> => {
+	return client.util.parseDuration(phrase);
+};
diff --git a/src/arguments/duration.ts b/src/arguments/duration.ts
index 6007b4e..f8b6ab1 100644
--- a/src/arguments/duration.ts
+++ b/src/arguments/duration.ts
@@ -1,21 +1,19 @@
 import { BushArgumentTypeCaster } from '../lib/extensions/discord-akairo/BushArgumentTypeCaster';
 import { BushMessage } from '../lib/extensions/discord.js/BushMessage';
-import { BushConstants } from '../lib/utils/BushConstants';
 
-export const durationTypeCaster: BushArgumentTypeCaster = async (_message: BushMessage, phrase): Promise<number> => {
-	if (!phrase) return null;
+export const durationTypeCaster: BushArgumentTypeCaster = (_message: BushMessage, phrase): number => {
+	// if (!phrase) return null;
+	// const regexString = Object.entries(BushConstants.TimeUnits)
+	// 	.map(([name, { label }]) => String.raw`(?:(?<${name}>-?(?:\d+)?\.?\d+) *${label})?`)
+	// 	.join('\\s*');
+	// const match = new RegExp(`^${regexString}$`, 'im').exec(phrase);
+	// if (!match) return null;
+	// let milliseconds = 0;
+	// for (const key in match.groups) {
+	// 	const value = Number(match.groups[key] || 0);
+	// 	milliseconds += value * BushConstants.TimeUnits[key].value;
+	// }
+	// return milliseconds;
 
-	const regexString = Object.entries(BushConstants.TimeUnits)
-		.map(([name, { label }]) => String.raw`(?:(?<${name}>-?(?:\d+)?\.?\d+) *${label})?`)
-		.join('\\s*');
-	const match = new RegExp(`^${regexString}$`, 'i').exec(phrase);
-	if (!match) return null;
-
-	let milliseconds = 0;
-	for (const key in match.groups) {
-		const value = Number(match.groups[key] || 0);
-		milliseconds += value * BushConstants.TimeUnits[key].value;
-	}
-
-	return milliseconds;
+	return client.util.parseDuration(phrase).duration;
 };
diff --git a/src/commands/moderation/ban.ts b/src/commands/moderation/ban.ts
index 29dc8a6..f3cc1e2 100644
--- a/src/commands/moderation/ban.ts
+++ b/src/commands/moderation/ban.ts
@@ -1,17 +1,5 @@
-import { Argument } from 'discord-akairo';
-import { CommandInteraction, Message, User } from 'discord.js';
-import moment from 'moment';
+import { Message, User } from 'discord.js';
 import { BushCommand } from '../../lib/extensions/discord-akairo/BushCommand';
-import { Ban, Guild, ModLog, ModLogType } from '../../lib/models';
-
-/* const durationAliases: Record<string, string[]> = {
-	weeks: ['w', 'weeks', 'week', 'wk', 'wks'],
-	days: ['d', 'days', 'day'],
-	hours: ['h', 'hours', 'hour', 'hr', 'hrs'],
-	minutes: ['m', 'min', 'mins', 'minutes', 'minute'],
-	months: ['mo', 'month', 'months']
-};
-const durationRegex = /(?:(\d+)(d(?:ays?)?|h(?:ours?|rs?)?|m(?:inutes?|ins?)?|mo(?:nths?)?|w(?:eeks?|ks?)?)(?: |$))/g; */
 
 export default class BanCommand extends BushCommand {
 	public constructor() {
@@ -29,18 +17,13 @@ export default class BanCommand extends BushCommand {
 				},
 				{
 					id: 'reason',
+					type: 'contentWithDuration',
 					match: 'restContent',
 					prompt: {
 						start: 'Why would you like to ban this user?',
 						retry: '{error} Choose a ban reason.',
 						optional: true
 					}
-				},
-				{
-					id: 'time',
-					type: 'duration',
-					match: 'option',
-					flag: '--time'
 				}
 			],
 			clientPermissions: ['BAN_MEMBERS'],
@@ -62,113 +45,112 @@ export default class BanCommand extends BushCommand {
 					name: 'reason',
 					description: 'Why are they getting banned?',
 					required: false
-				},
-				{
-					type: 'STRING',
-					name: 'time',
-					description: 'How long should they be banned for?',
-					required: false
 				}
 			],
 			slash: true
 		});
 	}
-	async *genResponses(
-		message: Message | CommandInteraction,
-		user: User,
-		reason?: string,
-		time?: number
-	): AsyncIterable<string> {
-		const duration = moment.duration();
-		let modLogEntry: ModLog;
-		let banEntry: Ban;
-		// const translatedTime: string[] = [];
-		// Create guild entry so postgres doesn't get mad when I try and add a modlog entry
-		await Guild.findOrCreate({
-			where: {
-				id: message.guild.id
-			},
-			defaults: {
-				id: message.guild.id
-			}
-		});
-		try {
-			if (time) {
-				duration.add(time);
-				/* 	const parsed = [...time.matchAll(durationRegex)];
-					if (parsed.length < 1) {
-						yield `${this.client.util.emojis.error} Invalid time.`;
-						return;
-					}
-					for (const part of parsed) {
-						const translated = Object.keys(durationAliases).find((k) => durationAliases[k].includes(part[2]));
-						translatedTime.push(part[1] + ' ' + translated);
-						duration.add(Number(part[1]), translated as 'weeks' | 'days' | 'hours' | 'months' | 'minutes');
-					} */
-				modLogEntry = ModLog.build({
-					user: user.id,
-					guild: message.guild.id,
-					reason,
-					type: ModLogType.TEMP_BAN,
-					duration: duration.asMilliseconds(),
-					moderator: message instanceof CommandInteraction ? message.user.id : message.author.id
-				});
-				banEntry = Ban.build({
-					user: user.id,
-					guild: message.guild.id,
-					reason,
-					expires: new Date(new Date().getTime() + duration.asMilliseconds()),
-					modlog: modLogEntry.id
-				});
-			} else {
-				modLogEntry = ModLog.build({
-					user: user.id,
-					guild: message.guild.id,
-					reason,
-					type: ModLogType.BAN,
-					moderator: message instanceof CommandInteraction ? message.user.id : message.author.id
-				});
-				banEntry = Ban.build({
-					user: user.id,
-					guild: message.guild.id,
-					reason,
-					modlog: modLogEntry.id
-				});
-			}
-			await modLogEntry.save();
-			await banEntry.save();
+	// async *genResponses(
+	// 	message: Message | CommandInteraction,
+	// 	user: User,
+	// 	reason?: string,
+	// 	time?: number
+	// ): AsyncIterable<string> {
+	// 	const duration = moment.duration();
+	// 	let modLogEntry: ModLog;
+	// 	let banEntry: Ban;
+	// 	// const translatedTime: string[] = [];
+	// 	// Create guild entry so postgres doesn't get mad when I try and add a modlog entry
+	// 	await Guild.findOrCreate({
+	// 		where: {
+	// 			id: message.guild.id
+	// 		},
+	// 		defaults: {
+	// 			id: message.guild.id
+	// 		}
+	// 	});
+	// 	try {
+	// 		if (time) {
+	// 			duration.add(time);
+	// 			/* 	const parsed = [...time.matchAll(durationRegex)];
+	// 				if (parsed.length < 1) {
+	// 					yield `${this.client.util.emojis.error} Invalid time.`;
+	// 					return;
+	// 				}
+	// 				for (const part of parsed) {
+	// 					const translated = Object.keys(durationAliases).find((k) => durationAliases[k].includes(part[2]));
+	// 					translatedTime.push(part[1] + ' ' + translated);
+	// 					duration.add(Number(part[1]), translated as 'weeks' | 'days' | 'hours' | 'months' | 'minutes');
+	// 				} */
+	// 			modLogEntry = ModLog.build({
+	// 				user: user.id,
+	// 				guild: message.guild.id,
+	// 				reason,
+	// 				type: ModLogType.TEMP_BAN,
+	// 				duration: duration.asMilliseconds(),
+	// 				moderator: message instanceof CommandInteraction ? message.user.id : message.author.id
+	// 			});
+	// 			banEntry = Ban.build({
+	// 				user: user.id,
+	// 				guild: message.guild.id,
+	// 				reason,
+	// 				expires: new Date(new Date().getTime() + duration.asMilliseconds()),
+	// 				modlog: modLogEntry.id
+	// 			});
+	// 		} else {
+	// 			modLogEntry = ModLog.build({
+	// 				user: user.id,
+	// 				guild: message.guild.id,
+	// 				reason,
+	// 				type: ModLogType.BAN,
+	// 				moderator: message instanceof CommandInteraction ? message.user.id : message.author.id
+	// 			});
+	// 			banEntry = Ban.build({
+	// 				user: user.id,
+	// 				guild: message.guild.id,
+	// 				reason,
+	// 				modlog: modLogEntry.id
+	// 			});
+	// 		}
+	// 		await modLogEntry.save();
+	// 		await banEntry.save();
 
-			try {
-				await user.send(
-					`You were banned in ${message.guild.name} ${duration ? duration.humanize() : 'permanently'} with reason \`${
-						reason || 'No reason given'
-					}\``
-				);
-			} catch {
-				yield `${this.client.util.emojis.warn} Unable to dm user`;
-			}
-			await message.guild.members.ban(user, {
-				reason: `Banned by ${message instanceof CommandInteraction ? message.user.tag : message.author.tag} with ${
-					reason ? `reason ${reason}` : 'no reason'
-				}`
-			});
-			yield `${this.client.util.emojis.success} Banned <@!${user.id}> ${
-				duration ? duration.humanize() : 'permanently'
-			} with reason \`${reason || 'No reason given'}\``;
-		} catch {
-			yield `${this.client.util.emojis.error} Error banning :/`;
-			await banEntry.destroy();
-			await modLogEntry.destroy();
-			return;
-		}
-	}
-	async exec(message: Message, { user, reason, time }: { user: User; reason?: string; time?: number | string }): Promise<void> {
-		if (typeof time === 'string') {
-			time = (await Argument.cast('duration', this.client.commandHandler.resolver, message, time)) as number;
-			//// time = this.client.commandHandler.resolver.type('duration')
-		}
-		for await (const response of this.genResponses(message, user, reason, time)) {
-			await message.util.send(response);
-		}
+	// 		try {
+	// 			await user.send(
+	// 				`You were banned in ${message.guild.name} ${duration ? duration.humanize() : 'permanently'} with reason \`${
+	// 					reason || 'No reason given'
+	// 				}\``
+	// 			);
+	// 		} catch {
+	// 			yield `${this.client.util.emojis.warn} Unable to dm user`;
+	// 		}
+	// 		await message.guild.members.ban(user, {
+	// 			reason: `Banned by ${message instanceof CommandInteraction ? message.user.tag : message.author.tag} with ${
+	// 				reason ? `reason ${reason}` : 'no reason'
+	// 			}`
+	// 		});
+	// 		yield `${this.client.util.emojis.success} Banned <@!${user.id}> ${
+	// 			duration ? duration.humanize() : 'permanently'
+	// 		} with reason \`${reason || 'No reason given'}\``;
+	// 	} catch {
+	// 		yield `${this.client.util.emojis.error} Error banning :/`;
+	// 		await banEntry.destroy();
+	// 		await modLogEntry.destroy();
+	// 		return;
+	// 	}
+	// }
+	async exec(
+		message: Message,
+		{ user, reason, time }: { user: User; reason?: string; time?: number | string }
+	): Promise<unknown> {
+		return message.util.reply(`${this.client.util.emojis.error} This command is not finished.`);
+
+		// if (typeof time === 'string') {
+		// 	time = (await Argument.cast('duration', this.client.commandHandler.resolver, message, time)) as number;
+		// 	//// time = this.client.commandHandler.resolver.type('duration')
+		// }
+		// for await (const response of this.genResponses(message, user, reason, time)) {
+		// 	await message.util.send(response);
+		// }
 	}
 }
diff --git a/src/commands/moderation/kick.ts b/src/commands/moderation/kick.ts
index 09d6abf..df538bc 100644
--- a/src/commands/moderation/kick.ts
+++ b/src/commands/moderation/kick.ts
@@ -1,7 +1,5 @@
-import { CommandInteraction, GuildMember, Message } from 'discord.js';
+import { GuildMember, Message } from 'discord.js';
 import { BushCommand } from '../../lib/extensions/discord-akairo/BushCommand';
-import { BushSlashMessage } from '../../lib/extensions/discord-akairo/BushSlashMessage';
-import { Guild, ModLog, ModLogType } from '../../lib/models';
 
 export default class KickCommand extends BushCommand {
 	public constructor() {
@@ -20,7 +18,12 @@ export default class KickCommand extends BushCommand {
 				{
 					id: 'reason',
 					type: 'string',
-					match: 'restContent'
+					match: 'restContent',
+					prompt: {
+						start: 'Why would you like to kick this user?',
+						retry: '{error} Choose a valid user to kick.',
+						optional: true
+					}
 				}
 			],
 			clientPermissions: ['KICK_MEMBERS'],
@@ -28,19 +31,19 @@ export default class KickCommand extends BushCommand {
 			description: {
 				content: 'Kick a member from the server.',
 				usage: 'kick <member> <reason>',
-				examples: ['kick @Tyman being cool']
+				examples: ['kick @user bad']
 			},
 			slashOptions: [
 				{
 					type: 'USER',
 					name: 'user',
-					description: 'The user to kick',
+					description: 'What user would you like to kick?',
 					required: true
 				},
 				{
 					type: 'STRING',
 					name: 'reason',
-					description: 'The reason to show in modlogs and audit log',
+					description: 'Why would you like to kick this user?',
 					required: false
 				}
 			],
@@ -48,63 +51,59 @@ export default class KickCommand extends BushCommand {
 		});
 	}
 
-	private async *genResponses(
-		message: Message | CommandInteraction,
-		user: GuildMember,
-		reason?: string
-	): AsyncIterable<string> {
-		let modlogEnry: ModLog;
-		// Create guild entry so postgres doesn't get mad when I try and add a modlog entry
-		await Guild.findOrCreate({
-			where: {
-				id: message.guild.id
-			},
-			defaults: {
-				id: message.guild.id
-			}
-		});
-		try {
-			modlogEnry = ModLog.build({
-				user: user.id,
-				guild: message.guild.id,
-				moderator: message instanceof Message ? message.author.id : message.user.id,
-				type: ModLogType.KICK,
-				reason
-			});
-			await modlogEnry.save();
-		} catch (e) {
-			this.client.console.error(`KickCommand`, `Error saving to database. ${e?.stack}`);
-			yield `${this.client.util.emojis.error} Error saving to database. Please report this to a developer.`;
-			return;
-		}
-		try {
-			await user.send(`You were kicked in ${message.guild.name} with reason \`${reason || 'No reason given'}\``);
-		} catch (e) {
-			yield `${this.client.util.emojis.warn} Unable to dm user`;
-		}
-		try {
-			await user.kick(
-				`Kicked by ${message instanceof Message ? message.author.tag : message.user.tag} with ${
-					reason ? `reason ${reason}` : 'no reason'
-				}`
-			);
-		} catch {
-			yield `${this.client.util.emojis.error} Error kicking :/`;
-			await modlogEnry.destroy();
-			return;
-		}
-		yield `${this.client.util.emojis.success} Kicked <@!${user.id}> with reason \`${reason || 'No reason given'}\``;
-	}
+	// private async *genResponses(
+	// 	message: Message | CommandInteraction,
+	// 	user: GuildMember,
+	// 	reason?: string
+	// ): AsyncIterable<string> {
+	// 	let modlogEnry: ModLog;
+	// 	// Create guild entry so postgres doesn't get mad when I try and add a modlog entry
+	// 	await Guild.findOrCreate({
+	// 		where: {
+	// 			id: message.guild.id
+	// 		},
+	// 		defaults: {
+	// 			id: message.guild.id
+	// 		}
+	// 	});
+	// 	try {
+	// 		modlogEnry = ModLog.build({
+	// 			user: user.id,
+	// 			guild: message.guild.id,
+	// 			moderator: message instanceof Message ? message.author.id : message.user.id,
+	// 			type: ModLogType.KICK,
+	// 			reason
+	// 		});
+	// 		await modlogEnry.save();
+	// 	} catch (e) {
+	// 		this.client.console.error(`KickCommand`, `Error saving to database. ${e?.stack}`);
+	// 		yield `${this.client.util.emojis.error} Error saving to database. Please report this to a developer.`;
+	// 		return;
+	// 	}
+	// 	try {
+	// 		await user.send(`You were kicked in ${message.guild.name} with reason \`${reason || 'No reason given'}\``);
+	// 	} catch (e) {
+	// 		yield `${this.client.util.emojis.warn} Unable to dm user`;
+	// 	}
+	// 	try {
+	// 		await user.kick(
+	// 			`Kicked by ${message instanceof Message ? message.author.tag : message.user.tag} with ${
+	// 				reason ? `reason ${reason}` : 'no reason'
+	// 			}`
+	// 		);
+	// 	} catch {
+	// 		yield `${this.client.util.emojis.error} Error kicking :/`;
+	// 		await modlogEnry.destroy();
+	// 		return;
+	// 	}
+	// 	yield `${this.client.util.emojis.success} Kicked <@!${user.id}> with reason \`${reason || 'No reason given'}\``;
+	// }
 
-	async exec(message: Message, { user, reason }: { user: GuildMember; reason?: string }): Promise<void> {
-		for await (const response of this.genResponses(message, user, reason)) {
-			await message.util.send(response);
-		}
-	}
+	async exec(message: Message, { user, reason }: { user: GuildMember; reason?: string }): Promise<unknown> {
+		return message.util.reply(`${this.client.util.emojis.error} This command is not finished.`);
 
-	async execSlash(message: BushSlashMessage, { user, reason }: { user: GuildMember; reason?: string }): Promise<void> {
-		for await (const response of this.genResponses(message.interaction, user, reason)) {
-			await message.interaction.reply(response);
-		}
+		// for await (const response of this.genResponses(message, user, reason)) {
+		// 	await message.util.send(response);
+		// }
 	}
 }
diff --git a/src/commands/moderation/modlog.ts b/src/commands/moderation/modlog.ts
index d094885..2f7601b 100644
--- a/src/commands/moderation/modlog.ts
+++ b/src/commands/moderation/modlog.ts
@@ -1,8 +1,8 @@
-import { stripIndent } from 'common-tags';
 import { Argument } from 'discord-akairo';
-import { Message, MessageEmbed } from 'discord.js';
+import { MessageEmbed } from 'discord.js';
 import moment from 'moment';
 import { BushCommand } from '../../lib/extensions/discord-akairo/BushCommand';
+import { BushMessage } from '../../lib/extensions/discord.js/BushMessage';
 import { ModLog } from '../../lib/models';
 
 export default class ModlogCommand extends BushCommand {
@@ -10,50 +10,47 @@ export default class ModlogCommand extends BushCommand {
 		super('modlog', {
 			aliases: ['modlog', 'modlogs'],
 			category: 'moderation',
+			description: {
+				content: "View a user's modlogs, or view a specific case.",
+				usage: 'modlogs <search>',
+				examples: ['modlogs @Tyman']
+			},
 			args: [
 				{
 					id: 'search',
+					type: Argument.union('user', 'string'),
 					prompt: {
-						start: 'What modlog id or user would you like to see?'
+						start: 'What case id or user would you like to see?',
+						retry: '{error} Choose a valid case id or user.'
 					}
-				},
-				{
-					id: 'page',
-					type: 'number'
 				}
 			],
 			userPermissions: ['MANAGE_MESSAGES'],
-			description: {
-				content: "View a user's modlogs, or view a specific modlog entry",
-				usage: 'warn <search> [page]',
-				examples: ['modlogs @Tyman', 'modlogs @Tyman 3']
-			}
+			slash: true,
+			slashOptions: [
+				{
+					name: 'search',
+					description: 'What case id or user would you like to see?',
+					type: 'STRING',
+					required: true
+				}
+			]
 		});
 	}
-	*args(): unknown {
-		const search = yield {
-			id: 'search',
-			type: Argument.union('user', 'string'),
-			prompt: {
-				start: 'What modlog id or user would you like to see?',
-				retry: '{error} Choose a valid modlog id or user.'
-			}
-		};
-		if (typeof search === 'string') return { search, page: null };
-		else {
-			const page = yield {
-				id: 'page',
-				type: 'number',
-				prompt: {
-					start: 'What page?',
-					retry: '{error} Choose a valid page to view.',
-					optional: true
-				}
-			};
-			return { search, page };
-		}
+
+	private generateModlogInfo(log: ModLog) {
+		const modLog = [
+			`**Case ID**: ${log.id}`,
+			`**Type**: ${log.type.toLowerCase()}`,
+			`**User**: <@!${log.user}> (${log.user})`,
+			`**Moderator**: <@!${log.moderator}> (${log.moderator})`
+		];
+		if (log.duration) modLog.push(`**Duration**: ${moment.duration(log.duration, 'milliseconds').humanize()}`);
+		modLog.push(`**Reason**: ${log.reason || 'No Reason Specified.'}`);
+		return modLog.join(`\n`);
 	}
-	async exec(message: Message, { search, page }: { search: string; page: number }): Promise<void> {
+
+	async exec(message: BushMessage, { search }: { search: string }): Promise<unknown> {
 		const foundUser = await this.client.util.resolveUserAsync(search);
 		if (foundUser) {
 			const logs = await ModLog.findAll({
@@ -65,75 +62,27 @@ export default class ModlogCommand extends BushCommand {
 			});
 			const niceLogs: string[] = [];
 			for (const log of logs) {
-				niceLogs.push(stripIndent`
-					**Case ID**: ${log.id}
-					**Type**: ${log.type.toLowerCase()}
-					**User**: <@!${log.user}> (${log.user})
-					**Moderator**: <@!${log.moderator}> (${log.moderator})
-					**Duration**: ${log.duration ? moment.duration(log.duration, 'milliseconds').humanize() : 'N/A'}
-					**Reason**: ${log.reason || 'None given'}
-					**${this.client.util.ordinal(logs.indexOf(log) + 1)}** action
-				`);
+				niceLogs.push(this.generateModlogInfo(log));
 			}
 			const chunked: string[][] = this.client.util.chunk(niceLogs, 3);
 			const embedPages = chunked.map(
-				(e, i) =>
+				(chunk) =>
 					new MessageEmbed({
-						title: foundUser.tag,
-						description: e.join('\n**---------------------------**\n'),
-						footer: {
-							text: `Page ${i + 1}/${chunked.length}`
-						},
+						title: `${foundUser.tag}'s Mod Logs`,
+						description: chunk.join('\n**―――――――――――――――――――――――――――**\n'),
 						color: this.client.util.colors.default
 					})
 			);
-			if (page) {
-				await message.util.send({ embeds: [embedPages[page - 1]] });
-				return;
-			} else {
-				await message.util.send({ embeds: [embedPages[0]] });
-				return;
-			}
+			this.client.util.buttonPaginate(message, embedPages, '', true);
 		} else if (search) {
 			const entry = await ModLog.findByPk(search);
-			if (!entry) {
-				await message.util.send(`${this.client.util.emojis.error} That modlog does not exist.`);
-				return;
-			}
-			await message.util.send({
-				embeds: [
-					new MessageEmbed({
-						title: `${entry.id}`,
-						fields: [
-							{
-								name: 'Type',
-								value: entry.type.toLowerCase(),
-								inline: true
-							},
-							{
-								name: 'Duration',
-								value: `${entry.duration ? moment.duration(entry.duration, 'milliseconds').humanize() : 'N/A'}`,
-								inline: true
-							},
-							{
-								name: 'Reason',
-								value: `${entry.reason || 'None given'}`,
-								inline: true
-							},
-							{
-								name: 'Moderator',
-								value: `<@!${entry.moderator}> (${entry.moderator})`,
-								inline: true
-							},
-							{
-								name: 'User',
-								value: `<@!${entry.user}> (${entry.user})`,
-								inline: true
-							}
-						]
-					})
-				]
+			if (!entry) return message.util.send(`${this.client.util.emojis.error} That modlog does not exist.`);
+			const embed = new MessageEmbed({
+				title: `Case ${entry.id}`,
+				description: this.generateModlogInfo(entry),
+				color: this.client.util.colors.default
 			});
+			return await this.client.util.buttonPaginate(message, [embed]);
 		}
 	}
 }
diff --git a/src/commands/moderation/mute.ts b/src/commands/moderation/mute.ts
index 3496489..ffad432 100644
--- a/src/commands/moderation/mute.ts
+++ b/src/commands/moderation/mute.ts
@@ -1,8 +1,8 @@
 import { Argument } from 'discord-akairo';
-import { CommandInteraction, Message, User } from 'discord.js';
-import moment from 'moment';
 import { BushCommand } from '../../lib/extensions/discord-akairo/BushCommand';
-import { Guild, ModLog, ModLogType, Mute } from '../../lib/models';
+import { BushGuildMember } from '../../lib/extensions/discord.js/BushGuildMember';
+import { BushMessage } from '../../lib/extensions/discord.js/BushMessage';
+import { BushUser } from '../../lib/extensions/discord.js/BushUser';
 
 export default class MuteCommand extends BushCommand {
 	public constructor() {
@@ -20,18 +20,13 @@ export default class MuteCommand extends BushCommand {
 				},
 				{
 					id: 'reason',
-					match: 'separate',
+					type: 'contentWithDuration',
+					match: 'rest',
 					prompt: {
 						start: 'Why would you like to mute this user?',
-						retry: '{error} Choose a mute reason.',
+						retry: '{error} Choose a mute reason and duration.',
 						optional: true
 					}
-				},
-				{
-					id: 'time',
-					type: 'duration',
-					match: 'option',
-					flag: '--time'
 				}
 			],
 			clientPermissions: ['MANAGE_ROLES'],
@@ -51,116 +46,143 @@ export default class MuteCommand extends BushCommand {
 				{
 					type: 'STRING',
 					name: 'reason',
-					description: 'Why the user is getting muted.',
-					required: false
-				},
-				{
-					type: 'STRING',
-					name: 'time',
-					description: 'How long the user should be muted for.',
+					description: 'Why is the user is getting muted, and how long should they be muted for?',
 					required: false
 				}
 			],
 			slash: true
 		});
 	}
-	async *genResponses(
-		message: Message | CommandInteraction,
-		user: User,
-		reason?: string,
-		time?: number
-	): AsyncIterable<string> {
-		const duration = moment.duration(time);
-		let modlogEnry: ModLog;
-		let muteEntry: Mute;
-		// Create guild entry so postgres doesn't get mad when I try and add a modlog entry
-		await Guild.findOrCreate({
-			where: {
-				id: message.guild.id
-			},
-			defaults: {
-				id: message.guild.id
-			}
-		});
-		try {
-			const muteRole = (await Guild.findByPk(message.guild.id)).get('muteRole');
-			try {
-				if (time) {
-					modlogEnry = ModLog.build({
-						user: user.id,
-						guild: message.guild.id,
-						reason,
-						type: ModLogType.TEMP_MUTE,
-						duration: duration.asMilliseconds(),
-						moderator: message instanceof CommandInteraction ? message.user.id : message.author.id
-					});
-					muteEntry = Mute.build({
-						user: user.id,
-						guild: message.guild.id,
-						reason,
-						expires: new Date(new Date().getTime() + duration.asMilliseconds()),
-						modlog: modlogEnry.id
-					});
-				} else {
-					modlogEnry = ModLog.build({
-						user: user.id,
-						guild: message.guild.id,
-						reason,
-						type: ModLogType.MUTE,
-						moderator: message instanceof CommandInteraction ? message.user.id : message.author.id
-					});
-					muteEntry = Mute.build({
-						user: user.id,
-						guild: message.guild.id,
-						reason,
-						modlog: modlogEnry.id
-					});
-				}
-				await modlogEnry.save();
-				await muteEntry.save();
-			} catch (e) {
-				this.client.console.error(`MuteCommand`, `Error saving to database. ${e?.stack}`);
-				yield `${this.client.util.emojis.error} Error saving to database. Please report this to a developer.`;
-				return;
-			}
-			try {
-				await user.send(
-					`You were muted in ${message.guild.name} ${time ? `for ${duration.humanize()}` : 'permanently'} with reason \`${
-						reason || 'No reason given'
-					}\``
-				);
-			} catch (e) {
-				yield `${this.client.util.emojis.warn} Unable to dm user`;
-			}
-			await (
-				await message.guild.members.fetch(user)
-			).roles.add(
-				muteRole,
-				`Muted by ${message instanceof CommandInteraction ? message.user.tag : message.author.tag} with ${
-					reason ? `reason ${reason}` : 'no reason'
-				}`
-			);
-			yield `${this.client.util.emojis.success} muted <@!${user.id}> ${
-				time ? `for ${duration.humanize()}` : 'permanently'
-			} with reason \`${reason || 'No reason given'}\``;
-		} catch {
-			yield `${this.client.util.emojis.error} Error muting :/`;
-			await muteEntry.destroy();
-			await modlogEnry.destroy();
-			return;
-		}
-	}
+	// async *genResponses(
+	// 	message: Message | CommandInteraction,
+	// 	user: User,
+	// 	reason?: string,
+	// 	time?: number
+	// ): AsyncIterable<string> {
+	// 	const duration = moment.duration(time);
+	// 	let modlogEnry: ModLog;
+	// 	let muteEntry: Mute;
+	// 	// Create guild entry so postgres doesn't get mad when I try and add a modlog entry
+	// 	await Guild.findOrCreate({
+	// 		where: {
+	// 			id: message.guild.id
+	// 		},
+	// 		defaults: {
+	// 			id: message.guild.id
+	// 		}
+	// 	});
+	// 	try {
+	// 		const muteRole = (await Guild.findByPk(message.guild.id)).get('muteRole');
+	// 		try {
+	// 			if (time) {
+	// 				modlogEnry = ModLog.build({
+	// 					user: user.id,
+	// 					guild: message.guild.id,
+	// 					reason,
+	// 					type: ModLogType.TEMP_MUTE,
+	// 					duration: duration.asMilliseconds(),
+	// 					moderator: message instanceof CommandInteraction ? message.user.id : message.author.id
+	// 				});
+	// 				muteEntry = Mute.build({
+	// 					user: user.id,
+	// 					guild: message.guild.id,
+	// 					reason,
+	// 					expires: new Date(new Date().getTime() + duration.asMilliseconds()),
+	// 					modlog: modlogEnry.id
+	// 				});
+	// 			} else {
+	// 				modlogEnry = ModLog.build({
+	// 					user: user.id,
+	// 					guild: message.guild.id,
+	// 					reason,
+	// 					type: ModLogType.MUTE,
+	// 					moderator: message instanceof CommandInteraction ? message.user.id : message.author.id
+	// 				});
+	// 				muteEntry = Mute.build({
+	// 					user: user.id,
+	// 					guild: message.guild.id,
+	// 					reason,
+	// 					modlog: modlogEnry.id
+	// 				});
+	// 			}
+	// 			await modlogEnry.save();
+	// 			await muteEntry.save();
+	// 		} catch (e) {
+	// 			this.client.console.error(`MuteCommand`, `Error saving to database. ${e?.stack}`);
+	// 			yield `${this.client.util.emojis.error} Error saving to database. Please report this to a developer.`;
+	// 			return;
+	// 		}
+	// 		try {
+	// 			await user.send(
+	// 				`You were muted in ${message.guild.name} ${time ? `for ${duration.humanize()}` : 'permanently'} with reason \`${
+	// 					reason || 'No reason given'
+	// 				}\``
+	// 			);
+	// 		} catch (e) {
+	// 			yield `${this.client.util.emojis.warn} Unable to dm user`;
+	// 		}
+	// 		await (
+	// 			await message.guild.members.fetch(user)
+	// 		).roles.add(
+	// 			muteRole,
+	// 			`Muted by ${message instanceof CommandInteraction ? message.user.tag : message.author.tag} with ${
+	// 				reason ? `reason ${reason}` : 'no reason'
+	// 			}`
+	// 		);
+	// 		yield `${this.client.util.emojis.success} muted <@!${user.id}> ${
+	// 			time ? `for ${duration.humanize()}` : 'permanently'
+	// 		} with reason \`${reason || 'No reason given'}\``;
+	// 	} catch {
+	// 		yield `${this.client.util.emojis.error} Error muting :/`;
+	// 		await muteEntry.destroy();
+	// 		await modlogEnry.destroy();
+	// 		return;
+	// 	}
+	// }
 	async exec(
-		message: Message,
-		{ user, reason, time }: { user: User; reason?: string[]; time?: string | number }
-	): Promise<void> {
-		this.client.console.debug(reason);
+		message: BushMessage,
+		{ user, reason }: { user: BushUser; reason?: { duration: number; contentWithoutTime: string } }
+	): Promise<unknown> {
+		return message.util.reply(`${this.client.util.emojis.error} This command is not finished.`);
+		// this.client.console.debug(reason);
 
-		if (typeof time === 'string') {
-			time = (await Argument.cast('duration', this.client.commandHandler.resolver, message, time)) as number;
+		// if (typeof time === 'string') {
+		// 	time = (await Argument.cast('duration', this.client.commandHandler.resolver, message, time)) as number;
+		// }
+		// for await (const response of this.genResponses(message, user, reason.join(' '), time)) {
+		// 	await message.util.sendNew(response);
+		// }
+
+		const member = message.guild.members.cache.get(user.id) as BushGuildMember;
+		if (!this.client.util.moderatorCanModerateUser(message.member, member)) {
+			return message.util.reply({
+				content: `${this.client.util.emojis.error} You cannot mute **${member.user.tag}**.`
+			});
 		}
-		for await (const response of this.genResponses(message, user, reason.join(' '), time)) {
-			await message.util.sendNew(response);
+
+		const time =
+			typeof reason === 'string'
+				? //@ts-ignore: you are unreachable bitch
+				  await Argument.cast('duration', this.client.commandHandler.resolver, message, reason)
+				: reason.duration;
+		const parsedReason = reason.contentWithoutTime;
+
+		const response = await member.mute({
+			reason: parsedReason,
+			moderator: message.author,
+			duration: time,
+			createModLogEntry: true
+		});
+
+		switch (response) {
+			case 'success':
+				return message.util.reply(`${this.client.util.emojis.success} Successfully muted **${member.user.tag}**.`);
+			case 'no mute role':
+				return message.util.reply(
+					`${this.client.util.emojis.error} Could not mute **${
+						member.user.tag
+					}**, you must set a mute role with ${message.guild.getSetting('prefix')}.`
+				);
 		}
 	}
 }
diff --git a/src/commands/moderation/warn.ts b/src/commands/moderation/warn.ts
index c146db0..d70c9f0 100644
--- a/src/commands/moderation/warn.ts
+++ b/src/commands/moderation/warn.ts
@@ -15,6 +15,7 @@ export default class WarnCommand extends BushCommand {
 				},
 				{
 					id: 'reason',
+					type: 'contentWithDuration',
 					match: 'rest'
 				}
 			],
@@ -25,7 +26,9 @@ export default class WarnCommand extends BushCommand {
 			}
 		});
 	}
-	public async exec(message: Message, { member, reason }: { member: GuildMember; reason: string }): Promise<void> {
+	public async exec(message: Message, { member, reason }: { member: GuildMember; reason: string }): Promise<unknown> {
+		return message.util.reply(`${this.client.util.emojis.error} This command is not finished.`);
+
 		// Create guild entry so postgres doesn't get mad when I try and add a modlog entry
 		await Guild.findOrCreate({
 			where: {
diff --git a/src/lib/extensions/discord-akairo/BushClient.ts b/src/lib/extensions/discord-akairo/BushClient.ts
index 724f01a..6911573 100644
--- a/src/lib/extensions/discord-akairo/BushClient.ts
+++ b/src/lib/extensions/discord-akairo/BushClient.ts
@@ -9,13 +9,13 @@ import {
 	MessagePayload,
 	ReplyMessageOptions,
 	Snowflake,
-	Structures,
-	UserResolvable
+	Structures
 } from 'discord.js';
 import * as path from 'path';
 import { exit } from 'process';
 import readline from 'readline';
 import { Sequelize } from 'sequelize';
+import { contentWithDurationTypeCaster } from '../../../arguments/contentWithDuration';
 import { durationTypeCaster } from '../../../arguments/duration';
 import * as config from '../../../config/options';
 import UpdateCacheTask from '../../../tasks/updateCache';
@@ -54,6 +54,9 @@ export type BotConfig = typeof config;
 export type BushReplyMessageType = string | MessagePayload | ReplyMessageOptions;
 export type BushEditMessageType = string | MessageEditOptions | MessagePayload;
 export type BushSendMessageType = string | MessagePayload | MessageOptions;
+export type BushThreadMemberResolvable = BushThreadMember | BushUserResolvable;
+export type BushUserResolvable = BushUser | Snowflake | BushMessage | BushGuildMember | BushThreadMember;
+export type BushGuildMemberResolvable = BushGuildMember | BushUserResolvable;
 
 const rl = readline.createInterface({
 	input: process.stdin,
@@ -168,7 +171,7 @@ export class BushClient extends AkairoClient {
 			dialect: 'postgres',
 			host: this.config.db.host,
 			port: this.config.db.port,
-			logging: this.config.logging.db ? (a) => this.logger.debug(a) : false
+			logging: this.config.logging.db ? (sql) => this.logger.debug(sql) : false
 		});
 		this.logger = new BushLogger(this);
 	}
@@ -198,7 +201,8 @@ export class BushClient extends AkairoClient {
 			gateway: this.ws
 		});
 		this.commandHandler.resolver.addTypes({
-			duration: durationTypeCaster
+			duration: durationTypeCaster,
+			contentWithDuration: contentWithDurationTypeCaster
 		});
 		// loads all the handlers
 		const loaders = {
@@ -240,7 +244,6 @@ export class BushClient extends AkairoClient {
 
 	/** Starts the bot */
 	public async start(): Promise<void> {
-		//@ts-ignore: stfu bitch
 		global.client = this;
 
 		try {
@@ -260,10 +263,10 @@ export class BushClient extends AkairoClient {
 		}
 	}
 
-	public isOwner(user: UserResolvable): boolean {
+	public isOwner(user: BushUserResolvable): boolean {
 		return this.config.owners.includes(this.users.resolveID(user));
 	}
-	public isSuperUser(user: UserResolvable): boolean {
+	public isSuperUser(user: BushUserResolvable): boolean {
 		const userID = this.users.resolveID(user);
 		return !!BushCache?.global?.superUsers?.includes(userID) || this.config.owners.includes(userID);
 	}
diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts
index 1f8c0f9..9289598 100644
--- a/src/lib/extensions/discord-akairo/BushClientUtil.ts
+++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts
@@ -25,10 +25,13 @@ import {
 } from 'discord.js';
 import got from 'got';
 import { promisify } from 'util';
-import { Global } from '../../models';
+import { Global, Guild, ModLog, ModLogType } from '../../models';
 import { BushCache } from '../../utils/BushCache';
+import { BushConstants } from '../../utils/BushConstants';
+import { BushGuildResolvable } from '../discord.js/BushCommandInteraction';
+import { BushGuildMember } from '../discord.js/BushGuildMember';
 import { BushMessage } from '../discord.js/BushMessage';
-import { BushClient } from './BushClient';
+import { BushClient, BushGuildMemberResolvable } from './BushClient';
 
 interface hastebinRes {
 	key: string;
@@ -281,6 +284,10 @@ export class BushClientUtil extends ClientUtil {
 	): Promise<void> {
 		if (deleteOnExit === undefined) deleteOnExit = true;
 
+		if (embeds.length === 1) {
+			return this.sendWithDeleteButton(message, { embeds: embeds });
+		}
+
 		embeds.forEach((_e, i) => {
 			embeds[i] = embeds[i].setFooter(`Page ${i + 1}/${embeds.length}`);
 		});
@@ -523,14 +530,68 @@ export class BushClientUtil extends ClientUtil {
 		return newArray;
 	}
 
-	// public createModLogEntry(
-	// 	user: User | Snowflake,
-	// 	guild: Guild | Snowflake,
-	// 	reason?: string,
-	// 	type?: ModLogType,
-	// 	duration?: number,
-	// 	moderator: User | Snowflake
-	// ): ModLog {
+	public parseDuration(content: string): { duration: number; contentWithoutTime: string } {
+		if (!content) return { duration: 0, contentWithoutTime: null };
+
+		let duration = 0,
+			contentWithoutTime = content;
 
-	// }
+		const regexString = Object.entries(BushConstants.TimeUnits)
+			.map(([name, { label }]) => String.raw`(?:(?<${name}>-?(?:\d+)?\.?\d+) *${label})?`)
+			.join('\\s*');
+		const match = new RegExp(`^${regexString}$`, 'im').exec(content);
+		if (!match) return null;
+
+		for (const key in match.groups) {
+			contentWithoutTime = contentWithoutTime.replace(match.groups[key], '');
+			const value = Number(match.groups[key] || 0);
+			duration += value * BushConstants.TimeUnits[key].value;
+		}
+
+		return { duration, contentWithoutTime };
+	}
+
+	/**
+	 * Checks if a moderator can perform a moderation action on another user.
+	 * @param moderator - The person trying to perform the action.
+	 * @param victim - The person getting punished.
+	 */
+	public moderatorCanModerateUser(moderator: BushGuildMember, victim: BushGuildMember): boolean {
+		throw 'not implemented';
+		if (moderator.guild.id !== victim.guild.id) throw 'wtf';
+		if (moderator.guild.ownerID === moderator.id) return true;
+	}
+
+	public async createModLogEntry(options: {
+		type: ModLogType;
+		user: BushGuildMemberResolvable;
+		moderator: BushGuildMemberResolvable;
+		reason: string;
+		duration: number;
+		guild: BushGuildResolvable;
+	}): Promise<void> {
+		const user = this.client.users.resolveID(options.user);
+		const moderator = this.client.users.resolveID(options.moderator);
+		const guild = this.client.guilds.resolveID(options.guild);
+
+		// If guild does not exist create it so the modlog can reference a guild.
+		await Guild.findOrCreate({
+			where: {
+				id: guild
+			},
+			defaults: {
+				id: guild
+			}
+		});
+
+		const modLogEntry = ModLog.build({
+			type: options.type,
+			user,
+			moderator,
+			reason: options.reason,
+			duration: options.duration,
+			guild
+		});
+		await modLogEntry.save();
+	}
 }
diff --git a/src/lib/extensions/discord.js/BushGuild.ts b/src/lib/extensions/discord.js/BushGuild.ts
index 95e07f9..ea34aec 100644
--- a/src/lib/extensions/discord.js/BushGuild.ts
+++ b/src/lib/extensions/discord.js/BushGuild.ts
@@ -4,6 +4,8 @@ import { BushClient } from '../discord-akairo/BushClient';
 
 export class BushGuild extends Guild {
 	public declare readonly client: BushClient;
+	// I cba to do this
+	//// public declare members: GuildMemberManager;
 	public constructor(client: BushClient, data: unknown) {
 		super(client, data);
 	}
diff --git a/src/lib/extensions/discord.js/BushGuildMember.ts b/src/lib/extensions/discord.js/BushGuildMember.ts
index 6bcb9b8..59dc777 100644
--- a/src/lib/extensions/discord.js/BushGuildMember.ts
+++ b/src/lib/extensions/discord.js/BushGuildMember.ts
@@ -1,13 +1,69 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
 import { GuildMember } from 'discord.js';
-import { BushClient } from '../discord-akairo/BushClient';
+import { BushClient, BushUserResolvable } from '../discord-akairo/BushClient';
 import { BushGuild } from './BushGuild';
 import { BushUser } from './BushUser';
 
+interface BushPunishmentOptions {
+	reason?: string;
+	moderator: BushUserResolvable;
+	createModLogEntry?: boolean;
+}
+
+interface BushTimedPunishmentOptions extends BushPunishmentOptions {
+	duration?: number;
+}
+
+type PunishmentResponse = 'success';
+
+type WarnResponse = PunishmentResponse;
+
+type MuteResponse = PunishmentResponse | 'no mute role';
+
+type UnmuteResponse = PunishmentResponse;
+
+type KickResponse = PunishmentResponse;
+
+interface BushBanOptions extends BushTimedPunishmentOptions {
+	deleteDays?: number;
+	duration?: number;
+}
+
+type BanResponse = PunishmentResponse;
+
 export class BushGuildMember extends GuildMember {
 	public declare readonly client: BushClient;
 	public declare guild: BushGuild;
-	public declare BushUser: BushUser;
+	public declare user: BushUser;
 	public constructor(client: BushClient, data: unknown, guild: BushGuild) {
 		super(client, data, guild);
 	}
+
+	public async warn(options: BushPunishmentOptions): Promise<WarnResponse> {
+		throw 'not implemented';
+	}
+
+	public async mute(options: BushTimedPunishmentOptions): Promise<MuteResponse> {
+		throw 'not implemented';
+	}
+
+	public async unmute(options: BushPunishmentOptions): Promise<UnmuteResponse> {
+		throw 'not implemented';
+	}
+
+	public async bushKick(options: BushPunishmentOptions): Promise<KickResponse> {
+		throw 'not implemented';
+	}
+
+	public async bushBan(options?: BushBanOptions): Promise<BanResponse> {
+		throw 'not implemented';
+	}
+
+	public isOwner(): boolean {
+		return this.client.isOwner(this);
+	}
+
+	public isSuperUser(): boolean {
+		return this.client.isSuperUser(this);
+	}
 }
diff --git a/src/lib/extensions/discord.js/BushGuildMemberManager.ts b/src/lib/extensions/discord.js/BushGuildMemberManager.ts
new file mode 100644
index 0000000..dbc2da5
--- /dev/null
+++ b/src/lib/extensions/discord.js/BushGuildMemberManager.ts
@@ -0,0 +1,11 @@
+// /* eslint-disable @typescript-eslint/no-explicit-any */
+// import { GuildMemberManager } from 'discord.js';
+// import { BushGuild } from './BushGuild';
+
+// export class BushGuildMemberManager extends GuildMemberManager {
+// 	public guild: BushGuild;
+
+// 	public constructor(guild: BushGuild, iterable?: Iterable<any>) {
+// 		super(guild, iterable);
+// 	}
+// }
diff --git a/src/lib/extensions/discord.js/BushMessageManager.ts b/src/lib/extensions/discord.js/BushMessageManager.ts
index efc6369..181808a 100644
--- a/src/lib/extensions/discord.js/BushMessageManager.ts
+++ b/src/lib/extensions/discord.js/BushMessageManager.ts
@@ -4,11 +4,12 @@ import { BushClient } from '../discord-akairo/BushClient';
 import { BushDMChannel } from './BushDMChannel';
 import { BushMessage } from './BushMessage';
 import { BushTextChannel } from './BushTextChannel';
+import { BushThreadChannel } from './BushThreadChannel';
 
 export class BushMessageManager extends MessageManager {
 	public declare readonly client: BushClient;
 	public declare cache: Collection<Snowflake, BushMessage>;
-	public constructor(channel: BushTextChannel | BushDMChannel, iterable?: Iterable<any>) {
+	public constructor(channel: BushTextChannel | BushDMChannel | BushThreadChannel, iterable?: Iterable<any>) {
 		super(channel, iterable);
 	}
 }
diff --git a/src/lib/extensions/discord.js/BushThreadMemberManager.ts b/src/lib/extensions/discord.js/BushThreadMemberManager.ts
index e375322..0c44f71 100644
--- a/src/lib/extensions/discord.js/BushThreadMemberManager.ts
+++ b/src/lib/extensions/discord.js/BushThreadMemberManager.ts
@@ -1,15 +1,8 @@
 /* eslint-disable @typescript-eslint/no-explicit-any */
 /* eslint-disable @typescript-eslint/no-empty-interface */
-import { Snowflake, ThreadMemberManager, UserResolvable } from 'discord.js';
+import { ThreadMemberManager } from 'discord.js';
 import { BushClient } from '../discord-akairo/BushClient';
-import { BushGuildMember } from './BushGuildMember';
-import { BushMessage } from './BushMessage';
 import { BushThreadChannel } from './BushThreadChannel';
-import { BushThreadMember } from './BushThreadMember';
-import { BushUser } from './BushUser';
-
-export type BushThreadMemberResolvable = BushThreadMember | UserResolvable;
-export type BushUserResolvable = BushUser | Snowflake | BushMessage | BushGuildMember | BushThreadMember;
 
 export interface BushThreadMemberManager extends ThreadMemberManager {}
 
diff --git a/src/lib/extensions/global.d.ts b/src/lib/extensions/global.d.ts
new file mode 100644
index 0000000..6b5d129
--- /dev/null
+++ b/src/lib/extensions/global.d.ts
@@ -0,0 +1,9 @@
+import { BushClient } from './discord-akairo/BushClient';
+declare global {
+	declare namespace NodeJS {
+		export interface Global {
+			client: BushClient;
+		}
+	}
+	const client: BushClient;
+}
diff --git a/src/lib/models/ModLog.ts b/src/lib/models/ModLog.ts
index 94c464d..1d850d9 100644
--- a/src/lib/models/ModLog.ts
+++ b/src/lib/models/ModLog.ts
@@ -1,3 +1,4 @@
+import { Snowflake } from 'discord.js';
 import { DataTypes, Sequelize } from 'sequelize';
 import { v4 as uuidv4 } from 'uuid';
 import { BaseModel } from './BaseModel';
@@ -16,31 +17,31 @@ export enum ModLogType {
 export interface ModLogModel {
 	id: string;
 	type: ModLogType;
-	user: string;
-	moderator: string;
+	user: Snowflake;
+	moderator: Snowflake;
 	reason: string;
 	duration: number;
-	guild: string;
+	guild: Snowflake;
 }
 
 export interface ModLogModelCreationAttributes {
 	id?: string;
 	type: ModLogType;
-	user: string;
-	moderator: string;
+	user: Snowflake;
+	moderator: Snowflake;
 	reason?: string;
 	duration?: number;
-	guild: string;
+	guild: Snowflake;
 }
 
 export class ModLog extends BaseModel<ModLogModel, ModLogModelCreationAttributes> implements ModLogModel {
 	id: string;
 	type: ModLogType;
-	user: string;
-	moderator: string;
-	guild: string;
+	user: Snowflake;
+	moderator: Snowflake;
 	reason: string | null;
 	duration: number | null;
+	guild: Snowflake;
 
 	static initModel(sequelize: Sequelize): void {
 		ModLog.init(
-- 
cgit