How do I check if user has permission to execute command

What the title says. I found ApplicationCommandPermissionsManager#has and tried it with
guilds.commands.permissions.has({ command: this.id, permissionId: member.id })
guilds.commands.permissions.has({ command: this.id, permissionId: member.id })
but all that method does is to check if that specific user has an override in the permissions. I want to check if the user's combined permissions (from all roles and individual overrides) are sufficient to invoke the command. Do I really have to iterate through every single channel in the guild and every single role on the user and call has() all those times to figure this out? Surely not, because that wouldn't even cover admin permissions AFAIK.
33 Replies
d.js docs
d.js docs2y ago
• What's your exact discord.js npm list discord.js and node node -v version? • Post the full error stack trace, not just the top part! • Show your code! • Explain what exactly your issue is. • Not a discord.js issue? Check out #useful-servers.
monbrey
monbrey2y ago
Im kinda confused - why do you need to check?
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
LeMorrow
LeMorrow2y ago
I need to check because I have a button that invokes a command Yeah exactly
monbrey
monbrey2y ago
Ahhh okay
LeMorrow
LeMorrow2y ago
The best thing would be if I could get a Bitfield with the command permissions Just like default permissions But I can't find anything like that
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
monbrey
monbrey2y ago
There is <ButtonInteraction>.memberPermissions
LeMorrow
LeMorrow2y ago
I need the permissions for the command though
monbrey
monbrey2y ago
Which is the calculated permissions for the member in the channel in which the interactionwas executed Yeah command permissions cant be simplified into a bitfield
LeMorrow
LeMorrow2y ago
Figured as much You're saying this isn't possible then? I don't necessarily need a bit field
monbrey
monbrey2y ago
I still kinda think you've overcomplicated it? So you have a command A that sends buttons, and when a button is clicked it will execute command B And you want to know if the user has permission to execute Command B In that channel?
LeMorrow
LeMorrow2y ago
Correct 👍 Small detail is that there is no command A, it's just buttons on a message But the buttons do invoke command B (and C, and so on) One button per command
monbrey
monbrey2y ago
Alright well for starters, per channel isnt necessary You probably would need to do it per role though to check But uhhh
LeMorrow
LeMorrow2y ago
et voilà
monbrey
monbrey2y ago
member.roles.cache.some(r => guild.commands.permissions.has(...)) should let you know if any role has an overwrite
LeMorrow
LeMorrow2y ago
for a role yeah, I guess I would also do it for the user that clicks the button
monbrey
monbrey2y ago
Yeah, a single user check would be another line
LeMorrow
LeMorrow2y ago
I kinda see where this is going
monbrey
monbrey2y ago
And if both of those are false, you can assume its default permissions
LeMorrow
LeMorrow2y ago
And first condition would be if the user has admin perms
monbrey
monbrey2y ago
yeah thats an easy check
LeMorrow
LeMorrow2y ago
yep
monbrey
monbrey2y ago
Yeah I'd pass this off to a re-usable utility function for sure
LeMorrow
LeMorrow2y ago
you said channel overrides are not needed, I couldn't really understand why?
monbrey
monbrey2y ago
Iterating over every channel isnt
LeMorrow
LeMorrow2y ago
right just check the channel we're pressing the button in
monbrey
monbrey2y ago
Do you mean that the admin might have disabled the /archivethread command in this spot, so you dont want the button to work? yup And you already have <ButtonInteraction>.memberPermissions which is the calculated set for that channel
LeMorrow
LeMorrow2y ago
but since it's in a thread I would have to get the parent's channel ID which can be NULL 😉 shoutout to this issue https://github.com/discordjs/discord.js/issues/8471 but yeah that's whatever I guess Is it documented anywhere in what order the permission overrides are calculated? Like does channel take precedence over user and does allow go over deny, etc because I would have to follow those rules too What to check first and whether an explicit disallow for a user makes an explicit allow on a role useless or not
monbrey
monbrey2y ago
Dunno tbh I imagine Channel > User > Role
LeMorrow
LeMorrow2y ago
I guess I have some experimenting to do then, I'll post here when I've figured it out for future reference Thanks for everything so far, just needed a nudge in the right direction Alright I think I've done it It became longer than I thought I don't know how well it will work for private threads or other edge cases but here it is
public async hasPermissionToExecute(member: GuildMember, channel: GuildTextBasedChannel): Promise<boolean> {
if (member.permissionsIn(channel).has(PermissionFlagsBits.Administrator)) return true;

const channelId = channel.isThread() ? channel.parentId : channel.id;
if (!channelId) return false; // Type error https://github.com/discordjs/discord.js/issues/8471

// Type error: https://github.com/discordjs/discord.js/issues/8096
const permissionOverrides = await member.guild.commands.permissions.fetch({});
const applicationId = this.bot.client.application?.id;
if (!applicationId) return false; // bot is not ready to receive events

const commandPermissions = permissionOverrides.get(this.id);
if (commandPermissions) {
return this.isAllowed(member, channelId, commandPermissions);
}

const botPermissions = permissionOverrides.get(applicationId);
if (botPermissions) {
return this.isAllowed(member, channelId, botPermissions);
}

// Use command default permissions because there are no server overrides
return member.permissionsIn(channel).has(getMinimumRequiredPermissions() | (this.defaultPermissions ?? 0n));
}
public async hasPermissionToExecute(member: GuildMember, channel: GuildTextBasedChannel): Promise<boolean> {
if (member.permissionsIn(channel).has(PermissionFlagsBits.Administrator)) return true;

const channelId = channel.isThread() ? channel.parentId : channel.id;
if (!channelId) return false; // Type error https://github.com/discordjs/discord.js/issues/8471

// Type error: https://github.com/discordjs/discord.js/issues/8096
const permissionOverrides = await member.guild.commands.permissions.fetch({});
const applicationId = this.bot.client.application?.id;
if (!applicationId) return false; // bot is not ready to receive events

const commandPermissions = permissionOverrides.get(this.id);
if (commandPermissions) {
return this.isAllowed(member, channelId, commandPermissions);
}

const botPermissions = permissionOverrides.get(applicationId);
if (botPermissions) {
return this.isAllowed(member, channelId, botPermissions);
}

// Use command default permissions because there are no server overrides
return member.permissionsIn(channel).has(getMinimumRequiredPermissions() | (this.defaultPermissions ?? 0n));
}
test why is clyde complaining when I send the rest of the code that I'm not friends with the recipient lmao
LeMorrow
LeMorrow2y ago
LeMorrow
LeMorrow2y ago
private async isAllowed(member: GuildMember, channelId: string, permissions: ApplicationCommandPermissions[]) {
const memberRoleOverrides = [];
let allChannelsAllowed = true; // default value, only in "permissions" if overwritten
let everyoneRoleAllowed = true; // default value, only in "permissions" if overwritten
let memberOverride = undefined;
let channelOverride = undefined;

for (const { id, permission: allowed, type } of permissions) {
const guildId = BigInt(member.guild.id);

// Application command permission constant (see Discord developer docs)
const isAnyChannel = id === (guildId - 1n).toString();
if (isAnyChannel) {
allChannelsAllowed = allowed;
continue;
}

// Application command permission constant (see Discord developer docs)
const isEveryoneRole = id === guildId.toString();
if (isEveryoneRole) {
everyoneRoleAllowed = allowed;
continue;
}
private async isAllowed(member: GuildMember, channelId: string, permissions: ApplicationCommandPermissions[]) {
const memberRoleOverrides = [];
let allChannelsAllowed = true; // default value, only in "permissions" if overwritten
let everyoneRoleAllowed = true; // default value, only in "permissions" if overwritten
let memberOverride = undefined;
let channelOverride = undefined;

for (const { id, permission: allowed, type } of permissions) {
const guildId = BigInt(member.guild.id);

// Application command permission constant (see Discord developer docs)
const isAnyChannel = id === (guildId - 1n).toString();
if (isAnyChannel) {
allChannelsAllowed = allowed;
continue;
}

// Application command permission constant (see Discord developer docs)
const isEveryoneRole = id === guildId.toString();
if (isEveryoneRole) {
everyoneRoleAllowed = allowed;
continue;
}
if (type === ApplicationCommandPermissionType.Channel && channelId === id) {
channelOverride = allowed;
continue;
}

const memberRoles = (await member.fetch(true)).roles.cache;
if (type === ApplicationCommandPermissionType.Role && memberRoles.has(id)) {
memberRoleOverrides.push(allowed);
continue;
}

if (type === ApplicationCommandPermissionType.User && member.id === id) {
memberOverride = allowed;
continue;
}
}

// Channel has presedence over roles & member overrides
if (!allChannelsAllowed) return channelOverride === true;
if (allChannelsAllowed && channelOverride === false) return false;

// Member has presedence over roles
if (memberOverride !== undefined) return memberOverride;

// If you have at least one role with explicit permission (other than @everyone), you are allowed
if (!everyoneRoleAllowed) return memberRoleOverrides.some(r => r === true);
if (everyoneRoleAllowed && memberRoleOverrides.every(r => r === false)) return false;

return true;
}
if (type === ApplicationCommandPermissionType.Channel && channelId === id) {
channelOverride = allowed;
continue;
}

const memberRoles = (await member.fetch(true)).roles.cache;
if (type === ApplicationCommandPermissionType.Role && memberRoles.has(id)) {
memberRoleOverrides.push(allowed);
continue;
}

if (type === ApplicationCommandPermissionType.User && member.id === id) {
memberOverride = allowed;
continue;
}
}

// Channel has presedence over roles & member overrides
if (!allChannelsAllowed) return channelOverride === true;
if (allChannelsAllowed && channelOverride === false) return false;

// Member has presedence over roles
if (memberOverride !== undefined) return memberOverride;

// If you have at least one role with explicit permission (other than @everyone), you are allowed
if (!everyoneRoleAllowed) return memberRoleOverrides.some(r => r === true);
if (everyoneRoleAllowed && memberRoleOverrides.every(r => r === false)) return false;

return true;
}
enjoy this.id is the command ID and this.defaultPermissions is the bigint default perms This was correct