diff options
Diffstat (limited to 'src')
90 files changed, 1473 insertions, 1272 deletions
| diff --git a/src/commands/_fake-command/ironmoon.ts b/src/commands/_fake-command/ironmoon.ts index ddc6ced..d7e737f 100644 --- a/src/commands/_fake-command/ironmoon.ts +++ b/src/commands/_fake-command/ironmoon.ts @@ -5,7 +5,9 @@ export default class IronmoonCommand extends BushCommand {  		super('ironmoon', {  			category: 'fake-commands',  			description: { content: '', examples: '', usage: '' }, -			pseudo: true +			pseudo: true, +			clientPermissions: [], +			userPermissions: []  		});  	}  	public override condition(message: BushMessage): boolean { diff --git a/src/commands/admin/channelPermissions.ts b/src/commands/admin/channelPermissions.ts index f313a8f..f8c97a9 100644 --- a/src/commands/admin/channelPermissions.ts +++ b/src/commands/admin/channelPermissions.ts @@ -1,5 +1,6 @@  import { GuildMember, MessageEmbed, Role } from 'discord.js';  import { BushCommand, BushMessage } from '../../lib'; +import { ButtonPaginator } from '../../lib/common/ButtonPaginator';  export default class ChannelPermissionsCommand extends BushCommand {  	public constructor() { @@ -42,9 +43,7 @@ export default class ChannelPermissionsCommand extends BushCommand {  					}  				}  			], -			ratelimit: 4, -			cooldown: 4000, -			clientPermissions: ['MANAGE_CHANNELS', 'SEND_MESSAGES'], +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['MANAGE_CHANNELS']),  			userPermissions: ['ADMINISTRATOR'],  			channel: 'guild'  		}); @@ -85,7 +84,7 @@ export default class ChannelPermissionsCommand extends BushCommand {  				paginate.push(new MessageEmbed().setDescription(failure.substring(i, Math.min(failure.length, i + 4000))));  			}  			const normalMessage = `Finished changing perms! Failed channels:`; -			return await client.util.buttonPaginate(message, paginate, normalMessage); +			return await ButtonPaginator.send(message, paginate, normalMessage);  		} else {  			return await message.util.reply({  				content: `Finished changing perms! Failed channels:`, diff --git a/src/commands/admin/roleAll.ts b/src/commands/admin/roleAll.ts index d965239..ec1f2b5 100644 --- a/src/commands/admin/roleAll.ts +++ b/src/commands/admin/roleAll.ts @@ -28,7 +28,7 @@ export default class RoleAllCommand extends BushCommand {  				}  			],  			channel: 'guild', -			clientPermissions: ['MANAGE_ROLES', 'SEND_MESSAGES'], +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['MANAGE_ROLES']),  			userPermissions: ['ADMINISTRATOR'],  			typing: true  		}); diff --git a/src/commands/config/blacklist.ts b/src/commands/config/blacklist.ts index 5dea36a..9f3737d 100644 --- a/src/commands/config/blacklist.ts +++ b/src/commands/config/blacklist.ts @@ -54,8 +54,8 @@ export default class BlacklistCommand extends BushCommand {  				}  			],  			channel: 'guild', -			clientPermissions: ['SEND_MESSAGES'], -			userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: ['MANAGE_GUILD']  		});  	} diff --git a/src/commands/config/config.ts b/src/commands/config/config.ts index 2075531..72ba566 100644 --- a/src/commands/config/config.ts +++ b/src/commands/config/config.ts @@ -12,6 +12,7 @@ import {  	MessageOptions,  	MessageSelectMenu,  	Role, +	Snowflake,  	User  } from 'discord.js';  import _ from 'lodash'; @@ -51,11 +52,7 @@ 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' -												| 'USER', +											type: guildSettingsObj[setting].type.replace('-array', '').toUpperCase() as SlashArgType,  											required: true  										}  									] @@ -70,11 +67,7 @@ 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' -												| 'USER', +											type: guildSettingsObj[setting].type.replace('-array', '').toUpperCase() as SlashArgType,  											required: true  										}  									] @@ -96,7 +89,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' | 'USER', +											type: guildSettingsObj[setting].type.toUpperCase() as SlashArgType,  											required: true  										}  									] @@ -105,13 +98,13 @@ export default class SettingsCommand extends BushCommand {  				};  			}),  			channel: 'guild', -			clientPermissions: ['SEND_MESSAGES'], -			userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: ['MANAGE_GUILD']  		});  	}  	// I make very readable code :) -	override *args(message: BushMessage): IterableIterator<ArgumentOptions | Flag> { +	override *args(message: BushMessage): Generator<ArgumentOptions | Flag> {  		const optional = message.util.parsed!.alias === 'settings';  		const setting = yield {  			id: 'setting', @@ -126,7 +119,7 @@ export default class SettingsCommand extends BushCommand {  		};  		const actionType = setting -			? guildSettingsObj[setting as unknown as GuildSettings]?.type.includes('-array') +			? guildSettingsObj[setting as GuildSettings]?.type.includes('-array')  				? ['view', 'add', 'remove']  				: ['view', 'set']  			: undefined; @@ -151,13 +144,13 @@ export default class SettingsCommand extends BushCommand {  		const valueType =  			setting && action && action !== 'view' -				? (guildSettingsObj[setting as unknown as GuildSettings].type.replace('-array', '') as 'string' | 'channel' | 'role') +				? (guildSettingsObj[setting as GuildSettings].type.replace('-array', '') as 'string' | 'channel' | 'role')  				: undefined;  		const grammar =  			setting && action && action !== 'view' -				? (action as unknown as 'add' | 'remove' | 'set') === 'add' +				? (action as 'add' | 'remove' | 'set') === 'add'  					? `to the ${setting} setting` -					: (action as unknown as 'remove' | 'set') === 'remove' +					: (action as 'remove' | 'set') === 'remove'  					? `from the ${setting} setting`  					: `the ${setting} setting to`  				: undefined; @@ -184,8 +177,8 @@ export default class SettingsCommand extends BushCommand {  		args: {  			setting?: GuildSettings;  			subcommandGroup?: GuildSettings; -			action?: 'view' | 'add' | 'remove' | 'set'; -			subcommand?: 'view' | 'add' | 'remove' | 'set'; +			action?: Action; +			subcommand?: Action;  			value: string | Channel | Role;  		}  	): Promise<unknown> { @@ -214,9 +207,9 @@ export default class SettingsCommand extends BushCommand {  			if (!value)  				return await message.util.reply(  					`${util.emojis.error} You must choose a value to ${action} ${ -						(action as unknown as 'add' | 'remove' | 'set') === 'add' +						action === 'add'  							? `to the ${setting} setting` -							: (action as unknown as 'remove' | 'set') === 'remove' +							: action === 'remove'  							? `from the ${setting} setting`  							: `the ${setting} setting to`  					}` @@ -295,12 +288,10 @@ export default class SettingsCommand extends BushCommand {  			return { embeds: [settingsEmbed], components: [selMenu] };  		} else {  			settingsEmbed.setTitle(guildSettingsObj[setting].name); -			const generateCurrentValue = async ( -				type: 'string' | 'channel' | 'channel-array' | 'role' | 'role-array' | 'user' | 'user-array' | 'custom' -			): Promise<string> => { +			const generateCurrentValue = async (type: SettingTypes): Promise<string> => {  				const feat = await message.guild!.getSetting(setting); -				switch (type.replace('-array', '') as 'string' | 'channel' | 'role' | 'user' | 'custom') { +				switch (type.replace('-array', '') as BaseSettingTypes) {  					case 'string': {  						return Array.isArray(feat)  							? feat.length @@ -315,21 +306,21 @@ export default class SettingsCommand extends BushCommand {  							? feat.length  								? feat.map((feat) => `<#${feat}>`).join('\n')  								: '[Empty Array]' -							: `<#${feat as string}>`; +							: `<#${feat as Snowflake}>`;  					}  					case 'role': {  						return Array.isArray(feat)  							? feat.length  								? feat.map((feat) => `<@&${feat}>`).join('\n')  								: '[Empty Array]' -							: `<@&${feat as string}>`; +							: `<@&${feat as Snowflake}>`;  					}  					case 'user': {  						return Array.isArray(feat)  							? feat.length  								? feat.map((feat) => `<@${feat}>`).join('\n')  								: '[Empty Array]' -							: `<@${feat as string}>`; +							: `<@${feat as Snowflake}>`;  					}  					case 'custom': {  						return util.inspectAndRedact(feat); @@ -355,13 +346,13 @@ export default class SettingsCommand extends BushCommand {  					guildSettingsObj[setting].type.includes('-array') ? 'add/remove' : 'set'  				} <value>" to set this setting.`  			); -			settingsEmbed.addField( -				'value', -				(await generateCurrentValue( -					guildSettingsObj[setting].type as 'string' | 'channel' | 'channel-array' | 'role' | 'role-array' -				)) || '[No Value Set]' -			); +			settingsEmbed.addField('value', (await generateCurrentValue(guildSettingsObj[setting].type)) || '[No Value Set]');  			return { embeds: [settingsEmbed], components: [components] };  		}  	}  } + +type SlashArgType = 'ROLE' | 'STRING' | 'CHANNEL' | 'USER'; +type BaseSettingTypes = 'string' | 'channel' | 'role' | 'user' | 'custom'; +type SettingTypes = 'string' | 'channel' | 'channel-array' | 'role' | 'role-array' | 'user' | 'user-array' | 'custom'; +type Action = 'view' | 'add' | 'remove' | 'set'; diff --git a/src/commands/config/customAutomodPhrases.ts b/src/commands/config/customAutomodPhrases.ts index 2cbe874..51e219a 100644 --- a/src/commands/config/customAutomodPhrases.ts +++ b/src/commands/config/customAutomodPhrases.ts @@ -49,8 +49,8 @@  // 			ownerOnly: true,  // 			channel: 'guild',  // 			hidden: true, -// 			clientPermissions: ['SEND_MESSAGES'], -// 			userPermissions: ['SEND_MESSAGES'] +// 			clientPermissions: (m) => util.clientSendAndPermCheck(m), +// 			userPermissions: []  // 		});  // 	} diff --git a/src/commands/config/disable.ts b/src/commands/config/disable.ts index db4909a..bca1207 100644 --- a/src/commands/config/disable.ts +++ b/src/commands/config/disable.ts @@ -52,8 +52,8 @@ export default class DisableCommand extends BushCommand {  				}  			],  			channel: 'guild', -			clientPermissions: ['SEND_MESSAGES'], -			userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: ['MANAGE_GUILD']  		});  	} @@ -63,13 +63,12 @@ export default class DisableCommand extends BushCommand {  		message: BushMessage | BushSlashMessage,  		args: { action: 'enable' | 'disable'; command: BushCommand | string; global: boolean }  	): Promise<unknown> { -		let action: 'disable' | 'enable' | 'toggle' = -			args.action ?? (message?.util?.parsed?.alias as 'disable' | 'enable') ?? 'toggle'; +		let action = (args.action ?? message?.util?.parsed?.alias ?? 'toggle') as 'disable' | 'enable' | 'toggle';  		const global = args.global && message.author.isOwner();  		const commandID = (args.command as BushCommand).id;  		if (global) { -			if ((action as 'disable' | 'enable' | 'toggle') === 'toggle') { +			if (action === 'toggle') {  				const disabledCommands = (  					(await Global.findByPk(client.config.environment)) ??  					(await Global.create({ environment: client.config.environment })) @@ -99,7 +98,7 @@ export default class DisableCommand extends BushCommand {  			// guild disable  		} else {  			const disabledCommands = await message.guild!.getSetting('disabledCommands'); -			if ((action as 'disable' | 'enable' | 'toggle') === 'toggle') { +			if (action === 'toggle') {  				action = disabledCommands.includes(commandID) ? 'disable' : 'enable';  			}  			const newValue = util.addOrRemoveFromArray(action === 'disable' ? 'remove' : 'add', disabledCommands, commandID); diff --git a/src/commands/config/features.ts b/src/commands/config/features.ts index 076d469..3c3075c 100644 --- a/src/commands/config/features.ts +++ b/src/commands/config/features.ts @@ -13,8 +13,8 @@ export default class FeaturesCommand extends BushCommand {  			},  			slash: true,  			channel: 'guild', -			clientPermissions: ['SEND_MESSAGES', 'EMBED_LINKS'], -			userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), +			userPermissions: ['MANAGE_GUILD']  		});  	} diff --git a/src/commands/config/levelRoles.ts b/src/commands/config/levelRoles.ts index 36dc50c..ee5f255 100644 --- a/src/commands/config/levelRoles.ts +++ b/src/commands/config/levelRoles.ts @@ -1,65 +1,65 @@ -import { BushCommand, BushMessage, BushSlashMessage } from '@lib'; +// import { BushCommand, BushMessage, BushSlashMessage } from '@lib'; -export default class LevelRolesCommand extends BushCommand { -	public constructor() { -		super('levelRole', { -			aliases: ['level-role', 'level-roles', 'lr'], -			category: 'config', -			description: { -				content: 'Configure roles to be assigned to users upon reaching certain levels.', -				usage: ['level-role add <level> <role>', 'level-role remove <level>'], -				examples: ['level-role 1 2'] -			}, -			args: [ -				{ -					id: 'action', -					customType: ['add', 'remove'] -				}, -				{ -					id: 'role', -					type: 'role', -					prompt: { -						start: 'What would you like to set your first argument to be?', -						retry: '{error} Pick a valid argument.', -						optional: false -					} -				}, -				{ -					id: 'level', -					type: 'integer', -					prompt: { -						start: 'What would you like to set your second argument to be?', -						retry: '{error} Pick a valid argument.', -						optional: false -					} -				} -			], -			slash: true, -			slashOptions: [ -				{ -					name: 'role', -					description: 'What would you like to set your first argument to be?', -					type: 'STRING', -					required: true -				}, -				{ -					name: 'level', -					description: 'What would you like to set your second argument to be?', -					type: 'STRING', -					required: true -				} -			], -			channel: 'guild', -			clientPermissions: ['SEND_MESSAGES'], -			userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD', 'MANAGE_ROLES'] -		}); -	} +// export default class LevelRolesCommand extends BushCommand { +// 	public constructor() { +// 		super('levelRole', { +// 			aliases: ['level-role', 'level-roles', 'lr'], +// 			category: 'config', +// 			description: { +// 				content: 'Configure roles to be assigned to users upon reaching certain levels.', +// 				usage: ['level-role add <level> <role>', 'level-role remove <level>'], +// 				examples: ['level-role 1 2'] +// 			}, +// 			args: [ +// 				{ +// 					id: 'action', +// 					customType: ['add', 'remove'] +// 				}, +// 				{ +// 					id: 'role', +// 					type: 'role', +// 					prompt: { +// 						start: 'What would you like to set your first argument to be?', +// 						retry: '{error} Pick a valid argument.', +// 						optional: false +// 					} +// 				}, +// 				{ +// 					id: 'level', +// 					type: 'integer', +// 					prompt: { +// 						start: 'What would you like to set your second argument to be?', +// 						retry: '{error} Pick a valid argument.', +// 						optional: false +// 					} +// 				} +// 			], +// 			slash: true, +// 			slashOptions: [ +// 				{ +// 					name: 'role', +// 					description: 'What would you like to set your first argument to be?', +// 					type: 'STRING', +// 					required: true +// 				}, +// 				{ +// 					name: 'level', +// 					description: 'What would you like to set your second argument to be?', +// 					type: 'STRING', +// 					required: true +// 				} +// 			], +// 			channel: 'guild', +// 			clientPermissions: (m) => util.clientSendAndPermCheck(m), +// 			userPermissions: ['MANAGE_GUILD', 'MANAGE_ROLES'] +// 		}); +// 	} -	public override async exec( -		message: BushMessage | BushSlashMessage, -		args: { required_argument: string; optional_argument: string } -	): Promise<unknown> { -		return await message.util.reply(`${util.emojis.error} Do not use the template command.`); -		args; -	} -} +// 	public override async exec( +// 		message: BushMessage | BushSlashMessage, +// 		args: { required_argument: string; optional_argument: string } +// 	): Promise<unknown> { +// 		return await message.util.reply(`${util.emojis.error} Do not use the template command.`); +// 		args; +// 	} +// } diff --git a/src/commands/config/log.ts b/src/commands/config/log.ts index 03e3582..363620a 100644 --- a/src/commands/config/log.ts +++ b/src/commands/config/log.ts @@ -29,8 +29,8 @@ export default class LogCommand extends BushCommand {  				}  			],  			channel: 'guild', -			clientPermissions: ['SEND_MESSAGES'], -			userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: ['MANAGE_GUILD']  		});  	} diff --git a/src/commands/dev/__template.ts b/src/commands/dev/__template.ts index 49549af..b9d9114 100644 --- a/src/commands/dev/__template.ts +++ b/src/commands/dev/__template.ts @@ -49,8 +49,8 @@ export default class TemplateCommand extends BushCommand {  			ownerOnly: true,  			channel: 'guild',  			hidden: true, -			clientPermissions: ['SEND_MESSAGES'], -			userPermissions: ['SEND_MESSAGES'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: []  		});  	} diff --git a/src/commands/dev/eval.ts b/src/commands/dev/eval.ts index 2f61822..c4a6e8f 100644 --- a/src/commands/dev/eval.ts +++ b/src/commands/dev/eval.ts @@ -45,7 +45,9 @@ export default class EvalCommand extends BushCommand {  				{ name: 'show_proto', description: 'Show prototype.', type: 'BOOLEAN', required: false },  				{ name: 'show_methods', description: 'Show class functions.', type: 'BOOLEAN', required: false }  			], -			ownerOnly: true +			ownerOnly: true, +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: []  		});  	} diff --git a/src/commands/dev/reload.ts b/src/commands/dev/reload.ts index 987cd01..43b8ab6 100644 --- a/src/commands/dev/reload.ts +++ b/src/commands/dev/reload.ts @@ -19,6 +19,7 @@ export default class ReloadCommand extends BushCommand {  			// ],  			ownerOnly: true,  			typing: true, +			slash: true,  			// slashOptions: [  			// 	{  			// 		name: 'fast', @@ -27,7 +28,8 @@ export default class ReloadCommand extends BushCommand {  			// 		required: false  			// 	}  			// ], -			slash: true +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: []  		});  	} diff --git a/src/commands/dev/say.ts b/src/commands/dev/say.ts index 1c797ea..a7e9943 100644 --- a/src/commands/dev/say.ts +++ b/src/commands/dev/say.ts @@ -13,7 +13,7 @@ export default class SayCommand extends BushCommand {  			},  			args: [  				{ -					id: 'say', +					id: 'content',  					type: 'string',  					match: 'rest',  					prompt: { start: 'What would you like the bot to say?', retry: '{error} Choose something valid to say.' } @@ -21,20 +21,21 @@ export default class SayCommand extends BushCommand {  			],  			slashOptions: [{ name: 'content', description: 'What would you like the bot to say?', type: 'STRING' }],  			ownerOnly: true, -			clientPermissions: ['SEND_MESSAGES'], +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: [],  			slash: true  		});  	} -	public override async exec(message: BushMessage, { say }: { say: string }): Promise<unknown> { +	public override async exec(message: BushMessage, args: { content: string }): Promise<unknown> {  		if (!message.author.isOwner())  			return await message.util.reply(`${util.emojis.error} Only my developers can run this command.`);  		await message.delete().catch(() => {}); -		await message.util.send({ content: say, allowedMentions: AllowedMentions.none() }); +		await message.util.send({ content: args.content, allowedMentions: AllowedMentions.none() });  	} -	public override async execSlash(message: AkairoMessage, { content }: { content: string }): Promise<unknown> { +	public override async execSlash(message: AkairoMessage, args: { content: string }): Promise<unknown> {  		if (!client.config.owners.includes(message.author.id)) {  			return await message.interaction.reply({  				content: `${util.emojis.error} Only my developers can run this command.`, @@ -42,6 +43,6 @@ export default class SayCommand extends BushCommand {  			});  		}  		await message.interaction.reply({ content: 'Attempting to send message.', ephemeral: true }); -		return message.channel!.send({ content, allowedMentions: AllowedMentions.none() }); +		return message.channel!.send({ content: args.content, allowedMentions: AllowedMentions.none() });  	}  } diff --git a/src/commands/dev/servers.ts b/src/commands/dev/servers.ts index c86e889..e366a64 100644 --- a/src/commands/dev/servers.ts +++ b/src/commands/dev/servers.ts @@ -1,5 +1,6 @@ -import { Guild, MessageEmbed } from 'discord.js'; +import { Guild, MessageEmbedOptions } from 'discord.js';  import { BushCommand, BushMessage, BushSlashMessage } from '../../lib'; +import { ButtonPaginator } from '../../lib/common/ButtonPaginator';  export default class ServersCommand extends BushCommand {  	public constructor() { @@ -11,38 +12,30 @@ export default class ServersCommand extends BushCommand {  				usage: 'servers',  				examples: ['servers']  			}, -			clientPermissions: ['SEND_MESSAGES'], -			userPermissions: ['SEND_MESSAGES'], +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: [],  			ownerOnly: true  		});  	}  	public override async exec(message: BushMessage | BushSlashMessage): Promise<unknown> { -		const maxLength = 10;  		const guilds = [...client.guilds.cache.sort((a, b) => (a.memberCount < b.memberCount ? 1 : -1)).values()]; -		const chunkedGuilds: Guild[][] = []; -		const embeds: MessageEmbed[] = []; - -		for (let i = 0, j = guilds.length; i < j; i += maxLength) { -			chunkedGuilds.push(guilds.slice(i, i + maxLength)); -		} - -		chunkedGuilds.forEach((c: Guild[]) => { -			const embed = new MessageEmbed(); -			c.forEach((g: Guild) => { -				const owner = client.users.cache.get(g.ownerId)?.tag; -				embed -					.addField( -						`**${g.name}**`, -						`**ID:** ${g.id}\n**Owner:** ${owner ? owner : g.ownerId}\n**Members:** ${g.memberCount.toLocaleString()}`, -						false -					) -					.setTitle(`Server List [\`${client.guilds.cache.size.toLocaleString()}\`]`) -					.setColor(util.colors.default); -			}); -			embeds.push(embed); +		const chunkedGuilds: Guild[][] = util.chunk(guilds, 10); +		const embeds: MessageEmbedOptions[] = chunkedGuilds.map((chunk) => { +			return { +				title: `Server List [\`${guilds.length.toLocaleString()}\`]`, +				color: util.colors.default, +				fields: chunk.map((guild) => ({ +					name: util.format.bold(guild.name), +					value: [ +						`**ID:** ${guild.id}`, +						`**Owner:** ${client.users.cache.has(guild.ownerId) ? client.users.cache.get(guild.ownerId)!.tag : guild.ownerId}`, +						`**Members:** ${guild.memberCount.toLocaleString()}` +					].join('\n') +				})) +			} as MessageEmbedOptions;  		}); -		return await util.buttonPaginate(message, embeds); +		return await ButtonPaginator.send(message, embeds);  	}  } diff --git a/src/commands/dev/sh.ts b/src/commands/dev/sh.ts index 067a0e6..3fca2b2 100644 --- a/src/commands/dev/sh.ts +++ b/src/commands/dev/sh.ts @@ -33,7 +33,9 @@ export default class ShCommand extends BushCommand {  					}  				}  			], -			ownerOnly: true +			ownerOnly: true, +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: []  		});  	} diff --git a/src/commands/dev/superUser.ts b/src/commands/dev/superUser.ts index 4dc4584..fcdec53 100644 --- a/src/commands/dev/superUser.ts +++ b/src/commands/dev/superUser.ts @@ -12,7 +12,8 @@ export default class SuperUserCommand extends BushCommand {  				usage: 'superuser <add/remove> <user>',  				examples: ['superuser add IRONM00N']  			}, -			clientPermissions: ['SEND_MESSAGES'], +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: [],  			ownerOnly: true  		});  	} diff --git a/src/commands/dev/test.ts b/src/commands/dev/test.ts index c77d7ec..a1e5052 100644 --- a/src/commands/dev/test.ts +++ b/src/commands/dev/test.ts @@ -7,6 +7,7 @@ import {  	MessageButton,  	MessageEmbed  } from 'discord.js'; +import { ButtonPaginator } from '../../lib/common/ButtonPaginator';  export default class TestCommand extends BushCommand {  	public constructor() { @@ -18,8 +19,6 @@ export default class TestCommand extends BushCommand {  				usage: 'test [feature]',  				examples: ['test lots of buttons', 'test buttons']  			}, -			clientPermissions: ['SEND_MESSAGES'], -			userPermissions: ['SEND_MESSAGES'],  			args: [  				{  					id: 'feature', @@ -32,7 +31,9 @@ export default class TestCommand extends BushCommand {  					}  				}  			], -			superUserOnly: true +			superUserOnly: true, +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: []  		});  	} @@ -95,7 +96,7 @@ export default class TestCommand extends BushCommand {  			for (let i = 1; i <= 5; i++) {  				embeds.push(new MessageEmbed().setDescription(i.toString()));  			} -			return await util.buttonPaginate(message, embeds); +			return await ButtonPaginator.send(message, embeds);  		} else if (['lots of embeds'].includes(args?.feature?.toLowerCase())) {  			const description = 'This is a description.';  			const _avatar = message.author.avatarURL({ dynamic: true }) ?? undefined; diff --git a/src/commands/fun/coinflip.ts b/src/commands/fun/coinflip.ts index e0892b7..42e7167 100644 --- a/src/commands/fun/coinflip.ts +++ b/src/commands/fun/coinflip.ts @@ -10,14 +10,15 @@ export default class CoinFlipCommand extends BushCommand {  				usage: 'coinflip',  				examples: ['coinflip']  			}, -			clientPermissions: ['SEND_MESSAGES'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: []  		});  	}  	public override async exec(message: BushMessage | BushSlashMessage): Promise<void> {  		const random = Math.random();  		let result: string; -		const fall = message.author.id === '322862723090219008' ? 0.1 : 0.001; +		const fall = message.author.id === '322862723090219008' ? 0.1 : 0.001; //dw about it  		if (random < fall) result = 'The coin fell off the table :(';  		else if (random <= 0.5 + fall / 2) result = 'Heads';  		else result = 'Tails'; diff --git a/src/commands/fun/dice.ts b/src/commands/fun/dice.ts index 241f1d2..9f18657 100644 --- a/src/commands/fun/dice.ts +++ b/src/commands/fun/dice.ts @@ -10,7 +10,8 @@ export default class EightBallCommand extends BushCommand {  				usage: 'dice',  				examples: ['dice']  			}, -			clientPermissions: ['SEND_MESSAGES'], +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: [],  			slash: true  		});  	} diff --git a/src/commands/fun/eightBall.ts b/src/commands/fun/eightBall.ts index efaaff5..b3c56da 100644 --- a/src/commands/fun/eightBall.ts +++ b/src/commands/fun/eightBall.ts @@ -30,8 +30,8 @@ export default class EightBallCommand extends BushCommand {  					required: true  				}  			], -			clientPermissions: ['SEND_MESSAGES'], -			userPermissions: ['SEND_MESSAGES'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: []  		});  	} diff --git a/src/commands/fun/minesweeper.ts b/src/commands/fun/minesweeper.ts index 57909a2..b45dcda 100644 --- a/src/commands/fun/minesweeper.ts +++ b/src/commands/fun/minesweeper.ts @@ -42,43 +42,15 @@ export default class MinesweeperCommand extends BushCommand {  					},  					default: 10  				}, -				{ -					id: 'spaces', -					match: 'flag', -					flag: '--spaces' -				}, -				{ -					id: 'reveal_first_cell', -					match: 'flag', -					flag: '--revealFirstCell' -				} +				{ id: 'spaces', match: 'flag', flag: '--spaces' }, +				{ id: 'reveal_first_cell', match: 'flag', flag: '--revealFirstCell' }  			],  			slash: true,  			slashOptions: [ -				{ -					name: 'rows', -					description: 'How many rows would you like?', -					type: 'INTEGER', -					required: false -				}, -				{ -					name: 'columns', -					description: 'How many rows would you like?', -					type: 'INTEGER', -					required: false -				}, -				{ -					name: 'mines', -					description: 'How many rows would you like?', -					type: 'INTEGER', -					required: false -				}, -				{ -					name: 'spaces', -					description: 'Would you like there to be spaces?', -					type: 'BOOLEAN', -					required: false -				}, +				{ name: 'rows', description: 'How many rows would you like?', type: 'INTEGER', required: false }, +				{ name: 'columns', description: 'How many rows would you like?', type: 'INTEGER', required: false }, +				{ name: 'mines', description: 'How many rows would you like?', type: 'INTEGER', required: false }, +				{ name: 'spaces', description: 'Would you like there to be spaces?', type: 'BOOLEAN', required: false },  				{  					name: 'reveal_first_cell',  					description: 'Would you like to automatically reveal the first cell?', @@ -86,35 +58,23 @@ export default class MinesweeperCommand extends BushCommand {  					required: false  				}  			], -			clientPermissions: ['SEND_MESSAGES'], -			userPermissions: ['SEND_MESSAGES'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: []  		});  	}  	public override async exec(  		message: BushMessage | BushSlashMessage, -		{ -			rows, -			columns, -			mines, -			spaces, -			reveal_first_cell -		}: { -			rows: number; -			columns: number; -			mines: number; -			spaces: boolean; -			reveal_first_cell: boolean; -		} +		args: { rows: number; columns: number; mines: number; spaces: boolean; reveal_first_cell: boolean }  	): Promise<unknown> {  		const minesweeper = new Minesweeper({ -			rows: rows ?? 9, -			columns: columns ?? 9, -			mines: mines ?? 10, +			rows: args.rows ?? 9, +			columns: args.columns ?? 9, +			mines: args.mines ?? 10,  			emote: 'boom', -			revealFirstCell: reveal_first_cell ?? false, +			revealFirstCell: args.reveal_first_cell ?? false,  			zeroFirstCell: true, -			spaces: spaces ?? false, +			spaces: args.spaces ?? false,  			returnType: 'emoji'  		});  		const matrix = minesweeper.start(); diff --git a/src/commands/info/avatar.ts b/src/commands/info/avatar.ts index 1e6496a..7625b61 100644 --- a/src/commands/info/avatar.ts +++ b/src/commands/info/avatar.ts @@ -22,7 +22,8 @@ export default class AvatarCommand extends BushCommand {  					}  				}  			], -			clientPermissions: ['SEND_MESSAGES', 'EMBED_LINKS'], +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), +			userPermissions: [],  			slash: true,  			slashOptions: [  				{ diff --git a/src/commands/info/botInfo.ts b/src/commands/info/botInfo.ts index 257dc90..ffb785c 100644 --- a/src/commands/info/botInfo.ts +++ b/src/commands/info/botInfo.ts @@ -14,8 +14,8 @@ export default class BotInfoCommand extends BushCommand {  				examples: ['botinfo']  			},  			slash: true, -			clientPermissions: ['SEND_MESSAGES', 'EMBED_LINKS'], -			userPermissions: ['SEND_MESSAGES'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), +			userPermissions: []  		});  	} diff --git a/src/commands/info/color.ts b/src/commands/info/color.ts index d641fd7..958aadd 100644 --- a/src/commands/info/color.ts +++ b/src/commands/info/color.ts @@ -31,7 +31,8 @@ export default class ColorCommand extends BushCommand {  				}  			],  			channel: 'guild', -			clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), +			userPermissions: []  		});  	} diff --git a/src/commands/info/guildInfo.ts b/src/commands/info/guildInfo.ts index f1db783..4a79918 100644 --- a/src/commands/info/guildInfo.ts +++ b/src/commands/info/guildInfo.ts @@ -31,8 +31,8 @@ export default class GuildInfoCommand extends BushCommand {  					required: false  				}  			], -			clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'], -			userPermissions: ['SEND_MESSAGES'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), +			userPermissions: []  		});  	} diff --git a/src/commands/info/help.ts b/src/commands/info/help.ts index ce171c9..5e80199 100644 --- a/src/commands/info/help.ts +++ b/src/commands/info/help.ts @@ -37,8 +37,8 @@ export default class HelpCommand extends BushCommand {  					required: false  				}  			], -			clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'], -			userPermissions: ['SEND_MESSAGES'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), +			userPermissions: []  		});  	} diff --git a/src/commands/info/icon.ts b/src/commands/info/icon.ts index 677fdaf..ce27cc0 100644 --- a/src/commands/info/icon.ts +++ b/src/commands/info/icon.ts @@ -11,7 +11,8 @@ export default class IconCommand extends BushCommand {  				usage: 'icon',  				examples: 'icon'  			}, -			clientPermissions: ['SEND_MESSAGES', 'EMBED_LINKS'], +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), +			userPermissions: [],  			channel: 'guild',  			slash: true  		}); diff --git a/src/commands/info/links.ts b/src/commands/info/links.ts index b3a762a..569b959 100644 --- a/src/commands/info/links.ts +++ b/src/commands/info/links.ts @@ -14,7 +14,8 @@ export default class LinksCommand extends BushCommand {  			},  			ratelimit: 4,  			cooldown: 4000, -			clientPermissions: ['SEND_MESSAGES'], +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: [],  			slash: true  		});  	} diff --git a/src/commands/info/ping.ts b/src/commands/info/ping.ts index 5f2220c..82db2ff 100644 --- a/src/commands/info/ping.ts +++ b/src/commands/info/ping.ts @@ -12,8 +12,8 @@ export default class PingCommand extends BushCommand {  				examples: ['ping']  			},  			slash: true, -			clientPermissions: ['SEND_MESSAGES', 'EMBED_LINKS'], -			userPermissions: ['SEND_MESSAGES'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), +			userPermissions: []  		});  	} diff --git a/src/commands/info/pronouns.ts b/src/commands/info/pronouns.ts index 77612da..cd66530 100644 --- a/src/commands/info/pronouns.ts +++ b/src/commands/info/pronouns.ts @@ -1,5 +1,4 @@  import { BushCommand, BushMessage, BushSlashMessage } from '@lib'; -import { Snowflake } from 'discord-api-types';  import { MessageEmbed, User } from 'discord.js';  export default class PronounsCommand extends BushCommand { @@ -15,7 +14,7 @@ export default class PronounsCommand extends BushCommand {  			args: [  				{  					id: 'user', -					customType: util.arg.union('user', 'snowflake'), +					type: 'globalUser',  					prompt: {  						start: 'Who would you like to view the pronouns of?',  						retry: '{error} Choose a valid user to view the pronouns of.', @@ -23,7 +22,8 @@ export default class PronounsCommand extends BushCommand {  					}  				}  			], -			clientPermissions: ['SEND_MESSAGES'], +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), +			userPermissions: [],  			slashOptions: [  				{  					name: 'user', @@ -35,18 +35,15 @@ export default class PronounsCommand extends BushCommand {  			slash: true  		});  	} -	override async exec(message: BushMessage | BushSlashMessage, args: { user?: User | Snowflake }): Promise<unknown> { -		const user = (await util.resolveNonCachedUser(args.user)) ?? message.author; - -		if (!user) return message.util.reply(`${util.emojis.error} Invalid user.`); - +	override async exec(message: BushMessage | BushSlashMessage, args: { user?: User }): Promise<unknown> { +		const user = args.user ?? message.author;  		const author = user.id === message.author.id;  		const pronouns = await util.getPronounsOf(user);  		if (!pronouns) {  			return await message.util.reply( -				`${author ? 'You do' : `${user.tag} does`} not appear to have any pronouns set. Please ${ -					author ? '' : 'tell them to' +				`${author ? 'You do' : `${user.tag} does`} not appear to have any pronouns set. Please${ +					author ? '' : ' tell them to'  				} go to https://pronoundb.org/ and set ${author ? 'your' : 'their'} pronouns.`  			);  		} else { diff --git a/src/commands/info/snowflake.ts b/src/commands/info/snowflake.ts index df11bce..81b3f9d 100644 --- a/src/commands/info/snowflake.ts +++ b/src/commands/info/snowflake.ts @@ -40,7 +40,8 @@ export default class SnowflakeCommand extends BushCommand {  					}  				}  			], -			clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'], +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), +			userPermissions: [],  			slash: true,  			slashOptions: [  				{ diff --git a/src/commands/info/userInfo.ts b/src/commands/info/userInfo.ts index b55bbcf..353d844 100644 --- a/src/commands/info/userInfo.ts +++ b/src/commands/info/userInfo.ts @@ -33,8 +33,8 @@ export default class UserInfoCommand extends BushCommand {  					required: false  				}  			], -			clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'], -			userPermissions: ['SEND_MESSAGES'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), +			userPermissions: []  		});  	} diff --git a/src/commands/leveling/leaderboard.ts b/src/commands/leveling/leaderboard.ts index 432fc60..e3344a0 100644 --- a/src/commands/leveling/leaderboard.ts +++ b/src/commands/leveling/leaderboard.ts @@ -1,5 +1,6 @@  import { BushCommand, BushMessage, BushSlashMessage, Level } from '@lib';  import { MessageEmbed } from 'discord.js'; +import { ButtonPaginator } from '../../lib/common/ButtonPaginator';  export default class LeaderboardCommand extends BushCommand {  	public constructor() { @@ -32,8 +33,8 @@ export default class LeaderboardCommand extends BushCommand {  				}  			],  			channel: 'guild', -			clientPermissions: ['SEND_MESSAGES'], -			userPermissions: ['SEND_MESSAGES'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: []  		});  	} @@ -62,6 +63,6 @@ export default class LeaderboardCommand extends BushCommand {  		const embeds = chunked.map((c) =>  			new MessageEmbed().setTitle(`${message.guild!.name}'s Leaderboard`).setDescription(c.join('\n'))  		); -		return await util.buttonPaginate(message, embeds, undefined, true, args?.page ?? undefined); +		return await ButtonPaginator.send(message, embeds, undefined, true, args?.page ?? undefined);  	}  } diff --git a/src/commands/leveling/level.ts b/src/commands/leveling/level.ts index 2073e1a..4d7adb3 100644 --- a/src/commands/leveling/level.ts +++ b/src/commands/leveling/level.ts @@ -45,11 +45,42 @@ export default class LevelCommand extends BushCommand {  				}  			],  			channel: 'guild', -			clientPermissions: ['SEND_MESSAGES'], -			userPermissions: ['SEND_MESSAGES'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: []  		});  	} +	public override async exec(message: BushMessage | BushSlashMessage, args: { user?: BushUser }): Promise<unknown> { +		if (!message.guild) return await message.util.reply(`${util.emojis.error} This command can only be run in a server.`); +		if (!(await message.guild.hasFeature('leveling'))) +			return await message.util.reply( +				`${util.emojis.error} This command can only be run in servers with the leveling feature enabled.${ +					message.member?.permissions.has('MANAGE_GUILD') +						? ` You can toggle features using the \`${ +								message.util.isSlash +									? '/' +									: client.config.isDevelopment +									? 'dev ' +									: message.util.parsed?.prefix ?? client.config.prefix +						  }features\` command.` +						: '' +				}` +			); +		const user = args.user ?? message.author; +		try { +			return await message.util.reply({ +				files: [new MessageAttachment(await this.getImage(user, message.guild!), 'level.png')] +			}); +		} catch (e) { +			if (e instanceof Error && e.message === 'User does not have a level') { +				return await message.util.reply({ +					content: `${util.emojis.error} ${user} does not have a level.`, +					allowedMentions: AllowedMentions.none() +				}); +			} else throw e; +		} +	} +  	private async getImage(user: BushUser, guild: BushGuild): Promise<Buffer> {  		const guildRows = await Level.findAll({ where: { guild: guild.id } });  		const rank = guildRows.sort((a, b) => b.xp - a.xp); @@ -111,35 +142,4 @@ export default class LevelCommand extends BushCommand {  		// Return image in buffer form  		return levelCard.toBuffer();  	} - -	public override async exec(message: BushMessage | BushSlashMessage, args: { user?: BushUser }): Promise<unknown> { -		if (!message.guild) return await message.util.reply(`${util.emojis.error} This command can only be run in a server.`); -		if (!(await message.guild.hasFeature('leveling'))) -			return await message.util.reply( -				`${util.emojis.error} This command can only be run in servers with the leveling feature enabled.${ -					message.member?.permissions.has('MANAGE_GUILD') -						? ` You can toggle features using the \`${ -								message.util.isSlash -									? '/' -									: client.config.isDevelopment -									? 'dev ' -									: message.util.parsed?.prefix ?? client.config.prefix -						  }features\` command.` -						: '' -				}` -			); -		const user = args.user ?? message.author; -		try { -			return await message.util.reply({ -				files: [new MessageAttachment(await this.getImage(user, message.guild!), 'level.png')] -			}); -		} catch (e) { -			if (e instanceof Error && e.message === 'User does not have a level') { -				return await message.util.reply({ -					content: `${util.emojis.error} ${user} does not have a level.`, -					allowedMentions: AllowedMentions.none() -				}); -			} else throw e; -		} -	}  } diff --git a/src/commands/leveling/setLevel.ts b/src/commands/leveling/setLevel.ts index b98b488..9a7337a 100644 --- a/src/commands/leveling/setLevel.ts +++ b/src/commands/leveling/setLevel.ts @@ -45,8 +45,8 @@ export default class SetLevelCommand extends BushCommand {  			],  			slash: true,  			channel: 'guild', -			clientPermissions: ['SEND_MESSAGES'], -			userPermissions: ['SEND_MESSAGES', 'ADMINISTRATOR'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: ['ADMINISTRATOR']  		});  	} diff --git a/src/commands/leveling/setXp.ts b/src/commands/leveling/setXp.ts index 3e00ea2..a73ae58 100644 --- a/src/commands/leveling/setXp.ts +++ b/src/commands/leveling/setXp.ts @@ -48,8 +48,8 @@ export default class SetXpCommand extends BushCommand {  			],  			slash: true,  			channel: 'guild', -			clientPermissions: ['SEND_MESSAGES'], -			userPermissions: ['SEND_MESSAGES', 'ADMINISTRATOR'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: ['ADMINISTRATOR']  		});  	} diff --git a/src/commands/moderation/_lockdown.ts b/src/commands/moderation/_lockdown.ts index 5df9f18..0086ff6 100644 --- a/src/commands/moderation/_lockdown.ts +++ b/src/commands/moderation/_lockdown.ts @@ -27,15 +27,15 @@ export default class LockdownCommand extends BushCommand {  				}  			],  			channel: 'guild', -			clientPermissions: ['SEND_MESSAGES'], -			userPermissions: ['SEND_MESSAGES'], +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: [],  			hidden: true  		});  	} -	public override async exec(message: BushMessage | BushSlashMessage, { all }: { all: boolean }): Promise<unknown> { +	public override async exec(message: BushMessage | BushSlashMessage, args: { all: boolean }): Promise<unknown> {  		return await message.util.reply('Unfortunately my developer is too lazy to implement this command.'); -		if (!all) { +		if (!args.all) {  			if (!['GUILD_TEXT', 'GUILD_NEWS'].includes(message.channel!.type))  				return message.util.reply(`${util.emojis.error} You can only lock down text and announcement channels.`); diff --git a/src/commands/moderation/ban.ts b/src/commands/moderation/ban.ts index 4c2b3d3..b3d97d2 100644 --- a/src/commands/moderation/ban.ts +++ b/src/commands/moderation/ban.ts @@ -1,6 +1,6 @@  import { AllowedMentions, BushCommand, BushMessage, BushSlashMessage } from '@lib';  import { Snowflake, User } from 'discord.js'; -import { Moderation } from '../../lib/common/moderation'; +import { Moderation } from '../../lib/common/Moderation';  export default class BanCommand extends BushCommand {  	public constructor() { diff --git a/src/commands/moderation/evidence.ts b/src/commands/moderation/evidence.ts index 250df24..a681bc1 100644 --- a/src/commands/moderation/evidence.ts +++ b/src/commands/moderation/evidence.ts @@ -27,8 +27,8 @@ export default class EvidenceCommand extends BushCommand {  				}  			],  			channel: 'guild', -			clientPermissions: ['SEND_MESSAGES'], -			userPermissions: ['SEND_MESSAGES', 'MANAGE_MESSAGES'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: (m) => util.userGuildPermCheck(m, ['MANAGE_MESSAGES'])  		});  	} diff --git a/src/commands/moderation/hideCase.ts b/src/commands/moderation/hideCase.ts index 2ed788a..38cfe31 100644 --- a/src/commands/moderation/hideCase.ts +++ b/src/commands/moderation/hideCase.ts @@ -20,10 +20,8 @@ export default class HideCaseCommand extends BushCommand {  					}  				}  			], -			userPermissions: (message) => { -				return message.member?.permissions.has('MANAGE_MESSAGES') ? null : ['MANAGE_MESSAGES']; -			}, -			clientPermissions: ['SEND_MESSAGES'], +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: (m) => util.userGuildPermCheck(m, ['MANAGE_MESSAGES']),  			slash: true,  			slashOptions: [  				{ diff --git a/src/commands/moderation/kick.ts b/src/commands/moderation/kick.ts index 715483a..9bd5658 100644 --- a/src/commands/moderation/kick.ts +++ b/src/commands/moderation/kick.ts @@ -1,5 +1,5 @@ -import { AllowedMentions, BushCommand, BushGuildMember, BushMessage, BushSlashMessage, BushUser } from '@lib'; -import { Moderation } from '../../lib/common/moderation'; +import { AllowedMentions, BushCommand, BushMessage, BushSlashMessage, BushUser } from '@lib'; +import { Moderation } from '../../lib/common/Moderation';  export default class KickCommand extends BushCommand {  	public constructor() { @@ -51,7 +51,7 @@ export default class KickCommand extends BushCommand {  					required: false  				}  			], -			clientPermissions: ['SEND_MESSAGES', 'KICK_MEMBERS'], +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['KICK_MEMBERS']),  			userPermissions: ['KICK_MEMBERS']  		});  	} @@ -60,7 +60,7 @@ export default class KickCommand extends BushCommand {  		message: BushMessage | BushSlashMessage,  		{ user, reason, force }: { user: BushUser; reason?: string; force: boolean }  	): Promise<unknown> { -		const member = message.guild!.members.cache.get(user.id) as BushGuildMember; +		const member = await message.guild!.members.fetch(user.id);  		if (!member)  			return await message.util.reply( diff --git a/src/commands/moderation/modlog.ts b/src/commands/moderation/modlog.ts index d5c6f91..eb37681 100644 --- a/src/commands/moderation/modlog.ts +++ b/src/commands/moderation/modlog.ts @@ -1,5 +1,6 @@  import { BushCommand, BushMessage, BushSlashMessage, BushUser, ModLog } from '@lib';  import { MessageEmbed, User } from 'discord.js'; +import { ButtonPaginator } from '../../lib/common/ButtonPaginator';  export default class ModlogCommand extends BushCommand {  	public constructor() { @@ -27,7 +28,8 @@ export default class ModlogCommand extends BushCommand {  					default: false  				}  			], -			userPermissions: [], +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: (m) => util.userGuildPermCheck(m, ['MANAGE_MESSAGES']),  			slash: true,  			slashOptions: [  				{ @@ -62,10 +64,6 @@ export default class ModlogCommand extends BushCommand {  		message: BushMessage | BushSlashMessage,  		{ search, hidden }: { search: BushUser | string; hidden: boolean }  	): Promise<unknown> { -		if (!message.member?.permissions.has('MANAGE_MESSAGES')) -			return await message.util.reply( -				`${util.emojis.error} You must have the **Manage Message** permission to use this command.` -			);  		const foundUser = search instanceof User ? search : await util.resolveUserAsync(search);  		if (foundUser) {  			const logs = await ModLog.findAll({ @@ -90,7 +88,7 @@ export default class ModlogCommand extends BushCommand {  						color: util.colors.default  					})  			); -			return await util.buttonPaginate(message, embedPages, undefined, true); +			return await ButtonPaginator.send(message, embedPages, undefined, true);  		} else if (search) {  			const entry = await ModLog.findByPk(search as string);  			if (!entry || entry.pseudo || (entry.hidden && !hidden)) @@ -102,7 +100,7 @@ export default class ModlogCommand extends BushCommand {  				description: this.#generateModlogInfo(entry, true),  				color: util.colors.default  			}; -			return await util.buttonPaginate(message, [embed]); +			return await ButtonPaginator.send(message, [embed]);  		}  	}  } diff --git a/src/commands/moderation/mute.ts b/src/commands/moderation/mute.ts index 942c0b0..03ecf2a 100644 --- a/src/commands/moderation/mute.ts +++ b/src/commands/moderation/mute.ts @@ -1,5 +1,5 @@  import { AllowedMentions, BushCommand, BushMessage, BushSlashMessage, BushUser } from '@lib'; -import { Moderation } from '../../lib/common/moderation'; +import { Moderation } from '../../lib/common/Moderation';  export default class MuteCommand extends BushCommand {  	public constructor() { @@ -52,8 +52,8 @@ export default class MuteCommand extends BushCommand {  				}  			],  			channel: 'guild', -			clientPermissions: ['SEND_MESSAGES', 'MANAGE_ROLES'], -			userPermissions: ['MANAGE_MESSAGES'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['MANAGE_ROLES']), +			userPermissions: (m) => util.userGuildPermCheck(m, ['MANAGE_MESSAGES'])  		});  	} @@ -66,7 +66,7 @@ export default class MuteCommand extends BushCommand {  		}: { user: BushUser; reason?: { duration: number | null; contentWithoutTime: string }; force: boolean }  	): Promise<unknown> {  		if (reason?.duration === null) reason.duration = 0; -		const member = message.guild!.members.cache.get(user.id); +		const member = await message.guild!.members.fetch(user.id).catch(() => null);  		if (!member)  			return await message.util.reply(  				`${util.emojis.error} The user you selected is not in the server or is not a valid user.` diff --git a/src/commands/moderation/purge.ts b/src/commands/moderation/purge.ts index 062dad0..a77e46a 100644 --- a/src/commands/moderation/purge.ts +++ b/src/commands/moderation/purge.ts @@ -37,7 +37,7 @@ export default class PurgeCommand extends BushCommand {  					required: false  				}  			], -			clientPermissions: ['MANAGE_MESSAGES', 'SEND_MESSAGES', 'EMBED_LINKS'], +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['MANAGE_MESSAGES', 'EMBED_LINKS'], true),  			userPermissions: ['MANAGE_MESSAGES'],  			channel: 'guild'  		}); diff --git a/src/commands/moderation/removeReactionEmoji.ts b/src/commands/moderation/removeReactionEmoji.ts index dc05883..63e20bd 100644 --- a/src/commands/moderation/removeReactionEmoji.ts +++ b/src/commands/moderation/removeReactionEmoji.ts @@ -11,8 +11,6 @@ export default class RemoveReactionEmojiCommand extends BushCommand {  				usage: 'remove-reaction-emoji <message> <emoji>',  				examples: ['remove-reaction-emoji 791413052347252786 <:omegaclown:782630946435366942>']  			}, -			clientPermissions: ['MANAGE_MESSAGES', 'SEND_MESSAGES', 'EMBED_LINKS'], -			userPermissions: ['MANAGE_MESSAGES', 'MANAGE_EMOJIS_AND_STICKERS'], // Can't undo the removal of 1000s of reactions  			args: [  				{  					id: 'messageToRemoveFrom', @@ -32,7 +30,9 @@ export default class RemoveReactionEmojiCommand extends BushCommand {  					}  				}  			], -			channel: 'guild' +			channel: 'guild', +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['MANAGE_MESSAGES', 'EMBED_LINKS'], true), +			userPermissions: ['MANAGE_MESSAGES', 'MANAGE_EMOJIS_AND_STICKERS'] // Can't undo the removal of 1000s of reactions  		});  	} diff --git a/src/commands/moderation/role.ts b/src/commands/moderation/role.ts index c39864b..fe34d75 100644 --- a/src/commands/moderation/role.ts +++ b/src/commands/moderation/role.ts @@ -50,8 +50,8 @@ export default class RoleCommand extends BushCommand {  			],  			channel: 'guild',  			typing: true, -			clientPermissions: ['MANAGE_ROLES', 'EMBED_LINKS', 'SEND_MESSAGES'], -			userPermissions: ['SEND_MESSAGES'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['MANAGE_ROLES', 'EMBED_LINKS'], true), +			userPermissions: []  		});  	} diff --git a/src/commands/moderation/slowmode.ts b/src/commands/moderation/slowmode.ts index 94e40ca..d90f122 100644 --- a/src/commands/moderation/slowmode.ts +++ b/src/commands/moderation/slowmode.ts @@ -42,8 +42,8 @@ export default class SlowModeCommand extends BushCommand {  				}  			],  			channel: 'guild', -			clientPermissions: ['MANAGE_CHANNELS', 'SEND_MESSAGES', 'EMBED_LINKS'], -			userPermissions: ['MANAGE_MESSAGES', 'SEND_MESSAGES'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['MANAGE_CHANNELS', 'EMBED_LINKS'], true), +			userPermissions: (m) => util.userGuildPermCheck(m, ['MANAGE_MESSAGES'])  		});  	} diff --git a/src/commands/moderation/unban.ts b/src/commands/moderation/unban.ts index a319ff9..61e27cf 100644 --- a/src/commands/moderation/unban.ts +++ b/src/commands/moderation/unban.ts @@ -1,5 +1,4 @@  import { AllowedMentions, BushCommand, BushMessage, BushSlashMessage, BushUser } from '@lib'; -import { User } from 'discord.js';  export default class UnbanCommand extends BushCommand {  	public constructor() { @@ -14,7 +13,7 @@ export default class UnbanCommand extends BushCommand {  			args: [  				{  					id: 'user', -					type: 'user', +					type: 'globalUser',  					prompt: {  						start: 'What user would you like to unban?',  						retry: '{error} Choose a valid user to unban.' @@ -55,10 +54,6 @@ export default class UnbanCommand extends BushCommand {  		message: BushMessage | BushSlashMessage,  		{ user, reason }: { user: BushUser; reason?: string }  	): Promise<unknown> { -		if (!(user instanceof User)) { -			user = util.resolveUser(user, client.users.cache) as BushUser; -		} -  		const responseCode = await message.guild!.bushUnban({  			user,  			moderator: message.author, diff --git a/src/commands/moderation/unmute.ts b/src/commands/moderation/unmute.ts index 3d592b7..e430b83 100644 --- a/src/commands/moderation/unmute.ts +++ b/src/commands/moderation/unmute.ts @@ -1,5 +1,5 @@  import { AllowedMentions, BushCommand, BushGuildMember, BushMessage, BushSlashMessage, BushUser } from '@lib'; -import { Moderation } from '../../lib/common/moderation'; +import { Moderation } from '../../lib/common/Moderation';  export default class UnmuteCommand extends BushCommand {  	public constructor() { @@ -52,8 +52,8 @@ export default class UnmuteCommand extends BushCommand {  				}  			],  			channel: 'guild', -			clientPermissions: ['SEND_MESSAGES', 'MANAGE_ROLES'], -			userPermissions: ['MANAGE_MESSAGES'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['MANAGE_ROLES']), +			userPermissions: (m) => util.userGuildPermCheck(m, ['MANAGE_MESSAGES'])  		});  	} diff --git a/src/commands/moderation/warn.ts b/src/commands/moderation/warn.ts index 3df4b3b..6ae8442 100644 --- a/src/commands/moderation/warn.ts +++ b/src/commands/moderation/warn.ts @@ -1,5 +1,5 @@  import { BushCommand, BushGuildMember, BushMessage, BushSlashMessage, BushUser } from '@lib'; -import { Moderation } from '../../lib/common/moderation'; +import { Moderation } from '../../lib/common/Moderation';  export default class WarnCommand extends BushCommand {  	public constructor() { @@ -51,8 +51,8 @@ export default class WarnCommand extends BushCommand {  				}  			],  			channel: 'guild', -			clientPermissions: ['SEND_MESSAGES'], -			userPermissions: ['MANAGE_MESSAGES'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: (m) => util.userGuildPermCheck(m, ['MANAGE_MESSAGES'])  		});  	} diff --git a/src/commands/moulberry-bush/capePerms.ts b/src/commands/moulberry-bush/capePerms.ts index 978d8e5..e26a5a4 100644 --- a/src/commands/moulberry-bush/capePerms.ts +++ b/src/commands/moulberry-bush/capePerms.ts @@ -23,7 +23,8 @@ export default class CapePermissionsCommand extends BushCommand {  					}  				}  			], -			clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'], +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), +			userPermissions: [],  			channel: 'guild',  			slash: true,  			slashOptions: [ @@ -38,7 +39,7 @@ export default class CapePermissionsCommand extends BushCommand {  	}  	public override async exec(message: BushMessage | BushSlashMessage, args: { ign: string }): Promise<unknown> { -		interface Capeperms { +		interface CapePerms {  			success: boolean;  			perms: User[];  		} @@ -48,7 +49,7 @@ export default class CapePermissionsCommand extends BushCommand {  			perms: string[];  		} -		let capeperms: Capeperms | null, uuid: string; +		let capePerms: CapePerms | null, uuid: string;  		try {  			uuid = await util.findUUID(args.ign);  		} catch (e) { @@ -56,18 +57,18 @@ export default class CapePermissionsCommand extends BushCommand {  		}  		try { -			capeperms = await got.get('http://moulberry.codes/permscapes.json').json(); +			capePerms = await got.get('http://moulberry.codes/permscapes.json').json();  		} catch (error) { -			capeperms = null; +			capePerms = null;  		} -		if (capeperms == null) { +		if (capePerms == null) {  			return await message.util.reply(`${util.emojis.error} There was an error finding cape perms for \`${args.ign}\`.`);  		} else { -			if (capeperms?.perms) { +			if (capePerms?.perms) {  				let index = null; -				for (let i = 0; i < capeperms.perms.length; i++) { -					if (capeperms.perms[i]._id == uuid) { +				for (let i = 0; i < capePerms.perms.length; i++) { +					if (capePerms.perms[i]._id == uuid) {  						index = i;  						break;  					} @@ -75,7 +76,7 @@ export default class CapePermissionsCommand extends BushCommand {  				}  				if (index == null)  					return await message.util.reply(`${util.emojis.error} \`${args.ign}\` does not appear to have any capes.`); -				const userPerm: string[] = capeperms.perms[index].perms; +				const userPerm: string[] = capePerms.perms[index].perms;  				const embed = new MessageEmbed()  					.setTitle(`${args.ign}'s Capes`)  					.setDescription(userPerm.join('\n')) diff --git a/src/commands/moulberry-bush/capes.ts b/src/commands/moulberry-bush/capes.ts index 14a972e..1a09eb0 100644 --- a/src/commands/moulberry-bush/capes.ts +++ b/src/commands/moulberry-bush/capes.ts @@ -1,6 +1,8 @@ -import { MessageEmbed } from 'discord.js'; +import { MessageEmbedOptions } from 'discord.js';  import got from 'got';  import { BushCommand, BushMessage } from '../../lib'; +import { ButtonPaginator } from '../../lib/common/ButtonPaginator'; +import { DeleteButton } from '../../lib/common/DeleteButton';  export interface GithubFile {  	path: string; @@ -58,8 +60,8 @@ export default class CapesCommand extends BushCommand {  					required: false  				}  			], -			clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'], -			userPermissions: ['SEND_MESSAGES'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), +			userPermissions: []  		});  	} @@ -102,30 +104,33 @@ export default class CapesCommand extends BushCommand {  			}  			return 0;  		}); +  		if (args.cape) { -			const capeObj = sortedCapes.find((s_cape) => s_cape.name === args.cape); -			if (capeObj) { -				const embed = new MessageEmbed({ -					title: `${capeObj.name} cape`, -					color: util.colors.default -				}).setTimestamp(); -				embed.setImage(capeObj.url); -				await util.sendWithDeleteButton(message, { embeds: [embed] }); +			const cape = sortedCapes.find((s_cape) => s_cape.name === args.cape); +			if (cape) { +				const embed = this.makeEmbed(cape); +				await DeleteButton.send(message, { embeds: [embed] });  			} else {  				await message.util.reply(`${util.emojis.error} Cannot find a cape called \`${args.cape}\`.`);  			}  		} else { -			const embeds = []; -			for (const capeObj of sortedCapes) { -				const embed = new MessageEmbed({ -					title: `${capeObj.name} cape`, -					color: util.colors.default -				}).setTimestamp(); -				embed.setImage(capeObj.url); -				if (capeObj.purchasable) embed.setDescription(':money_with_wings: **purchasable** :money_with_wings:'); -				embeds.push(embed); -			} -			await util.buttonPaginate(message, embeds, null); +			const embeds: MessageEmbedOptions[] = sortedCapes.map(this.makeEmbed); +			await ButtonPaginator.send(message, embeds, null);  		}  	} + +	private makeEmbed(cape: { +		name: string; +		url: string; +		index: number; +		purchasable?: boolean | undefined; +	}): MessageEmbedOptions { +		return { +			title: `${cape.name} cape`, +			color: util.colors.default, +			timestamp: Date.now(), +			image: { url: cape.url }, +			description: cape.purchasable ? ':money_with_wings: **purchasable** :money_with_wings:' : undefined +		}; +	}  } diff --git a/src/commands/moulberry-bush/giveawayPing.ts b/src/commands/moulberry-bush/giveawayPing.ts index 18f4acf..d6005b0 100644 --- a/src/commands/moulberry-bush/giveawayPing.ts +++ b/src/commands/moulberry-bush/giveawayPing.ts @@ -10,8 +10,8 @@ export default class GiveawayPingCommand extends BushCommand {  				usage: 'giveaway-ping',  				examples: ['giveaway-ping']  			}, -			clientPermissions: ['MANAGE_MESSAGES'], -			userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD', 'MANAGE_MESSAGES', 'BAN_MEMBERS', 'KICK_MEMBERS', 'VIEW_CHANNEL'], +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['MANAGE_MESSAGES'], true), +			userPermissions: ['MANAGE_GUILD', 'MANAGE_MESSAGES', 'BAN_MEMBERS', 'KICK_MEMBERS', 'VIEW_CHANNEL'],  			channel: 'guild',  			ignoreCooldown: [],  			ignorePermissions: [], diff --git a/src/commands/moulberry-bush/moulHammer.ts b/src/commands/moulberry-bush/moulHammer.ts index d5930b5..129eb84 100644 --- a/src/commands/moulberry-bush/moulHammer.ts +++ b/src/commands/moulberry-bush/moulHammer.ts @@ -11,8 +11,6 @@ export default class MoulHammerCommand extends BushCommand {  				usage: 'moulHammer <user>',  				examples: ['moulHammer @IRONM00N']  			}, -			clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'], -			userPermissions: ['SEND_MESSAGES'],  			args: [  				{  					id: 'user', @@ -23,7 +21,9 @@ export default class MoulHammerCommand extends BushCommand {  					}  				}  			], -			restrictedGuilds: ['516977525906341928'] +			restrictedGuilds: ['516977525906341928'], +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), +			userPermissions: []  		});  	} diff --git a/src/commands/moulberry-bush/report.ts b/src/commands/moulberry-bush/report.ts index a5c4cb2..13976bb 100644 --- a/src/commands/moulberry-bush/report.ts +++ b/src/commands/moulberry-bush/report.ts @@ -33,8 +33,6 @@ export default class ReportCommand extends BushCommand {  					}  				}  			], -			clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'], -			channel: 'guild',  			slash: true,  			slashOptions: [  				{ @@ -49,7 +47,10 @@ export default class ReportCommand extends BushCommand {  					type: 'STRING',  					required: false  				} -			] +			], +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), +			userPermissions: [], +			channel: 'guild'  		});  	} @@ -102,7 +103,6 @@ export default class ReportCommand extends BushCommand {  				true  			); -		//reusing code pog  		if (message.attachments.size > 0) {  			const fileName = message.attachments.first()!.name!.toLowerCase();  			if (fileName.endsWith('.png') || fileName.endsWith('.jpg') || fileName.endsWith('.gif') || fileName.endsWith('.webp')) { diff --git a/src/commands/moulberry-bush/rule.ts b/src/commands/moulberry-bush/rule.ts index 0c1e435..59c9e43 100644 --- a/src/commands/moulberry-bush/rule.ts +++ b/src/commands/moulberry-bush/rule.ts @@ -81,9 +81,6 @@ export default class RuleCommand extends BushCommand {  					}  				}  			], -			clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'], -			channel: 'guild', -			restrictedGuilds: ['516977525906341928'],  			slash: true,  			slashOptions: [  				{ @@ -99,7 +96,11 @@ export default class RuleCommand extends BushCommand {  					required: false  				}  			], -			slashGuilds: ['516977525906341928'] +			slashGuilds: ['516977525906341928'], +			channel: 'guild', +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), +			userPermissions: [], +			restrictedGuilds: ['516977525906341928']  		});  	} diff --git a/src/commands/moulberry-bush/serverStatus.ts b/src/commands/moulberry-bush/serverStatus.ts index 8ff9803..f4a80d4 100644 --- a/src/commands/moulberry-bush/serverStatus.ts +++ b/src/commands/moulberry-bush/serverStatus.ts @@ -14,7 +14,8 @@ export default class ServerStatusCommand extends BushCommand {  			},  			ratelimit: 4,  			cooldown: 4000, -			clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'], +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), +			userPermissions: [],  			slash: true  		});  	} diff --git a/src/commands/utilities/activity.ts b/src/commands/utilities/activity.ts index de7e79f..c3839e5 100644 --- a/src/commands/utilities/activity.ts +++ b/src/commands/utilities/activity.ts @@ -100,8 +100,8 @@ export default class YouTubeCommand extends BushCommand {  					choices: Object.keys(activityMap).map((key) => ({ name: key, value: activityMap[key as keyof typeof activityMap] }))  				}  			], -			clientPermissions: ['SEND_MESSAGES'], -			userPermissions: ['SEND_MESSAGES'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: []  		});  	} diff --git a/src/commands/utilities/calculator.ts b/src/commands/utilities/calculator.ts index a2c91e4..743e9b2 100644 --- a/src/commands/utilities/calculator.ts +++ b/src/commands/utilities/calculator.ts @@ -33,8 +33,8 @@ export default class CalculatorCommand extends BushCommand {  					required: true  				}  			], -			clientPermissions: ['SEND_MESSAGES'], -			userPermissions: ['SEND_MESSAGES'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: []  		});  	}  	public override async exec(message: BushMessage | BushSlashMessage, args: { expression: string }): Promise<unknown> { diff --git a/src/commands/utilities/decode.ts b/src/commands/utilities/decode.ts index e48c644..c497183 100644 --- a/src/commands/utilities/decode.ts +++ b/src/commands/utilities/decode.ts @@ -3,7 +3,7 @@ import { AkairoMessage } from 'discord-akairo';  import { MessageEmbed } from 'discord.js';  const encodingTypesArray = ['ascii', 'utf8', 'utf-8', 'utf16le', 'ucs2', 'ucs-2', 'base64', 'latin1', 'binary', 'hex']; -const encodingTypesString = '`ascii`, `utf8`, `utf-8`, `utf16le`, `ucs2`, `ucs-2`, `base64`, `latin1`, `binary`, `hex`'; +const encodingTypesString = encodingTypesArray.map((e) => `\`${e}\``).join(', ');  export default class DecodeCommand extends BushCommand {  	public constructor() { @@ -42,43 +42,20 @@ export default class DecodeCommand extends BushCommand {  					}  				}  			], -			clientPermissions: ['SEND_MESSAGES'],  			slash: true,  			slashOptions: [  				{  					name: 'from',  					description: 'The type of data you are inputting.',  					type: 'STRING', -					choices: [ -						{ name: 'ascii', value: 'ascii' }, -						{ name: 'utf8', value: 'utf8' }, -						{ name: 'utf-8', value: 'utf-8' }, -						{ name: 'utf16le', value: 'utf16le' }, -						{ name: 'ucs2', value: 'ucs2' }, -						{ name: 'ucs-2', value: 'ucs-2' }, -						{ name: 'base64', value: 'base64' }, -						{ name: 'latin1', value: 'latin1' }, -						{ name: 'binary', value: 'binary' }, -						{ name: 'hex', value: 'hex' } -					], +					choices: encodingTypesArray.map((e) => ({ name: e, value: e })),  					required: true  				},  				{  					name: 'to',  					description: 'The type of data you want the output to be.',  					type: 'STRING', -					choices: [ -						{ name: 'ascii', value: 'ascii' }, -						{ name: 'utf8', value: 'utf8' }, -						{ name: 'utf-8', value: 'utf-8' }, -						{ name: 'utf16le', value: 'utf16le' }, -						{ name: 'ucs2', value: 'ucs2' }, -						{ name: 'ucs-2', value: 'ucs-2' }, -						{ name: 'base64', value: 'base64' }, -						{ name: 'latin1', value: 'latin1' }, -						{ name: 'binary', value: 'binary' }, -						{ name: 'hex', value: 'hex' } -					], +					choices: encodingTypesArray.map((e) => ({ name: e, value: e })),  					required: true  				},  				{ @@ -87,7 +64,9 @@ export default class DecodeCommand extends BushCommand {  					type: 'STRING',  					required: true  				} -			] +			], +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: []  		});  	} diff --git a/src/commands/utilities/hash.ts b/src/commands/utilities/hash.ts index 16ace93..df604c8 100644 --- a/src/commands/utilities/hash.ts +++ b/src/commands/utilities/hash.ts @@ -22,7 +22,8 @@ export default class HashCommand extends BushCommand {  					}  				}  			], -			clientPermissions: ['SEND_MESSAGES'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: []  		});  	} diff --git a/src/commands/utilities/price.ts b/src/commands/utilities/price.ts index 497935a..a012501 100644 --- a/src/commands/utilities/price.ts +++ b/src/commands/utilities/price.ts @@ -51,15 +51,11 @@ export default class PriceCommand extends BushCommand {  		super('price', {  			aliases: ['price'],  			category: 'utilities', -			clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'],  			description: {  				usage: 'price <item id>',  				examples: ['price ASPECT_OF_THE_END'],  				content: 'Finds the price information of an item.'  			}, -			ratelimit: 4, -			cooldown: 4000, -			typing: true,  			args: [  				{  					id: 'item', @@ -91,7 +87,12 @@ export default class PriceCommand extends BushCommand {  					type: 'BOOLEAN',  					required: false  				} -			] +			], +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), +			userPermissions: [], +			ratelimit: 4, +			cooldown: 4000, +			typing: true  		});  	} diff --git a/src/commands/utilities/steal.ts b/src/commands/utilities/steal.ts index 7d61016..767534e 100644 --- a/src/commands/utilities/steal.ts +++ b/src/commands/utilities/steal.ts @@ -27,8 +27,8 @@ export default class StealCommand extends BushCommand {  			],  			slash: false,  			channel: 'guild', -			clientPermissions: ['SEND_MESSAGES', 'MANAGE_EMOJIS_AND_STICKERS'], -			userPermissions: ['SEND_MESSAGES', 'MANAGE_EMOJIS_AND_STICKERS'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['MANAGE_EMOJIS_AND_STICKERS']), +			userPermissions: ['MANAGE_EMOJIS_AND_STICKERS']  		});  	}  	public override async exec( diff --git a/src/commands/utilities/suicide.ts b/src/commands/utilities/suicide.ts index 58119ef..9289b1c 100644 --- a/src/commands/utilities/suicide.ts +++ b/src/commands/utilities/suicide.ts @@ -12,8 +12,8 @@ export default class TemplateCommand extends BushCommand {  				examples: ['suicide']  			},  			slash: true, -			clientPermissions: ['SEND_MESSAGES'], -			userPermissions: ['SEND_MESSAGES'], +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: [],  			bypassChannelBlacklist: true  		});  	} @@ -29,16 +29,20 @@ export default class TemplateCommand extends BushCommand {  			)  			.addField(  				'**National Suicide Prevention Hotline (U.S.):**', -				`**Call:** 1-800-273-8255, available 24/7 for emotional support -**Text: HOME** to 741741 -https://suicidepreventionlifeline.org/chat/ - -**Outside the U.S**: Find a supportive resource on [this Wikipedia list of worldwide crisis hotlines](https://en.wikipedia.org/wiki/List_of_suicide_crisis_lines)` +				[ +					'**Call:** 1-800-273-8255, available 24/7 for emotional support', +					'**Text: HOME** to 741741', +					'https://suicidepreventionlifeline.org/chat/', +					'', +					'**Outside the U.S**: Find a supportive resource on [this Wikipedia list of worldwide crisis hotlines](https://en.wikipedia.org/wiki/List_of_suicide_crisis_lines)' +				].join('\n')  			)  			.addField(  				'**More Support**', -				`For Substance Abuse Support, Eating Disorder Support & Child Abuse and Domestic Violence: -[Click to go to Discord's Health & Safety Page](https://discord.com/safety/360044103771-Mental-health-on-Discord#h_01EGRGT08QSZ5BNCH2E9HN0NYV)` +				[ +					'For Substance Abuse Support, Eating Disorder Support & Child Abuse and Domestic Violence:', +					"[Click to go to Discord's Health & Safety Page](https://discord.com/safety/360044103771-Mental-health-on-Discord#h_01EGRGT08QSZ5BNCH2E9HN0NYV)" +				].join('\n')  			);  		return ( diff --git a/src/commands/utilities/uuid.ts b/src/commands/utilities/uuid.ts index 50280a0..9484729 100644 --- a/src/commands/utilities/uuid.ts +++ b/src/commands/utilities/uuid.ts @@ -14,7 +14,6 @@ export default class UuidCommand extends BushCommand {  				{  					id: 'ign',  					customType: /\w{1,16}/im, -  					prompt: {  						start: 'What ign would you like to find the uuid of?',  						retry: '{error} Choose a valid ign.', @@ -22,9 +21,8 @@ export default class UuidCommand extends BushCommand {  					}  				}  			], -			cooldown: 4000, -			ratelimit: 1, -			clientPermissions: ['SEND_MESSAGES'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: []  		});  	} diff --git a/src/commands/utilities/viewraw.ts b/src/commands/utilities/viewraw.ts index bd185d4..b7017c6 100644 --- a/src/commands/utilities/viewraw.ts +++ b/src/commands/utilities/viewraw.ts @@ -3,11 +3,9 @@ import { BushCommand, BushMessage, BushSlashMessage } from '../../lib';  export default class ViewRawCommand extends BushCommand {  	public constructor() { -		super('viewraw', { -			aliases: ['viewraw'], +		super('view-raw', { +			aliases: ['view-raw', 'vr'],  			category: 'utilities', -			clientPermissions: ['EMBED_LINKS'], -			channel: 'guild',  			description: {  				usage: 'viewraw <message id> <channel>',  				examples: ['viewraw 322862723090219008'], @@ -42,7 +40,37 @@ export default class ViewRawCommand extends BushCommand {  					match: 'flag',  					flag: '--js'  				} -			] +			], +			slash: true, +			slashOptions: [ +				{ +					name: 'message', +					description: 'What message would you like to view?', +					type: 'STRING', +					required: true +				}, +				{ +					name: 'channel', +					description: 'What channel is the message in?', +					type: 'CHANNEL', +					required: false +				}, +				{ +					name: 'json', +					description: 'Would you like to view the raw JSON message data?', +					type: 'BOOLEAN', +					required: false +				}, +				{ +					name: 'js', +					description: 'Would you like to view the raw message data?', +					type: 'BOOLEAN', +					required: false +				} +			], +			channel: 'guild', +			clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), +			userPermissions: []  		});  	} diff --git a/src/commands/utilities/whoHasRole.ts b/src/commands/utilities/whoHasRole.ts index 1b72d65..bce88d6 100644 --- a/src/commands/utilities/whoHasRole.ts +++ b/src/commands/utilities/whoHasRole.ts @@ -1,5 +1,6 @@  import { BushCommand, BushMessage, BushSlashMessage } from '@lib';  import { CommandInteraction, Role, Util } from 'discord.js'; +import { ButtonPaginator } from '../../lib/common/ButtonPaginator';  export default class WhoHasRoleCommand extends BushCommand {  	public constructor() { @@ -32,8 +33,8 @@ export default class WhoHasRoleCommand extends BushCommand {  				}  			],  			channel: 'guild', -			clientPermissions: ['SEND_MESSAGES'], -			userPermissions: ['SEND_MESSAGES'], +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: [],  			typing: true  		});  	} @@ -51,6 +52,6 @@ export default class WhoHasRoleCommand extends BushCommand {  			color  		})); -		return await util.buttonPaginate(message, embedPages, null, true); +		return await ButtonPaginator.send(message, embedPages, null, true);  	}  } diff --git a/src/commands/utilities/wolframAlpha.ts b/src/commands/utilities/wolframAlpha.ts index 4a357b8..049dd60 100644 --- a/src/commands/utilities/wolframAlpha.ts +++ b/src/commands/utilities/wolframAlpha.ts @@ -1,5 +1,5 @@  import { AllowedMentions, BushCommand, BushMessage, BushSlashMessage } from '@lib'; -import { CommandInteraction, MessageEmbed } from 'discord.js'; +import { CommandInteraction, MessageEmbed, MessageOptions } from 'discord.js';  import WolframAlphaAPI from 'wolfram-alpha-api';  export default class WolframAlphaCommand extends BushCommand { @@ -13,6 +13,7 @@ export default class WolframAlphaCommand extends BushCommand {  				examples: ['wolfram-alpha what is the population of france']  			},  			args: [ +				{ id: 'image', match: 'flag', flag: '--image' },  				{  					id: 'expression',  					type: 'string', @@ -31,30 +32,49 @@ export default class WolframAlphaCommand extends BushCommand {  					description: 'What would you like to look up?',  					type: 'STRING',  					required: true +				}, +				{ +					name: 'image', +					description: 'Would you like to use the Simple API instead of the Short Answers API?', +					type: 'BOOLEAN', +					required: false  				}  			], -			clientPermissions: ['SEND_MESSAGES'], -			userPermissions: ['SEND_MESSAGES'] +			clientPermissions: (m) => util.clientSendAndPermCheck(m), +			userPermissions: []  		});  	} -	public override async exec(message: BushMessage | BushSlashMessage, args: { expression: string }): Promise<unknown> { +	public override async exec( +		message: BushMessage | BushSlashMessage, +		args: { expression: string; image: boolean } +	): Promise<unknown> {  		if (message.util.isSlash) await (message.interaction as CommandInteraction).deferReply(); +		args.image && void message.util.reply({ content: `${util.emojis.loading} Loading...`, embeds: [] });  		const waApi = WolframAlphaAPI(client.config.credentials.wolframAlphaAppId);  		const decodedEmbed = new MessageEmbed().addField('📥 Input', await util.inspectCleanRedactCodeblock(args.expression)); +		const sendOptions: MessageOptions = { content: null, allowedMentions: AllowedMentions.none() };  		try { -			const calculated = await waApi.getShort(args.expression); -			decodedEmbed -				.setTitle(`${util.emojis.successFull} Successfully Queried Expression`) -				.setColor(util.colors.success) -				.addField('📤 Output', await util.inspectCleanRedactCodeblock(calculated.toString())); +			const calculated = await (args.image +				? waApi.getSimple({ i: args.expression, timeout: 1, background: '2C2F33', foreground: 'white' }) +				: waApi.getShort(args.expression)); +			decodedEmbed.setTitle(`${util.emojis.successFull} Successfully Queried Expression`).setColor(util.colors.success); + +			if (args.image) { +				decodedEmbed.setImage(await util.uploadImageToImgur(calculated.split(',')[1])); +				decodedEmbed.addField('📤 Output', '​'); +			} else { +				decodedEmbed.addField('📤 Output', await util.inspectCleanRedactCodeblock(calculated.toString())); +			}  		} catch (error) {  			decodedEmbed  				.setTitle(`${util.emojis.errorFull} Unable to Query Expression`)  				.setColor(util.colors.error)  				.addField(`📤 Error`, await util.inspectCleanRedactCodeblock(`${error.name}: ${error.message}`, 'js'));  		} -		return await message.util.reply({ embeds: [decodedEmbed], allowedMentions: AllowedMentions.none() }); +		sendOptions.embeds = [decodedEmbed]; + +		return await message.util.reply(sendOptions);  	}  } diff --git a/src/config/example-options.ts b/src/config/example-options.ts index c7838ab..01519b2 100644 --- a/src/config/example-options.ts +++ b/src/config/example-options.ts @@ -6,7 +6,9 @@ export default new Config({  		betaToken: '[TOKEN]',  		devToken: '[TOKEN]',  		hypixelApiKey: '[API_KEY]', -		wolframAlphaAppId: '[APP_ID]' +		wolframAlphaAppId: '[APP_ID]', +		imgurClientId: '[CLIENT_ID]', +		imgurClientSecret: '[CLIENT_SECRET]'  	},  	environment: 'development',  	owners: [ diff --git a/src/context-menu-commands/message/viewRaw.ts b/src/context-menu-commands/message/viewRaw.ts index c04ec3c..77fd0b9 100644 --- a/src/context-menu-commands/message/viewRaw.ts +++ b/src/context-menu-commands/message/viewRaw.ts @@ -1,6 +1,6 @@  import { ContextMenuCommand } from 'discord-akairo';  import { ContextMenuInteraction } from 'discord.js'; -import ViewRawCommand from '../../commands/utilities/viewraw'; +import ViewRawCommand from '../../commands/utilities/viewRaw';  import { BushMessage } from '../../lib';  export default class ViewRawContextMenuCommand extends ContextMenuCommand { diff --git a/src/lib/badlinks.ts b/src/lib/badlinks.ts index 933fce3..059d3d9 100644 --- a/src/lib/badlinks.ts +++ b/src/lib/badlinks.ts @@ -792,6 +792,7 @@ export default [  	"discordcreators.net",  	"discordd.buzz",  	"discordd.gg", +	"discordd.gift",  	"discorddaapp.com",  	"discorddev.com",  	"discorddiscord.com", diff --git a/src/lib/badwords.ts b/src/lib/badwords.ts index 975df24..e5033d7 100644 --- a/src/lib/badwords.ts +++ b/src/lib/badwords.ts @@ -1,4 +1,4 @@ -import { BadWords, Severity } from "./common/autoMod"; +import { BadWords, Severity } from "./common/AutoMod";  export default {  	/* -------------------------------------------------------------------------- */ diff --git a/src/lib/common/ButtonPaginator.ts b/src/lib/common/ButtonPaginator.ts new file mode 100644 index 0000000..c74f6ad --- /dev/null +++ b/src/lib/common/ButtonPaginator.ts @@ -0,0 +1,186 @@ +import { +	Constants, +	MessageActionRow, +	MessageButton, +	MessageComponentInteraction, +	MessageEmbed, +	MessageEmbedOptions +} from 'discord.js'; +import { BushMessage, BushSlashMessage } from '..'; +import { DeleteButton } from './DeleteButton'; + +export class ButtonPaginator { +	protected message: BushMessage | BushSlashMessage; +	protected embeds: MessageEmbed[] | MessageEmbedOptions[]; +	protected text: string | null; +	protected deleteOnExit: boolean; +	protected curPage: number; +	protected sentMessage: BushMessage | undefined; + +	/** +	 * Sends multiple embeds with controls to switch between them +	 * @param message - The message to respond to +	 * @param embeds - The embeds to switch between +	 * @param text - The text send with the embeds (optional) +	 * @param deleteOnExit - Whether to delete the message when the exit button is clicked (defaults to true) +	 * @param startOn - The page to start from (**not** the index) +	 */ +	public static async send( +		message: BushMessage | BushSlashMessage, +		embeds: MessageEmbed[] | MessageEmbedOptions[], +		text: string | null = null, +		deleteOnExit = true, +		startOn = 1 +	): Promise<void> { +		// no need to paginate if there is only one page +		if (embeds.length === 1) return DeleteButton.send(message, { embeds: embeds }); + +		return await new ButtonPaginator(message, embeds, text, deleteOnExit, startOn).send(); +	} + +	protected get numPages(): number { +		return this.embeds.length; +	} + +	protected constructor( +		message: BushMessage | BushSlashMessage, +		embeds: MessageEmbed[] | MessageEmbedOptions[], +		text: string | null, +		deleteOnExit: boolean, +		startOn: number +	) { +		this.message = message; +		this.embeds = embeds; +		// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing +		this.text = text || null; +		this.deleteOnExit = deleteOnExit; +		this.curPage = startOn - 1; + +		// add footers +		for (let i = 0; i < embeds.length; i++) { +			if (embeds[i] instanceof MessageEmbed) { +				(embeds[i] as MessageEmbed).setFooter(`Page ${(i + 1).toLocaleString()}/${embeds.length.toLocaleString()}`); +			} else { +				(embeds[i] as MessageEmbedOptions).footer = { +					text: `Page ${(i + 1).toLocaleString()}/${embeds.length.toLocaleString()}` +				}; +			} +		} +	} + +	protected async send() { +		this.sentMessage = (await this.message.util.reply({ +			content: this.text, +			embeds: [this.embeds[this.curPage]], +			components: [this.getPaginationRow()] +		})) as BushMessage; + +		const collector = this.sentMessage.createMessageComponentCollector({ +			filter: (i) => i.customId.startsWith('paginate_') && i.message.id === this.sentMessage!.id, +			time: 300000 +		}); + +		collector.on('collect', (i) => void this.collect(i)); +		collector.on('end', () => void this.end()); +	} + +	protected async collect(interaction: MessageComponentInteraction) { +		if (interaction.user.id !== this.message.author.id || !client.config.owners.includes(interaction.user.id)) +			return await interaction?.deferUpdate().catch(() => undefined); + +		switch (interaction.customId) { +			case 'paginate_beginning': +				this.curPage = 0; +				return this.edit(interaction); +			case 'paginate_back': +				this.curPage--; +				return await this.edit(interaction); +			case 'paginate_stop': +				if (this.deleteOnExit) { +					await interaction.deferUpdate().catch(() => undefined); +					return await this.sentMessage!.delete().catch(() => undefined); +				} else { +					return await interaction +						?.update({ +							content: `${this.text ? `${this.text}\n` : ''}Command closed by user.`, +							embeds: [], +							components: [] +						}) +						.catch(() => undefined); +				} +			case 'paginate_next': +				this.curPage++; +				return await this.edit(interaction); +			case 'paginate_end': +				this.curPage = this.embeds.length - 1; +				return await this.edit(interaction); +		} +	} + +	protected async end() { +		try { +			return this.sentMessage!.edit({ +				content: this.text, +				embeds: [this.embeds[this.curPage]], +				components: [this.getPaginationRow(true)] +			}); +		} catch (e) { +			return undefined; +		} +	} + +	protected async edit(interaction: MessageComponentInteraction) { +		try { +			return interaction?.update({ +				content: this.text, +				embeds: [this.embeds[this.curPage]], +				components: [this.getPaginationRow()] +			}); +		} catch (e) { +			return undefined; +		} +	} + +	protected getPaginationRow(disableAll = false): MessageActionRow { +		return new MessageActionRow().addComponents( +			new MessageButton({ +				style: Constants.MessageButtonStyles.PRIMARY, +				customId: 'paginate_beginning', +				emoji: PaginateEmojis.BEGGING, +				disabled: disableAll || this.curPage === 0 +			}), +			new MessageButton({ +				style: Constants.MessageButtonStyles.PRIMARY, +				customId: 'paginate_back', +				emoji: PaginateEmojis.BACK, +				disabled: disableAll || this.curPage === 0 +			}), +			new MessageButton({ +				style: Constants.MessageButtonStyles.PRIMARY, +				customId: 'paginate_stop', +				emoji: PaginateEmojis.STOP, +				disabled: disableAll +			}), +			new MessageButton({ +				style: Constants.MessageButtonStyles.PRIMARY, +				customId: 'paginate_next', +				emoji: PaginateEmojis.FORWARD, +				disabled: disableAll || this.curPage === this.numPages - 1 +			}), +			new MessageButton({ +				style: Constants.MessageButtonStyles.PRIMARY, +				customId: 'paginate_end', +				emoji: PaginateEmojis.END, +				disabled: disableAll || this.curPage === this.numPages - 1 +			}) +		); +	} +} + +export const enum PaginateEmojis { +	BEGGING = '853667381335162910', +	BACK = '853667410203770881', +	STOP = '853667471110570034', +	FORWARD = '853667492680564747', +	END = '853667514915225640' +} diff --git a/src/lib/common/DeleteButton.ts b/src/lib/common/DeleteButton.ts new file mode 100644 index 0000000..7d2e41b --- /dev/null +++ b/src/lib/common/DeleteButton.ts @@ -0,0 +1,61 @@ +import { Constants, MessageActionRow, MessageButton, MessageComponentInteraction, MessageOptions } from 'discord.js'; +import { BushMessage, BushSlashMessage } from '..'; +import { PaginateEmojis } from './ButtonPaginator'; + +export class DeleteButton { +	protected messageOptions: MessageOptions; +	protected message: BushMessage | BushSlashMessage; + +	/** +	 * Sends a message with a button for the user to delete it. +	 * @param message - The message to respond to +	 * @param options - The send message options +	 */ +	static async send(message: BushMessage | BushSlashMessage, options: Omit<MessageOptions, 'components'>) { +		return new DeleteButton(message, options).send(); +	} + +	protected constructor(message: BushMessage | BushSlashMessage, options: MessageOptions) { +		this.message = message; +		this.messageOptions = options; +	} + +	protected async send() { +		this.updateComponents(); + +		const msg = (await this.message.util.reply(this.messageOptions)) as BushMessage; + +		const collector = msg.createMessageComponentCollector({ +			filter: (interaction) => interaction.customId == 'paginate__stop' && interaction.message.id == msg.id, +			time: 300000 +		}); + +		collector.on('collect', async (interaction: MessageComponentInteraction) => { +			await interaction.deferUpdate().catch(() => undefined); +			if (interaction.user.id == this.message.author.id || client.config.owners.includes(interaction.user.id)) { +				if (msg.deletable && !msg.deleted) await msg.delete(); +			} +		}); + +		collector.on('end', async () => { +			this.updateComponents(true, true); +			await msg.edit(this.messageOptions).catch(() => undefined); +		}); +	} + +	protected updateComponents(edit = false, disable = false): void { +		this.messageOptions.components = [ +			new MessageActionRow().addComponents( +				new MessageButton({ +					style: Constants.MessageButtonStyles.PRIMARY, +					customId: 'paginate__stop', +					emoji: PaginateEmojis.STOP, +					disabled: disable +				}) +			) +		]; +		if (edit) { +			this.messageOptions.reply = undefined; +		} +	} +} diff --git a/src/lib/common/Format.ts b/src/lib/common/Format.ts new file mode 100644 index 0000000..ba1ee9f --- /dev/null +++ b/src/lib/common/Format.ts @@ -0,0 +1,72 @@ +import { Formatters, Util } from 'discord.js'; +import { CodeBlockLang } from './typings/CodeBlockLang'; + +/** + * Formats and escapes content for formatting + */ +export class Format { +	/** +	 * Wraps the content inside a codeblock with no language. +	 * @param content The content to wrap. +	 */ +	public static codeBlock(content: string): string; +	/** +	 * Wraps the content inside a codeblock with the specified language. +	 * @param language The language for the codeblock. +	 * @param content The content to wrap. +	 */ +	public static codeBlock(language: CodeBlockLang, content: string): string; +	public static codeBlock(languageOrContent: string, content?: string): string { +		return typeof content === 'undefined' +			? Formatters.codeBlock(Util.escapeCodeBlock(languageOrContent)) +			: Formatters.codeBlock(languageOrContent, Util.escapeCodeBlock(languageOrContent)); +	} + +	/** +	 * Wraps the content inside \`backticks\`, which formats it as inline code. +	 * @param content The content to wrap. +	 */ +	public static inlineCode(content: string): string { +		return Formatters.inlineCode(Util.escapeInlineCode(content)); +	} + +	/** +	 * Formats the content into italic text. +	 * @param content The content to wrap. +	 */ +	public static italic(content: string): string { +		return Formatters.italic(Util.escapeItalic(content)); +	} + +	/** +	 * Formats the content into bold text. +	 * @param content The content to wrap. +	 */ +	public static bold(content: string): string { +		return Formatters.bold(Util.escapeBold(content)); +	} + +	/** +	 * Formats the content into underscored text. +	 * @param content The content to wrap. +	 */ +	public static underscore(content: string): string { +		return Formatters.underscore(Util.escapeUnderline(content)); +	} + +	/** +	 * Formats the content into strike-through text. +	 * @param content The content to wrap. +	 */ +	public static strikethrough(content: string): string { +		return Formatters.strikethrough(Util.escapeStrikethrough(content)); +	} + +	/** +	 * Wraps the content inside spoiler (hidden text). +	 * @param content The content to wrap. +	 */ +	public static spoiler(content: string): string { +		return Formatters.spoiler(Util.escapeSpoiler(content)); +	} +} diff --git a/src/lib/common/autoMod.ts b/src/lib/common/autoMod.ts index 0bdbebf..312beb3 100644 --- a/src/lib/common/autoMod.ts +++ b/src/lib/common/autoMod.ts @@ -1,17 +1,17 @@ -import { Formatters, MessageActionRow, MessageButton, MessageEmbed, TextChannel } from 'discord.js'; -import badLinksArray from '../../lib/badlinks'; -import badLinksSecretArray from '../../lib/badlinks-secret'; // I cannot make this public so just make a new file that export defaults an empty array -import badWords from '../../lib/badwords'; +import { GuildMember, MessageActionRow, MessageButton, MessageEmbed, TextChannel } from 'discord.js'; +import badLinksArray from '../badlinks'; +import badLinksSecretArray from '../badlinks-secret'; // I cannot make this public so just make a new file that export defaults an empty array +import badWords from '../badwords';  import { BushButtonInteraction } from '../extensions/discord.js/BushButtonInteraction'; -import { BushGuildMember } from '../extensions/discord.js/BushGuildMember';  import { BushMessage } from '../extensions/discord.js/BushMessage'; -import { Moderation } from './moderation'; +import { Moderation } from './Moderation';  export class AutoMod {  	private message: BushMessage;  	public constructor(message: BushMessage) {  		this.message = message; +		if (message.author.id === client.user?.id) return;  		void this.handle();  	} @@ -21,17 +21,10 @@ export class AutoMod {  		const customAutomodPhrases = (await this.message.guild.getSetting('autoModPhases')) ?? {};  		const badLinks: BadWords = {}; -		const badLinksSecret: BadWords = {}; -		badLinksArray.forEach((link) => { -			badLinks[link] = { -				severity: Severity.PERM_MUTE, -				ignoreSpaces: true, -				ignoreCapitalization: true, -				reason: 'malicious link' -			}; -		}); -		badLinksSecretArray.forEach((link) => { +		const uniqueLinks = [...new Set([...badLinksArray, ...badLinksSecretArray])]; + +		uniqueLinks.forEach((link) => {  			badLinks[link] = {  				severity: Severity.PERM_MUTE,  				ignoreSpaces: true, @@ -43,9 +36,7 @@ export class AutoMod {  		const result = {  			...this.checkWords(customAutomodPhrases),  			...this.checkWords((await this.message.guild.hasFeature('excludeDefaultAutomod')) ? {} : badWords), -			...this.checkWords( -				(await this.message.guild.hasFeature('excludeAutomodScamLinks')) ? {} : { ...badLinks, ...badLinksSecret } -			) +			...this.checkWords((await this.message.guild.hasFeature('excludeAutomodScamLinks')) ? {} : badLinks)  		};  		if (Object.keys(result).length === 0) return; @@ -59,9 +50,7 @@ export class AutoMod {  				embeds: [  					{  						title: 'AutoMod Error', -						description: `Unable to find severity information for ${Formatters.inlineCode( -							util.discord.escapeInlineCode(highestOffence.word) -						)}`, +						description: `Unable to find severity information for ${util.format.inlineCode(highestOffence.word)}`,  						color: util.colors.error  					}  				] @@ -128,7 +117,7 @@ export class AutoMod {  				break;  			}  			default: { -				throw new Error('Invalid severity'); +				throw new Error(`Invalid severity: ${highestOffence.severity}`);  			}  		} @@ -163,8 +152,8 @@ export class AutoMod {  					.setDescription(  						`**User:** ${this.message.author} (${this.message.author.tag})\n**Sent From**: <#${  							this.message.channel.id -						}> [Jump to context](${this.message.url})\n**Blacklisted Words:** ${util -							.surroundArray(Object.keys(offences), '`') +						}> [Jump to context](${this.message.url})\n**Blacklisted Words:** ${Object.keys(offences) +							.map((key) => `\`${key}\``)  							.join(', ')}`  					)  					.addField('Message Content', `${await util.codeblock(this.message.content, 1024)}`) @@ -194,12 +183,13 @@ export class AutoMod {  		const [action, userId, reason] = interaction.customId.replace('automod;', '').split(';');  		switch (action) {  			case 'ban': { -				const check = await Moderation.permissionCheck( -					interaction.member as BushGuildMember, -					interaction.guild!.members.cache.get(userId)!, -					'ban', -					true -				); +				const victim = await interaction.guild!.members.fetch(userId); +				const moderator = +					interaction.member instanceof GuildMember +						? interaction.member +						: await interaction.guild!.members.fetch(interaction.user.id); + +				const check = victim ? await Moderation.permissionCheck(moderator, victim, 'ban', true) : true;  				if (check !== true)  					return interaction.reply({ diff --git a/src/lib/common/moderation.ts b/src/lib/common/moderation.ts index c8779fc..29d66fa 100644 --- a/src/lib/common/moderation.ts +++ b/src/lib/common/moderation.ts @@ -123,7 +123,7 @@ export class Moderation {  		const expires = options.duration ? new Date(+new Date() + options.duration ?? 0) : undefined;  		const user = (await util.resolveNonCachedUser(options.user))!.id;  		const guild = client.guilds.resolveId(options.guild)!; -		const type = this.#findTypeEnum(options.type)!; +		const type = this.findTypeEnum(options.type)!;  		const entry = ActivePunishment.build(  			options.extraInfo @@ -144,7 +144,7 @@ export class Moderation {  	}): Promise<boolean> {  		const user = await util.resolveNonCachedUser(options.user);  		const guild = client.guilds.resolveId(options.guild); -		const type = this.#findTypeEnum(options.type); +		const type = this.findTypeEnum(options.type);  		if (!user || !guild) return false; @@ -160,18 +160,19 @@ export class Moderation {  			success = false;  		});  		if (entries) { -			// eslint-disable-next-line @typescript-eslint/no-misused-promises -			entries.forEach(async (entry) => { -				await entry.destroy().catch(async (e) => { +			const promises = entries.map(async (entry) => +				entry.destroy().catch(async (e) => {  					await util.handleError('removePunishmentEntry', e); -				}); -				success = false; -			}); +					success = false; +				}) +			); + +			await Promise.all(promises);  		}  		return success;  	} -	static #findTypeEnum(type: 'mute' | 'ban' | 'role' | 'block') { +	private static findTypeEnum(type: 'mute' | 'ban' | 'role' | 'block') {  		const typeMap = {  			['mute']: ActivePunishmentType.MUTE,  			['ban']: ActivePunishmentType.BAN, diff --git a/src/lib/common/typings/BushInspectOptions.d.ts b/src/lib/common/typings/BushInspectOptions.d.ts new file mode 100644 index 0000000..c2a2360 --- /dev/null +++ b/src/lib/common/typings/BushInspectOptions.d.ts @@ -0,0 +1,91 @@ +import { InspectOptions } from 'util'; + +/** + * {@link https://nodejs.org/api/util.html#util_util_inspect_object_options} + */ +export interface BushInspectOptions extends InspectOptions { +	/** +	 * If `true`, object's non-enumerable symbols and properties are included in the +	 * formatted result. [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) and [`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) entries are also included as well as +	 * user defined prototype properties (excluding method properties). +	 * +	 * **Default**: `false`. +	 */ +	showHidden?: boolean | undefined; +	/** +	 * Specifies the number of times to recurse while formatting `object`. This is useful +	 * for inspecting large objects. To recurse up to the maximum call stack size pass +	 * `Infinity` or `null`. +	 * +	 * **Default**: `2`. +	 */ +	depth?: number | null | undefined; +	/** +	 * If `true`, the output is styled with ANSI color codes. Colors are customizable. See [Customizing util.inspect colors](https://nodejs.org/api/util.html#util_customizing_util_inspect_colors). +	 * +	 * **Default**: `false`. +	 */ +	colors?: boolean | undefined; +	/** +	 * If `false`, `[util.inspect.custom](depth, opts)` functions are not invoked. +	 * +	 * **Default**: `true`. +	 */ +	customInspect?: boolean | undefined; +	/** +	 * If `true`, `Proxy` inspection includes the [`target` and `handler`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy#Terminology) objects. +	 * +	 * **Default**: `false`. +	 */ +	showProxy?: boolean | undefined; +	/** +	 * Specifies the maximum number of `Array`, [`TypedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray), [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) and +	 * [`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) elements to include when formatting. Set to `null` or `Infinity` to +	 * show all elements. Set to `0` or negative to show no elements. +	 * +	 * **Default**: `100`. +	 */ +	maxArrayLength?: number | null | undefined; +	/** +	 * Specifies the maximum number of characters to include when formatting. Set to +	 * `null` or `Infinity` to show all elements. Set to `0` or negative to show no +	 * characters. +	 * +	 * **Default**: `10000`. +	 */ +	maxStringLength?: number | null | undefined; +	/** +	 * The length at which input values are split across multiple lines. Set to +	 * `Infinity` to format the input as a single line (in combination with compact set +	 * to `true` or any number >= `1`). +	 * +	 * **Default**: `80`. +	 */ +	breakLength?: number | undefined; +	/** +	 * Setting this to `false` causes each object key to be displayed on a new line. It +	 * will break on new lines in text that is longer than `breakLength`. If set to a +	 * number, the most `n` inner elements are united on a single line as long as all +	 * properties fit into `breakLength`. Short array elements are also grouped together. +	 * +	 * **Default**: `3` +	 */ +	compact?: boolean | number | undefined; +	/** +	 * If set to `true` or a function, all properties of an object, and `Set` and `Map` +	 * entries are sorted in the resulting string. If set to `true` the [default sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) is used. +	 * If set to a function, it is used as a [compare function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#parameters). +	 * +	 * **Default**: `false`. +	 */ +	sorted?: boolean | ((a: string, b: string) => number) | undefined; +	/** +	 * If set to `true`, getters are inspected. If set to `'get'`, only getters without a +	 * corresponding setter are inspected. If set to `'set'`, only getters with a +	 * corresponding setter are inspected. This might cause side effects depending on +	 * the getter function. +	 * +	 * **Default**: `false`. +	 */ +	getters?: 'get' | 'set' | boolean | undefined; +} diff --git a/src/lib/common/typings/CodeBlockLang.d.ts b/src/lib/common/typings/CodeBlockLang.d.ts new file mode 100644 index 0000000..5a1aeba --- /dev/null +++ b/src/lib/common/typings/CodeBlockLang.d.ts @@ -0,0 +1,310 @@ +export type CodeBlockLang = +	| '1c' +	| 'abnf' +	| 'accesslog' +	| 'actionscript' +	| 'ada' +	| 'arduino' +	| 'ino' +	| 'armasm' +	| 'arm' +	| 'avrasm' +	| 'actionscript' +	| 'as' +	| 'angelscript' +	| 'asc' +	| 'apache' +	| 'apacheconf' +	| 'applescript' +	| 'osascript' +	| 'arcade' +	| 'asciidoc' +	| 'adoc' +	| 'aspectj' +	| 'autohotkey' +	| 'autoit' +	| 'awk' +	| 'mawk' +	| 'nawk' +	| 'gawk' +	| 'bash' +	| 'sh' +	| 'zsh' +	| 'basic' +	| 'bnf' +	| 'brainfuck' +	| 'bf' +	| 'csharp' +	| 'cs' +	| 'c' +	| 'h' +	| 'cpp' +	| 'hpp' +	| 'cc' +	| 'hh' +	| 'c++' +	| 'h++' +	| 'cxx' +	| 'hxx' +	| 'cal' +	| 'cos' +	| 'cls' +	| 'cmake' +	| 'cmake.in' +	| 'coq' +	| 'csp' +	| 'css' +	| 'capnproto' +	| 'capnp' +	| 'clojure' +	| 'clj' +	| 'coffeescript' +	| 'coffee' +	| 'cson' +	| 'iced' +	| 'crmsh' +	| 'crm' +	| 'pcmk' +	| 'crystal' +	| 'cr' +	| 'd' +	| 'dns' +	| 'zone' +	| 'bind' +	| 'dos' +	| 'bat' +	| 'cmd' +	| 'dart' +	| 'dpr' +	| 'dfm' +	| 'pas' +	| 'pascal' +	| 'diff' +	| 'patch' +	| 'django' +	| 'jinja' +	| 'dockerfile' +	| 'docker' +	| 'dsconfig' +	| 'dts' +	| 'dust' +	| 'dst' +	| 'ebnf' +	| 'elixir' +	| 'elm' +	| 'erlang' +	| 'erl' +	| 'excel' +	| 'xls' +	| 'xlsx' +	| 'fsharp' +	| 'fs' +	| 'fix' +	| 'fortran' +	| 'f90' +	| 'f95' +	| 'gcode' +	| 'nc' +	| 'gams' +	| 'gms' +	| 'gauss' +	| 'gss' +	| 'gherkin' +	| 'go' +	| 'golang' +	| 'golo' +	| 'gololang' +	| 'gradle' +	| 'groovy' +	| 'xml' +	| 'html' +	| 'xhtml' +	| 'rss' +	| 'atom' +	| 'xjb' +	| 'xsd' +	| 'xsl' +	| 'plist' +	| 'svg' +	| 'http' +	| 'https' +	| 'haml' +	| 'handlebars' +	| 'hbs' +	| 'html.hbs' +	| 'html.handlebars' +	| 'haskell' +	| 'hs' +	| 'haxe' +	| 'hx' +	| 'hlsl' +	| 'hy' +	| 'hylang' +	| 'ini' +	| 'toml' +	| 'inform7' +	| 'i7' +	| 'irpf90' +	| 'json' +	| 'java' +	| 'jsp' +	| 'javascript' +	| 'js' +	| 'jsx' +	| 'julia' +	| 'julia-repl' +	| 'kotlin' +	| 'kt' +	| 'tex' +	| 'leaf' +	| 'lasso' +	| 'ls' +	| 'lassoscript' +	| 'less' +	| 'ldif' +	| 'lisp' +	| 'livecodeserver' +	| 'livescript' +	| 'ls' +	| 'lua' +	| 'makefile' +	| 'mk' +	| 'mak' +	| 'make' +	| 'markdown' +	| 'md' +	| 'mkdown' +	| 'mkd' +	| 'mathematica' +	| 'mma' +	| 'wl' +	| 'matlab' +	| 'maxima' +	| 'mel' +	| 'mercury' +	| 'mizar' +	| 'mojolicious' +	| 'monkey' +	| 'moonscript' +	| 'moon' +	| 'n1ql' +	| 'nsis' +	| 'nginx' +	| 'nginxconf' +	| 'nim' +	| 'nimrod' +	| 'nix' +	| 'ocaml' +	| 'ml' +	| 'objectivec' +	| 'mm' +	| 'objc' +	| 'obj-c' +	| 'obj-c++' +	| 'objective-c++' +	| 'glsl' +	| 'openscad' +	| 'scad' +	| 'ruleslanguage' +	| 'oxygene' +	| 'pf' +	| 'pf.conf' +	| 'php' +	| 'parser3' +	| 'perl' +	| 'pl' +	| 'pm' +	| 'plaintext' +	| 'txt' +	| 'text' +	| 'pony' +	| 'pgsql' +	| 'postgres' +	| 'postgresql' +	| 'powershell' +	| 'ps' +	| 'ps1' +	| 'processing' +	| 'prolog' +	| 'properties' +	| 'protobuf' +	| 'puppet' +	| 'pp' +	| 'python' +	| 'py' +	| 'gyp' +	| 'profile' +	| 'python-repl' +	| 'pycon' +	| 'k' +	| 'kdb' +	| 'qml' +	| 'r' +	| 'reasonml' +	| 're' +	| 'rib' +	| 'rsl' +	| 'graph' +	| 'instances' +	| 'ruby' +	| 'rb' +	| 'gemspec' +	| 'podspec' +	| 'thor' +	| 'irb' +	| 'rust' +	| 'rs' +	| 'sas' +	| 'scss' +	| 'sql' +	| 'p21' +	| 'step' +	| 'stp' +	| 'scala' +	| 'scheme' +	| 'scilab' +	| 'sci' +	| 'shell' +	| 'console' +	| 'smali' +	| 'smalltalk' +	| 'st' +	| 'sml' +	| 'ml' +	| 'stan' +	| 'stanfuncs' +	| 'stata' +	| 'stylus' +	| 'styl' +	| 'subunit' +	| 'swift' +	| 'tcl' +	| 'tk' +	| 'tap' +	| 'thrift' +	| 'tp' +	| 'twig' +	| 'craftcms' +	| 'typescript' +	| 'ts' +	| 'vbnet' +	| 'vb' +	| 'vbscript' +	| 'vbs' +	| 'vhdl' +	| 'vala' +	| 'verilog' +	| 'v' +	| 'vim' +	| 'axapta' +	| 'x++' +	| 'x86asm' +	| 'xl' +	| 'tao' +	| 'xquery' +	| 'xpath' +	| 'xq' +	| 'yml' +	| 'yaml' +	| 'zephir' +	| 'zep'; diff --git a/src/lib/common/util/Arg.ts b/src/lib/common/util/Arg.ts new file mode 100644 index 0000000..84d5aeb --- /dev/null +++ b/src/lib/common/util/Arg.ts @@ -0,0 +1,120 @@ +import { Argument, ArgumentTypeCaster, Flag, ParsedValuePredicate, TypeResolver } from 'discord-akairo'; +import { Message } from 'discord.js'; +import { BushArgumentType } from '../..'; + +export class Arg { +	/** +	 * Casts a phrase to this argument's type. +	 * @param type - The type to cast to. +	 * @param resolver - The type resolver. +	 * @param message - Message that called the command. +	 * @param phrase - Phrase to process. +	 */ +	public static cast(type: BushArgumentType, resolver: TypeResolver, message: Message, phrase: string): Promise<any> { +		return Argument.cast(type, resolver, message, phrase); +	} + +	/** +	 * Creates a type that is the left-to-right composition of the given types. +	 * If any of the types fails, the entire composition fails. +	 * @param types - Types to use. +	 */ +	public static compose(...types: BushArgumentType[]): ArgumentTypeCaster { +		return Argument.compose(...types); +	} + +	/** +	 * Creates a type that is the left-to-right composition of the given types. +	 * If any of the types fails, the composition still continues with the failure passed on. +	 * @param types - Types to use. +	 */ +	public static composeWithFailure(...types: BushArgumentType[]): ArgumentTypeCaster { +		return Argument.composeWithFailure(...types); +	} + +	/** +	 * Checks if something is null, undefined, or a fail flag. +	 * @param value - Value to check. +	 */ +	public static isFailure(value: any): value is null | undefined | (Flag & { value: any }) { +		return Argument.isFailure(value); +	} + +	/** +	 * Creates a type from multiple types (product type). +	 * Only inputs where each type resolves with a non-void value are valid. +	 * @param types - Types to use. +	 */ +	public static product(...types: BushArgumentType[]): ArgumentTypeCaster { +		return Argument.product(...types); +	} + +	/** +	 * Creates a type where the parsed value must be within a range. +	 * @param type - The type to use. +	 * @param min - Minimum value. +	 * @param max - Maximum value. +	 * @param inclusive - Whether or not to be inclusive on the upper bound. +	 */ +	public static range(type: BushArgumentType, min: number, max: number, inclusive?: boolean): ArgumentTypeCaster { +		return Argument.range(type, min, max, inclusive); +	} + +	/** +	 * Creates a type that parses as normal but also tags it with some data. +	 * Result is in an object `{ tag, value }` and wrapped in `Flag.fail` when failed. +	 * @param type - The type to use. +	 * @param tag - Tag to add. Defaults to the `type` argument, so useful if it is a string. +	 */ +	public static tagged(type: BushArgumentType, tag?: any): ArgumentTypeCaster { +		return Argument.tagged(type, tag); +	} + +	/** +	 * Creates a type from multiple types (union type). +	 * The first type that resolves to a non-void value is used. +	 * Each type will also be tagged using `tagged` with themselves. +	 * @param types - Types to use. +	 */ +	public static taggedUnion(...types: BushArgumentType[]): ArgumentTypeCaster { +		return Argument.taggedUnion(...types); +	} + +	/** +	 * Creates a type that parses as normal but also tags it with some data and carries the original input. +	 * Result is in an object `{ tag, input, value }` and wrapped in `Flag.fail` when failed. +	 * @param type - The type to use. +	 * @param tag - Tag to add. Defaults to the `type` argument, so useful if it is a string. +	 */ +	public static taggedWithInput(type: BushArgumentType, tag?: any): ArgumentTypeCaster { +		return Argument.taggedWithInput(type, tag); +	} + +	/** +	 * Creates a type from multiple types (union type). +	 * The first type that resolves to a non-void value is used. +	 * @param types - Types to use. +	 */ +	public static union(...types: BushArgumentType[]): ArgumentTypeCaster { +		return Argument.union(...types); +	} + +	/** +	 * Creates a type with extra validation. +	 * If the predicate is not true, the value is considered invalid. +	 * @param type - The type to use. +	 * @param predicate - The predicate function. +	 */ +	public static validate(type: BushArgumentType, predicate: ParsedValuePredicate): ArgumentTypeCaster { +		return Argument.validate(type, predicate); +	} + +	/** +	 * Creates a type that parses as normal but also carries the original input. +	 * Result is in an object `{ input, value }` and wrapped in `Flag.fail` when failed. +	 * @param type - The type to use. +	 */ +	public static withInput(type: BushArgumentType): ArgumentTypeCaster { +		return Argument.withInput(type); +	} +} diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts index 448eaf3..499d2c7 100644 --- a/src/lib/extensions/discord-akairo/BushClientUtil.ts +++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts @@ -1,5 +1,4 @@  import { -	BushArgumentType,  	BushCache,  	BushClient,  	BushConstants, @@ -11,30 +10,16 @@ import {  	PronounCode  } from '@lib';  import { exec } from 'child_process'; -import { -	Argument, -	ArgumentTypeCaster, -	ClientUtil, -	Flag, -	ParsedValuePredicate, -	TypeResolver, -	Util as AkairoUtil -} from 'discord-akairo'; +import { ClientUtil, Util as AkairoUtil } from 'discord-akairo';  import { APIMessage } from 'discord-api-types';  import {  	ColorResolvable,  	CommandInteraction, -	Constants,  	GuildMember,  	InteractionReplyOptions,  	Message, -	MessageActionRow, -	MessageButton, -	MessageComponentInteraction, -	MessageEditOptions,  	MessageEmbed, -	MessageEmbedOptions, -	MessageOptions, +	PermissionResolvable,  	Snowflake,  	TextChannel,  	ThreadMember, @@ -46,443 +31,16 @@ import got from 'got';  import humanizeDuration from 'humanize-duration';  import _ from 'lodash';  import moment from 'moment'; -import { inspect, InspectOptions, promisify } from 'util'; +import { inspect, promisify } from 'util';  import CommandErrorListener from '../../../listeners/commands/commandError'; +import { Format } from '../../common/Format'; +import { BushInspectOptions } from '../../common/typings/BushInspectOptions'; +import { CodeBlockLang } from '../../common/typings/CodeBlockLang'; +import { Arg } from '../../common/util/Arg';  import { BushNewsChannel } from '../discord.js/BushNewsChannel';  import { BushTextChannel } from '../discord.js/BushTextChannel';  import { BushSlashEditMessageType, BushSlashSendMessageType, BushUserResolvable } from './BushClient'; -interface hastebinRes { -	key: string; -} - -export interface uuidRes { -	uuid: string; -	username: string; -	username_history?: { username: string }[] | null; -	textures: { -		custom: boolean; -		slim: boolean; -		skin: { -			url: string; -			data: string; -		}; -		raw: { -			value: string; -			signature: string; -		}; -	}; -	created_at: string; -} - -interface MojangProfile { -	username: string; -	uuid: string; -} - -// #region codeblock type -export type CodeBlockLang = -	| '1c' -	| 'abnf' -	| 'accesslog' -	| 'actionscript' -	| 'ada' -	| 'arduino' -	| 'ino' -	| 'armasm' -	| 'arm' -	| 'avrasm' -	| 'actionscript' -	| 'as' -	| 'angelscript' -	| 'asc' -	| 'apache' -	| 'apacheconf' -	| 'applescript' -	| 'osascript' -	| 'arcade' -	| 'asciidoc' -	| 'adoc' -	| 'aspectj' -	| 'autohotkey' -	| 'autoit' -	| 'awk' -	| 'mawk' -	| 'nawk' -	| 'gawk' -	| 'bash' -	| 'sh' -	| 'zsh' -	| 'basic' -	| 'bnf' -	| 'brainfuck' -	| 'bf' -	| 'csharp' -	| 'cs' -	| 'c' -	| 'h' -	| 'cpp' -	| 'hpp' -	| 'cc' -	| 'hh' -	| 'c++' -	| 'h++' -	| 'cxx' -	| 'hxx' -	| 'cal' -	| 'cos' -	| 'cls' -	| 'cmake' -	| 'cmake.in' -	| 'coq' -	| 'csp' -	| 'css' -	| 'capnproto' -	| 'capnp' -	| 'clojure' -	| 'clj' -	| 'coffeescript' -	| 'coffee' -	| 'cson' -	| 'iced' -	| 'crmsh' -	| 'crm' -	| 'pcmk' -	| 'crystal' -	| 'cr' -	| 'd' -	| 'dns' -	| 'zone' -	| 'bind' -	| 'dos' -	| 'bat' -	| 'cmd' -	| 'dart' -	| 'dpr' -	| 'dfm' -	| 'pas' -	| 'pascal' -	| 'diff' -	| 'patch' -	| 'django' -	| 'jinja' -	| 'dockerfile' -	| 'docker' -	| 'dsconfig' -	| 'dts' -	| 'dust' -	| 'dst' -	| 'ebnf' -	| 'elixir' -	| 'elm' -	| 'erlang' -	| 'erl' -	| 'excel' -	| 'xls' -	| 'xlsx' -	| 'fsharp' -	| 'fs' -	| 'fix' -	| 'fortran' -	| 'f90' -	| 'f95' -	| 'gcode' -	| 'nc' -	| 'gams' -	| 'gms' -	| 'gauss' -	| 'gss' -	| 'gherkin' -	| 'go' -	| 'golang' -	| 'golo' -	| 'gololang' -	| 'gradle' -	| 'groovy' -	| 'xml' -	| 'html' -	| 'xhtml' -	| 'rss' -	| 'atom' -	| 'xjb' -	| 'xsd' -	| 'xsl' -	| 'plist' -	| 'svg' -	| 'http' -	| 'https' -	| 'haml' -	| 'handlebars' -	| 'hbs' -	| 'html.hbs' -	| 'html.handlebars' -	| 'haskell' -	| 'hs' -	| 'haxe' -	| 'hx' -	| 'hlsl' -	| 'hy' -	| 'hylang' -	| 'ini' -	| 'toml' -	| 'inform7' -	| 'i7' -	| 'irpf90' -	| 'json' -	| 'java' -	| 'jsp' -	| 'javascript' -	| 'js' -	| 'jsx' -	| 'julia' -	| 'julia-repl' -	| 'kotlin' -	| 'kt' -	| 'tex' -	| 'leaf' -	| 'lasso' -	| 'ls' -	| 'lassoscript' -	| 'less' -	| 'ldif' -	| 'lisp' -	| 'livecodeserver' -	| 'livescript' -	| 'ls' -	| 'lua' -	| 'makefile' -	| 'mk' -	| 'mak' -	| 'make' -	| 'markdown' -	| 'md' -	| 'mkdown' -	| 'mkd' -	| 'mathematica' -	| 'mma' -	| 'wl' -	| 'matlab' -	| 'maxima' -	| 'mel' -	| 'mercury' -	| 'mizar' -	| 'mojolicious' -	| 'monkey' -	| 'moonscript' -	| 'moon' -	| 'n1ql' -	| 'nsis' -	| 'nginx' -	| 'nginxconf' -	| 'nim' -	| 'nimrod' -	| 'nix' -	| 'ocaml' -	| 'ml' -	| 'objectivec' -	| 'mm' -	| 'objc' -	| 'obj-c' -	| 'obj-c++' -	| 'objective-c++' -	| 'glsl' -	| 'openscad' -	| 'scad' -	| 'ruleslanguage' -	| 'oxygene' -	| 'pf' -	| 'pf.conf' -	| 'php' -	| 'parser3' -	| 'perl' -	| 'pl' -	| 'pm' -	| 'plaintext' -	| 'txt' -	| 'text' -	| 'pony' -	| 'pgsql' -	| 'postgres' -	| 'postgresql' -	| 'powershell' -	| 'ps' -	| 'ps1' -	| 'processing' -	| 'prolog' -	| 'properties' -	| 'protobuf' -	| 'puppet' -	| 'pp' -	| 'python' -	| 'py' -	| 'gyp' -	| 'profile' -	| 'python-repl' -	| 'pycon' -	| 'k' -	| 'kdb' -	| 'qml' -	| 'r' -	| 'reasonml' -	| 're' -	| 'rib' -	| 'rsl' -	| 'graph' -	| 'instances' -	| 'ruby' -	| 'rb' -	| 'gemspec' -	| 'podspec' -	| 'thor' -	| 'irb' -	| 'rust' -	| 'rs' -	| 'sas' -	| 'scss' -	| 'sql' -	| 'p21' -	| 'step' -	| 'stp' -	| 'scala' -	| 'scheme' -	| 'scilab' -	| 'sci' -	| 'shell' -	| 'console' -	| 'smali' -	| 'smalltalk' -	| 'st' -	| 'sml' -	| 'ml' -	| 'stan' -	| 'stanfuncs' -	| 'stata' -	| 'stylus' -	| 'styl' -	| 'subunit' -	| 'swift' -	| 'tcl' -	| 'tk' -	| 'tap' -	| 'thrift' -	| 'tp' -	| 'twig' -	| 'craftcms' -	| 'typescript' -	| 'ts' -	| 'vbnet' -	| 'vb' -	| 'vbscript' -	| 'vbs' -	| 'vhdl' -	| 'vala' -	| 'verilog' -	| 'v' -	| 'vim' -	| 'axapta' -	| 'x++' -	| 'x86asm' -	| 'xl' -	| 'tao' -	| 'xquery' -	| 'xpath' -	| 'xq' -	| 'yml' -	| 'yaml' -	| 'zephir' -	| 'zep'; -//#endregion - -/** - * {@link https://nodejs.org/api/util.html#util_util_inspect_object_options} - */ -export interface BushInspectOptions extends InspectOptions { -	/** -	 * If `true`, object's non-enumerable symbols and properties are included in the -	 * formatted result. [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) and [`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) entries are also included as well as -	 * user defined prototype properties (excluding method properties). -	 * -	 * **Default**: `false`. -	 */ -	showHidden?: boolean | undefined; -	/** -	 * Specifies the number of times to recurse while formatting `object`. This is useful -	 * for inspecting large objects. To recurse up to the maximum call stack size pass -	 * `Infinity` or `null`. -	 * -	 * **Default**: `2`. -	 */ -	depth?: number | null | undefined; -	/** -	 * If `true`, the output is styled with ANSI color codes. Colors are customizable. See [Customizing util.inspect colors](https://nodejs.org/api/util.html#util_customizing_util_inspect_colors). -	 * -	 * **Default**: `false`. -	 */ -	colors?: boolean | undefined; -	/** -	 * If `false`, `[util.inspect.custom](depth, opts)` functions are not invoked. -	 * -	 * **Default**: `true`. -	 */ -	customInspect?: boolean | undefined; -	/** -	 * If `true`, `Proxy` inspection includes the [`target` and `handler`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy#Terminology) objects. -	 * -	 * **Default**: `false`. -	 */ -	showProxy?: boolean | undefined; -	/** -	 * Specifies the maximum number of `Array`, [`TypedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray), [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) and -	 * [`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) elements to include when formatting. Set to `null` or `Infinity` to -	 * show all elements. Set to `0` or negative to show no elements. -	 * -	 * **Default**: `100`. -	 */ -	maxArrayLength?: number | null | undefined; -	/** -	 * Specifies the maximum number of characters to include when formatting. Set to -	 * `null` or `Infinity` to show all elements. Set to `0` or negative to show no -	 * characters. -	 * -	 * **Default**: `10000`. -	 */ -	maxStringLength?: number | null | undefined; -	/** -	 * The length at which input values are split across multiple lines. Set to -	 * `Infinity` to format the input as a single line (in combination with compact set -	 * to `true` or any number >= `1`). -	 * -	 * **Default**: `80`. -	 */ -	breakLength?: number | undefined; -	/** -	 * Setting this to `false` causes each object key to be displayed on a new line. It -	 * will break on new lines in text that is longer than `breakLength`. If set to a -	 * number, the most `n` inner elements are united on a single line as long as all -	 * properties fit into `breakLength`. Short array elements are also grouped together. -	 * -	 * **Default**: `3` -	 */ -	compact?: boolean | number | undefined; -	/** -	 * If set to `true` or a function, all properties of an object, and `Set` and `Map` -	 * entries are sorted in the resulting string. If set to `true` the [default sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) is used. -	 * If set to a function, it is used as a [compare function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#parameters). -	 * -	 * **Default**: `false`. -	 */ -	sorted?: boolean | ((a: string, b: string) => number) | undefined; -	/** -	 * If set to `true`, getters are inspected. If set to `'get'`, only getters without a -	 * corresponding setter are inspected. If set to `'set'`, only getters with a -	 * corresponding setter are inspected. This might cause side effects depending on -	 * the getter function. -	 * -	 * **Default**: `false`. -	 */ -	getters?: 'get' | 'set' | boolean | undefined; -} -  export class BushClientUtil extends ClientUtil {  	/**  	 * The client. @@ -504,22 +62,6 @@ export class BushClientUtil extends ClientUtil {  	];  	/** -	 * Emojis used for {@link BushClientUtil.buttonPaginate} -	 */ -	#paginateEmojis = { -		beginning: '853667381335162910', -		back: '853667410203770881', -		stop: '853667471110570034', -		forward: '853667492680564747', -		end: '853667514915225640' -	}; - -	/** -	 * A simple promise exec method -	 */ -	#exec = promisify(exec); - -	/**  	 * Creates this client util  	 * @param client The client to initialize with  	 */ @@ -554,7 +96,7 @@ export class BushClientUtil extends ClientUtil {  		stdout: string;  		stderr: string;  	}> { -		return await this.#exec(command); +		return await promisify(exec)(command);  	}  	/** @@ -677,193 +219,13 @@ export class BushClientUtil extends ClientUtil {  	}  	/** -	 * Paginates an array of embeds using buttons. -	 */ -	public async buttonPaginate( -		message: BushMessage | BushSlashMessage, -		embeds: MessageEmbed[] | MessageEmbedOptions[], -		text: string | null = null, -		deleteOnExit?: boolean, -		startOn?: number -	): Promise<void> { -		const paginateEmojis = this.#paginateEmojis; -		if (deleteOnExit === undefined) deleteOnExit = true; - -		if (embeds.length === 1) { -			return this.sendWithDeleteButton(message, { embeds: embeds }); -		} - -		embeds.forEach((_e, i) => { -			embeds[i] instanceof MessageEmbed -				? (embeds[i] as MessageEmbed).setFooter(`Page ${(i + 1).toLocaleString()}/${embeds.length.toLocaleString()}`) -				: ((embeds[i] as MessageEmbedOptions).footer = { -						text: `Page ${(i + 1).toLocaleString()}/${embeds.length.toLocaleString()}` -				  }); -		}); - -		const style = Constants.MessageButtonStyles.PRIMARY; -		let curPage = startOn ? startOn - 1 : 0; -		if (typeof embeds !== 'object') throw new Error('embeds must be an object'); -		const msg = (await message.util.reply({ -			// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -			content: text || null, -			embeds: [embeds[curPage]], -			components: [getPaginationRow()] -		})) as Message; -		const filter = (interaction: MessageComponentInteraction) => -			interaction.customId.startsWith('paginate_') && interaction.message.id === msg.id; -		const collector = msg.createMessageComponentCollector({ filter, time: 300000 }); -		collector.on('collect', async (interaction: MessageComponentInteraction) => { -			if (interaction.user.id === message.author.id || client.config.owners.includes(interaction.user.id)) { -				switch (interaction.customId) { -					case 'paginate_beginning': { -						curPage = 0; -						await edit(interaction); -						break; -					} -					case 'paginate_back': { -						curPage--; -						await edit(interaction); -						break; -					} -					case 'paginate_stop': { -						if (deleteOnExit) { -							await interaction.deferUpdate().catch(() => undefined); -							if (msg.deletable && !msg.deleted) { -								await msg.delete(); -							} -						} else { -							await interaction -								?.update({ -									content: `${text ? `${text}\n` : ''}Command closed by user.`, -									embeds: [], -									components: [] -								}) -								.catch(() => undefined); -						} -						return; -					} -					case 'paginate_next': { -						curPage++; -						await edit(interaction); -						break; -					} -					case 'paginate_end': { -						curPage = embeds.length - 1; -						await edit(interaction); -						break; -					} -				} -			} else { -				return await interaction?.deferUpdate().catch(() => undefined); -			} -		}); - -		collector.on('end', async () => { -			await msg -				.edit({ -					content: text, -					embeds: [embeds[curPage]], -					components: [getPaginationRow(true)] -				}) -				.catch(() => undefined); -		}); - -		async function edit(interaction: MessageComponentInteraction): Promise<void> { -			return await interaction -				?.update({ content: text, embeds: [embeds[curPage]], components: [getPaginationRow()] }) -				.catch(() => undefined); -		} -		function getPaginationRow(disableAll = false): MessageActionRow { -			return new MessageActionRow().addComponents( -				new MessageButton({ -					style, -					customId: 'paginate_beginning', -					emoji: paginateEmojis.beginning, -					disabled: disableAll || curPage === 0 -				}), -				new MessageButton({ -					style, -					customId: 'paginate_back', -					emoji: paginateEmojis.back, -					disabled: disableAll || curPage === 0 -				}), -				new MessageButton({ -					style, -					customId: 'paginate_stop', -					emoji: paginateEmojis.stop, -					disabled: disableAll -				}), -				new MessageButton({ -					style, -					customId: 'paginate_next', -					emoji: paginateEmojis.forward, -					disabled: disableAll || curPage === embeds.length - 1 -				}), -				new MessageButton({ -					style, -					customId: 'paginate_end', -					emoji: paginateEmojis.end, -					disabled: disableAll || curPage === embeds.length - 1 -				}) -			); -		} -	} - -	/** -	 * Sends a message with a button for the user to delete it. -	 */ -	public async sendWithDeleteButton(message: BushMessage | BushSlashMessage, options: MessageOptions): Promise<void> { -		const paginateEmojis = this.#paginateEmojis; -		updateOptions(); -		const msg = (await message.util.reply(options as MessageOptions & { split?: false })) as Message; -		const filter = (interaction: MessageComponentInteraction) => -			interaction.customId == 'paginate__stop' && interaction.message == msg; -		const collector = msg.createMessageComponentCollector({ filter, time: 300000 }); -		collector.on('collect', async (interaction: MessageComponentInteraction) => { -			if (interaction.user.id == message.author.id || client.config.owners.includes(interaction.user.id)) { -				await interaction.deferUpdate().catch(() => undefined); -				if (msg.deletable && !msg.deleted) { -					await msg.delete(); -				} -				return; -			} else { -				return await interaction?.deferUpdate().catch(() => undefined); -			} -		}); - -		collector.on('end', async () => { -			updateOptions(true, true); -			await msg.edit(options as MessageEditOptions).catch(() => undefined); -		}); - -		function updateOptions(edit?: boolean, disable?: boolean) { -			if (edit == undefined) edit = false; -			if (disable == undefined) disable = false; -			options.components = [ -				new MessageActionRow().addComponents( -					new MessageButton({ -						style: Constants.MessageButtonStyles.PRIMARY, -						customId: 'paginate__stop', -						emoji: paginateEmojis.stop, -						disabled: disable -					}) -				) -			]; -			if (edit) { -				options.reply = undefined; -			} -		} -	} - -	/**  	 * Surrounds text in a code block with the specified language and puts it in a hastebin if its too long.  	 * * Embed Description Limit = 4096 characters  	 * * Embed Field Limit = 1024 characters  	 */  	public async codeblock(code: string, length: number, language?: CodeBlockLang, substr = false): Promise<string> {  		let hasteOut = ''; -		code = util.discord.escapeCodeBlock(code); +		code = this.discord.escapeCodeBlock(code);  		const prefix = `\`\`\`${language}\n`;  		const suffix = '\n```';  		language = language ?? 'txt'; @@ -887,7 +249,12 @@ export class BushClientUtil extends ClientUtil {  		return code3;  	} -	public inspect(code: any, options?: BushInspectOptions): string { +	/** +	 * Uses {@link inspect} with custom defaults +	 * @param object - The object you would like to inspect +	 * @param options - The options you would like to use to inspect the object +	 */ +	public inspect(object: any, options?: BushInspectOptions): string {  		const {  			showHidden: _showHidden = false,  			depth: _depth = 2, @@ -914,7 +281,7 @@ export class BushClientUtil extends ClientUtil {  			sorted: _sorted,  			getters: _getters  		}; -		return inspect(code, optionsWithDefaults); +		return inspect(object, optionsWithDefaults);  	}  	#mapCredential(old: string): string { @@ -931,6 +298,7 @@ export class BushClientUtil extends ClientUtil {  	/**  	 * Redacts credentials from a string +	 * @param text - The text to redact credentials from  	 */  	public redact(text: string) {  		for (const credentialName in { ...client.config.credentials, dbPassword: client.config.db.password }) { @@ -1028,7 +396,7 @@ export class BushClientUtil extends ClientUtil {  		const newValue = this.addOrRemoveFromArray(action, oldValue, value);  		row[key] = newValue;  		client.cache.global[key] = newValue; -		return await row.save().catch((e) => util.handleError('insertOrRemoveFromGlobal', e)); +		return await row.save().catch((e) => this.handleError('insertOrRemoveFromGlobal', e));  	}  	/** @@ -1233,136 +601,108 @@ export class BushClientUtil extends ClientUtil {  		return str.replace(/[\u0000-\u001F\u007F-\u009F\u200B]/g, '');  	} -	get arg() { -		return class Arg { -			/** -			 * Casts a phrase to this argument's type. -			 * @param type - The type to cast to. -			 * @param resolver - The type resolver. -			 * @param message - Message that called the command. -			 * @param phrase - Phrase to process. -			 */ -			public static cast(type: BushArgumentType, resolver: TypeResolver, message: Message, phrase: string): Promise<any> { -				return Argument.cast(type, resolver, message, phrase); -			} +	public async uploadImageToImgur(image: string) { +		const clientId = this.client.config.credentials.imgurClientId; -			/** -			 * Creates a type that is the left-to-right composition of the given types. -			 * If any of the types fails, the entire composition fails. -			 * @param types - Types to use. -			 */ -			public static compose(...types: BushArgumentType[]): ArgumentTypeCaster { -				return Argument.compose(...types); -			} +		const resp = (await got +			.post('https://api.imgur.com/3/upload', { +				headers: { +					// Authorization: `Bearer ${token}`, +					Authorization: `Client-ID ${clientId}`, +					Accept: 'application/json' +				}, +				form: { +					image: image, +					type: 'base64' +				}, +				followRedirect: true +			}) +			.json()) as { data: { link: string } }; -			/** -			 * Creates a type that is the left-to-right composition of the given types. -			 * If any of the types fails, the composition still continues with the failure passed on. -			 * @param types - Types to use. -			 */ -			public static composeWithFailure(...types: BushArgumentType[]): ArgumentTypeCaster { -				return Argument.composeWithFailure(...types); -			} +		return resp.data.link; +	} -			/** -			 * Checks if something is null, undefined, or a fail flag. -			 * @param value - Value to check. -			 */ -			public static isFailure(value: any): value is null | undefined | (Flag & { value: any }) { -				return Argument.isFailure(value); -			} +	public userGuildPermCheck(message: BushMessage | BushSlashMessage, permissions: PermissionResolvable) { +		const missing = message.member?.permissions.missing(permissions) ?? []; -			/** -			 * Creates a type from multiple types (product type). -			 * Only inputs where each type resolves with a non-void value are valid. -			 * @param types - Types to use. -			 */ -			public static product(...types: BushArgumentType[]): ArgumentTypeCaster { -				return Argument.product(...types); -			} +		return missing.length ? missing : null; +	} -			/** -			 * Creates a type where the parsed value must be within a range. -			 * @param type - The type to use. -			 * @param min - Minimum value. -			 * @param max - Maximum value. -			 * @param inclusive - Whether or not to be inclusive on the upper bound. -			 */ -			public static range(type: BushArgumentType, min: number, max: number, inclusive?: boolean): ArgumentTypeCaster { -				return Argument.range(type, min, max, inclusive); -			} +	public clientGuildPermCheck(message: BushMessage | BushSlashMessage, permissions: PermissionResolvable) { +		const missing = message.guild?.me?.permissions.missing(permissions) ?? []; -			/** -			 * Creates a type that parses as normal but also tags it with some data. -			 * Result is in an object `{ tag, value }` and wrapped in `Flag.fail` when failed. -			 * @param type - The type to use. -			 * @param tag - Tag to add. Defaults to the `type` argument, so useful if it is a string. -			 */ -			public static tagged(type: BushArgumentType, tag?: any): ArgumentTypeCaster { -				return Argument.tagged(type, tag); -			} +		return missing.length ? missing : null; +	} -			/** -			 * Creates a type from multiple types (union type). -			 * The first type that resolves to a non-void value is used. -			 * Each type will also be tagged using `tagged` with themselves. -			 * @param types - Types to use. -			 */ -			public static taggedUnion(...types: BushArgumentType[]): ArgumentTypeCaster { -				return Argument.taggedUnion(...types); -			} +	public clientSendAndPermCheck( +		message: BushMessage | BushSlashMessage, +		permissions: PermissionResolvable = [], +		checkChannel = false +	) { +		const missing = []; +		const sendPerm = message.channel!.isThread() ? 'SEND_MESSAGES' : 'SEND_MESSAGES_IN_THREADS'; -			/** -			 * Creates a type that parses as normal but also tags it with some data and carries the original input. -			 * Result is in an object `{ tag, input, value }` and wrapped in `Flag.fail` when failed. -			 * @param type - The type to use. -			 * @param tag - Tag to add. Defaults to the `type` argument, so useful if it is a string. -			 */ -			public static taggedWithInput(type: BushArgumentType, tag?: any): ArgumentTypeCaster { -				return Argument.taggedWithInput(type, tag); -			} +		if (!message.guild!.me!.permissionsIn(message.channel!.id!).has(sendPerm)) missing.push(sendPerm); -			/** -			 * Creates a type from multiple types (union type). -			 * The first type that resolves to a non-void value is used. -			 * @param types - Types to use. -			 */ -			public static union(...types: BushArgumentType[]): ArgumentTypeCaster { -				return Argument.union(...types); -			} +		missing.push( +			...(checkChannel +				? message.guild!.me!.permissionsIn(message.channel!.id!).missing(permissions) +				: this.clientGuildPermCheck(message, permissions) ?? []) +		); -			/** -			 * Creates a type with extra validation. -			 * If the predicate is not true, the value is considered invalid. -			 * @param type - The type to use. -			 * @param predicate - The predicate function. -			 */ -			public static validate(type: BushArgumentType, predicate: ParsedValuePredicate): ArgumentTypeCaster { -				return Argument.validate(type, predicate); -			} +		return missing.length ? missing : null; +	} -			/** -			 * Creates a type that parses as normal but also carries the original input. -			 * Result is in an object `{ input, value }` and wrapped in `Flag.fail` when failed. -			 * @param type - The type to use. -			 */ -			public static withInput(type: BushArgumentType): ArgumentTypeCaster { -				return Argument.withInput(type); -			} -		}; +	public get arg() { +		return Arg; +	} + +	/** +	 * Formats and escapes content for formatting +	 */ +	public get format() { +		return Format;  	}  	/**  	 * Discord.js's Util class  	 */ -	get discord() { +	public get discord() {  		return DiscordUtil;  	}  	/**  	 * discord-akairo's Util class  	 */ -	get akairo() { +	public get akairo() {  		return AkairoUtil;  	}  } + +interface hastebinRes { +	key: string; +} + +export interface uuidRes { +	uuid: string; +	username: string; +	username_history?: { username: string }[] | null; +	textures: { +		custom: boolean; +		slim: boolean; +		skin: { +			url: string; +			data: string; +		}; +		raw: { +			value: string; +			signature: string; +		}; +	}; +	created_at: string; +} + +interface MojangProfile { +	username: string; +	uuid: string; +} diff --git a/src/lib/extensions/discord-akairo/BushCommand.ts b/src/lib/extensions/discord-akairo/BushCommand.ts index 0be128b..22d4aae 100644 --- a/src/lib/extensions/discord-akairo/BushCommand.ts +++ b/src/lib/extensions/discord-akairo/BushCommand.ts @@ -158,9 +158,9 @@ export interface BushCommandOptions extends Omit<CommandOptions, 'userPermission  	/** Allow this command to be run in channels that are blacklisted. */  	bypassChannelBlacklist?: boolean;  	/** Permissions required by the client to run this command. */ -	clientPermissions?: PermissionResolvable | PermissionResolvable[] | BushMissingPermissionSupplier; +	clientPermissions: PermissionResolvable | PermissionResolvable[] | BushMissingPermissionSupplier;  	/** Permissions required by the user to run this command. */ -	userPermissions?: PermissionResolvable | PermissionResolvable[] | BushMissingPermissionSupplier; +	userPermissions: PermissionResolvable | PermissionResolvable[] | BushMissingPermissionSupplier;  }  export class BushCommand extends Command { diff --git a/src/lib/extensions/discord.js/BushGuild.ts b/src/lib/extensions/discord.js/BushGuild.ts index 8d44cd4..51c2795 100644 --- a/src/lib/extensions/discord.js/BushGuild.ts +++ b/src/lib/extensions/discord.js/BushGuild.ts @@ -1,6 +1,6 @@  import { Guild, MessageOptions, UserResolvable } from 'discord.js';  import { RawGuildData } from 'discord.js/typings/rawDataTypes'; -import { Moderation } from '../../common/moderation'; +import { Moderation } from '../../common/Moderation';  import { Guild as GuildDB, GuildFeatures, GuildLogType, GuildModel } from '../../models/Guild';  import { ModLogType } from '../../models/ModLog';  import { BushClient, BushUserResolvable } from '../discord-akairo/BushClient'; diff --git a/src/lib/extensions/discord.js/BushGuildMember.ts b/src/lib/extensions/discord.js/BushGuildMember.ts index 954342d..77d03b1 100644 --- a/src/lib/extensions/discord.js/BushGuildMember.ts +++ b/src/lib/extensions/discord.js/BushGuildMember.ts @@ -1,6 +1,6 @@  import { GuildMember, MessageEmbed, Partialize, Role } from 'discord.js';  import { RawGuildMemberData } from 'discord.js/typings/rawDataTypes'; -import { Moderation } from '../../common/moderation'; +import { Moderation } from '../../common/Moderation';  import { ModLogType } from '../../models/ModLog';  import { BushClient } from '../discord-akairo/BushClient';  import { BushGuild } from './BushGuild'; diff --git a/src/lib/models/Guild.ts b/src/lib/models/Guild.ts index d23bb0f..7b51e61 100644 --- a/src/lib/models/Guild.ts +++ b/src/lib/models/Guild.ts @@ -1,6 +1,6 @@  import { Snowflake } from 'discord.js';  import { DataTypes, Sequelize } from 'sequelize'; -import { BadWords } from '../common/autoMod'; +import { BadWords } from '../common/AutoMod';  import { BushClient } from '../extensions/discord-akairo/BushClient';  import { BaseModel } from './BaseModel';  import { jsonArrayInit, jsonParseGet, jsonParseSet, NEVER_USED } from './__helpers'; diff --git a/src/lib/utils/Config.ts b/src/lib/utils/Config.ts index ed73d40..393dd44 100644 --- a/src/lib/utils/Config.ts +++ b/src/lib/utils/Config.ts @@ -1,25 +1,14 @@  import { Snowflake } from 'discord.js'; -export interface ConfigOptions { -	credentials: { token: string; betaToken: string; devToken: string; hypixelApiKey: string; wolframAlphaAppId: string }; -	environment: 'production' | 'beta' | 'development'; -	owners: Snowflake[]; -	prefix: string; -	channels: { log: Snowflake; error: Snowflake; dm: Snowflake }; -	db: { host: string; port: number; username: string; password: string }; -	logging: { db: boolean; verbose: boolean; info: boolean }; -	supportGuild: { id: Snowflake; invite: string }; -} -  export class Config { -	public credentials: { token: string; betaToken: string; devToken: string; hypixelApiKey: string; wolframAlphaAppId: string }; -	public environment: 'production' | 'beta' | 'development'; +	public credentials: Credentials; +	public environment: Environment;  	public owners: Snowflake[];  	public prefix: string; -	public channels: { log: Snowflake; error: Snowflake; dm: Snowflake }; -	public db: { host: string; port: number; username: string; password: string }; -	public logging: { db: boolean; verbose: boolean; info: boolean }; -	public supportGuild: { id: Snowflake; invite: string }; +	public channels: Channels; +	public db: DataBase; +	public logging: Logging; +	public supportGuild: SupportGuild;  	public constructor(options: ConfigOptions) {  		this.credentials = options.credentials; @@ -43,10 +32,59 @@ export class Config {  	public get isProduction(): boolean {  		return this.environment === 'production';  	} +  	public get isBeta(): boolean {  		return this.environment === 'beta';  	} +  	public get isDevelopment(): boolean {  		return this.environment === 'development';  	}  } + +export interface ConfigOptions { +	credentials: Credentials; +	environment: Environment; +	owners: Snowflake[]; +	prefix: string; +	channels: Channels; +	db: DataBase; +	logging: Logging; +	supportGuild: SupportGuild; +} + +interface Credentials { +	token: string; +	betaToken: string; +	devToken: string; +	hypixelApiKey: string; +	wolframAlphaAppId: string; +	imgurClientId: string; +	imgurClientSecret: string; +} + +type Environment = 'production' | 'beta' | 'development'; + +interface Channels { +	log: Snowflake; +	error: Snowflake; +	dm: Snowflake; +} + +interface DataBase { +	host: string; +	port: number; +	username: string; +	password: string; +} + +interface Logging { +	db: boolean; +	verbose: boolean; +	info: boolean; +} + +interface SupportGuild { +	id: Snowflake; +	invite: string; +} diff --git a/src/listeners/client/interactionCreate.ts b/src/listeners/client/interactionCreate.ts index d2e02ab..e2042d0 100644 --- a/src/listeners/client/interactionCreate.ts +++ b/src/listeners/client/interactionCreate.ts @@ -1,5 +1,5 @@  import { BushButtonInteraction, BushListener } from '@lib'; -import { AutoMod } from '../../lib/common/autoMod'; +import { AutoMod } from '../../lib/common/AutoMod';  import { BushClientEvents } from '../../lib/extensions/discord.js/BushClientEvents';  export default class InteractionCreateListener extends BushListener { diff --git a/src/listeners/message/automodCreate.ts b/src/listeners/message/automodCreate.ts index b801356..8857aee 100644 --- a/src/listeners/message/automodCreate.ts +++ b/src/listeners/message/automodCreate.ts @@ -1,5 +1,5 @@  import { BushListener } from '@lib'; -import { AutoMod } from '../../lib/common/autoMod'; +import { AutoMod } from '../../lib/common/AutoMod';  import { BushClientEvents } from '../../lib/extensions/discord.js/BushClientEvents';  export default class AutomodMessageCreateListener extends BushListener { diff --git a/src/listeners/message/automodUpdate.ts b/src/listeners/message/automodUpdate.ts index 08d4911..9464890 100644 --- a/src/listeners/message/automodUpdate.ts +++ b/src/listeners/message/automodUpdate.ts @@ -1,5 +1,5 @@  import { BushListener, BushMessage } from '@lib'; -import { AutoMod } from '../../lib/common/autoMod'; +import { AutoMod } from '../../lib/common/AutoMod';  import { BushClientEvents } from '../../lib/extensions/discord.js/BushClientEvents';  export default class AutomodMessageUpdateListener extends BushListener { | 
