diff --git a/src/errors/Messages.js b/src/errors/Messages.js
index 2e04d168094d..2bdec03e3b92 100644
--- a/src/errors/Messages.js
+++ b/src/errors/Messages.js
@@ -119,8 +119,9 @@ const Messages = {
MEMBER_FETCH_NONCE_LENGTH: 'Nonce length must not exceed 32 characters.',
GLOBAL_COMMAND_PERMISSIONS:
- 'Permissions for global commands may only be fetched or modified by providing a guildID' +
+ 'Permissions for global commands may only be fetched or modified by providing a GuildResolvable ' +
"or from a guild's application command manager.",
+ GUILD_UNCACHED_ROLE_RESOLVE: 'Cannot resolve roles from an arbitrary guild, provide an ID instead',
INTERACTION_ALREADY_REPLIED: 'This interaction has already been deferred or replied to.',
INTERACTION_NOT_REPLIED: 'This interaction has not been deferred or replied to.',
diff --git a/src/managers/ApplicationCommandManager.js b/src/managers/ApplicationCommandManager.js
index 8b5101473a27..63b4715c7d37 100644
--- a/src/managers/ApplicationCommandManager.js
+++ b/src/managers/ApplicationCommandManager.js
@@ -1,10 +1,10 @@
'use strict';
+const ApplicationCommandPermissionsManager = require('./ApplicationCommandPermissionsManager');
const BaseManager = require('./BaseManager');
-const { Error, TypeError } = require('../errors');
+const { TypeError } = require('../errors');
const ApplicationCommand = require('../structures/ApplicationCommand');
const Collection = require('../util/Collection');
-const { ApplicationCommandPermissionTypes } = require('../util/Constants');
/**
* Manages API methods for application commands and stores their cache.
@@ -13,6 +13,12 @@ const { ApplicationCommandPermissionTypes } = require('../util/Constants');
class ApplicationCommandManager extends BaseManager {
constructor(client, iterable) {
super(client, iterable, ApplicationCommand);
+
+ /**
+ * The manager for permissions of arbitrary commands on arbitrary guilds
+ * @type {ApplicationCommandPermissionsManager}
+ */
+ this.permissions = new ApplicationCommandPermissionsManager(this);
}
/**
@@ -21,8 +27,8 @@ class ApplicationCommandManager extends BaseManager {
* @name ApplicationCommandManager#cache
*/
- add(data, cache) {
- return super.add(data, cache, { extras: [this.guild] });
+ add(data, cache, guildID) {
+ return super.add(data, cache, { extras: [this.guild, guildID] });
}
/**
@@ -90,7 +96,7 @@ class ApplicationCommandManager extends BaseManager {
}
const data = await this.commandPath({ guildID }).get();
- return data.reduce((coll, command) => coll.set(command.id, this.add(command, cache)), new Collection());
+ return data.reduce((coll, command) => coll.set(command.id, this.add(command, cache, guildID)), new Collection());
}
/**
@@ -112,7 +118,7 @@ class ApplicationCommandManager extends BaseManager {
const data = await this.commandPath({ guildID }).post({
data: this.constructor.transformCommand(command),
});
- return this.add(data);
+ return this.add(data, undefined, guildID);
}
/**
@@ -141,7 +147,10 @@ class ApplicationCommandManager extends BaseManager {
const data = await this.commandPath({ guildID }).put({
data: commands.map(c => this.constructor.transformCommand(c)),
});
- return data.reduce((coll, command) => coll.set(command.id, this.add(command)), new Collection());
+ return data.reduce(
+ (coll, command) => coll.set(command.id, this.add(command, undefined, guildID)),
+ new Collection(),
+ );
}
/**
@@ -164,7 +173,7 @@ class ApplicationCommandManager extends BaseManager {
if (!id) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
const patched = await this.commandPath({ id, guildID }).patch({ data: this.constructor.transformCommand(data) });
- return this.add(patched);
+ return this.add(patched, undefined, guildID);
}
/**
@@ -204,146 +213,6 @@ class ApplicationCommandManager extends BaseManager {
default_permission: command.defaultPermission,
};
}
-
- /**
- * Fetches the permissions for one or multiple commands.
- * When calling this on ApplicationCommandManager, guildID is required.
- * To fetch all permissions for an uncached guild use `fetchPermissions(undefined, '123456789012345678')`
- * @param {ApplicationCommandResolvable} [command] The command to get the permissions from
- * @param {Snowflake} [guildID] ID of the guild to get the permissions for,
- * ignored when using a {@link GuildApplicationCommandManager}
- * @returns {Promise>}
- * @example
- * // Fetch permissions for one command
- * guild.commands.fetchPermissions('123456789012345678')
- * .then(perms => console.log(`Fetched permissions for ${perms.length} users`))
- * .catch(console.error);
- * @example
- * // Fetch permissions for all commands
- * client.application.commands.fetchPermissions()
- * .then(perms => console.log(`Fetched permissions for ${perms.size} commands`))
- * .catch(console.error);
- */
- async fetchPermissions(command, guildID) {
- if (!this.guild && !guildID) throw new Error('GLOBAL_COMMAND_PERMISSIONS');
- if (command) {
- const id = this.resolveID(command);
- if (!id) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
-
- const data = await this.commandPath({ id, guildID }).permissions.get();
- return data.permissions.map(perm => this.constructor.transformPermissions(perm, true));
- }
-
- const data = await this.commandPath({ guildID }).permissions.get();
- return data.reduce(
- (coll, perm) =>
- coll.set(
- perm.id,
- perm.permissions.map(p => this.constructor.transformPermissions(p, true)),
- ),
- new Collection(),
- );
- }
-
- /**
- * Data used for overwriting the permissions for all application commands in a guild.
- * @typedef {Object} GuildApplicationCommandPermissionData
- * @prop {Snowflake} id The ID of the command
- * @prop {ApplicationCommandPermissionData[]} permissions The permissions for this command
- */
-
- /**
- * Sets the permissions for a command.
- * When calling this on ApplicationCommandManager, guildID is required.
- * To set multiple permissions for an uncached guild use `setPermissions(permissions, '123456789012345678')`
- * @param {ApplicationCommandResolvable|GuildApplicationCommandPermissionData[]} command The command to edit the
- * permissions for, or an array of guild application command permissions to set the permissions of all commands
- * @param {ApplicationCommandPermissionData[]} [permissions] The new permissions for the command
- * @param {Snowflake} [guildID] ID of the guild to get the permissions for,
- * ignored when using a {@link GuildApplicationCommandManager}
- * @returns {Promise>}
- * @example
- * // Set the permissions for one command
- * client.application.commands.setPermissions('123456789012345678', [
- * {
- * id: '876543210987654321',
- * type: 'USER',
- * permission: false,
- * },
- * ])
- * .then(console.log)
- * .catch(console.error);
- * @example
- * // Set the permissions for all commands
- * guild.commands.setPermissions([
- * {
- * id: '123456789012345678',
- * permissions: [{
- * id: '876543210987654321',
- * type: 'USER',
- * permission: false,
- * }],
- * },
- * ])
- * .then(console.log)
- * .catch(console.error);
- */
- async setPermissions(command, permissions, guildID) {
- const id = this.resolveID(command);
-
- if (id) {
- if (!this.guild && !guildID) throw new Error('GLOBAL_COMMAND_PERMISSIONS');
- const data = await this.commandPath({ id, guildID }).permissions.put({
- data: { permissions: permissions.map(perm => this.constructor.transformPermissions(perm)) },
- });
- return data.permissions.map(perm => this.constructor.transformPermissions(perm, true));
- }
-
- if (typeof permissions === 'string') {
- guildID = permissions;
- permissions = undefined;
- }
-
- if (!this.guild && !guildID) throw new Error('GLOBAL_COMMAND_PERMISSIONS');
-
- const data = await this.commandPath({ guildID }).permissions.put({
- data: command.map(perm => ({
- id: perm.id,
- permissions: perm.permissions.map(p => this.constructor.transformPermissions(p)),
- })),
- });
- return data.reduce(
- (coll, perm) =>
- coll.set(
- perm.id,
- perm.permissions.map(p => this.constructor.transformPermissions(p, true)),
- ),
- new Collection(),
- );
- }
-
- /**
- * Transforms an {@link ApplicationCommandPermissionData} object into something that can be used with the API.
- * @param {ApplicationCommandPermissionData} permissions The permissions to transform
- * @param {boolean} [received] Whether these permissions have been received from Discord
- * @returns {APIApplicationCommandPermissions}
- * @private
- */
- static transformPermissions(permissions, received) {
- return {
- id: permissions.id,
- permission: permissions.permission,
- type:
- typeof permissions.type === 'number' && !received
- ? permissions.type
- : ApplicationCommandPermissionTypes[permissions.type],
- };
- }
}
module.exports = ApplicationCommandManager;
-
-/**
- * @external APIApplicationCommandPermissions
- * @see {@link https://discord.com/developers/docs/interactions/slash-commands#applicationcommandpermissions}
- */
diff --git a/src/managers/ApplicationCommandPermissionsManager.js b/src/managers/ApplicationCommandPermissionsManager.js
new file mode 100644
index 000000000000..bf10454efb31
--- /dev/null
+++ b/src/managers/ApplicationCommandPermissionsManager.js
@@ -0,0 +1,420 @@
+'use strict';
+
+const { Error, TypeError } = require('../errors');
+const Collection = require('../util/Collection');
+const { ApplicationCommandPermissionTypes, APIErrors } = require('../util/Constants');
+
+/**
+ * Manages API methods for permissions of Application Commands.
+ */
+class ApplicationCommandPermissionsManager {
+ constructor(manager) {
+ /**
+ * The manager or command that this manager belongs to
+ * @type {ApplicationCommandManager|ApplicationCommand}
+ */
+ this.manager = manager;
+
+ /**
+ * The guild that this manager acts on
+ * @type {?Guild}
+ */
+ this.guild = manager.guild ?? null;
+
+ /**
+ * The id of the guild that this manager acts on
+ * @type {?Snowflake}
+ */
+ this.guildID = manager.guildID ?? manager.guild?.id ?? null;
+
+ /**
+ * The id of the command this manager acts on
+ * @type {?Snowflake}
+ */
+ this.commandID = manager.id ?? null;
+
+ /**
+ * The client that instantiated this Manager
+ * @name ApplicationCommandPermissionsManager#client
+ * @type {Client}
+ * @readonly
+ */
+ Object.defineProperty(this, 'client', { value: manager.client });
+ }
+
+ /**
+ * The APIRouter path to the commands
+ * @param {Snowflake} guildID ID of the guild to use in the path,
+ * @param {Snowflake} [commandID] ID of the application command
+ * @returns {Object}
+ * @private
+ */
+ permissionsPath(guildID, commandID) {
+ return this.client.api.applications(this.client.application.id).guilds(guildID).commands(commandID).permissions;
+ }
+
+ /**
+ * Data for setting the permissions of an application command.
+ * @typedef {Object} ApplicationCommandPermissionData
+ * @property {Snowflake} id The ID of the role or user
+ * @property {ApplicationCommandPermissionType|number} type Whether this permission is for a role or a user
+ * @property {boolean} permission Whether the role or user has the permission to use this command
+ */
+
+ /**
+ * The object returned when fetching permissions for an application command.
+ * @typedef {Object} ApplicationCommandPermissions
+ * @property {Snowflake} id The ID of the role or user
+ * @property {ApplicationCommandPermissionType} type Whether this permission is for a role or a user
+ * @property {boolean} permission Whether the role or user has the permission to use this command
+ */
+
+ /**
+ * Options for managing permissions for one or more Application Commands
+ * When passing these options to a manager where `guildID` is `null`,
+ * `guild` is a required parameter
+ * @typedef {Object} BaseApplicationCommandPermissionsOptions
+ * @param {GuildResolvable} [guild] The guild to modify / check permissions for
+ * Ignored when the manager has a non-null `guildID` property
+ * @param {ApplicationCommandResolvable} [command] The command to modify / check permissions for
+ * Ignored when the manager has a non-null `commandID` property
+ */
+
+ /**
+ * Fetches the permissions for one or multiple commands.
+ * @param {BaseApplicationCommandPermissionsOptions} [options] Options used to fetch permissions
+ * @returns {Promise>}
+ * @example
+ * // Fetch permissions for one command
+ * guild.commands.permissions.fetch({ command: '123456789012345678' })
+ * .then(perms => console.log(`Fetched permissions for ${perms.length} users`))
+ * .catch(console.error);
+ * @example
+ * // Fetch permissions for all commands in a guild
+ * client.application.commands.permissions.fetch({ guild: '123456789012345678' })
+ * .then(perms => console.log(`Fetched permissions for ${perms.size} commands`))
+ * .catch(console.error);
+ */
+ async fetch({ guild, command } = {}) {
+ const { guildID, commandID } = this._validateOptions(guild, command);
+ if (commandID) {
+ const data = await this.permissionsPath(guildID, commandID).get();
+ return data.permissions.map(perm => this.constructor.transformPermissions(perm, true));
+ }
+
+ const data = await this.permissionsPath(guildID).get();
+ return data.reduce(
+ (coll, perm) =>
+ coll.set(
+ perm.id,
+ perm.permissions.map(p => this.constructor.transformPermissions(p, true)),
+ ),
+ new Collection(),
+ );
+ }
+
+ /**
+ * Data used for overwriting the permissions for all application commands in a guild.
+ * @typedef {Object} GuildApplicationCommandPermissionData
+ * @property {Snowflake} id The ID of the command
+ * @property {ApplicationCommandPermissionData[]} permissions The permissions for this command
+ */
+
+ /**
+ * Options used to set permissions for one or more Application Commands in a guild
+ * One of `command` AND `permissions`, OR `fullPermissions` is required.
+ * `fullPermissions` is not a valid option when passing to a manager where `commandID` is non-null
+ * @typedef {BaseApplicationCommandPermissionsOptions} SetApplicationCommandPermissionsOptions
+ * @param {ApplicationCommandPermissionData[]} [permissions] The new permissions for the command
+ * @param {GuildApplicationCommandPermissionData[]} [fullPermissions] The new permissions for all commands
+ * in a guild When this parameter is set, permissions and command are ignored
+ */
+
+ /**
+ * Sets the permissions for one or more commands.
+ * @param {SetApplicationCommandPermissionsOptions} options Options used to set permissions
+ * @returns {Promise>}
+ * @example
+ * // Set the permissions for one command
+ * client.application.commands.permissions.set({ command: '123456789012345678', permissions: [
+ * {
+ * id: '876543210987654321',
+ * type: 'USER',
+ * permission: false,
+ * },
+ * ]})
+ * .then(console.log)
+ * .catch(console.error);
+ * @example
+ * // Set the permissions for all commands
+ * guild.commands.permissions.set({ fullPermissions: [
+ * {
+ * id: '123456789012345678',
+ * permissions: [{
+ * id: '876543210987654321',
+ * type: 'USER',
+ * permission: false,
+ * }],
+ * },
+ * ]})
+ * .then(console.log)
+ * .catch(console.error);
+ */
+ async set({ guild, command, permissions, fullPermissions } = {}) {
+ const { guildID, commandID } = this._validateOptions(guild, command);
+
+ if (commandID) {
+ if (!Array.isArray(permissions)) {
+ throw new TypeError('INVALID_TYPE', 'permissions', 'Array of ApplicationCommandPermissionData', true);
+ }
+ const data = await this.permissionsPath(guildID, commandID).put({
+ data: { permissions: permissions.map(perm => this.constructor.transformPermissions(perm)) },
+ });
+ return data.permissions.map(perm => this.constructor.transformPermissions(perm, true));
+ }
+
+ if (!Array.isArray(fullPermissions)) {
+ throw new TypeError('INVALID_TYPE', 'fullPermissions', 'Array of GuildApplicationCommandPermissionData', true);
+ }
+
+ const APIPermissions = [];
+ for (const perm of fullPermissions) {
+ if (!Array.isArray(perm.permissions)) throw new TypeError('INVALID_ELEMENT', 'Array', 'fullPermissions', perm);
+ APIPermissions.push({
+ id: perm.id,
+ permissions: perm.permissions.map(p => this.constructor.transformPermissions(p)),
+ });
+ }
+ const data = await this.permissionsPath(guildID).put({
+ data: APIPermissions,
+ });
+ return data.reduce(
+ (coll, perm) =>
+ coll.set(
+ perm.id,
+ perm.permissions.map(p => this.constructor.transformPermissions(p, true)),
+ ),
+ new Collection(),
+ );
+ }
+
+ /**
+ * Options used to add permissions to a command
+ * The `command` parameter is not optional when the managers `commandID` is `null`
+ * @typedef {BaseApplicationCommandPermissionsOptions} AddApplicationCommandPermissionsOptions
+ * @param {ApplicationCommandPermissionData[]} permissions The permissions to add to the command
+ */
+
+ /**
+ * Add permissions to a command.
+ * @param {AddApplicationCommandPermissionsOptions} options Options used to add permissions
+ * @returns {Promise}
+ * @example
+ * // Block a role from the command permissions
+ * guild.commands.permissions.add({ command: '123456789012345678', permissions: [
+ * {
+ * id: '876543211234567890',
+ * type: 'ROLE',
+ * permission: false
+ * },
+ * ]})
+ * .then(console.log)
+ * .catch(console.error);
+ */
+ async add({ guild, command, permissions }) {
+ const { guildID, commandID } = this._validateOptions(guild, command);
+ if (!commandID) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
+ if (!Array.isArray(permissions)) {
+ throw new TypeError('INVALID_TYPE', 'permissions', 'Array of ApplicationCommandPermissionData', true);
+ }
+
+ let existing = [];
+ try {
+ existing = await this.fetch({ guild: guildID, command: commandID });
+ } catch (error) {
+ if (error.code !== APIErrors.UNKNOWN_APPLICATION_COMMAND_PERMISSIONS) throw error;
+ }
+
+ const newPermissions = permissions.slice();
+ for (const perm of existing) {
+ if (!newPermissions.some(x => x.id === perm.id)) {
+ newPermissions.push(perm);
+ }
+ }
+
+ return this.set({ guild: guildID, command: commandID, permissions: newPermissions });
+ }
+
+ /**
+ * Options used to remove permissions from a command
+ * The `command` parameter is not optional when the managers `commandID` is `null`
+ * @typedef {BaseApplicationCommandPermissionsOptions} RemoveApplicationCommandPermissionsOptions
+ * @param {UserResolvable|UserResolvable[]} [users] The user(s) to remove from the command permissions
+ * One of `users` or `roles` is required
+ * @param {RoleResolvable|RoleResolvable[]} [roles] The role(s) to remove from the command permissions
+ * One of `users` or `roles` is required
+ */
+
+ /**
+ * Remove permissions from a command.
+ * @param {RemoveApplicationCommandPermissionsOptions} options Options used to remove permissions
+ * @returns {Promise}
+ * @example
+ * // Remove a user permission from this command
+ * guild.commands.permissions.remove({ command: '123456789012345678', users: '876543210123456789' })
+ * .then(console.log)
+ * .catch(console.error);
+ * @example
+ * // Remove multiple roles from this command
+ * guild.commands.permissions.remove({
+ * command: '123456789012345678', roles: ['876543210123456789', '765432101234567890']
+ * })
+ * .then(console.log)
+ * .catch(console.error);
+ */
+ async remove({ guild, command, users, roles }) {
+ const { guildID, commandID } = this._validateOptions(guild, command);
+ if (!commandID) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
+
+ if (!users && !roles) throw new TypeError('INVALID_TYPE', 'users OR roles', 'Array or Resolvable', true);
+
+ let resolvedIDs = [];
+ if (Array.isArray(users)) {
+ users.forEach(user => {
+ const userID = this.client.users.resolveID(user);
+ if (!userID) throw new TypeError('INVALID_ELEMENT', 'Array', 'users', user);
+ resolvedIDs.push(userID);
+ });
+ } else if (users) {
+ const userID = this.client.users.resolveID(users);
+ if (!userID) {
+ throw new TypeError('INVALID_TYPE', 'users', 'Array or UserResolvable');
+ }
+ resolvedIDs.push(userID);
+ }
+
+ if (Array.isArray(roles)) {
+ roles.forEach(role => {
+ if (typeof role === 'string') {
+ resolvedIDs.push(role);
+ return;
+ }
+ if (!this.guild) throw new Error('GUILD_UNCACHED_ROLE_RESOLVE');
+ const roleID = this.guild.roles.resolveID(role);
+ if (!roleID) throw new TypeError('INVALID_ELEMENT', 'Array', 'users', role);
+ resolvedIDs.push(roleID);
+ });
+ } else if (roles) {
+ if (typeof roles === 'string') {
+ resolvedIDs.push(roles);
+ } else {
+ if (!this.guild) throw new Error('GUILD_UNCACHED_ROLE_RESOLVE');
+ const roleID = this.guild.roles.resolveID(roles);
+ if (!roleID) {
+ throw new TypeError('INVALID_TYPE', 'users', 'Array or RoleResolvable');
+ }
+ resolvedIDs.push(roleID);
+ }
+ }
+
+ let existing = [];
+ try {
+ existing = await this.fetch({ guild: guildID, command: commandID });
+ } catch (error) {
+ if (error.code !== APIErrors.UNKNOWN_APPLICATION_COMMAND_PERMISSIONS) throw error;
+ }
+
+ const permissions = existing.filter(perm => !resolvedIDs.includes(perm.id));
+
+ return this.set({ guild: guildID, command: commandID, permissions });
+ }
+
+ /**
+ * Options used to check existance of permissions on a command
+ * The `command` parameter is not optional when the managers `commandID` is `null`
+ * @typedef {BaseApplicationCommandPermissionsOptions} HasApplicationCommandPermissionsOptions
+ * @param {UserResolvable|RoleResolvable} permissionID The user or role to check if a permission exists for
+ * on this command.
+ */
+
+ /**
+ * Check whether a permission exists for a user or role
+ * @param {AddApplicationCommandPermissionsOptions} options Options used to check permissions
+ * @returns {Promise}
+ * @example
+ * // Check whether a user has permission to use a command
+ * guild.commands.permissions.has({ command: '123456789012345678', permissionID: '876543210123456789' })
+ * .then(console.log)
+ * .catch(console.error);
+ */
+ async has({ guild, command, permissionID }) {
+ const { guildID, commandID } = this._validateOptions(guild, command);
+ if (!commandID) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
+
+ if (!permissionID) throw new TypeError('INVALID_TYPE', 'permissionsID', 'UserResolvable or RoleResolvable');
+ let resolvedID = permissionID;
+ if (typeof permissionID !== 'string') {
+ resolvedID = this.client.users.resolveID(permissionID);
+ if (!resolvedID) {
+ if (!this.guild) throw new Error('GUILD_UNCACHED_ROLE_RESOLVE');
+ resolvedID = this.guild.roles.resolveID(permissionID);
+ }
+ if (!resolvedID) {
+ throw new TypeError('INVALID_TYPE', 'permissionID', 'UserResolvable or RoleResolvable');
+ }
+ }
+
+ let existing = [];
+ try {
+ existing = await this.fetch({ guild: guildID, command: commandID });
+ } catch (error) {
+ if (error.code !== APIErrors.UNKNOWN_APPLICATION_COMMAND_PERMISSIONS) throw error;
+ }
+
+ return existing.some(perm => perm.id === resolvedID);
+ }
+
+ _validateOptions(guild, command) {
+ const guildID = this.guildID ?? this.client.guilds.resolveID(guild);
+ if (!guildID) throw new Error('GLOBAL_COMMAND_PERMISSIONS');
+ let commandID = this.commandID;
+ if (command && !commandID) {
+ commandID = this.manager.resolveID?.(command);
+ if (!commandID && this.guild) {
+ commandID = this.guild.commands.resolveID(command);
+ }
+ if (!commandID) {
+ commandID = this.client.application?.commands.resolveID(command);
+ }
+ if (!commandID) {
+ throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable', true);
+ }
+ }
+ return { guildID, commandID };
+ }
+
+ /**
+ * Transforms an {@link ApplicationCommandPermissionData} object into something that can be used with the API.
+ * @param {ApplicationCommandPermissionData} permissions The permissions to transform
+ * @param {boolean} [received] Whether these permissions have been received from Discord
+ * @returns {APIApplicationCommandPermissions}
+ * @private
+ */
+ static transformPermissions(permissions, received) {
+ return {
+ id: permissions.id,
+ permission: permissions.permission,
+ type:
+ typeof permissions.type === 'number' && !received
+ ? permissions.type
+ : ApplicationCommandPermissionTypes[permissions.type],
+ };
+ }
+}
+
+module.exports = ApplicationCommandPermissionsManager;
+
+/**
+ * @external APIApplicationCommandPermissions
+ * @see {@link https://discord.com/developers/docs/interactions/slash-commands#applicationcommandpermissions}
+ */
diff --git a/src/managers/GuildApplicationCommandManager.js b/src/managers/GuildApplicationCommandManager.js
index 3a78fbf7f864..97fea5e45bdd 100644
--- a/src/managers/GuildApplicationCommandManager.js
+++ b/src/managers/GuildApplicationCommandManager.js
@@ -1,6 +1,7 @@
'use strict';
const ApplicationCommandManager = require('./ApplicationCommandManager');
+const ApplicationCommandPermissionsManager = require('./ApplicationCommandPermissionsManager');
/**
* An extension for guild-specific application commands.
@@ -15,6 +16,12 @@ class GuildApplicationCommandManager extends ApplicationCommandManager {
* @type {Guild}
*/
this.guild = guild;
+
+ /**
+ * The manager for permissions of arbitrary commands on this guild
+ * @type {ApplicationCommandPermissionsManager}
+ */
+ this.permissions = new ApplicationCommandPermissionsManager(this);
}
}
diff --git a/src/structures/ApplicationCommand.js b/src/structures/ApplicationCommand.js
index 9b7be236a3a3..2b81b0e48355 100644
--- a/src/structures/ApplicationCommand.js
+++ b/src/structures/ApplicationCommand.js
@@ -1,6 +1,7 @@
'use strict';
const Base = require('./Base');
+const ApplicationCommandPermissionsManager = require('../managers/ApplicationCommandPermissionsManager');
const { ApplicationCommandOptionTypes } = require('../util/Constants');
const SnowflakeUtil = require('../util/SnowflakeUtil');
@@ -9,7 +10,7 @@ const SnowflakeUtil = require('../util/SnowflakeUtil');
* @extends {Base}
*/
class ApplicationCommand extends Base {
- constructor(client, data, guild) {
+ constructor(client, data, guild, guildID) {
super(client);
/**
@@ -24,6 +25,19 @@ class ApplicationCommand extends Base {
*/
this.guild = guild ?? null;
+ /**
+ * The guild ID this command is part of, this may be non-null when `guild` is `null` if the command
+ * was fetched from the `ApplicationCommandManager`
+ * @type {?Snowflake}
+ */
+ this.guildID = guild?.id ?? guildID ?? null;
+
+ /**
+ * The manager for permissions of this command on its guild or arbitrary gulds when the command is global
+ * @type {ApplicationCommandPermissionsManager}
+ */
+ this.permissions = new ApplicationCommandPermissionsManager(this);
+
this._patch(data);
}
@@ -113,7 +127,7 @@ class ApplicationCommand extends Base {
* .catch(console.error);
*/
edit(data) {
- return this.manager.edit(this, data);
+ return this.manager.edit(this, data, this.guildID);
}
/**
@@ -126,62 +140,7 @@ class ApplicationCommand extends Base {
* .catch(console.error);
*/
delete() {
- return this.manager.delete(this);
- }
-
- /**
- * Data for setting the permissions of an application command.
- * @typedef {Object} ApplicationCommandPermissionData
- * @property {Snowflake} id The ID of the role or user
- * @property {ApplicationCommandPermissionType|number} type Whether this permission is for a role or a user
- * @property {boolean} permission Whether the role or user has the permission to use this command
- */
-
- /**
- * The object returned when fetching permissions for an application command.
- * @typedef {Object} ApplicationCommandPermissions
- * @property {Snowflake} id The ID of the role or user
- * @property {ApplicationCommandPermissionType} type Whether this permission is for a role or a user
- * @property {boolean} permission Whether the role or user has the permission to use this command
- */
-
- /**
- * Fetches the permissions for this command.
- * You must specify guildID if this command is handled by a {@link ApplicationCommandManager},
- * including commands fetched for arbitrary guilds from it, otherwise it is ignored.
- * @param {Snowflake} [guildID] ID for the guild to fetch permissions for if this is a global command
- * @returns {Promise}
- * @example
- * // Fetch permissions for this command
- * command.fetchPermissions()
- * .then(perms => console.log(`Fetched permissions for ${perms.length} users`))
- * .catch(console.error);
- */
- fetchPermissions(guildID) {
- return this.manager.fetchPermissions(this, guildID);
- }
-
- /**
- * Sets the permissions for this command.
- * You must specify guildID if this command is handled by a {@link ApplicationCommandManager},
- * including commands fetched for arbitrary guilds from it, otherwise it is ignored.
- * @param {ApplicationCommandPermissionData[]} permissions The new permissions for the command
- * @param {Snowflake} [guildID] ID for the guild to fetch permissions for if this is a global command
- * @returns {Promise}
- * @example
- * // Set the permissions for this command
- * command.setPermissions([
- * {
- * id: '876543210987654321',
- * type: 'USER',
- * permission: false,
- * },
- * ])
- * .then(console.log)
- * .catch(console.error);
- */
- setPermissions(permissions, guildID) {
- return this.manager.setPermissions(this, permissions, guildID);
+ return this.manager.delete(this, this.guildID);
}
/**
diff --git a/typings/index.d.ts b/typings/index.d.ts
index 125bbf5e3431..e738671d8b1c 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -248,24 +248,26 @@ declare module 'discord.js' {
public toString(): string | null;
}
- export class ApplicationCommand extends Base {
- constructor(client: Client, data: unknown, guild?: Guild);
+ export class ApplicationCommand extends Base {
+ constructor(client: Client, data: unknown, guild?: Guild, guildID?: Snowflake);
public readonly createdAt: Date;
public readonly createdTimestamp: number;
public defaultPermission: boolean;
public description: string;
public guild: Guild | null;
+ public guildID: Snowflake | null;
public readonly manager: ApplicationCommandManager;
public id: Snowflake;
public name: string;
public options: ApplicationCommandOption[];
- public delete(): Promise;
- public edit(data: ApplicationCommandData): Promise;
- public fetchPermissions(guildID?: Snowflake): Promise;
- public setPermissions(
- permissions: ApplicationCommandPermissionData[],
- guildID?: Snowflake,
- ): Promise;
+ public permissions: ApplicationCommandPermissionsManager<
+ PermissionsFetchType,
+ PermissionsFetchType,
+ Guild | null,
+ Snowflake
+ >;
+ public delete(): Promise>;
+ public edit(data: ApplicationCommandData): Promise>;
private static transformOption(option: ApplicationCommandOptionData, received?: boolean): unknown;
}
@@ -512,7 +514,7 @@ declare module 'discord.js' {
}
export class CommandInteraction extends Interaction {
- public readonly command: ApplicationCommand | null;
+ public readonly command: ApplicationCommand | ApplicationCommand<{ guild: GuildResolvable }> | null;
public readonly channel: TextChannel | DMChannel | NewsChannel | PartialDMChannel | null;
public channelID: Snowflake;
public commandID: Snowflake;
@@ -2292,47 +2294,88 @@ declare module 'discord.js' {
public valueOf(): Collection;
}
- export class ApplicationCommandManager extends BaseManager<
- Snowflake,
- ApplicationCommand,
- ApplicationCommandResolvable
- > {
+ export class ApplicationCommandManager<
+ ApplicationCommandType = ApplicationCommand<{ guild: GuildResolvable }>,
+ PermissionsOptionsExtras = { guild: GuildResolvable },
+ PermissionsGuildType = null,
+ > extends BaseManager {
constructor(client: Client, iterable?: Iterable);
+ public permissions: ApplicationCommandPermissionsManager<
+ { command?: ApplicationCommandResolvable } & PermissionsOptionsExtras,
+ { command: ApplicationCommandResolvable } & PermissionsOptionsExtras,
+ PermissionsGuildType,
+ null
+ >;
private commandPath({ id, guildID }: { id?: Snowflake; guildID?: Snowflake }): unknown;
- public create(command: ApplicationCommandData, guildID?: Snowflake): Promise;
- public delete(command: ApplicationCommandResolvable, guildID?: Snowflake): Promise;
+ public create(command: ApplicationCommandData, guildID: Snowflake): Promise;
+ public create(command: ApplicationCommandData, guildID?: Snowflake): Promise;
+ public delete(command: ApplicationCommandResolvable, guildID?: Snowflake): Promise;
+ public edit(
+ command: ApplicationCommandResolvable,
+ data: ApplicationCommandData,
+ guildID: Snowflake,
+ ): Promise;
public edit(
command: ApplicationCommandResolvable,
data: ApplicationCommandData,
guildID?: Snowflake,
+ ): Promise;
+ public fetch(
+ id: Snowflake,
+ options: FetchApplicationCommandOptions & { guild: GuildResolvable },
): Promise;
- public fetch(id: Snowflake, options?: FetchApplicationCommandOptions): Promise;
+ public fetch(id: Snowflake, options?: FetchApplicationCommandOptions): Promise;
public fetch(
id?: Snowflake,
options?: FetchApplicationCommandOptions,
- ): Promise>;
- public fetchPermissions(
- command: undefined,
- guildID: Snowflake,
- ): Promise>;
- public fetchPermissions(
- command: ApplicationCommandResolvable,
- guildID: Snowflake,
- ): Promise;
+ ): Promise>;
public set(
commands: ApplicationCommandData[],
guildID?: Snowflake,
): Promise>;
- public setPermissions(
- command: ApplicationCommandResolvable,
- permissions: ApplicationCommandPermissionData[],
- guildID: Snowflake,
+ public set(
+ commands: ApplicationCommandData[],
+ guildID?: Snowflake,
+ ): Promise>;
+ private static transformCommand(command: ApplicationCommandData): unknown;
+ }
+
+ export class ApplicationCommandPermissionsManager {
+ constructor(manager: ApplicationCommandManager | GuildApplicationCommandManager | ApplicationCommand);
+ public client: Client;
+ public commandID: CommandIDType;
+ public guild: GuildType;
+ public guildID: Snowflake | null;
+ public manager: ApplicationCommandManager | GuildApplicationCommandManager | ApplicationCommand;
+ public add(
+ options: BaseOptions & { permissions: ApplicationCommandPermissionData[] },
): Promise;
- public setPermissions(
- permissions: GuildApplicationCommandPermissionData[],
- guildID: Snowflake,
+ public has(options: BaseOptions & { permissionsID: UserResolvable | RoleResolvable }): Promise;
+ public fetch(options: FetchSingleOptions): Promise;
+ public fetch(options: BaseOptions): Promise>;
+ public remove(
+ options:
+ | (BaseOptions & {
+ users: UserResolvable | UserResolvable[];
+ roles?: RoleResolvable | RoleResolvable[];
+ })
+ | (BaseOptions & {
+ users?: UserResolvable | UserResolvable[];
+ roles: RoleResolvable | RoleResolvable[];
+ }),
+ ): Promise;
+ public set(
+ options: BaseOptions & {
+ command: ApplicationCommandResolvable;
+ permissions: ApplicationCommandPermissionData[];
+ },
+ ): Promise;
+ public set(
+ options: BaseOptions & {
+ fullPermissions: GuildApplicationCommandPermissionData[];
+ },
): Promise>;
- private static transformCommand(command: ApplicationCommandData): unknown;
+ private permissionsPath(guildID: Snowflake, commandID?: Snowflake): unknown;
private static transformPermissions(permissions: ApplicationCommandPermissionData, received?: boolean): unknown;
}
@@ -2346,7 +2389,7 @@ declare module 'discord.js' {
public fetch(id: Snowflake, options?: BaseFetchOptions): Promise;
}
- export class GuildApplicationCommandManager extends ApplicationCommandManager {
+ export class GuildApplicationCommandManager extends ApplicationCommandManager {
constructor(guild: Guild, iterable?: Iterable);
public guild: Guild;
public create(command: ApplicationCommandData): Promise;