EDA.Permission (EDA v0.3.0)

Copy Markdown View Source

Discord permission flags and calculator.

Computes effective permissions for a member at guild or channel level, following Discord's official algorithm with the 3-tier overwrite cascade.

Features

  • Correct 3-tier overwrite cascade: @everyone → roles (merged) → member
  • Access gates: returns 0 if VIEW_CHANNEL is missing, or if VOICE_CONNECT is missing on voice/stage channels
  • has_permission?/3: one-call convenience for permission checks
  • Pure bitwise hot path: no atom-list conversion during calculation
  • All 50+ Discord permissions up to date (bit 52)
  • Nil-safe: returns {:error, reason} instead of crashing on missing data

Usage

# Check if a member can manage messages in a channel
EDA.Permission.has_permission?(guild_id, user_id, channel_id, :manage_messages)

# Get all effective permissions in a channel
{:ok, bitset} = EDA.Permission.in_channel(guild_id, user_id, channel_id)
perms = EDA.Permission.to_list(bitset)

# Guild-level permissions
{:ok, bitset} = EDA.Permission.in_guild(guild_id, user_id)

Summary

Functions

Returns the bitset with all permissions set.

Returns all known permission flag atoms.

Returns the flag atom for a bit value, or :error.

Checks if a specific flag is set in a bitset.

Checks if a member has a specific permission at guild level.

Checks if a member has a specific permission in a channel.

Computes effective channel-level permissions for a member.

Computes effective guild-level permissions for a member.

Returns the bit value for a permission flag.

Converts a list of flag atoms to a combined bitset.

Converts a bitset to a list of flag atoms. Unknown bits are skipped.

Types

bitset()

@type bitset() :: non_neg_integer()

flag()

@type flag() ::
  :create_instant_invite
  | :kick_members
  | :ban_members
  | :administrator
  | :manage_channels
  | :manage_guild
  | :add_reactions
  | :view_audit_log
  | :priority_speaker
  | :stream
  | :view_channel
  | :send_messages
  | :send_tts_messages
  | :manage_messages
  | :embed_links
  | :attach_files
  | :read_message_history
  | :mention_everyone
  | :use_external_emojis
  | :view_guild_insights
  | :connect
  | :speak
  | :mute_members
  | :deafen_members
  | :move_members
  | :use_vad
  | :change_nickname
  | :manage_nicknames
  | :manage_roles
  | :manage_webhooks
  | :manage_guild_expressions
  | :use_application_commands
  | :request_to_speak
  | :manage_events
  | :manage_threads
  | :create_public_threads
  | :create_private_threads
  | :use_external_stickers
  | :send_messages_in_threads
  | :use_embedded_activities
  | :moderate_members
  | :view_creator_monetization_analytics
  | :use_soundboard
  | :create_guild_expressions
  | :create_events
  | :use_external_sounds
  | :send_voice_messages
  | :set_voice_channel_status
  | :send_polls
  | :use_external_apps
  | :pin_messages
  | :bypass_slowmode

Functions

all()

@spec all() :: bitset()

Returns the bitset with all permissions set.

all_flags()

@spec all_flags() :: [flag()]

Returns all known permission flag atoms.

from_bit(bit)

@spec from_bit(bitset()) :: {:ok, flag()} | :error

Returns the flag atom for a bit value, or :error.

has?(bitset, flag)

@spec has?(bitset(), flag()) :: boolean()

Checks if a specific flag is set in a bitset.

has_guild_permission?(guild_id, user_id, permission)

@spec has_guild_permission?(String.t(), String.t(), flag()) :: boolean()

Checks if a member has a specific permission at guild level.

has_permission?(guild_id, user_id, channel_id, permission)

@spec has_permission?(String.t(), String.t(), String.t(), flag()) :: boolean()

Checks if a member has a specific permission in a channel.

Convenience function — most common use case for bots.

in_channel(guild_id, user_id, channel_id)

@spec in_channel(String.t(), String.t(), String.t()) ::
  {:ok, bitset()} | {:error, term()}

Computes effective channel-level permissions for a member.

Returns {:ok, bitset} or {:error, reason}.

Algorithm (matches Discord's official spec + JDA)

  1. Owner → ALL_PERMISSIONS
  2. Compute guild base permissions
  3. ADMINISTRATOR → ALL_PERMISSIONS (skips all overwrites)
  4. Apply 3-tier overwrite cascade: a. @everyone role overwrite b. All role overwrites (merged via OR, then applied) c. Member-specific overwrite (highest priority)
  5. Access gate: no VIEW_CHANNEL → 0
  6. Access gate: voice/stage channel + no CONNECT → 0

in_guild(guild_id, user_id)

@spec in_guild(String.t(), String.t()) :: {:ok, bitset()} | {:error, term()}

Computes effective guild-level permissions for a member.

Returns {:ok, bitset} or {:error, reason}.

Algorithm

  1. Guild owner → ALL_PERMISSIONS
  2. OR all role permission bits together
  3. If ADMINISTRATOR is set → ALL_PERMISSIONS

to_bit(flag)

@spec to_bit(flag()) :: bitset()

Returns the bit value for a permission flag.

to_bitset(flags)

@spec to_bitset([flag()]) :: bitset()

Converts a list of flag atoms to a combined bitset.

to_list(bitset)

@spec to_list(bitset()) :: [flag()]

Converts a bitset to a list of flag atoms. Unknown bits are skipped.