diff --git a/src/client/InteractionClient.js b/src/client/InteractionClient.js index cb76ee39..08d27d6a 100644 --- a/src/client/InteractionClient.js +++ b/src/client/InteractionClient.js @@ -2,6 +2,7 @@ const BaseClient = require('./BaseClient'); const Interaction = require('../structures/Interaction'); +const ApplicationCommand = require('../structures/InteractionCommand'); const { ApplicationCommandOptionType, InteractionType, InteractionResponseType } = require('../util/Constants'); let sodium; @@ -46,20 +47,32 @@ class InteractionClient extends BaseClient { this.interactionClient = this; } - getCommands(guildID) { + /** + * Get registered slash commands. + * @param {Snowflake?} guildID Optional guild ID. + * @returns {[Command]} + */ + async getCommands(guildID) { let path = this.client.api.applications('@me'); if (guildID) { path = path.guilds(guildID); } - return path.commands.get(); + const commands = await path.commands.get(); + return commands.map(c => new ApplicationCommand(this, c, guildID)); } + /** + * Create a command. + * @param {Object} command The command description. + * @param {Snowflake?} guildID Optional guild ID. + * @returns {ApplicationCommand} The created command. + */ createCommand(command, guildID) { let path = this.client.api.applications('@me'); if (guildID) { path = path.guilds(guildID); } - return path.commands.post({ + const c = path.commands.post({ data: { name: command.name, description: command.description, @@ -71,19 +84,12 @@ class InteractionClient extends BaseClient { default: o.default, required: o.required, choices: o.choices, - options: o.options.map(m), + options: o.options ? o.options.map(m) : undefined, }; }), }, }); - } - - deleteCommand(commandID, guildID) { - let path = this.client.api.applications('@me'); - if (guildID) { - path = path.guilds(guildID); - } - return path.commands(commandID).delete(); + return new ApplicationCommand(this, c, guildID); } async handle(data) { @@ -129,6 +135,11 @@ class InteractionClient extends BaseClient { } } + /** + * An express-like middleware factory which can be used + * with webhook interactions. + * @returns {Function} The middleware function. + */ middleware() { return async (req, res, next) => { const timestamp = req.get('x-signature-timestamp'); diff --git a/src/index.js b/src/index.js index e68fdbec..fb1a09f6 100644 --- a/src/index.js +++ b/src/index.js @@ -59,6 +59,7 @@ module.exports = { // Structures Application: require('./structures/interfaces/Application'), + ApplicationCommand: require('./structures/ApplicationCommand'), Base: require('./structures/Base'), Activity: require('./structures/Presence').Activity, APIMessage: require('./structures/APIMessage'), @@ -81,6 +82,7 @@ module.exports = { GuildPreview: require('./structures/GuildPreview'), GuildTemplate: require('./structures/GuildTemplate'), Integration: require('./structures/Integration'), + Interaction: require('./structures/Interaction'), Invite: require('./structures/Invite'), Message: require('./structures/Message'), MessageAttachment: require('./structures/MessageAttachment'), diff --git a/src/structures/Guild.js b/src/structures/Guild.js index c9c4fe9f..524ea64c 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -1432,6 +1432,23 @@ class Guild extends Base { .then(() => this); } + /** + * Get the commands associated with this guild. + * @returns {ApplicationCommand[]} A list of commands. + */ + getCommands() { + return this.client.interactionClient.getCommands(this.id); + } + + /** + * Create a command. See {@link InteractionClient}. + * @param {Object} command The command description. + * @returns {ApplicationCommand} The created command. + */ + createCommand(command) { + return this.client.interactionClient.createCommand(command, this.id); + } + /** * Leaves the guild. * @returns {Promise} diff --git a/src/structures/Interaction.js b/src/structures/Interaction.js index 7b2c7adf..c3b931e2 100644 --- a/src/structures/Interaction.js +++ b/src/structures/Interaction.js @@ -67,22 +67,20 @@ class Interaction extends Base { } /** - * The timestamp the emoji was created at, or null if unicode - * @type {?number} + * The timestamp the interaction was created at. + * @type {number} * @readonly */ get createdTimestamp() { - if (!this.id) return null; return Snowflake.deconstruct(this.id).timestamp; } /** - * The time the emoji was created at, or null if unicode - * @type {?Date} + * The time the interaction was created at. + * @type {Date} * @readonly */ get createdAt() { - if (!this.id) return null; return new Date(this.createdTimestamp); } diff --git a/src/structures/InteractionCommand.js b/src/structures/InteractionCommand.js new file mode 100644 index 00000000..b4d23c7e --- /dev/null +++ b/src/structures/InteractionCommand.js @@ -0,0 +1,77 @@ +'use strict'; + +const Base = require('./Base'); +const { ApplicationCommandOptionType } = require('../util/Constants'); +const Snowflake = require('../util/Snowflake'); + +/** + * Represents an application command, see {@link InteractionClient}. + * @extends {Base} + */ +class ApplicationCommand extends Base { + constructor(client, data, guildID) { + super(client); + + /** + * The ID of the guild this command is part of, if any. + * @type {Snowflake?} + * @readonly + */ + this.guildID = guildID; + + this._patch(data); + } + + _patch(data) { + this.id = data.id; + + this.appplicationID = data.application_id; + + this.name = data.name; + + this.description = data.description; + + this.options = data.options.map(function m(o) { + return { + type: ApplicationCommandOptionType[o.type], + name: o.name, + description: o.description, + default: o.default, + required: o.required, + choices: o.choices, + options: o.options ? o.options.map(m) : undefined, + }; + }); + } + + /** + * The timestamp the command was created at. + * @type {number} + * @readonly + */ + get createdTimestamp() { + return Snowflake.deconstruct(this.id).timestamp; + } + + /** + * The time the command was created at. + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * Delete this command. + */ + async delete() { + let path = this.client.api.applications('@me'); + if (this.guildID) { + path = path.guilds(this.guildID); + } + await path.commands(this.id).delete(); + } +} + +module.exports = ApplicationCommand; diff --git a/src/util/Constants.js b/src/util/Constants.js index 9eeb4147..0429fa12 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -689,6 +689,9 @@ exports.ApplicationCommandOptionType = { CHANNEL: 7, ROLE: 8, }; +Object.entries(exports.ApplicationCommandOptionType).forEach(([k, v]) => { + exports.ApplicationCommandOptionType[v] = k; +}); exports.InteractionType = { PING: 1,