diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/commands/config/features.ts | 105 | ||||
-rw-r--r-- | src/lib/extensions/discord-akairo/BushClientUtil.ts | 93 | ||||
-rw-r--r-- | src/lib/extensions/discord.js/BushGuild.ts | 16 | ||||
-rw-r--r-- | src/lib/utils/BushConstants.ts | 2 | ||||
-rw-r--r-- | src/listeners/client/interactionCreate.ts | 1 | ||||
-rw-r--r-- | src/listeners/commands/commandError.ts | 2 |
6 files changed, 125 insertions, 94 deletions
diff --git a/src/commands/config/features.ts b/src/commands/config/features.ts index cb7f4bc..a694483 100644 --- a/src/commands/config/features.ts +++ b/src/commands/config/features.ts @@ -1,44 +1,65 @@ -// import { BushCommand, BushMessage, BushSlashMessage, guildFeatures } from '@lib'; -// import { MessageEmbed } from 'discord.js'; +import { BushCommand, BushMessage, BushSlashMessage, GuildFeatures, guildFeatures } from '@lib'; +import { Message, MessageActionRow, MessageEmbed, MessageSelectMenu, SelectMenuInteraction } from 'discord.js'; -// export default class FeaturesCommand extends BushCommand { -// public constructor() { -// super('features', { -// aliases: ['features'], -// category: 'config', -// description: { -// content: 'Toggle features the server.', -// usage: 'features', -// examples: ['features'] -// }, -// slash: true, -// channel: 'guild', -// clientPermissions: ['SEND_MESSAGES', 'EMBED_LINKS'], -// userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD'], -// ownerOnly: true -// }); -// } -// public override async exec(message: BushMessage | BushSlashMessage): Promise<unknown> { -// if (!message.guild) return await message.util.reply(`${util.emojis.error} This command can only be used in servers.`); -// const featureEmbed = await this.generateEmbed(message); -// return await message.util.reply({ embeds: [featureEmbed] }); -// } +//todo: fix this so that it doesn't just select one feature but instead toggles it +export default class FeaturesCommand extends BushCommand { + public constructor() { + super('features', { + aliases: ['features'], + category: 'config', + description: { + content: 'Toggle features the server.', + usage: 'features', + examples: ['features'] + }, + slash: true, + channel: 'guild', + clientPermissions: ['SEND_MESSAGES', 'EMBED_LINKS'], + userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD'], + ownerOnly: true + }); + } + public override async exec(message: BushMessage | BushSlashMessage): Promise<unknown> { + if (!message.guild) return await message.util.reply(`${util.emojis.error} This command can only be used in servers.`); + const featureEmbed = new MessageEmbed().setTitle(`${message.guild!.name}'s Features`).setColor(util.colors.default); -// public async handleInteraction(): Promise<void> { - -// } - -// public async generateEmbed(message: BushMessage | BushSlashMessage): Promise<MessageEmbed> { -// const featureEmbed = new MessageEmbed().setTitle(`${message.guild!.name}'s Features`).setColor(util.colors.default); -// const featureList: string[] = []; -// const enabledFeatures = await message.guild!.getSetting('enabledFeatures'); -// guildFeatures.forEach((feature) => { -// featureList.push(`**${feature}:** ${enabledFeatures.includes(feature) ? util.emojis.check : util.emojis.cross}`); -// }); -// return featureEmbed.setDescription(featureList.join('\n')); -// } - -// public async generateButtons(): Promise<void>{ - -// } -// } + const enabledFeatures = await message.guild!.getSetting('enabledFeatures'); + let featureList = guildFeatures.map( + (feature) => `**${feature}:** ${enabledFeatures.includes(feature) ? util.emojis.check : util.emojis.cross}` + ); + featureEmbed.setDescription(featureList.join('\n')); + const components = new MessageActionRow().addComponents( + new MessageSelectMenu() + .addOptions(...guildFeatures.map((f) => ({ label: f, value: f }))) + .setPlaceholder('Select A Feature to Toggle') + .setMaxValues(1) + .setMinValues(1) + .setCustomId('featureCommand_selectFeature') + ); + const x = (await message.util.reply({ embeds: [featureEmbed], components: [components] })) as Message; + const collector = x.createMessageComponentCollector({ + channel: message.channel ?? undefined, + guild: message.guild, + componentType: 'SELECT_MENU', + message: message as Message, + time: 300_000 + }); + collector.on('collect', async (interaction: SelectMenuInteraction) => { + if (interaction.user.id == message.author.id || client.config.owners.includes(interaction.user.id)) { + if (!message.guild) throw new Error('message.guild is null'); + const [selected] = interaction.values; + if (!guildFeatures.includes(selected)) throw new Error('Invalid guild feature selected'); + await message.guild.toggleFeature(selected as GuildFeatures); + const enabledFeatures = await message.guild!.getSetting('enabledFeatures'); + featureList = guildFeatures.map( + (feature) => `**${feature}:** ${enabledFeatures.includes(feature) ? util.emojis.check : util.emojis.cross}` + ); + featureEmbed.setDescription(featureList.join('\n')); + await interaction.update({ embeds: [featureEmbed] }).catch(() => undefined); + return; + } else { + return await interaction?.deferUpdate().catch(() => undefined); + } + }); + } +} diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts index 3c3a9c4..26f890a 100644 --- a/src/lib/extensions/discord-akairo/BushClientUtil.ts +++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts @@ -81,6 +81,7 @@ interface MojangProfile { uuid: string; } +// #region codeblock type export type CodeBlockLang = | '1c' | 'abnf' @@ -391,6 +392,7 @@ export type CodeBlockLang = | 'yaml' | 'zephir' | 'zep'; +//#endregion /** * {@link https://nodejs.org/api/util.html#util_util_inspect_object_options} @@ -825,7 +827,6 @@ export class BushClientUtil extends ClientUtil { /** * 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 */ @@ -944,7 +945,7 @@ export class BushClientUtil extends ClientUtil { newResponseOptions = responseOptions; } if (interaction.replied || interaction.deferred) { - //@ts-expect-error: stop being dumb + // @ts-expect-error: stop being dumb delete newResponseOptions.ephemeral; // Cannot change a preexisting message to be ephemeral return (await interaction.editReply(newResponseOptions)) as Message | APIMessage; } else { @@ -962,10 +963,9 @@ export class BushClientUtil extends ClientUtil { /** * Takes an array and combines the elements using the supplied conjunction. - * - * @param {string[]} array The array to combine. - * @param {string} conjunction The conjunction to use. - * @param {string} ifEmpty What to return if the array is empty. + * @param array The array to combine. + * @param conjunction The conjunction to use. + * @param ifEmpty What to return if the array is empty. * @returns The combined elements or `ifEmpty` * * @example @@ -996,26 +996,20 @@ export class BushClientUtil extends ClientUtil { return await row.save().catch((e) => client.logger.error('insertOrRemoveFromGlobal', e?.stack || e)); } - public addOrRemoveFromArray(action: 'add' | 'remove', _array: any[], value: any): any[] { - const array = new Array(..._array); // prevent modifying the original array - let newValue: any[]; - if (!array) throw new Error('array is either null or undefined'); - if (action === 'add') { - if (!array.includes(action)) array.push(value); - newValue = array; - } else { - newValue = Array.from(array).filter((ae) => ae !== value); - } - return newValue; + /** + * Add or remove an item from an array. All duplicates will be removed. + */ + public addOrRemoveFromArray(action: 'add' | 'remove', array: any[], value: any): any[] { + const set = new Set(...array); + action === 'add' ? set.add(value) : set.delete(value); + return [...set]; } /** * Surrounds a string to the begging an end of each element in an array. - * - * @param {string[]} array The array you want to surround. - * @param {string} surroundChar1 The character placed in the beginning of the element (or end if surroundChar2 isn't supplied). - * @param {string} [surroundChar2=surroundChar1] The character placed in the end of the element. - * @returns {string[]} + * @param array - The array you want to surround. + * @param surroundChar1 - The character placed in the beginning of the element. + * @param surroundChar2 - The character placed in the end of the element. Defaults to `surroundChar1`. */ public surroundArray(array: string[], surroundChar1: string, surroundChar2?: string): string[] { const newArray: string[] = []; @@ -1036,8 +1030,7 @@ export class BushClientUtil extends ClientUtil { for (const unit in BushConstants.TimeUnits) { const regex = BushConstants.TimeUnits[unit].match; const match = regex.exec(contentWithoutTime); - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const value = Number(match?.groups?.[unit] || 0); + const value = Number(match?.groups?.[unit] ?? 0); duration += value * BushConstants.TimeUnits[unit].value; if (remove) contentWithoutTime = contentWithoutTime.replace(regex, ''); @@ -1365,32 +1358,32 @@ export class BushClientUtil extends ClientUtil { return new Promise((resolve) => setTimeout(resolve, s * 1000)); } - // modified from https://stackoverflow.com/questions/31054910/get-functions-methods-of-a-class - // answer by Bruno Grieder - // public getMethods(obj: any): string { - // let props = []; - - // do { - // const l = Object.getOwnPropertyNames(obj) - // .concat(Object.getOwnPropertySymbols(obj).map((s) => s.toString())) - // .sort() - // .filter( - // (p, i, arr) => - // typeof obj[p] === 'function' && //only the methods - // p !== 'constructor' && //not the constructor - // (i == 0 || p !== arr[i - 1]) && //not overriding in this prototype - // props.indexOf(p) === -1 //not overridden in a child - // ); - // props = props.concat( - // l /* .map((p) => (obj[p] && obj[p][Symbol.toStringTag] === 'AsyncFunction' ? 'async ' : '' + p + '();')) */ - // ); - // } while ( - // (obj = Object.getPrototypeOf(obj)) && //walk-up the prototype chain - // Object.getPrototypeOf(obj) //not the the Object prototype methods (hasOwnProperty, etc...) - // ); - - // return props.join('\n'); - // } + //~ modified from https://stackoverflow.com/questions/31054910/get-functions-methods-of-a-class + //~ answer by Bruno Grieder + //~ public getMethods(obj: any): string { + //~ let props = []; + + //~ do { + //~ const l = Object.getOwnPropertyNames(obj) + //~ .concat(Object.getOwnPropertySymbols(obj).map((s) => s.toString())) + //~ .sort() + //~ .filter( + //~ (p, i, arr) => + //~ typeof obj[p] === 'function' && //only the methods + //~ p !== 'constructor' && //not the constructor + //~ (i == 0 || p !== arr[i - 1]) && //not overriding in this prototype + //~ props.indexOf(p) === -1 //not overridden in a child + //~ ); + //~ props = props.concat( + //~ l /* .map((p) => (obj[p] && obj[p][Symbol.toStringTag] === 'AsyncFunction' ? 'async ' : '' + p + '();')) */ + //~ ); + //~ } while ( + //~ (obj = Object.getPrototypeOf(obj)) && //walk-up the prototype chain + //~ Object.getPrototypeOf(obj) //not the the Object prototype methods (hasOwnProperty, etc...) + //~ ); + + //~ return props.join('\n'); + //~ } /** * Discord.js's Util class diff --git a/src/lib/extensions/discord.js/BushGuild.ts b/src/lib/extensions/discord.js/BushGuild.ts index 908fcec..4fc27a7 100644 --- a/src/lib/extensions/discord.js/BushGuild.ts +++ b/src/lib/extensions/discord.js/BushGuild.ts @@ -20,6 +20,22 @@ export class BushGuild extends Guild { return features.includes(feature); } + public async addFeature(feature: GuildFeatures): Promise<GuildModel['enabledFeatures']> { + const features = await this.getSetting('enabledFeatures'); + const newFeatures = util.addOrRemoveFromArray('add', features, feature); + return (await this.setSetting('enabledFeatures', newFeatures)).enabledFeatures; + } + + public async removeFeature(feature: GuildFeatures): Promise<GuildModel['enabledFeatures']> { + const features = await this.getSetting('enabledFeatures'); + const newFeatures = util.addOrRemoveFromArray('remove', features, feature); + return (await this.setSetting('enabledFeatures', newFeatures)).enabledFeatures; + } + + public async toggleFeature(feature: GuildFeatures): Promise<GuildModel['enabledFeatures']> { + return (await this.hasFeature(feature)) ? await this.removeFeature(feature) : await this.addFeature(feature); + } + public async getSetting<K extends keyof GuildModel>(setting: K): Promise<GuildModel[K]> { // client.console.debug(`getSetting: ${setting}`); return ( diff --git a/src/lib/utils/BushConstants.ts b/src/lib/utils/BushConstants.ts index bcc34ae..dcc551e 100644 --- a/src/lib/utils/BushConstants.ts +++ b/src/lib/utils/BushConstants.ts @@ -39,7 +39,7 @@ export class BushConstants { idleCircle: '<:idle:787550520956551218>', onlineCircle: '<:online:787550449435803658>', cross: '<:cross:878319362539421777>', - check: '' + check: '<:check:878320135297961995>' }; public static colors: bushColors = { diff --git a/src/listeners/client/interactionCreate.ts b/src/listeners/client/interactionCreate.ts index d76a484..b4cc353 100644 --- a/src/listeners/client/interactionCreate.ts +++ b/src/listeners/client/interactionCreate.ts @@ -28,6 +28,7 @@ export default class InteractionCreateListener extends BushListener { if (interaction.customId.startsWith('paginate_')) return; return await interaction.reply({ content: 'Buttons go brrr', ephemeral: true }); } else if (interaction.isSelectMenu()) { + if (interaction.customId.startsWith('featureCommand_')) return; return await interaction.reply({ content: `You selected ${ Array.isArray(interaction.values) diff --git a/src/listeners/commands/commandError.ts b/src/listeners/commands/commandError.ts index 029394d..f43e17d 100644 --- a/src/listeners/commands/commandError.ts +++ b/src/listeners/commands/commandError.ts @@ -96,7 +96,7 @@ export default class CommandErrorListener extends BushListener { } const inspectOptions = { showHidden: false, - depth: 4, + depth: 5, colors: false, customInspect: true, showProxy: false, |