slash-create-modify
Version:
Create and sync Discord slash commands!
330 lines (329 loc) • 16 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MessageInteractionContext = void 0;
const constants_1 = require("../../constants");
const util_1 = require("../../util");
const member_1 = require("../member");
const user_1 = require("../user");
const message_1 = require("../message");
const permissions_1 = require("../permissions");
/** Represents a interaction context that handles messages. */
class MessageInteractionContext {
/**
* @param creator The instantiating creator.
* @param data The interaction data.
* @param respond The response function for the interaction.
*/
constructor(creator, data, respond) {
/** The time when the interaction was created. */
this.invokedAt = Date.now();
/** Whether the initial response was sent. */
this.initiallyResponded = false;
/** Whether there is a deferred message available. */
this.deferred = false;
this.creator = creator;
this._respond = respond;
this.interactionToken = data.token;
this.interactionID = data.id;
this.channelID = data.channel_id;
this.guildID = 'guild_id' in data ? data.guild_id : undefined;
this.locale = 'locale' in data ? data.locale : undefined;
this.guildLocale = 'guild_locale' in data ? data.guild_locale : undefined;
this.member = 'guild_id' in data ? new member_1.Member(data.member, this.creator, data.guild_id) : undefined;
this.user = new user_1.User('guild_id' in data ? data.member.user : data.user, this.creator);
this.appPermissions = data.app_permissions ? new permissions_1.Permissions(BigInt(data.app_permissions)) : undefined;
}
/** Whether the interaction has expired. Interactions last 15 minutes. */
get expired() {
return this.invokedAt + 1000 * 60 * 15 < Date.now();
}
/**
* Fetches a message.
* @param messageID The ID of the message, defaults to the original message
*/
async fetch(messageID = '@original') {
const data = await this.creator.requestHandler.request('GET', constants_1.Endpoints.MESSAGE(this.creator.options.applicationID, this.interactionToken, messageID));
if (messageID === '@original')
this.messageID = data.id;
return new message_1.Message(data, this.creator, this);
}
/**
* Sends a message, if it already made an initial response, this will create a follow-up message.
* IF the context has created a deferred message, it will edit that deferred message,
* and future calls to this function create follow ups.
* This will return a boolean if it's an initial response, otherwise a {@link Message} will be returned.
* Note that when making a follow-up message, the `ephemeral` option is ignored.
* @param content The content of the message
* @param options The message options
*/
async send(content, options) {
if (this.expired)
throw new Error('This interaction has expired');
if (typeof content !== 'string')
options = content;
else if (typeof options !== 'object')
options = {};
if (typeof options !== 'object')
throw new Error('Message options is not an object.');
options = { ...options };
if (!options.content && typeof content === 'string')
options.content = content;
if (!options.content && !options.embeds && !options.file)
throw new Error('Message content, embeds and files are not given.');
if (options.ephemeral && !options.flags)
options.flags = constants_1.InteractionResponseFlags.EPHEMERAL;
const allowedMentions = options.allowedMentions
? util_1.formatAllowedMentions(options.allowedMentions, this.creator.allowedMentions)
: this.creator.allowedMentions;
if (!this.initiallyResponded) {
this.initiallyResponded = true;
clearTimeout(this._timeout);
await this._respond({
status: 200,
body: {
type: constants_1.InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
tts: options.tts,
content: options.content,
embeds: options.embeds,
flags: options.flags,
allowed_mentions: allowedMentions,
components: options.components,
attachments: options.attachments
}
},
files: options.file ? (Array.isArray(options.file) ? options.file : [options.file]) : undefined
});
return true;
}
else if (this.initiallyResponded && this.deferred)
return this.editOriginal(content, options);
else
return this.sendFollowUp(content, options);
}
/**
* Sends a follow-up message.
* @param content The content of the message
* @param options The message options
*/
async sendFollowUp(content, options) {
if (this.expired)
throw new Error('This interaction has expired');
if (typeof content !== 'string')
options = content;
else if (typeof options !== 'object')
options = {};
if (typeof options !== 'object')
throw new Error('Message options is not an object.');
options = { ...options };
if (!options.content && typeof content === 'string')
options.content = content;
if (!options.content && !options.embeds && !options.file)
throw new Error('Message content, embeds or files need to be given.');
if (options.ephemeral && !options.flags)
options.flags = constants_1.InteractionResponseFlags.EPHEMERAL;
const allowedMentions = options.allowedMentions
? util_1.formatAllowedMentions(options.allowedMentions, this.creator.allowedMentions)
: this.creator.allowedMentions;
const data = await this.creator.requestHandler.request('POST', constants_1.Endpoints.FOLLOWUP_MESSAGE(this.creator.options.applicationID, this.interactionToken), true, {
tts: options.tts,
content: options.content,
embeds: options.embeds,
allowed_mentions: allowedMentions,
components: options.components,
flags: options.flags,
attachments: options.attachments
}, options.file);
return new message_1.Message(data, this.creator, this);
}
/**
* Edits a message.
* @param messageID The message's ID
* @param content The content of the message
* @param options The message options
*/
async edit(messageID, content, options) {
if (this.expired)
throw new Error('This interaction has expired');
if (typeof content !== 'string')
options = content;
else if (typeof options !== 'object')
options = {};
if (typeof options !== 'object')
throw new Error('Message options is not an object.');
options = { ...options };
if (!options.content && typeof content === 'string')
options.content = content;
if (!options.content && !options.embeds && !options.components && !options.file && !options.attachments)
throw new Error('No valid options were given.');
const allowedMentions = options.allowedMentions
? util_1.formatAllowedMentions(options.allowedMentions, this.creator.allowedMentions)
: this.creator.allowedMentions;
let data;
try {
data = await this.creator.requestHandler.request('PATCH', constants_1.Endpoints.MESSAGE(this.creator.options.applicationID, this.interactionToken, messageID), true, {
content: options.content,
embeds: options.embeds,
allowed_mentions: allowedMentions,
components: options.components,
attachments: options.attachments
}, options.file);
}
catch (e) {
data = await this.creator.requestHandler.request('PATCH', `/channels/${this.channelID}/messages/${this.message.id}`, true, {
content: options.content,
embeds: options.embeds,
allowed_mentions: allowedMentions,
components: options.components,
attachments: options.attachments
}, options.file);
}
return new message_1.Message(data, this.creator, this);
}
/**
* Edits the original message.
* Note: This will error with ephemeral messages or deferred ephemeral messages.
* @param content The content of the message
* @param options The message options
*/
async editOriginal(content, options) {
this.deferred = false;
const message = await this.edit('@original', content, options);
this.messageID = message.id;
return message;
}
/**
* Deletes a message. If the message ID was not defined, the original message is used.
* @param messageID The message's ID
*/
async delete(messageID) {
if (this.expired)
throw new Error('This interaction has expired');
const res = await this.creator.requestHandler.request('DELETE', constants_1.Endpoints.MESSAGE(this.creator.options.applicationID, this.interactionToken, messageID));
if (!messageID || messageID === '@original')
this.messageID = undefined;
return res;
}
/**
* Creates a deferred message. To users, this will show as
* "Bot is thinking..." until the deferred message is edited.
* @param ephemeral Whether to make the deferred message ephemeral.
* @returns Whether the deferred message passed
*/
async defer(ephemeral = false) {
if (!this.initiallyResponded && !this.deferred) {
this.initiallyResponded = true;
this.deferred = true;
clearTimeout(this._timeout);
await this._respond({
status: 200,
body: {
type: constants_1.InteractionResponseType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE,
data: {
flags: ephemeral ? constants_1.InteractionResponseFlags.EPHEMERAL : 0
}
}
});
return true;
}
return false;
}
/**
* Registers a component callback from the initial message.
* This unregisters automatically when the context expires.
* @param custom_id The custom ID of the component to register
* @param callback The callback to use on interaction
* @param expiration The expiration time of the callback in milliseconds. Use null for no expiration (Although, in this case, global components might be more consistent).
* @param onExpired A function to be called when the component expires.
*/
registerComponent(custom_id, callback, expiration = 1000 * 60 * 15, onExpired) {
if (!this.initiallyResponded || this.deferred)
throw new Error('You must send a message before registering components');
if (!this.messageID)
throw new Error('Fetch your original message or use deferred messages before registering components');
this.creator._componentCallbacks.set(`${this.messageID}-${custom_id}`, {
callback,
expires: expiration != null ? Date.now() + expiration : undefined,
onExpired
});
if (expiration != null && this.creator.options.componentTimeouts)
setTimeout(() => {
if (this.creator._componentCallbacks.has(`${this.messageID}-${custom_id}`)) {
if (onExpired)
onExpired();
this.creator._componentCallbacks.delete(`${this.messageID}-${custom_id}`);
}
}, expiration);
}
/**
* Registers a component callback from a message.
* This unregisters automatically when the context expires.
* @param message_id The message ID of the component to register
* @param custom_id The custom ID of the component to register
* @param callback The callback to use on interaction
* @param expiration The expiration time of the callback in milliseconds. Use null for no expiration (Although, in this case, global components might be more consistent).
* @param onExpired A function to be called when the component expires.
*/
registerComponentFrom(message_id, custom_id, callback, expiration = 1000 * 60 * 15, onExpired) {
this.creator._componentCallbacks.set(`${message_id}-${custom_id}`, {
callback,
expires: expiration != null ? Date.now() + expiration : undefined,
onExpired
});
if (expiration != null && this.creator.options.componentTimeouts)
setTimeout(() => {
if (this.creator._componentCallbacks.has(`${message_id}-${custom_id}`)) {
if (onExpired)
onExpired();
this.creator._componentCallbacks.delete(`${message_id}-${custom_id}`);
}
}, expiration);
}
/**
* Unregisters a component callback.
* @param custom_id The custom ID of the component to unregister
* @param message_id The message ID of the component to unregister, defaults to initial message ID if any
*/
unregisterComponent(custom_id, message_id) {
if (!message_id) {
if (!this.messageID)
throw new Error('The initial message ID was not provided by the context!');
else
message_id = this.messageID;
}
return this.creator._componentCallbacks.delete(`${message_id}-${custom_id}`);
}
/**
* Registers a wildcard component callback on a message.
* This unregisters automatically when the context expires.
* @param message_id The message ID of the component to register
* @param callback The callback to use on interaction
* @param expiration The expiration time of the callback in milliseconds. Use null for no expiration (Although, in this case, global components might be more consistent).
* @param onExpired A function to be called when the component expires.
*/
registerWildcardComponent(message_id, callback, expiration = 1000 * 60 * 15, onExpired) {
if (this.expired)
throw new Error('This interaction has expired');
this.creator._componentCallbacks.set(`${message_id}-*`, {
callback,
expires: expiration != null ? this.invokedAt + expiration : undefined,
onExpired
});
if (expiration != null && this.creator.options.componentTimeouts)
setTimeout(() => {
if (this.creator._componentCallbacks.has(`${message_id}-*`)) {
if (onExpired)
onExpired();
this.creator._componentCallbacks.delete(`${message_id}-*`);
}
}, expiration);
}
/**
* Unregisters a component callback.
* @param message_id The message ID of the component to unregister, defaults to the invoking message ID.
*/
unregisterWildcardComponent(message_id) {
return this.creator._componentCallbacks.delete(`${message_id}-*`);
}
}
exports.MessageInteractionContext = MessageInteractionContext;