UNPKG

discord-akairo

Version:

A highly customizable bot framework for Discord.js.

833 lines (733 loc) 33.7 kB
const { ArgumentMatches, ArgumentTypes } = require('../../../util/Constants'); const Flag = require('../Flag'); const { choice, intoCallable, isPromise } = require('../../../util/Util'); /** * Represents an argument for a command. * @param {Command} command - Command of the argument. * @param {ArgumentOptions} options - Options for the argument. */ class Argument { constructor(command, { match = ArgumentMatches.PHRASE, type = ArgumentTypes.STRING, flag = null, multipleFlags = false, index = null, unordered = false, limit = Infinity, prompt = null, default: defaultValue = null, otherwise = null, modifyOtherwise = null } = {}) { /** * The command this argument belongs to. * @type {Command} */ this.command = command; /** * The method to match text. * @type {ArgumentMatch} */ this.match = match; /** * The type to cast to or a function to use to cast. * @type {ArgumentType|ArgumentTypeCaster} */ this.type = typeof type === 'function' ? type.bind(this) : type; /** * The string(s) to use for flag or option match. * @type {?string|string[]} */ this.flag = flag; /** * Whether to process multiple option flags instead of just the first. * @type {boolean} */ this.multipleFlags = multipleFlags; /** * The index to start from. * @type {?number} */ this.index = index; /** * Whether or not the argument is unordered. * @type {boolean|number|number[]} */ this.unordered = unordered; /** * The amount of phrases to match for rest, separate, content, or text match. * @type {number} */ this.limit = limit; /** * The prompt options. * @type {?ArgumentPromptOptions} */ this.prompt = prompt; /** * The default value of the argument or a function supplying the default value. * @type {DefaultValueSupplier|any} */ this.default = typeof defaultValue === 'function' ? defaultValue.bind(this) : defaultValue; /** * The content or function supplying the content sent when argument parsing fails. * @type {?StringResolvable|MessageOptions|MessageAdditions|OtherwiseContentSupplier} */ this.otherwise = typeof otherwise === 'function' ? otherwise.bind(this) : otherwise; /** * Function to modify otherwise content. * @type {?OtherwiseContentModifier} */ this.modifyOtherwise = modifyOtherwise; } /** * The client. * @type {AkairoClient} */ get client() { return this.command.client; } /** * The command handler. * @type {CommandHandler} */ get handler() { return this.command.handler; } /** * Processes the type casting and prompting of the argument for a phrase. * @param {Message} message - The message that called the command. * @param {string} phrase - The phrase to process. * @returns {Promise<Flag|any>} */ async process(message, phrase) { const commandDefs = this.command.argumentDefaults; const handlerDefs = this.handler.argumentDefaults; const optional = choice( this.prompt && this.prompt.optional, commandDefs.prompt && commandDefs.prompt.optional, handlerDefs.prompt && handlerDefs.prompt.optional ); const doOtherwise = async failure => { const otherwise = choice( this.otherwise, commandDefs.otherwise, handlerDefs.otherwise ); const modifyOtherwise = choice( this.modifyOtherwise, commandDefs.modifyOtherwise, handlerDefs.modifyOtherwise ); let text = await intoCallable(otherwise).call(this, message, { phrase, failure }); if (Array.isArray(text)) { text = text.join('\n'); } if (modifyOtherwise) { text = await modifyOtherwise.call(this, message, text, { phrase, failure }); if (Array.isArray(text)) { text = text.join('\n'); } } if (text) { const sent = await message.channel.send(text); if (message.util) message.util.addMessage(sent); } return Flag.cancel(); }; if (!phrase && optional) { if (this.otherwise != null) { return doOtherwise(null); } return intoCallable(this.default)(message, { phrase, failure: null }); } const res = await this.cast(message, phrase); if (Argument.isFailure(res)) { if (this.otherwise != null) { return doOtherwise(res); } if (this.prompt != null) { return this.collect(message, phrase, res); } return this.default == null ? res : intoCallable(this.default)(message, { phrase, failure: res }); } return res; } /** * Casts a phrase to this argument's type. * @param {Message} message - Message that called the command. * @param {string} phrase - Phrase to process. * @returns {Promise<any>} */ cast(message, phrase) { return Argument.cast(this.type, this.handler.resolver, message, phrase); } /** * Collects input from the user by prompting. * @param {Message} message - Message to prompt. * @param {string} [commandInput] - Previous input from command if there was one. * @param {any} [parsedInput] - Previous parsed input from command if there was one. * @returns {Promise<Flag|any>} */ async collect(message, commandInput = '', parsedInput = null) { const promptOptions = {}; Object.assign(promptOptions, this.handler.argumentDefaults.prompt); Object.assign(promptOptions, this.command.argumentDefaults.prompt); Object.assign(promptOptions, this.prompt || {}); const isInfinite = promptOptions.infinite || (this.match === ArgumentMatches.SEPARATE && !commandInput); const additionalRetry = Number(Boolean(commandInput)); const values = isInfinite ? [] : null; const getText = async (promptType, prompter, retryCount, inputMessage, inputPhrase, inputParsed) => { let text = await intoCallable(prompter).call(this, message, { retries: retryCount, infinite: isInfinite, message: inputMessage, phrase: inputPhrase, failure: inputParsed }); if (Array.isArray(text)) { text = text.join('\n'); } const modifier = { start: promptOptions.modifyStart, retry: promptOptions.modifyRetry, timeout: promptOptions.modifyTimeout, ended: promptOptions.modifyEnded, cancel: promptOptions.modifyCancel }[promptType]; if (modifier) { text = await modifier.call(this, message, text, { retries: retryCount, infinite: isInfinite, message: inputMessage, phrase: inputPhrase, failure: inputParsed }); if (Array.isArray(text)) { text = text.join('\n'); } } return text; }; // eslint-disable-next-line complexity const promptOne = async (prevMessage, prevInput, prevParsed, retryCount) => { let sentStart; // This is either a retry prompt, the start of a non-infinite, or the start of an infinite. if (retryCount !== 1 || !isInfinite || !values.length) { const promptType = retryCount === 1 ? 'start' : 'retry'; const prompter = retryCount === 1 ? promptOptions.start : promptOptions.retry; const startText = await getText(promptType, prompter, retryCount, prevMessage, prevInput, prevParsed); if (startText) { sentStart = await (message.util || message.channel).send(startText); if (message.util) { message.util.setEditable(false); message.util.setLastResponse(sentStart); message.util.addMessage(sentStart); } } } let input; try { input = (await message.channel.awaitMessages(m => m.author.id === message.author.id, { max: 1, time: promptOptions.time, errors: ['time'] })).first(); if (message.util) message.util.addMessage(input); } catch (err) { const timeoutText = await getText('timeout', promptOptions.timeout, retryCount, prevMessage, prevInput, ''); if (timeoutText) { const sentTimeout = await message.channel.send(timeoutText); if (message.util) message.util.addMessage(sentTimeout); } return Flag.cancel(); } if (promptOptions.breakout) { const looksLike = await this.handler.parseCommand(input); if (looksLike && looksLike.command) return Flag.retry(input); } if (input.content.toLowerCase() === promptOptions.cancelWord.toLowerCase()) { const cancelText = await getText('cancel', promptOptions.cancel, retryCount, input, input.content, 'cancel'); if (cancelText) { const sentCancel = await message.channel.send(cancelText); if (message.util) message.util.addMessage(sentCancel); } return Flag.cancel(); } if (isInfinite && input.content.toLowerCase() === promptOptions.stopWord.toLowerCase()) { if (!values.length) return promptOne(input, input.content, null, retryCount + 1); return values; } const parsedValue = await this.cast(input, input.content); if (Argument.isFailure(parsedValue)) { if (retryCount <= promptOptions.retries) { return promptOne(input, input.content, parsedValue, retryCount + 1); } const endedText = await getText('ended', promptOptions.ended, retryCount, input, input.content, 'stop'); if (endedText) { const sentEnded = await message.channel.send(endedText); if (message.util) message.util.addMessage(sentEnded); } return Flag.cancel(); } if (isInfinite) { values.push(parsedValue); const limit = promptOptions.limit; if (values.length < limit) return promptOne(message, input.content, parsedValue, 1); return values; } return parsedValue; }; this.handler.addPrompt(message.channel, message.author); const returnValue = await promptOne(message, commandInput, parsedInput, 1 + additionalRetry); if (this.handler.commandUtil) { message.util.setEditable(false); } this.handler.removePrompt(message.channel, message.author); return returnValue; } /** * Casts a phrase to the specified type. * @param {ArgumentType|ArgumentTypeCaster} type - Type to use. * @param {TypeResolver} resolver - Type resolver to use. * @param {Message} message - Message that called the command. * @param {string} phrase - Phrase to process. * @returns {Promise<any>} */ static async cast(type, resolver, message, phrase) { if (Array.isArray(type)) { for (const entry of type) { if (Array.isArray(entry)) { if (entry.some(t => t.toLowerCase() === phrase.toLowerCase())) { return entry[0]; } } else if (entry.toLowerCase() === phrase.toLowerCase()) { return entry; } } return null; } if (typeof type === 'function') { let res = type(message, phrase); if (isPromise(res)) res = await res; return res; } if (type instanceof RegExp) { const match = phrase.match(type); if (!match) return null; const matches = []; if (type.global) { let matched; while ((matched = type.exec(phrase)) != null) { matches.push(matched); } } return { match, matches }; } if (resolver.type(type)) { let res = resolver.type(type).call(this, message, phrase); if (isPromise(res)) res = await res; return res; } return phrase || null; } /* eslint-disable no-invalid-this */ /** * Creates a type from multiple types (union type). * The first type that resolves to a non-void value is used. * @param {...ArgumentType|ArgumentTypeCaster} types - Types to use. * @returns {ArgumentTypeCaster} */ static union(...types) { return async function typeFn(message, phrase) { for (let entry of types) { if (typeof entry === 'function') entry = entry.bind(this); const res = await Argument.cast(entry, this.handler.resolver, message, phrase); if (!Argument.isFailure(res)) return res; } return null; }; } /** * Creates a type from multiple types (product type). * Only inputs where each type resolves with a non-void value are valid. * @param {...ArgumentType|ArgumentTypeCaster} types - Types to use. * @returns {ArgumentTypeCaster} */ static product(...types) { return async function typeFn(message, phrase) { const results = []; for (let entry of types) { if (typeof entry === 'function') entry = entry.bind(this); const res = await Argument.cast(entry, this.handler.resolver, message, phrase); if (Argument.isFailure(res)) return res; results.push(res); } return results; }; } /** * Creates a type with extra validation. * If the predicate is not true, the value is considered invalid. * @param {ArgumentType|ArgumentTypeCaster} type - The type to use. * @param {ParsedValuePredicate} predicate - The predicate function. * @returns {ArgumentTypeCaster} */ static validate(type, predicate) { return async function typeFn(message, phrase) { if (typeof type === 'function') type = type.bind(this); const res = await Argument.cast(type, this.handler.resolver, message, phrase); if (Argument.isFailure(res)) return res; if (!predicate.call(this, message, phrase, res)) return null; return res; }; } /** * Creates a type where the parsed value must be within a range. * @param {ArgumentType|ArgumentTypeCaster} type - The type to use. * @param {number} min - Minimum value. * @param {number} max - Maximum value. * @param {boolean} [inclusive=false] - Whether or not to be inclusive on the upper bound. * @returns {ArgumentTypeCaster} */ static range(type, min, max, inclusive = false) { return Argument.validate(type, (msg, p, x) => { /* eslint-disable-next-line valid-typeof */ const o = typeof x === 'number' || typeof x === 'bigint' ? x : x.length != null ? x.length : x.size != null ? x.size : x; return o >= min && (inclusive ? o <= max : o < max); }); } /** * 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 {...ArgumentType|ArgumentTypeCaster} types - Types to use. * @returns {ArgumentTypeCaster} */ static compose(...types) { return async function typeFn(message, phrase) { let acc = phrase; for (let entry of types) { if (typeof entry === 'function') entry = entry.bind(this); acc = await Argument.cast(entry, this.handler.resolver, message, acc); if (Argument.isFailure(acc)) return acc; } return acc; }; } /** * 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 {...ArgumentType|ArgumentTypeCaster} types - Types to use. * @returns {ArgumentTypeCaster} */ static composeWithFailure(...types) { return async function typeFn(message, phrase) { let acc = phrase; for (let entry of types) { if (typeof entry === 'function') entry = entry.bind(this); acc = await Argument.cast(entry, this.handler.resolver, message, acc); } return acc; }; } /** * 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 {ArgumentType|ArgumentTypeCaster} type - The type to use. * @returns {ArgumentTypeCaster} */ static withInput(type) { return async function typeFn(message, phrase) { if (typeof type === 'function') type = type.bind(this); const res = await Argument.cast(type, this.handler.resolver, message, phrase); if (Argument.isFailure(res)) { return Flag.fail({ input: phrase, value: res }); } return { input: phrase, value: res }; }; } /** * 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 {ArgumentType|ArgumentTypeCaster} type - The type to use. * @param {any} [tag] - Tag to add. * Defaults to the `type` argument, so useful if it is a string. * @returns {ArgumentTypeCaster} */ static tagged(type, tag = type) { return async function typeFn(message, phrase) { if (typeof type === 'function') type = type.bind(this); const res = await Argument.cast(type, this.handler.resolver, message, phrase); if (Argument.isFailure(res)) { return Flag.fail({ tag, value: res }); } return { tag, value: res }; }; } /** * 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 {ArgumentType|ArgumentTypeCaster} type - The type to use. * @param {any} [tag] - Tag to add. * Defaults to the `type` argument, so useful if it is a string. * @returns {ArgumentTypeCaster} */ static taggedWithInput(type, tag = type) { return async function typeFn(message, phrase) { if (typeof type === 'function') type = type.bind(this); const res = await Argument.cast(type, this.handler.resolver, message, phrase); if (Argument.isFailure(res)) { return Flag.fail({ tag, input: phrase, value: res }); } return { tag, input: phrase, value: res }; }; } /** * 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 {...ArgumentType|ArgumentTypeCaster} types - Types to use. * @returns {ArgumentTypeCaster} */ static taggedUnion(...types) { return async function typeFn(message, phrase) { for (let entry of types) { entry = Argument.tagged(entry); const res = await Argument.cast(entry, this.handler.resolver, message, phrase); if (!Argument.isFailure(res)) return res; } return null; }; } /* eslint-enable no-invalid-this */ /** * Checks if something is null, undefined, or a fail flag. * @param {any} value - Value to check. * @returns {boolean} */ static isFailure(value) { return value == null || Flag.is(value, 'fail'); } } module.exports = Argument; /** * Options for how an argument parses text. * @typedef {Object} ArgumentOptions * @prop {string} id - ID of the argument for use in the args object. * This does nothing inside an ArgumentGenerator. * @prop {ArgumentMatch} [match='phrase'] - Method to match text. * @prop {ArgumentType|ArgumentTypeCaster} [type='string'] - Type to cast to. * @prop {string|string[]} [flag] - The string(s) to use as the flag for flag or option match. * @prop {boolean} [multipleFlags=false] - Whether or not to have flags process multiple inputs. * For option flags, this works like the separate match; the limit option will also work here. * For flags, this will count the number of occurrences. * @prop {number} [index] - Index of phrase to start from. * Applicable to phrase, text, content, rest, or separate match only. * Ignored when used with the unordered option. * @prop {boolean|number|number[]} [unordered=false] - Marks the argument as unordered. * Each phrase is evaluated in order until one matches (no input at all means no evaluation). * Passing in a number forces evaluation from that index onwards. * Passing in an array of numbers forces evaluation on those indices only. * If there is a match, that index is considered used and future unordered args will not check that index again. * If there is no match, then the prompting or default value is used. * Applicable to phrase match only. * @prop {number} [limit=Infinity] - Amount of phrases to match when matching more than one. * Applicable to text, content, rest, or separate match only. * @prop {DefaultValueSupplier|any} [default=null] - Default value if no input or did not cast correctly. * If using a flag match, setting the default value to a non-void value inverses the result. * @prop {StringResolvable|MessageOptions|MessageAdditions|OtherwiseContentSupplier} [otherwise] - Text sent if argument parsing fails. * This overrides the `default` option and all prompt options. * @prop {OtherwiseContentModifier} [modifyOtherwise] - Function to modify otherwise content. * @prop {ArgumentPromptOptions} [prompt] - Prompt options for when user does not provide input. */ /** * Data passed to argument prompt functions. * @typedef {Object} ArgumentPromptData * @prop {number} retries - Amount of retries so far. * @prop {boolean} infinite - Whether the prompt is infinite or not. * @prop {Message} message - The message that caused the prompt. * @prop {string} phrase - The input phrase that caused the prompt if there was one, otherwise an empty string. * @param {void|Flag} failure - The value that failed if there was one, otherwise null. */ /** * A prompt to run if the user did not input the argument correctly. * Can only be used if there is not a default value (unless optional is true). * @typedef {Object} ArgumentPromptOptions * @prop {number} [retries=1] - Amount of retries allowed. * @prop {number} [time=30000] - Time to wait for input. * @prop {string} [cancelWord='cancel'] - Word to use for cancelling the command. * @prop {string} [stopWord='stop'] - Word to use for ending infinite prompts. * @prop {boolean} [optional=false] - Prompts only when argument is provided but was not of the right type. * @prop {boolean} [infinite=false] - Prompts forever until the stop word, cancel word, time limit, or retry limit. * Note that the retry count resets back to one on each valid entry. * The final evaluated argument will be an array of the inputs. * @prop {number} [limit=Infinity] - Amount of inputs allowed for an infinite prompt before finishing. * @prop {boolean} [breakout=true] - Whenever an input matches the format of a command, this option controls whether or not to cancel this command and run that command. * The command to be run may be the same command or some other command. * @prop {StringResolvable|MessageOptions|MessageAdditions|PromptContentSupplier} [start] - Text sent on start of prompt. * @prop {StringResolvable|MessageOptions|MessageAdditions|PromptContentSupplier} [retry] - Text sent on a retry (failure to cast type). * @prop {StringResolvable|MessageOptions|MessageAdditions|PromptContentSupplier} [timeout] - Text sent on collector time out. * @prop {StringResolvable|MessageOptions|MessageAdditions|PromptContentSupplier} [ended] - Text sent on amount of tries reaching the max. * @prop {StringResolvable|MessageOptions|MessageAdditions|PromptContentSupplier} [cancel] - Text sent on cancellation of command. * @prop {PromptContentModifier} [modifyStart] - Function to modify start prompts. * @prop {PromptContentModifier} [modifyRetry] - Function to modify retry prompts. * @prop {PromptContentModifier} [modifyTimeout] - Function to modify timeout messages. * @prop {PromptContentModifier} [modifyEnded] - Function to modify out of tries messages. * @prop {PromptContentModifier} [modifyCancel] - Function to modify cancel messages. */ /** * The method to match arguments from text. * - `phrase` matches by the order of the phrases inputted. * It ignores phrases that matches a flag. * - `flag` matches phrases that are the same as its flag. * The evaluated argument is either true or false. * - `option` matches phrases that starts with the flag. * The phrase after the flag is the evaluated argument. * - `rest` matches the rest of the phrases. * It ignores phrases that matches a flag. * It preserves the original whitespace between phrases and the quotes around phrases. * - `separate` matches the rest of the phrases and processes each individually. * It ignores phrases that matches a flag. * - `text` matches the entire text, except for the command. * It ignores phrases that matches a flag. * It preserves the original whitespace between phrases and the quotes around phrases. * - `content` matches the entire text as it was inputted, except for the command. * It preserves the original whitespace between phrases and the quotes around phrases. * - `restContent` matches the rest of the text as it was inputted. * It preserves the original whitespace between phrases and the quotes around phrases. * - `none` matches nothing at all and an empty string will be used for type operations. * @typedef {string} ArgumentMatch */ /** * The type that the argument should be cast to. * - `string` does not cast to any type. * - `lowercase` makes the input lowercase. * - `uppercase` makes the input uppercase. * - `charCodes` transforms the input to an array of char codes. * - `number` casts to a number. * - `integer` casts to an integer. * - `bigint` casts to a big integer. * - `url` casts to an `URL` object. * - `date` casts to a `Date` object. * - `color` casts a hex code to an integer. * - `commandAlias` tries to resolve to a command from an alias. * - `command` matches the ID of a command. * - `inhibitor` matches the ID of an inhibitor. * - `listener` matches the ID of a listener. * * Possible Discord-related types. * These types can be plural (add an 's' to the end) and a collection of matching objects will be used. * - `user` tries to resolve to a user. * - `member` tries to resolve to a member. * - `relevant` tries to resolve to a relevant user, works in both guilds and DMs. * - `channel` tries to resolve to a channel. * - `textChannel` tries to resolve to a text channel. * - `voiceChannel` tries to resolve to a voice channel. * - `role` tries to resolve to a role. * - `emoji` tries to resolve to a custom emoji. * - `guild` tries to resolve to a guild. * * Other Discord-related types: * - `message` tries to fetch a message from an ID within the channel. * - `guildMessage` tries to fetch a message from an ID within the guild. * - `relevantMessage` is a combination of the above, works in both guilds and DMs. * - `invite` tries to fetch an invite object from a link. * - `userMention` matches a mention of a user. * - `memberMention` matches a mention of a guild member. * - `channelMention` matches a mention of a channel. * - `roleMention` matches a mention of a role. * - `emojiMention` matches a mention of an emoji. * * An array of strings can be used to restrict input to only those strings, case insensitive. * The array can also contain an inner array of strings, for aliases. * If so, the first entry of the array will be used as the final argument. * * A regular expression can also be used. * The evaluated argument will be an object containing the `match` and `matches` if global. * @typedef {string|string[]} ArgumentType */ /** * A function for processing user input to use as an argument. * A void return value will use the default value for the argument or start a prompt. * Any other truthy return value will be used as the evaluated argument. * If returning a Promise, the resolved value will go through the above steps. * @typedef {Function} ArgumentTypeCaster * @param {Message} message - Message that triggered the command. * @param {string} phrase - The user input. * @returns {any} */ /** * A function for processing some value to use as an argument. * This is mainly used in composing argument types. * @typedef {Function} ArgumentTypeCaster * @param {Message} message - Message that triggered the command. * @param {any} value - Some value. * @returns {any} */ /** * Data passed to functions that run when things failed. * @typedef {Object} FailureData * @prop {string} phrase - The input phrase that failed if there was one, otherwise an empty string. * @param {void|Flag} failure - The value that failed if there was one, otherwise null. */ /** * Defaults for argument options. * @typedef {Object} DefaultArgumentOptions * @prop {ArgumentPromptOptions} [prompt] - Default prompt options. * @prop {StringResolvable|MessageOptions|MessageAdditions|OtherwiseContentSupplier} [otherwise] - Default text sent if argument parsing fails. * @prop {OtherwiseContentModifier} [modifyOtherwise] - Function to modify otherwise content. */ /** * Function get the default value of the argument. * @typedef {Function} DefaultValueSupplier * @param {Message} message - Message that triggered the command. * @param {FailureData} data - Miscellaneous data. * @returns {any} */ /** * A function for validating parsed arguments. * @typedef {Function} ParsedValuePredicate * @param {Message} message - Message that triggered the command. * @param {string} phrase - The user input. * @param {any} value - The parsed value. * @returns {boolean} */ /** * A function modifying a prompt text. * @typedef {Function} OtherwiseContentModifier * @param {Message} message - Message that triggered the command. * @param {string|MessageEmbed|MessageAttachment|MessageAttachment[]|MessageOptions} text - Text to modify. * @param {FailureData} data - Miscellaneous data. * @returns {StringResolvable|MessageOptions|MessageAdditions|Promise<StringResolvable|MessageOptions|MessageAdditions>} */ /** * A function returning the content if argument parsing fails. * @typedef {Function} OtherwiseContentSupplier * @param {Message} message - Message that triggered the command. * @param {FailureData} data - Miscellaneous data. * @returns {StringResolvable|MessageOptions|MessageAdditions|Promise<StringResolvable|MessageOptions|MessageAdditions>} */ /** * A function modifying a prompt text. * @typedef {Function} PromptContentModifier * @param {Message} message - Message that triggered the command. * @param {string|MessageEmbed|MessageAttachment|MessageAttachment[]|MessageOptions} text - Text from the prompt to modify. * @param {ArgumentPromptData} data - Miscellaneous data. * @returns {StringResolvable|MessageOptions|MessageAdditions|Promise<StringResolvable|MessageOptions|MessageAdditions>} */ /** * A function returning text for the prompt. * @typedef {Function} PromptContentSupplier * @param {Message} message - Message that triggered the command. * @param {ArgumentPromptData} data - Miscellaneous data. * @returns {StringResolvable|MessageOptions|MessageAdditions|Promise<StringResolvable|MessageOptions|MessageAdditions>} */