Permissions
Permissions are one of the key concepts in Discord. As the name implies, they can be used to grant or revoke access to certain actions for certain users based on the roles they have and the channel they're in.
In disnake, permissions are controlled via two objects: Permissions
and PermissionOverwrite
, which we'll discuss below.
Creating permission objects
Permissions
can be constructed in two ways: using manual instantiation with allowed/denied permissions specified via
keyword arguments, or shorthand methods that create permission objects with predefined groups of permissions.
There's actually a third option: specifying the raw value, but it's usage is generally discouraged as it's hard to understand at a glance what the result will be. Anyway, here's how you do it:
permissions = disnake.Permissions(64) # Represents the "Add Reactions" permission.
assert permissions.value == 64
Manual creation
Manual creation is usually preferred over shorthand methods due to the flexibility it provides while also still being as intuitive as possible, and, as such, is also what you'll usually encounter in other examples and documentation.
permissions = disnake.Permissions(
# Each permission is specified using a keyword argument,
# where a value of `True` means that it is allowed, while
# `False` means that it is denied.
view_audit_log=True, # This allows the "View Audit Log" permission.
# Note that not all permission names are the same as in the
# official UI. For example, in the UI guilds are called "Servers",
# and, as such, the `manage_guild` kwarg is actually responsible
# for controlling the "Manage Server" permission.
manage_guild=False, # Denies the "Manage Server" permission.
use_voic_activation=True, # Allows the "Use Voice Activity" permission.
# There are also a few aliases for certain permissions.
manage_permissions=True, # Allows the "Manage Roles" permission.
manage_roles=False, # Denies the "Manage Roles" permission.
)
It is often misunderstood that setting True
or False
on a Permissions
object will permanently "grant"/"revoke"
the permission, but this is not true.
Permissions
do not "grant" or "revoke" permission(s) by themselves - they only represent them. Detecting whether
a certain permission will be allowed or denied in the end is not possible unless you go through the full process of
calculating permissions (or, more commonly, querying pre-computed
ones).
Note that, quoting from docs, "Arguments are applied in order, which notably also means that supplying a flag and
its alias will make whatever comes last overwrite the first one". As an example, in the above case the latter
manage_roles=False
argument will overwrite the former manage_permissions
, and the resulting object will have
both of them set to False
(since they both represent the same permission).
assert not permissions.manage_roles
assert not permissions.manage_permissions
Shorthand methods
Sometimes you may want to allow a group of permissions, like the ones defined in the official UI. Manually creating
them can become tedious, and you'll also need to keep them up-to-date with the UI. Luckily, disnake already does that
for you via a range of methods defined on the Permissions
class.
# All permissions grouped under "Advanced" in the official UI.
advanced_permissions = disnake.Permissions.advanced()
# ALL permissions allowed, including advanced ones.
all_permissions = disnake.Permissions.all()
To not duplicate what's already said in docs
, we'll only
list the available methods here:
Permissions.advanced
Permissions.all
Permissions.all_channel
Permissions.events
Permissions.general
Permissions.membership
Permissions.none
Permissions.private_channel
Permissions.stage
Permissions.stage_moderator
Permissions.text
Permissions.voice
Permission Overwrites
A PermissionOverwrite
is an override for server-wide permissions (i.e., these granted
by roles). Permission overwrites are assigned per-channel and to a specific user/role, but, unlike regular Permissions
objects, each permission in an overwrite can have three values (intead of two for Permissions
): True
(explicitly allowed),
False
(explicitly denied) and None
("untouched", i.e., not overriden). The latter is the default value for permissions
in permission overwrites.
overwrite = disnake.PermissionOverwrite(
add_reactions=True, # This overrides permission as allowed.
view_audit_log=False, # This overrides permission as denied.
manage_guild=None, # Does not override anything (this is the default behavior).
)
# disnake allows you to query individual permissions just as easily
# as with regular `Permissions`.
assert overwrite.add_reactions
assert not overwrite.view_audit_log
assert overwrite.manage_guild is None
# Although not being the recommended usage (except for creating copies,
# as you'll see a bit below), you can also access permissions using
# `.pair()`, which returns a tuple of two `Permissions` objects, first
# having the allowed permissions set to `True` and second having the
# denied ones set to `True`.
allow, deny = overwrite.pair()
assert allow == disnake.Permissions(add_reactions=True)
assert deny == disnake.Permissions(view_audit_log=False)
You can update overwrites in-place by simply assigning values to attributes or by using
the PermissionOverwrite.update
method.
overwrite.view_audit_log = True
assert overwrite.view_audit_log
overwrite.update(view_audit_log=False)
assert not overwrite.view_audit_log
If you need to create a new overwrite object based on an existing one, you can do the following.
allow, deny = overwrite.pair()
new_overwrite = disnake.PermissionOverwrite.from_pair(allow, deny)
new_overwrite.add_reactions = False
assert not new_overwrite.add_reactions
Using permissions
Creating a "chat moderator" role:
permissions = disnake.Permissions(
manage_messages=True,
moderate_members=True,
)
chatmod_role = await guild.create_role(
name="Chat Moderator",
permissions=permissions,
hoist=True,
)
Creating an #only-mods
TextChannel
:
# Allow mods to see the channel
overwrite = disnake.PermissionOverwrite(view_channel=True)
# See `TextChannel.edit()` documentation.
mapping = {chatmod_role: overwrite}
only_mods_channel = await guild.create_text_channel("only-mods", overwrites=mapping)
assert not only_mods_channel.permissions_for(guild.default_role).view_channel
assert only_mods_channel.permissions_for(chatmod_role).view_channel
A slash command that checks whether the command author can add reactions in the current channel:
@commands.slash_command(description="Checks whether you can add reactions!")
async def can_add_reactions(inter: disnake.ApplicationCommandInteraction) -> None:
await inter.send("Sure you can!" if inter.permissions.add_reactions else "Sadly no :(")
Querying permissions
There are various ways to query permission information, and they also depend on the context you're in. This section tries to list all possible methods for getting permission info, grouped by context they can be used in.
Context-agnostic
If you have an instance of any class implementing abc.GuildChannel
,
then the easiest way to query permissions for a member/role is
through abc.GuildChannel.permissions_for
.
This also works with Thread
s.
If you need a bit more lower level control, you can also get raw overwrite objects
using abc.GuildChannel.overwrites_for
or abc.GuildChannel.overwrites
.
In prefix commands
There are no direct methods on ext.commands.Context
for querying permission
information, however, you can still access ext.commands.Context.channel
and
call above-mentioned methods on it, but be sure to check that it's not a PartialMessageable
as it doesn't have any permissions-related methods on it.
In interactions
When working with interactions (i.e., with application commands or components), there are two main properties
defined on Interaction
: Interaction.permissions
(for getting the interaction author's permissions in the current channel) and Interaction.app_permissions
(for getting the bot's permissions).
How permissions are calculated
Permissions can be a tough thing to understand at first, but understanding how permissions are applied can help you better grasp this concept.
In case we're calculating permissions for a member, the strategy is as follows:
If member is the guild owner, all permissions are granted - no questions asked. Otherwise..
First we apply the
@everyone
role's permissions.Then, for each of the member's roles, apply its permissions too.
If at this point the member has the "Administrator" permission, all other permissions are granted automatically (the member is a guild-wide administrator, and, as such, is functionally equivalent to the owner except they can't delete the guild).
After that, if we're in a channel, the following is additionally done:
We apply its overwrites in the same order:
@everyone
overwrites, role overwrites and finally member overwrites.Then channel-specific implicit permissions are handled.
In the end, we take into account guild-wide implicit permissions.
In case of calculating permissions for a role, the strategy is the same as if we were calculating permissions
for a member with only that role (and @everyone
), except that member overwrites are
not taken into account.
Curious about the implementation? At the time of writing, implementation could be found here.
Implicit permissions
In Discord, there are some permissions that, when revoked, basically make it impossible to utilize other granted permissions. For example, take a user with two permissions: "View Channel" and "Send Messages". If at some point user loses the "View Channel" permission, it wouldn't matter whether they can send messages in that channel: they just can't see the channel in the UI. disnake's permission system attempts to handle such cases via "implicit permissions" - permissions that, when granted or, more commonly, revoked, automatically grant (revoke) other permissions as well. A few handled cases are demonstrated below, but note that this is by no means an exhaustive list: quoting from the official Discord documentation, "in all cases, these are based on logical conclusions about how a user with certain permissions should or should not interact with Discord", and, as such, it's hardly possible to accomodate all possibilities.
Guild-wide implicit permissions
- Timed out members are spectators.
This rule automatically revokes all permissions for timed out members except "View Channel" and "Read Message History", which are left untouched.
Channel-specific implicit permissions
- If you can't send a message in a channel, then you can't sent any message in it.
This rule automatically revokes "Send TTS Messages", "Send Voice Messages", "Mention Everyone", "Embed Links" and "Attach Files" permissions, if the member doesn't have the "Send Messages" permission.
- If you can't view a channel, they you can't do anything in it.
This rule automatically revokes "All Channel" permissions, as specified by Permissions.all_channel
,
if the member doesn't have the "View Channel" permission.
- If you can't connect to a vocal channel, then you can't interfere with it's participants.
This rule automatically revokes "Voice", "Text" and "Stage" permission groups from the official Discord UI if the member doesn't have the "Connect" permission.
Operations on permission values
Since disnake v2.6, it is possible to do various operations on permission objects with minimal effort. While not something you'd usually use often, they can come in very handy when it comes to complex permission math you'd have to do manually otherwise.
This section is meant as a companion to the official reference
,
meaning that ideally you should read both.
The following variables will be used throughout in the examples below.
base = disnake.Permissions(add_reactions=True, manage_guild=True)
same = disnake.Permissions(add_reactions=True, manage_guild=True)
superset = disnake.Permissions(add_reactions=True, manage_guild=True, send_messages=True)
subset = disnake.Permissions(add_reactions=True)
different = disnake.Permissions(use_voice_activation=True)
Basic
These are operations that usually require no explanation. In any way, comments will guide you through.
# Check that two permission object have same permissions set to the same value.
assert base == same
# Check that one (base) permission object has at least all of the allowed permissions
# of the second one allowed too.
assert base >= same
# Check that one (base) permission object has all of the allowed permissions
# of the second one allowed too, plus at least one extra.
assert base > subset
# Check that one (base) permission object has at maximum all of the allowed permissions
# of the second one allowed.
assert base <= same
# Check that one (base) permission object has at maximum of the allowed permissions
# of the second one allowed, minus one or more.
assert base < superset
# Check that one (base) permission object has at least one permission allowed
# that is denied in the second one (and the other way around).
assert base != different
Advanced
If you're familar with bit operators in Python, then you'll feel right at home. Otherwise, read the comments to understand what does what.
# Creates a new Permissions object with all allowed permissions from either one of them.
assert subset | different == disnake.Permissions(add_reactions=True, use_voice_activation=True)
# Creates a new Permissions object with only permissions allowed on both.
assert base & subset == disnake.Permissions(add_reactions=True)
# Creates a new Permissions object with only permissions allowed on one of base or different,
# but not both.
assert base ^ different
# Notably, all of operators above support in-place updates using the `OP=` syntax, like so:
# `base &= subset`.
# Creates a new Permissions object with all permissions from subset inverted.
assert not (~subset).add_reactions and (~subset).manage_guild
# Creates a new Permissions object with all permissions except add_reactions allowed.
assert not (~disnake.Permissions.add_reactions).add_reactions
# Creates a new Permissions object with all specified permissions allowed.
assert disnake.Permissions.add_reactions | disnake.Permissions.manage_guild == base
assert subset | disnake.Permissions.manage_guild == base
Additional
There's also a few additional operations supported that do not involve operators.
# Returns the permissions's hash. Allows permission objects to
# be used as dictionary keys.
hash(base)
# `Permissions` and `PermissionOverwrite` both implement `__iter__()`,
# and therefore can be used in for loops.
allowed = []
for name, value in base:
if value:
allowed.append(name)
assert allowed == ["add_reactions", "manage_guild"]