@sapphire/discord.js-utilities
Advanced tools
Comparing version 1.6.0 to 2.0.0-pr-134.0505c63c.0
export * from '@sapphire/discord-utilities'; | ||
export * from './lib/builders/MessageBuilder'; | ||
export * from './lib/LazyPaginatedMessage'; | ||
export * from './lib/MessagePrompter'; | ||
export * from './lib/PaginatedMessage'; | ||
export * from './lib/PaginatedMessages'; | ||
export * from './lib/type-guards'; | ||
//# sourceMappingURL=index.d.ts.map |
1195
dist/index.js
@@ -6,3 +6,5 @@ 'use strict'; | ||
var discordUtilities = require('@sapphire/discord-utilities'); | ||
var utilities = require('@sapphire/utilities'); | ||
var discord_js = require('discord.js'); | ||
require('@sapphire/time-utilities'); | ||
@@ -199,3 +201,456 @@ /** | ||
exports.MessagePrompterStrategies = void 0; | ||
(function (MessagePrompterStrategies) { | ||
MessagePrompterStrategies["Confirm"] = "confirm"; | ||
MessagePrompterStrategies["Number"] = "number"; | ||
MessagePrompterStrategies["Message"] = "message"; | ||
MessagePrompterStrategies["Reaction"] = "reaction"; | ||
})(exports.MessagePrompterStrategies || (exports.MessagePrompterStrategies = {})); | ||
class MessagePrompterBaseStrategy { | ||
/** | ||
* Constructor for the {@link MessagePrompterBaseStrategy} class | ||
* @param messagePrompter The used instance of {@link MessagePrompter} | ||
* @param options Overrideable options if needed. | ||
*/ | ||
constructor(type, message, options) { | ||
var _a, _b; | ||
/** | ||
* The type of strategy that was used | ||
*/ | ||
Object.defineProperty(this, "type", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
/** | ||
* The timeout that was used in the collector | ||
*/ | ||
Object.defineProperty(this, "timeout", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
/** | ||
* Wether to return an explicit object with data, or the strategies' default | ||
*/ | ||
Object.defineProperty(this, "explicitReturn", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
/** | ||
* The message that has been sent in {@link MessagePrompter.run} | ||
*/ | ||
Object.defineProperty(this, "appliedMessage", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: null | ||
}); | ||
/** | ||
* The message that will be sent in {@link MessagePrompter.run} | ||
*/ | ||
Object.defineProperty(this, "message", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
this.type = type; | ||
this.timeout = (_a = options === null || options === void 0 ? void 0 : options.timeout) !== null && _a !== void 0 ? _a : MessagePrompterBaseStrategy.defaultStrategyOptions.timeout; | ||
this.explicitReturn = (_b = options === null || options === void 0 ? void 0 : options.explicitReturn) !== null && _b !== void 0 ? _b : MessagePrompterBaseStrategy.defaultStrategyOptions.explicitReturn; | ||
this.message = message; | ||
} | ||
async collectReactions(channel, authorOrFilter, reactions) { | ||
this.appliedMessage = await channel.send(this.message); | ||
const collector = this.appliedMessage.createReactionCollector(this.createReactionPromptFilter(reactions, authorOrFilter), { | ||
max: 1, | ||
time: this.timeout | ||
}); | ||
let resolved = false; | ||
const collected = new Promise((resolve, reject) => { | ||
collector.on('collect', (r) => { | ||
resolve(r); | ||
resolved = true; | ||
collector.stop(); | ||
}); | ||
collector.on('end', (collected) => { | ||
resolved = true; | ||
if (!collected.size) | ||
reject(new Error('Collector has ended')); | ||
}); | ||
}); | ||
for (const reaction of reactions) { | ||
if (resolved) | ||
break; | ||
await this.appliedMessage.react(reaction); | ||
} | ||
const firstReaction = await collected; | ||
const emoji = firstReaction === null || firstReaction === void 0 ? void 0 : firstReaction.emoji; | ||
const reaction = reactions.find((r) => { var _a; return ((_a = emoji === null || emoji === void 0 ? void 0 : emoji.id) !== null && _a !== void 0 ? _a : emoji === null || emoji === void 0 ? void 0 : emoji.name) === r; }); | ||
return { | ||
emoji, | ||
reaction, | ||
strategy: this, | ||
appliedMessage: this.appliedMessage, | ||
message: this.message | ||
}; | ||
} | ||
/** | ||
* Creates a filter for the collector to filter on | ||
* @return The filter for awaitReactions function | ||
*/ | ||
createReactionPromptFilter(reactions, authorOrFilter) { | ||
return async (reaction, user) => { | ||
var _a; | ||
return reactions.includes((_a = reaction.emoji.id) !== null && _a !== void 0 ? _a : reaction.emoji.name) && | ||
(typeof authorOrFilter === 'function' ? await authorOrFilter(reaction, user) : user.id === authorOrFilter.id) && | ||
!user.bot; | ||
}; | ||
} | ||
} | ||
/** | ||
* The default strategy options | ||
*/ | ||
Object.defineProperty(MessagePrompterBaseStrategy, "defaultStrategyOptions", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: { | ||
timeout: 10 * 1000, | ||
explicitReturn: false | ||
} | ||
}); | ||
class MessagePrompterConfirmStrategy extends MessagePrompterBaseStrategy { | ||
/** | ||
* Constructor for the {@link MessagePrompterBaseStrategy} class | ||
* @param message The message to be sent {@link MessagePrompter} | ||
* @param options Overrideable options if needed. | ||
*/ | ||
constructor(message, options) { | ||
var _a, _b; | ||
super('confirm', message, options); | ||
/** | ||
* The confirm emoji used | ||
*/ | ||
Object.defineProperty(this, "confirmEmoji", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
/** | ||
* The cancel emoji used | ||
*/ | ||
Object.defineProperty(this, "cancelEmoji", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
this.confirmEmoji = (_a = options === null || options === void 0 ? void 0 : options.confirmEmoji) !== null && _a !== void 0 ? _a : MessagePrompterConfirmStrategy.confirmEmoji; | ||
this.cancelEmoji = (_b = options === null || options === void 0 ? void 0 : options.cancelEmoji) !== null && _b !== void 0 ? _b : MessagePrompterConfirmStrategy.cancelEmoji; | ||
} | ||
/** | ||
* This executes the {@link MessagePrompter} and sends the message if {@link IMessagePrompterOptions.type} equals confirm. | ||
* The handler will wait for one (1) reaction. | ||
* @param channel The channel to use. | ||
* @param authorOrFilter An author object to validate or a {@linkplain https://discord.js.org/#/docs/main/stable/typedef/CollectorFilter CollectorFilter} predicate callback. | ||
* @returns A promise that resolves to a boolean denoting the value of the input (`true` for yes, `false` for no). | ||
*/ | ||
async run(channel, authorOrFilter) { | ||
var _a, _b, _c; | ||
const response = await this.collectReactions(channel, authorOrFilter, [this.confirmEmoji, this.cancelEmoji]); | ||
const confirmed = ((_b = (_a = response === null || response === void 0 ? void 0 : response.emoji) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : (_c = response === null || response === void 0 ? void 0 : response.emoji) === null || _c === void 0 ? void 0 : _c.name) === this.confirmEmoji; | ||
// prettier-ignore | ||
return this.explicitReturn ? { ...response, confirmed } : confirmed; | ||
} | ||
} | ||
/** | ||
* The default confirm emoji used for {@link MessagePrompterConfirmStrategy} | ||
*/ | ||
Object.defineProperty(MessagePrompterConfirmStrategy, "confirmEmoji", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: '๐พ' | ||
}); | ||
/** | ||
* The default cancel emoji used for {@link MessagePrompterConfirmStrategy} | ||
*/ | ||
Object.defineProperty(MessagePrompterConfirmStrategy, "cancelEmoji", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: '๐ณ' | ||
}); | ||
class MessagePrompterMessageStrategy extends MessagePrompterBaseStrategy { | ||
/** | ||
* Constructor for the {@link MessagePrompterBaseStrategy} class | ||
* @param messagePrompter The used instance of {@link MessagePrompter} | ||
* @param options Overrideable options if needed. | ||
*/ | ||
constructor(message, options) { | ||
super('message', message, options); | ||
} | ||
/** | ||
* This executes the {@link MessagePrompter} and sends the message if {@link IMessagePrompterOptions.type} equals message. | ||
* The handler will wait for one (1) message. | ||
* @param channel The channel to use. | ||
* @param authorOrFilter An author object to validate or a {@linkplain https://discord.js.org/#/docs/main/stable/typedef/CollectorFilter CollectorFilter} predicate callback. | ||
* @returns A promise that resolves to the message object received. | ||
*/ | ||
async run(channel, authorOrFilter) { | ||
this.appliedMessage = await channel.send(this.message); | ||
const collector = await channel.awaitMessages(this.createMessagePromptFilter(authorOrFilter), { | ||
max: 1, | ||
time: this.timeout, | ||
errors: ['time'] | ||
}); | ||
const response = collector.first(); | ||
if (!response) { | ||
throw new Error('No messages received'); | ||
} | ||
return this.explicitReturn | ||
? { | ||
response, | ||
strategy: this, | ||
appliedMessage: this.appliedMessage, | ||
message: this.message | ||
} | ||
: response; | ||
} | ||
/** | ||
* Creates a filter for the collector to filter on | ||
* @return The filter for awaitMessages function | ||
*/ | ||
createMessagePromptFilter(authorOrFilter) { | ||
return async (message) => (typeof authorOrFilter === 'function' ? await authorOrFilter(message) : message.author.id === authorOrFilter.id) && !message.author.bot; | ||
} | ||
} | ||
class MessagePrompterNumberStrategy extends MessagePrompterBaseStrategy { | ||
/** | ||
* Constructor for the {@link MessagePrompterBaseStrategy} class | ||
* @param messagePrompter The used instance of {@link MessagePrompter} | ||
* @param options Overrideable options if needed. | ||
*/ | ||
constructor(message, options) { | ||
var _a, _b, _c; | ||
super('number', message, options); | ||
/** | ||
* The available number emojis | ||
*/ | ||
Object.defineProperty(this, "numberEmojis", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
/** | ||
* The available number emojis | ||
*/ | ||
Object.defineProperty(this, "start", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
/** | ||
* The available number emojis | ||
*/ | ||
Object.defineProperty(this, "end", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
this.numberEmojis = (_a = options === null || options === void 0 ? void 0 : options.numberEmojis) !== null && _a !== void 0 ? _a : MessagePrompterNumberStrategy.numberEmojis; | ||
this.start = (_b = options === null || options === void 0 ? void 0 : options.start) !== null && _b !== void 0 ? _b : 0; | ||
this.end = (_c = options === null || options === void 0 ? void 0 : options.end) !== null && _c !== void 0 ? _c : 10; | ||
} | ||
/** | ||
* This executes the {@link MessagePrompter} and sends the message if {@link IMessagePrompterOptions.type} equals number. | ||
* The handler will wait for one (1) reaction. | ||
* @param channel The channel to use. | ||
* @param authorOrFilter An author object to validate or a {@linkplain https://discord.js.org/#/docs/main/stable/typedef/CollectorFilter CollectorFilter} predicate callback. | ||
* @returns A promise that resolves to the selected number within the range. | ||
*/ | ||
async run(channel, authorOrFilter) { | ||
// 0 and 10 are the maximum available emojis as a number | ||
if (this.start < 0) | ||
throw new TypeError('Starting number cannot be less than 0.'); | ||
if (this.end > 10) | ||
throw new TypeError('Ending number cannot be more than 10.'); | ||
const numbers = Array.from({ length: this.end - this.start + 1 }, (_, n) => n + this.start); | ||
const emojis = this.numberEmojis.slice(this.start, this.end); | ||
const response = await this.collectReactions(channel, authorOrFilter, emojis); | ||
const emojiIndex = emojis.findIndex((emoji) => { var _a, _b, _c; return ((_b = (_a = response === null || response === void 0 ? void 0 : response.emoji) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : (_c = response === null || response === void 0 ? void 0 : response.emoji) === null || _c === void 0 ? void 0 : _c.name) === emoji; }); | ||
const number = numbers[emojiIndex]; | ||
// prettier-ignore | ||
return this.explicitReturn ? { ...response, number } : number; | ||
} | ||
} | ||
/** | ||
* The default available number emojis | ||
*/ | ||
Object.defineProperty(MessagePrompterNumberStrategy, "numberEmojis", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: ['0๏ธโฃ', '1๏ธโฃ', '2๏ธโฃ', '3๏ธโฃ', '4๏ธโฃ', '5๏ธโฃ', '6๏ธโฃ', '7๏ธโฃ', '8๏ธโฃ', '9๏ธโฃ', '๐'] | ||
}); | ||
class MessagePrompterReactionStrategy extends MessagePrompterBaseStrategy { | ||
/** | ||
* Constructor for the {@link MessagePrompterReactionStrategy} class | ||
* @param messagePrompter The used instance of {@link MessagePrompter} | ||
* @param options Overrideable options if needed. | ||
*/ | ||
constructor(message, options) { | ||
super('reactions', message, options); | ||
/** | ||
* The emojis used | ||
*/ | ||
Object.defineProperty(this, "reactions", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
this.reactions = options === null || options === void 0 ? void 0 : options.reactions; | ||
} | ||
/** | ||
* This executes the {@link MessagePrompterReactionStrategy} and sends the message. | ||
* The handler will wait for one (1) reaction. | ||
* @param channel The channel to use. | ||
* @param authorOrFilter An author object to validate or a {@linkplain https://discord.js.org/#/docs/main/stable/typedef/CollectorFilter CollectorFilter} predicate callback. | ||
* @returns A promise that resolves to the reaction object. | ||
*/ | ||
async run(channel, authorOrFilter) { | ||
var _a, _b; | ||
if (!((_a = this.reactions) === null || _a === void 0 ? void 0 : _a.length)) | ||
throw new TypeError('There are no reactions provided.'); | ||
const response = await this.collectReactions(channel, authorOrFilter, this.reactions); | ||
return this.explicitReturn ? response : (_b = response.reaction) !== null && _b !== void 0 ? _b : response; | ||
} | ||
} | ||
/** | ||
* This is a {@link MessagePrompter}, a utility that sends a message, prompting for user input. The prompt can resolve to any kind of input. | ||
* There are several specifiable types to prompt for user input, and they are as follows: | ||
* - Confirm | ||
* This will send a simple Yes/No prompt, using reactions. | ||
* - Number | ||
* This will prompt for an integer. By default it will be a number between 0 and 10 (inclusive), however you can also specify your own custom range (inclusive). | ||
* - Reactions | ||
* This can be any kind of reaction emoji that Discord supports, and as many as you want. This type will return that reaction instead of a boolean. | ||
* - Message | ||
* This will prompt the user and require a response in the form of a message. This can be helpful if you require a user to upload an image for example, or give text input. | ||
* | ||
* You must either use this class directly or extend it. | ||
* | ||
* {@link MessagePrompter} uses reactions to prompt for a yes/no answer and returns it. | ||
* You can modify the confirm and cancel reaction used for each message, or use the {@link MessagePrompter.defaultPrompts}. | ||
* {@link MessagePrompter.defaultPrompts} is also static so you can modify these directly. | ||
* | ||
* @example | ||
* ```typescript | ||
* const handler = new MessagePrompter('Are you sure you want to continue?'); | ||
* const result = await handler.run(channel, author); | ||
* ``` | ||
* | ||
* @example | ||
* ```typescript | ||
* const handler = new MessagePrompter('Choose a number between 5 and 10?', 'number', { | ||
* start: 5, | ||
* end: 10 | ||
* }); | ||
* const result = await handler.run(channel, author); | ||
* ``` | ||
* | ||
* @example | ||
* ```typescript | ||
* const handler = new MessagePrompter('Are you happy or sad?', 'reaction', { | ||
* reactions: ['๐', '๐'] | ||
* }); | ||
* const result = await handler.run(channel, author); | ||
* ``` | ||
* | ||
* @example | ||
* ```typescript | ||
* const handler = new MessagePrompter('Do you love me?', 'message'); | ||
* const result = await handler.run(channel, author); | ||
* ``` | ||
*/ | ||
class MessagePrompter { | ||
/** | ||
* Constructor for the {@link MessagePrompter} class | ||
* @param message The message to send. | ||
* @param strategy The strategy name or Instance to use | ||
* @param strategyOptions The options that are passed to the strategy | ||
*/ | ||
constructor(message, strategy, strategyOptions) { | ||
/** | ||
* The strategy used in {@link MessagePrompter.run} | ||
*/ | ||
Object.defineProperty(this, "strategy", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
let strategyToRun = undefined; | ||
if (message instanceof MessagePrompterBaseStrategy) { | ||
strategyToRun = message; | ||
} | ||
else { | ||
const mapStrategy = MessagePrompter.strategies.get(strategy !== null && strategy !== void 0 ? strategy : MessagePrompter.defaultStrategy); | ||
if (!mapStrategy) { | ||
throw new Error('No strategy provided'); | ||
} | ||
strategyToRun = new mapStrategy(message, strategyOptions); | ||
} | ||
this.strategy = strategyToRun; | ||
} | ||
/** | ||
* This executes the {@link MessagePrompter} and sends the message. | ||
* @param channel The channel to use. | ||
* @param authorOrFilter An author object to validate or a {@linkplain https://discord.js.org/#/docs/main/stable/typedef/CollectorFilter CollectorFilter} predicate callback. | ||
*/ | ||
run(channel, authorOrFilter) { | ||
return this.strategy.run(channel, authorOrFilter); | ||
} | ||
} | ||
/** | ||
* The available strategies | ||
*/ | ||
Object.defineProperty(MessagePrompter, "strategies", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: new Map([ | ||
["confirm" /* Confirm */, MessagePrompterConfirmStrategy], | ||
["number" /* Number */, MessagePrompterNumberStrategy], | ||
["reaction" /* Reaction */, MessagePrompterReactionStrategy], | ||
["message" /* Message */, MessagePrompterMessageStrategy] | ||
]) | ||
}); | ||
/** | ||
* The default strategy to use | ||
*/ | ||
Object.defineProperty(MessagePrompter, "defaultStrategy", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: "confirm" /* Confirm */ | ||
}); | ||
/** | ||
* Checks whether a given channel is a {@linkplain https://discord.js.org/#/docs/main/stable/class/CategoryChannel CategoryChannel} | ||
@@ -302,3 +757,3 @@ * @param channel The channel to check | ||
*/ | ||
constructor({ pages, actions } = {}) { | ||
constructor({ pages, actions, template } = {}) { | ||
/** | ||
@@ -359,3 +814,3 @@ * The pages to be converted to {@link PaginatedMessage.messages} | ||
/** | ||
* The amount of time to idle before the paginator is closed. Defaults to `20 * 1000`. | ||
* The amount of milliseconds to idle before the paginator is closed. Defaults to 20 minutes. | ||
*/ | ||
@@ -366,4 +821,14 @@ Object.defineProperty(this, "idle", { | ||
writable: true, | ||
value: 20 * 1000 | ||
value: 60000 /* Minute */ * 20 | ||
}); | ||
/** | ||
* The template for this {@link PaginatedMessage}. | ||
* You can use templates to set defaults that will apply to each and every page in the {@link PaginatedMessage} | ||
*/ | ||
Object.defineProperty(this, "template", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
this.pages = pages !== null && pages !== void 0 ? pages : []; | ||
@@ -374,2 +839,3 @@ for (const page of this.pages) | ||
this.actions.set(action.id, action); | ||
this.template = PaginatedMessage.resolveTemplate(template); | ||
} | ||
@@ -440,2 +906,8 @@ setPromptMessage(message) { | ||
* Adds a page to the existing ones. This will be added as the last page. | ||
* @remark While you can use this method you should first check out | ||
* {@link PaginatedMessage.addPageBuilder}, | ||
* {@link PaginatedMessage.addPageContent} and | ||
* {@link PaginatedMessage.addPageEmbed} as | ||
* these are easier functional methods of adding pages and will likely already suffice for your needs. | ||
* | ||
* @param page The page to add. | ||
@@ -449,2 +921,133 @@ */ | ||
/** | ||
* Adds a page to the existing ones using a {@link MessageBuilder}. This will be added as the last page. | ||
* @param builder Either a callback whose first parameter is `new MessageBuilder()`, or an already constructed {@link MessageBuilder} | ||
* @example | ||
* ```typescript | ||
* const { PaginatedMessage } = require('@sapphire/discord.js-utilities'); | ||
* const { MessageEmbed } = require('discord.js'); | ||
* | ||
* const paginatedMessage = new PaginatedMessage() | ||
* .addPageBuilder((builder) => { | ||
* const embed = new MessageEmbed() | ||
* .setColor('#FF0000') | ||
* .setDescription('example description'); | ||
* | ||
* return builder | ||
* .setContent('example content') | ||
* .setEmbed(embed); | ||
* }); | ||
* ``` | ||
* @example | ||
* ```typescript | ||
* const { MessageEmbed } = require('discord.js'); | ||
* const { MessageBuilder, PaginatedMessage } = require('@sapphire/discord.js-utilities'); | ||
* | ||
* const embed = new MessageEmbed() | ||
* .setColor('#FF0000') | ||
* .setDescription('example description'); | ||
* | ||
* const builder = new MessageBuilder() | ||
* .setContent('example content') | ||
* .setEmbed(embed); | ||
* | ||
* const paginatedMessage = new PaginatedMessage() | ||
* .addPageBuilder(builder); | ||
* ``` | ||
*/ | ||
addPageBuilder(builder) { | ||
return this.addPage(utilities.isFunction(builder) ? builder(new MessageBuilder()) : builder); | ||
} | ||
/** | ||
* Adds a page to the existing ones asynchronously using a {@link MessageBuilder}. This wil be added as the last page. | ||
* @param builder Either a callback whose first parameter is `new MessageBuilder()`, or an already constructed {@link MessageBuilder} | ||
* @example | ||
* ```typescript | ||
* const { PaginatedMessage } = require('@sapphire/discord.js-utilities'); | ||
* const { MessageEmbed } = require('discord.js'); | ||
* | ||
* const paginatedMessage = new PaginatedMessage() | ||
* .addAsyncPageBuilder(async (builder) => { | ||
* const someRemoteData = await fetch('https://contoso.com/api/users'); | ||
* | ||
* const embed = new MessageEmbed() | ||
* .setColor('#FF0000') | ||
* .setDescription(someRemoteData.data); | ||
* | ||
* return builder | ||
* .setContent('example content') | ||
* .setEmbed(embed); | ||
* }); | ||
* ``` | ||
*/ | ||
addAsyncPageBuilder(builder) { | ||
return this.addPage(async () => (utilities.isFunction(builder) ? builder(new MessageBuilder()) : builder)); | ||
} | ||
/** | ||
* Adds a page to the existing ones using simple message content. This will be added as the last page. | ||
* @param content The content to set. | ||
* @example | ||
* ```typescript | ||
* const { PaginatedMessage } = require('@sapphire/discord.js-utilities'); | ||
* | ||
* const paginatedMessage = new PaginatedMessage() | ||
* .addPageContent('example content'); | ||
* ``` | ||
*/ | ||
addPageContent(content) { | ||
return this.addPage({ content }); | ||
} | ||
/** | ||
* Adds a page to the existing ones using a {@link MessageEmbed}. This wil be added as the last page. | ||
* @param embed Either a callback whose first paramter is `new MessageEmbed()`, or an already constructed {@link MessageEmbed} | ||
* @example | ||
* ```typescript | ||
* const { PaginatedMessage } = require('@sapphire/discord.js-utilities'); | ||
* | ||
* const paginatedMessage = new PaginatedMessage() | ||
* .addPageEmbed((embed) => { | ||
* embed | ||
* .setColor('#FF0000') | ||
* .setDescription('example description'); | ||
* | ||
* return embed; | ||
* }); | ||
* ``` | ||
* @example | ||
* ```typescript | ||
* const { PaginatedMessage } = require('@sapphire/discord.js-utilities'); | ||
* | ||
* const embed = new MessageEmbed() | ||
* .setColor('#FF0000') | ||
* .setDescription('example description'); | ||
* | ||
* const paginatedMessage = new PaginatedMessage() | ||
* .addPageEmbed(embed); | ||
* ``` | ||
*/ | ||
addPageEmbed(embed) { | ||
return this.addPage({ embed: utilities.isFunction(embed) ? embed(new discord_js.MessageEmbed()) : embed }); | ||
} | ||
/** | ||
* Adds a page to the existing ones asynchronously using a {@link MessageEmbed}. This wil be added as the last page. | ||
* @param embed Either a callback whose first paramter is `new MessageEmbed()`, or an already constructed {@link MessageEmbed} | ||
* @example | ||
* ```typescript | ||
* const { PaginatedMessage } = require('@sapphire/discord.js-utilities'); | ||
* | ||
* const paginatedMessage = new PaginatedMessage() | ||
* .addAsyncPageEmbed(async (embed) => { | ||
* const someRemoteData = await fetch('https://contoso.com/api/users'); | ||
* | ||
* embed | ||
* .setColor('#FF0000') | ||
* .setDescription(someRemoteData.data); | ||
* | ||
* return embed; | ||
* }); | ||
* ``` | ||
*/ | ||
addAsyncPageEmbed(embed) { | ||
return this.addPage(async () => ({ embed: utilities.isFunction(embed) ? await embed(new discord_js.MessageEmbed()) : embed })); | ||
} | ||
/** | ||
* Add pages to the existing ones. The order given is the order they will be used. | ||
@@ -461,7 +1064,15 @@ * @param pages The pages to add. | ||
* The handler will start collecting reactions and running actions once all actions have been reacted to the message. | ||
* @param author The author to validate. | ||
* @param channel The channel to use. | ||
* @param message The message that triggered this {@link PaginatedMessage}. | ||
* Generally this will be the command message, but it can also be another message from your bot, i.e. to indicate a loading state. | ||
*/ | ||
async run(author, channel) { | ||
await this.resolvePagesOnRun(channel); | ||
async run(message) { | ||
// Try to get the previous PaginatedMessage for this user | ||
const paginatedMessage = PaginatedMessage.handlers.get(message.author.id); | ||
// If a PaginatedMessage was found then stop it | ||
if (paginatedMessage) | ||
paginatedMessage.collector.stop(); | ||
// If the message was sent by a bot, then set the response as this one | ||
if (message.author.bot) | ||
this.response = message; | ||
await this.resolvePagesOnRun(message.channel); | ||
// Sanity checks to handle | ||
@@ -472,4 +1083,13 @@ if (!this.messages.length) | ||
throw new Error('There are no messages.'); | ||
await this.setUpMessage(channel, author); | ||
await this.setUpReactions(channel, author); | ||
await this.setUpMessage(message.channel, message.author); | ||
await this.setUpReactions(message.channel, message.author); | ||
const messageId = this.response.id; | ||
if (this.collector) { | ||
this.collector.once('end', () => { | ||
PaginatedMessage.messages.delete(messageId); | ||
PaginatedMessage.handlers.delete(message.author.id); | ||
}); | ||
PaginatedMessage.messages.set(messageId, this); | ||
PaginatedMessage.handlers.set(message.author.id, this); | ||
} | ||
return this; | ||
@@ -505,2 +1125,3 @@ } | ||
clone.response = this.response; | ||
clone.template = this.template; | ||
return clone; | ||
@@ -521,9 +1142,11 @@ } | ||
async setUpReactions(channel, author) { | ||
this.collector = this.response.createReactionCollector((reaction, user) => user.id === author.id && (this.actions.has(reaction.emoji.identifier) || this.actions.has(reaction.emoji.name)), { idle: this.idle }) | ||
.on('collect', this.handleCollect.bind(this, author, channel)) | ||
.on('end', this.handleEnd.bind(this)); | ||
for (const id of this.actions.keys()) { | ||
if (this.collector.ended) | ||
break; | ||
await this.response.react(id); | ||
if (this.pages.length > 1) { | ||
this.collector = this.response.createReactionCollector((reaction, user) => user.id === author.id && (this.actions.has(reaction.emoji.identifier) || this.actions.has(reaction.emoji.name)), { idle: this.idle }) | ||
.on('collect', this.handleCollect.bind(this, author, channel)) | ||
.on('end', this.handleEnd.bind(this)); | ||
for (const id of this.actions.keys()) { | ||
if (this.collector.ended) | ||
break; | ||
await this.response.react(id); | ||
} | ||
} | ||
@@ -538,4 +1161,5 @@ } | ||
async handlePageLoad(page, channel, index) { | ||
const options = typeof page === 'function' ? await page(index, this.pages, this) : page; | ||
return (options instanceof discord_js.APIMessage ? options : new discord_js.APIMessage(channel, options)).resolveData(); | ||
const options = utilities.isFunction(page) ? await page(index, this.pages, this) : page; | ||
const resolved = options instanceof discord_js.APIMessage ? options : new discord_js.APIMessage(channel, this.applyTemplate(this.template, options)); | ||
return this.applyFooter(resolved.resolveData(), index); | ||
} | ||
@@ -584,2 +1208,53 @@ /** | ||
} | ||
applyFooter(message, index) { | ||
var _a, _b, _c, _d; | ||
var _e; | ||
const data = message.data; | ||
if (!data.embed) | ||
return message; | ||
(_a = (_e = data.embed).footer) !== null && _a !== void 0 ? _a : (_e.footer = { text: (_d = (_c = (_b = this.template.embed) === null || _b === void 0 ? void 0 : _b.footer) === null || _c === void 0 ? void 0 : _c.text) !== null && _d !== void 0 ? _d : '' }); | ||
data.embed.footer.text = `${index + 1} / ${this.pages.length}${data.embed.footer.text}`; | ||
return message; | ||
} | ||
applyTemplate(template, options) { | ||
return { ...template, ...options, embed: this.applyTemplateEmbed(template.embed, options.embed) }; | ||
} | ||
applyTemplateEmbed(template, embed) { | ||
if (!embed) | ||
return template; | ||
if (!template) | ||
return embed; | ||
return this.mergeEmbeds(template, embed); | ||
} | ||
mergeEmbeds(template, embed) { | ||
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v; | ||
return { | ||
title: (_b = (_a = embed.title) !== null && _a !== void 0 ? _a : template.title) !== null && _b !== void 0 ? _b : undefined, | ||
description: (_d = (_c = embed.description) !== null && _c !== void 0 ? _c : template.description) !== null && _d !== void 0 ? _d : undefined, | ||
url: (_f = (_e = embed.url) !== null && _e !== void 0 ? _e : template.url) !== null && _f !== void 0 ? _f : undefined, | ||
timestamp: (_h = (_g = embed.timestamp) !== null && _g !== void 0 ? _g : template.timestamp) !== null && _h !== void 0 ? _h : undefined, | ||
color: (_k = (_j = embed.color) !== null && _j !== void 0 ? _j : template.color) !== null && _k !== void 0 ? _k : undefined, | ||
fields: this.mergeArrays(template.fields, embed.fields), | ||
files: this.mergeArrays(template.files, embed.files), | ||
author: (_m = (_l = embed.author) !== null && _l !== void 0 ? _l : template.author) !== null && _m !== void 0 ? _m : undefined, | ||
thumbnail: (_p = (_o = embed.thumbnail) !== null && _o !== void 0 ? _o : template.thumbnail) !== null && _p !== void 0 ? _p : undefined, | ||
image: (_r = (_q = embed.image) !== null && _q !== void 0 ? _q : template.image) !== null && _r !== void 0 ? _r : undefined, | ||
video: (_t = (_s = embed.video) !== null && _s !== void 0 ? _s : template.video) !== null && _t !== void 0 ? _t : undefined, | ||
footer: (_v = (_u = embed.footer) !== null && _u !== void 0 ? _u : template.footer) !== null && _v !== void 0 ? _v : undefined | ||
}; | ||
} | ||
mergeArrays(template, array) { | ||
if (!array) | ||
return template; | ||
if (!template) | ||
return array; | ||
return [...template, ...array]; | ||
} | ||
static resolveTemplate(template) { | ||
if (template === undefined) | ||
return {}; | ||
if (template instanceof discord_js.MessageEmbed) | ||
return { embed: template }; | ||
return template; | ||
} | ||
} | ||
@@ -599,3 +1274,3 @@ /** | ||
const collected = await channel | ||
.awaitMessages((message) => message.author.id === author.id, { max: 1, idle: 15 * 1000 }) | ||
.awaitMessages((message) => message.author.id === author.id, { max: 1, idle: 60000 /* Minute */ * 20 }) | ||
.catch(() => null); | ||
@@ -676,2 +1351,28 @@ if (collected) { | ||
}); | ||
/** | ||
* The messages that are currently being handled by a {@link PaginatedMessage} | ||
* The key is the ID of the message that triggered this {@link PaginatedMessage} | ||
* | ||
* This is to ensure that only 1 {@link PaginatedMessage} can run on a specified message at once. | ||
* This is important when having an editable commands solution. | ||
*/ | ||
Object.defineProperty(PaginatedMessage, "messages", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: new Map() | ||
}); | ||
/** | ||
* The current {@link ReactionCollector} handlers that are active. | ||
* The key is the ID of of the author who sent the message that triggered this {@link PaginatedMessage} | ||
* | ||
* This is to ensure that any given author can only trigger 1 {@link PaginatedMessage}. | ||
* This is important for performance reasons, and users should not have more than 1 {@link PaginatedMessage} open at once. | ||
*/ | ||
Object.defineProperty(PaginatedMessage, "handlers", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: new Map() | ||
}); | ||
@@ -701,457 +1402,13 @@ /** | ||
} | ||
} | ||
exports.MessagePrompterStrategies = void 0; | ||
(function (MessagePrompterStrategies) { | ||
MessagePrompterStrategies["Confirm"] = "confirm"; | ||
MessagePrompterStrategies["Number"] = "number"; | ||
MessagePrompterStrategies["Message"] = "message"; | ||
MessagePrompterStrategies["Reaction"] = "reaction"; | ||
})(exports.MessagePrompterStrategies || (exports.MessagePrompterStrategies = {})); | ||
class MessagePrompterBaseStrategy { | ||
/** | ||
* Constructor for the {@link MessagePrompterBaseStrategy} class | ||
* @param messagePrompter The used instance of {@link MessagePrompter} | ||
* @param options Overrideable options if needed. | ||
*/ | ||
constructor(type, message, options) { | ||
var _a, _b; | ||
/** | ||
* The type of strategy that was used | ||
*/ | ||
Object.defineProperty(this, "type", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
/** | ||
* The timeout that was used in the collector | ||
*/ | ||
Object.defineProperty(this, "timeout", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
/** | ||
* Wether to return an explicit object with data, or the strategies' default | ||
*/ | ||
Object.defineProperty(this, "explicitReturn", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
/** | ||
* The message that has been sent in {@link MessagePrompter.run} | ||
*/ | ||
Object.defineProperty(this, "appliedMessage", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: null | ||
}); | ||
/** | ||
* The message that will be sent in {@link MessagePrompter.run} | ||
*/ | ||
Object.defineProperty(this, "message", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
this.type = type; | ||
this.timeout = (_a = options === null || options === void 0 ? void 0 : options.timeout) !== null && _a !== void 0 ? _a : MessagePrompterBaseStrategy.defaultStrategyOptions.timeout; | ||
this.explicitReturn = (_b = options === null || options === void 0 ? void 0 : options.explicitReturn) !== null && _b !== void 0 ? _b : MessagePrompterBaseStrategy.defaultStrategyOptions.explicitReturn; | ||
this.message = message; | ||
addPageBuilder(builder) { | ||
return this.addPage(() => (utilities.isFunction(builder) ? builder(new MessageBuilder()) : builder)); | ||
} | ||
async collectReactions(channel, authorOrFilter, reactions) { | ||
this.appliedMessage = await channel.send(this.message); | ||
const collector = this.appliedMessage.createReactionCollector(this.createReactionPromptFilter(reactions, authorOrFilter), { | ||
max: 1, | ||
time: this.timeout | ||
}); | ||
let resolved = false; | ||
const collected = new Promise((resolve, reject) => { | ||
collector.on('collect', (r) => { | ||
resolve(r); | ||
resolved = true; | ||
collector.stop(); | ||
}); | ||
collector.on('end', (collected) => { | ||
resolved = true; | ||
if (!collected.size) | ||
reject(new Error('Collector has ended')); | ||
}); | ||
}); | ||
for (const reaction of reactions) { | ||
if (resolved) | ||
break; | ||
await this.appliedMessage.react(reaction); | ||
} | ||
const firstReaction = await collected; | ||
const emoji = firstReaction === null || firstReaction === void 0 ? void 0 : firstReaction.emoji; | ||
const reaction = reactions.find((r) => { var _a; return ((_a = emoji === null || emoji === void 0 ? void 0 : emoji.id) !== null && _a !== void 0 ? _a : emoji === null || emoji === void 0 ? void 0 : emoji.name) === r; }); | ||
return { | ||
emoji, | ||
reaction, | ||
strategy: this, | ||
appliedMessage: this.appliedMessage, | ||
message: this.message | ||
}; | ||
addPageContent(content) { | ||
return this.addPage(() => ({ content })); | ||
} | ||
/** | ||
* Creates a filter for the collector to filter on | ||
* @return The filter for awaitReactions function | ||
*/ | ||
createReactionPromptFilter(reactions, authorOrFilter) { | ||
return async (reaction, user) => { | ||
var _a; | ||
return reactions.includes((_a = reaction.emoji.id) !== null && _a !== void 0 ? _a : reaction.emoji.name) && | ||
(typeof authorOrFilter === 'function' ? await authorOrFilter(reaction, user) : user.id === authorOrFilter.id) && | ||
!user.bot; | ||
}; | ||
addPageEmbed(cb) { | ||
return this.addPage(() => ({ embed: typeof cb === 'function' ? cb(new discord_js.MessageEmbed()) : cb })); | ||
} | ||
} | ||
/** | ||
* The default strategy options | ||
*/ | ||
Object.defineProperty(MessagePrompterBaseStrategy, "defaultStrategyOptions", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: { | ||
timeout: 10 * 1000, | ||
explicitReturn: false | ||
} | ||
}); | ||
class MessagePrompterConfirmStrategy extends MessagePrompterBaseStrategy { | ||
/** | ||
* Constructor for the {@link MessagePrompterBaseStrategy} class | ||
* @param message The message to be sent {@link MessagePrompter} | ||
* @param options Overrideable options if needed. | ||
*/ | ||
constructor(message, options) { | ||
var _a, _b; | ||
super('confirm', message, options); | ||
/** | ||
* The confirm emoji used | ||
*/ | ||
Object.defineProperty(this, "confirmEmoji", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
/** | ||
* The cancel emoji used | ||
*/ | ||
Object.defineProperty(this, "cancelEmoji", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
this.confirmEmoji = (_a = options === null || options === void 0 ? void 0 : options.confirmEmoji) !== null && _a !== void 0 ? _a : MessagePrompterConfirmStrategy.confirmEmoji; | ||
this.cancelEmoji = (_b = options === null || options === void 0 ? void 0 : options.cancelEmoji) !== null && _b !== void 0 ? _b : MessagePrompterConfirmStrategy.cancelEmoji; | ||
} | ||
/** | ||
* This executes the {@link MessagePrompter} and sends the message if {@link IMessagePrompterOptions.type} equals confirm. | ||
* The handler will wait for one (1) reaction. | ||
* @param channel The channel to use. | ||
* @param authorOrFilter An author object to validate or a {@linkplain https://discord.js.org/#/docs/main/stable/typedef/CollectorFilter CollectorFilter} predicate callback. | ||
* @returns A promise that resolves to a boolean denoting the value of the input (`true` for yes, `false` for no). | ||
*/ | ||
async run(channel, authorOrFilter) { | ||
var _a, _b, _c; | ||
const response = await this.collectReactions(channel, authorOrFilter, [this.confirmEmoji, this.cancelEmoji]); | ||
const confirmed = ((_b = (_a = response === null || response === void 0 ? void 0 : response.emoji) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : (_c = response === null || response === void 0 ? void 0 : response.emoji) === null || _c === void 0 ? void 0 : _c.name) === this.confirmEmoji; | ||
// prettier-ignore | ||
return this.explicitReturn ? { ...response, confirmed } : confirmed; | ||
} | ||
} | ||
/** | ||
* The default confirm emoji used for {@link MessagePrompterConfirmStrategy} | ||
*/ | ||
Object.defineProperty(MessagePrompterConfirmStrategy, "confirmEmoji", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: '๐พ' | ||
}); | ||
/** | ||
* The default cancel emoji used for {@link MessagePrompterConfirmStrategy} | ||
*/ | ||
Object.defineProperty(MessagePrompterConfirmStrategy, "cancelEmoji", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: '๐ณ' | ||
}); | ||
class MessagePrompterMessageStrategy extends MessagePrompterBaseStrategy { | ||
/** | ||
* Constructor for the {@link MessagePrompterBaseStrategy} class | ||
* @param messagePrompter The used instance of {@link MessagePrompter} | ||
* @param options Overrideable options if needed. | ||
*/ | ||
constructor(message, options) { | ||
super('message', message, options); | ||
} | ||
/** | ||
* This executes the {@link MessagePrompter} and sends the message if {@link IMessagePrompterOptions.type} equals message. | ||
* The handler will wait for one (1) message. | ||
* @param channel The channel to use. | ||
* @param authorOrFilter An author object to validate or a {@linkplain https://discord.js.org/#/docs/main/stable/typedef/CollectorFilter CollectorFilter} predicate callback. | ||
* @returns A promise that resolves to the message object received. | ||
*/ | ||
async run(channel, authorOrFilter) { | ||
this.appliedMessage = await channel.send(this.message); | ||
const collector = await channel.awaitMessages(this.createMessagePromptFilter(authorOrFilter), { | ||
max: 1, | ||
time: this.timeout, | ||
errors: ['time'] | ||
}); | ||
const response = collector.first(); | ||
if (!response) { | ||
throw new Error('No messages received'); | ||
} | ||
return this.explicitReturn | ||
? { | ||
response, | ||
strategy: this, | ||
appliedMessage: this.appliedMessage, | ||
message: this.message | ||
} | ||
: response; | ||
} | ||
/** | ||
* Creates a filter for the collector to filter on | ||
* @return The filter for awaitMessages function | ||
*/ | ||
createMessagePromptFilter(authorOrFilter) { | ||
return async (message) => (typeof authorOrFilter === 'function' ? await authorOrFilter(message) : message.author.id === authorOrFilter.id) && !message.author.bot; | ||
} | ||
} | ||
class MessagePrompterNumberStrategy extends MessagePrompterBaseStrategy { | ||
/** | ||
* Constructor for the {@link MessagePrompterBaseStrategy} class | ||
* @param messagePrompter The used instance of {@link MessagePrompter} | ||
* @param options Overrideable options if needed. | ||
*/ | ||
constructor(message, options) { | ||
var _a, _b, _c; | ||
super('number', message, options); | ||
/** | ||
* The available number emojis | ||
*/ | ||
Object.defineProperty(this, "numberEmojis", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
/** | ||
* The available number emojis | ||
*/ | ||
Object.defineProperty(this, "start", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
/** | ||
* The available number emojis | ||
*/ | ||
Object.defineProperty(this, "end", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
this.numberEmojis = (_a = options === null || options === void 0 ? void 0 : options.numberEmojis) !== null && _a !== void 0 ? _a : MessagePrompterNumberStrategy.numberEmojis; | ||
this.start = (_b = options === null || options === void 0 ? void 0 : options.start) !== null && _b !== void 0 ? _b : 0; | ||
this.end = (_c = options === null || options === void 0 ? void 0 : options.end) !== null && _c !== void 0 ? _c : 10; | ||
} | ||
/** | ||
* This executes the {@link MessagePrompter} and sends the message if {@link IMessagePrompterOptions.type} equals number. | ||
* The handler will wait for one (1) reaction. | ||
* @param channel The channel to use. | ||
* @param authorOrFilter An author object to validate or a {@linkplain https://discord.js.org/#/docs/main/stable/typedef/CollectorFilter CollectorFilter} predicate callback. | ||
* @returns A promise that resolves to the selected number within the range. | ||
*/ | ||
async run(channel, authorOrFilter) { | ||
// 0 and 10 are the maximum available emojis as a number | ||
if (this.start < 0) | ||
throw new TypeError('Starting number cannot be less than 0.'); | ||
if (this.end > 10) | ||
throw new TypeError('Ending number cannot be more than 10.'); | ||
const numbers = Array.from({ length: this.end - this.start + 1 }, (_, n) => n + this.start); | ||
const emojis = this.numberEmojis.slice(this.start, this.end); | ||
const response = await this.collectReactions(channel, authorOrFilter, emojis); | ||
const emojiIndex = emojis.findIndex((emoji) => { var _a, _b, _c; return ((_b = (_a = response === null || response === void 0 ? void 0 : response.emoji) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : (_c = response === null || response === void 0 ? void 0 : response.emoji) === null || _c === void 0 ? void 0 : _c.name) === emoji; }); | ||
const number = numbers[emojiIndex]; | ||
// prettier-ignore | ||
return this.explicitReturn ? { ...response, number } : number; | ||
} | ||
} | ||
/** | ||
* The default available number emojis | ||
*/ | ||
Object.defineProperty(MessagePrompterNumberStrategy, "numberEmojis", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: ['0๏ธโฃ', '1๏ธโฃ', '2๏ธโฃ', '3๏ธโฃ', '4๏ธโฃ', '5๏ธโฃ', '6๏ธโฃ', '7๏ธโฃ', '8๏ธโฃ', '9๏ธโฃ', '๐'] | ||
}); | ||
class MessagePrompterReactionStrategy extends MessagePrompterBaseStrategy { | ||
/** | ||
* Constructor for the {@link MessagePrompterReactionStrategy} class | ||
* @param messagePrompter The used instance of {@link MessagePrompter} | ||
* @param options Overrideable options if needed. | ||
*/ | ||
constructor(message, options) { | ||
super('reactions', message, options); | ||
/** | ||
* The emojis used | ||
*/ | ||
Object.defineProperty(this, "reactions", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
this.reactions = options === null || options === void 0 ? void 0 : options.reactions; | ||
} | ||
/** | ||
* This executes the {@link MessagePrompterReactionStrategy} and sends the message. | ||
* The handler will wait for one (1) reaction. | ||
* @param channel The channel to use. | ||
* @param authorOrFilter An author object to validate or a {@linkplain https://discord.js.org/#/docs/main/stable/typedef/CollectorFilter CollectorFilter} predicate callback. | ||
* @returns A promise that resolves to the reaction object. | ||
*/ | ||
async run(channel, authorOrFilter) { | ||
var _a, _b; | ||
if (!((_a = this.reactions) === null || _a === void 0 ? void 0 : _a.length)) | ||
throw new TypeError('There are no reactions provided.'); | ||
const response = await this.collectReactions(channel, authorOrFilter, this.reactions); | ||
return this.explicitReturn ? response : (_b = response.reaction) !== null && _b !== void 0 ? _b : response; | ||
} | ||
} | ||
/** | ||
* This is a {@link MessagePrompter}, a utility that sends a message, prompting for user input. The prompt can resolve to any kind of input. | ||
* There are several specifiable types to prompt for user input, and they are as follows: | ||
* - Confirm | ||
* This will send a simple Yes/No prompt, using reactions. | ||
* - Number | ||
* This will prompt for an integer. By default it will be a number between 0 and 10 (inclusive), however you can also specify your own custom range (inclusive). | ||
* - Reactions | ||
* This can be any kind of reaction emoji that Discord supports, and as many as you want. This type will return that reaction instead of a boolean. | ||
* - Message | ||
* This will prompt the user and require a response in the form of a message. This can be helpful if you require a user to upload an image for example, or give text input. | ||
* | ||
* You must either use this class directly or extend it. | ||
* | ||
* {@link MessagePrompter} uses reactions to prompt for a yes/no answer and returns it. | ||
* You can modify the confirm and cancel reaction used for each message, or use the {@link MessagePrompter.defaultPrompts}. | ||
* {@link MessagePrompter.defaultPrompts} is also static so you can modify these directly. | ||
* | ||
* @example | ||
* ```typescript | ||
* const handler = new MessagePrompter('Are you sure you want to continue?'); | ||
* const result = await handler.run(channel, author); | ||
* ``` | ||
* | ||
* @example | ||
* ```typescript | ||
* const handler = new MessagePrompter('Choose a number between 5 and 10?', 'number', { | ||
* start: 5, | ||
* end: 10 | ||
* }); | ||
* const result = await handler.run(channel, author); | ||
* ``` | ||
* | ||
* @example | ||
* ```typescript | ||
* const handler = new MessagePrompter('Are you happy or sad?', 'reaction', { | ||
* reactions: ['๐', '๐'] | ||
* }); | ||
* const result = await handler.run(channel, author); | ||
* ``` | ||
* | ||
* @example | ||
* ```typescript | ||
* const handler = new MessagePrompter('Do you love me?', 'message'); | ||
* const result = await handler.run(channel, author); | ||
* ``` | ||
*/ | ||
class MessagePrompter { | ||
/** | ||
* Constructor for the {@link MessagePrompter} class | ||
* @param message The message to send. | ||
* @param strategy The strategy name or Instance to use | ||
* @param strategyOptions The options that are passed to the strategy | ||
*/ | ||
constructor(message, strategy, strategyOptions) { | ||
/** | ||
* The strategy used in {@link MessagePrompter.run} | ||
*/ | ||
Object.defineProperty(this, "strategy", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
let strategyToRun = undefined; | ||
if (message instanceof MessagePrompterBaseStrategy) { | ||
strategyToRun = message; | ||
} | ||
else { | ||
const mapStrategy = MessagePrompter.strategies.get(strategy !== null && strategy !== void 0 ? strategy : MessagePrompter.defaultStrategy); | ||
if (!mapStrategy) { | ||
throw new Error('No strategy provided'); | ||
} | ||
strategyToRun = new mapStrategy(message, strategyOptions); | ||
} | ||
this.strategy = strategyToRun; | ||
} | ||
/** | ||
* This executes the {@link MessagePrompter} and sends the message. | ||
* @param channel The channel to use. | ||
* @param authorOrFilter An author object to validate or a {@linkplain https://discord.js.org/#/docs/main/stable/typedef/CollectorFilter CollectorFilter} predicate callback. | ||
*/ | ||
run(channel, authorOrFilter) { | ||
return this.strategy.run(channel, authorOrFilter); | ||
} | ||
} | ||
/** | ||
* The available strategies | ||
*/ | ||
Object.defineProperty(MessagePrompter, "strategies", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: new Map([ | ||
["confirm" /* Confirm */, MessagePrompterConfirmStrategy], | ||
["number" /* Number */, MessagePrompterNumberStrategy], | ||
["reaction" /* Reaction */, MessagePrompterReactionStrategy], | ||
["message" /* Message */, MessagePrompterMessageStrategy] | ||
]) | ||
}); | ||
/** | ||
* The default strategy to use | ||
*/ | ||
Object.defineProperty(MessagePrompter, "defaultStrategy", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: "confirm" /* Confirm */ | ||
}); | ||
exports.LazyPaginatedMessage = LazyPaginatedMessage; | ||
@@ -1158,0 +1415,0 @@ exports.MessageBuilder = MessageBuilder; |
{ | ||
"name": "@sapphire/discord.js-utilities", | ||
"version": "1.6.0", | ||
"version": "2.0.0-pr-134.0505c63c.0", | ||
"description": "Discord.js specific utilities for your JavaScript/TypeScript bots", | ||
@@ -53,5 +53,7 @@ "author": "@sapphire", | ||
"dependencies": { | ||
"@sapphire/discord-utilities": "^2.1.5" | ||
"@sapphire/discord-utilities": "^2.1.5", | ||
"@sapphire/time-utilities": "^1.3.7", | ||
"@sapphire/utilities": "^2.0.0" | ||
}, | ||
"gitHead": "632c305cb9666dcff8c0ee71167570b8df46ccab" | ||
"gitHead": "0505c63cffb8808455b18a5c4b3a30b56645c280" | ||
} |
@@ -71,3 +71,3 @@ <div align="center"> | ||
<td align="center"><a href="https://github.com/vladfrangu"><img src="https://avatars3.githubusercontent.com/u/17960496?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vlad Frangu</b></sub></a><br /><a href="https://github.com/sapphiredev/utilities/commits?author=vladfrangu" title="Code">๐ป</a> <a href="https://github.com/sapphiredev/utilities/issues?q=author%3Avladfrangu" title="Bug reports">๐</a> <a href="https://github.com/sapphiredev/utilities/pulls?q=is%3Apr+reviewed-by%3Avladfrangu" title="Reviewed Pull Requests">๐</a> <a href="#userTesting-vladfrangu" title="User Testing">๐</a> <a href="https://github.com/sapphiredev/utilities/commits?author=vladfrangu" title="Tests">โ ๏ธ</a></td> | ||
<td align="center"><a href="https://github.com/Soumil07"><img src="https://avatars0.githubusercontent.com/u/29275227?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Soumil07</b></sub></a><br /><a href="https://github.com/sapphiredev/utilities/commits?author=Soumil07" title="Code">๐ป</a> <a href="#projectManagement-Soumil07" title="Project Management">๐</a> <a href="https://github.com/sapphiredev/utilities/commits?author=Soumil07" title="Tests">โ ๏ธ</a></td> | ||
<td align="center"><a href="https://github.com/Stitch07"><img src="https://avatars0.githubusercontent.com/u/29275227?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Stitch07</b></sub></a><br /><a href="https://github.com/sapphiredev/utilities/commits?author=Stitch07" title="Code">๐ป</a> <a href="#projectManagement-Stitch07" title="Project Management">๐</a> <a href="https://github.com/sapphiredev/utilities/commits?author=Stitch07" title="Tests">โ ๏ธ</a></td> | ||
<td align="center"><a href="https://github.com/apps/depfu"><img src="https://avatars3.githubusercontent.com/in/715?v=4?s=100" width="100px;" alt=""/><br /><sub><b>depfu[bot]</b></sub></a><br /><a href="#maintenance-depfu[bot]" title="Maintenance">๐ง</a></td> | ||
@@ -74,0 +74,0 @@ <td align="center"><a href="https://github.com/apps/allcontributors"><img src="https://avatars0.githubusercontent.com/in/23186?v=4?s=100" width="100px;" alt=""/><br /><sub><b>allcontributors[bot]</b></sub></a><br /><a href="https://github.com/sapphiredev/utilities/commits?author=allcontributors[bot]" title="Documentation">๐</a></td> |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
349401
39
3743
3
1
+ Added@sapphire/utilities@^2.0.0
+ Added@sapphire/cron@1.2.1(transitive)
+ Added@sapphire/duration@1.1.4(transitive)
+ Added@sapphire/time-utilities@1.7.14(transitive)
+ Added@sapphire/timer-manager@1.0.4(transitive)
+ Added@sapphire/timestamp@1.0.5(transitive)
+ Added@sapphire/utilities@2.0.13.18.2(transitive)