Unified cache interface for EDA.
This module provides a simple API for accessing cached Discord data. Data is automatically cached as events are received from the Gateway.
Examples
# Get a guild
guild = EDA.Cache.get_guild("123456789")
# Get a user
user = EDA.Cache.get_user("987654321")
# Get a channel
channel = EDA.Cache.get_channel("111222333")
# Get the bot user
me = EDA.Cache.me()Obfuscated channels
From 2026-11-16 Discord redacts channels the bot cannot view rather than hiding
them: they are still dispatched over the gateway, with name set to "___hidden___",
sensitive fields nulled, and the CHANNEL_OBFUSCATED flag set. Before that date the
behaviour is opt-in via config :eda, capabilities: [:channel_obfuscation].
EDA caches them like any other channel. They are dropped neither on write nor on read, because "this channel exists and I cannot see it" is real information — Discord explicitly expects apps that manage channels or compute permissions across a guild to detect them and surface that state. Hiding them in the cache would make a channel that demonstrably exists look absent.
Redaction nulls the sensitive fields rather than omitting them, which matters here:
EDA.Cache.Channel.update/2 merges the incoming payload over the cached one, so a channel
that becomes obfuscated has its name, topic, status and last_message_id genuinely
replaced — the previously cached values do not survive. Verified against a live guild on
2026-09-19. Redaction is also selective: position, parent_id, nsfw, bitrate and
rate_limit_per_user keep their real values.
The consequences are therefore yours to handle:
channels/0andchannels_for_guild/1include them — reject withEDA.Channel.obfuscated?/1before showing a channel list to a user;their
permission_overwriteshold a single synthetic@everyoneVIEW_CHANNELdeny.EDA.Permission.in_channel/3already refuses to compute from it and returns{:error, :channel_obfuscated}, so that path is safe;a cache admission policy can drop them if you would rather not see them at all:
config :eda, cache: [ channels: [policy: fn _entity, _key, channel -> if EDA.Channel.obfuscated?(channel), do: :skip, else: :cache end] ]
Summary
Functions
Returns the number of cached channels.
Gets all cached channels.
Gets all channels for a guild.
Fetches a channel from cache, falling back to REST on miss.
Fetches a guild from cache, falling back to REST on miss.
Fetches a member from cache, falling back to REST on miss.
Fetches a role from cache, falling back to REST on miss.
Fetches a user from cache, falling back to REST on miss.
Gets a channel by ID.
Gets a guild by ID.
Gets a member in a guild.
Gets a user's presence in a guild.
Gets a role by ID.
Gets a user by ID.
Gets a user's voice state in a guild.
Returns the number of cached guilds.
Gets all cached guilds.
Gets the current bot user as an %EDA.User{} struct.
Returns the raw map of the current bot user (string keys).
Returns the total number of cached members.
Gets all members for a guild.
Gets all presences for a guild.
Stores the current bot user. Saves both the parsed %EDA.User{} struct
and the raw map for backward compatibility.
Returns the total number of cached roles.
Gets all roles for a guild.
Returns the number of cached users.
Gets all cached users.
Gets all voice states for a specific channel in a guild.
Gets all voice states for a guild.
Functions
@spec channel_count() :: non_neg_integer()
Returns the number of cached channels.
@spec channels() :: [map()]
Gets all cached channels.
Includes channels Discord has obfuscated; see channels_for_guild/1.
Gets all channels for a guild.
Channels the bot cannot view are included, with their metadata redacted by
Discord (name is "___hidden___"). Filter them out when listing channels for
a user:
guild_id
|> EDA.Cache.channels_for_guild()
|> Enum.reject(&EDA.Channel.obfuscated?/1)See the "Obfuscated channels" section of EDA.Cache for why they are kept.
Fetches a channel from cache, falling back to REST on miss.
Fetches a guild from cache, falling back to REST on miss.
@spec fetch_member(String.t() | integer(), String.t() | integer()) :: {:ok, map()} | {:error, term()}
Fetches a member from cache, falling back to REST on miss.
Fetches a role from cache, falling back to REST on miss.
Note: the REST API returns all roles for the guild, so guild_id is required.
Fetches a user from cache, falling back to REST on miss.
Gets a channel by ID.
Gets a guild by ID.
Gets a member in a guild.
Gets a user's presence in a guild.
Gets a role by ID.
Gets a user by ID.
Gets a user's voice state in a guild.
@spec guild_count() :: non_neg_integer()
Returns the number of cached guilds.
@spec guilds() :: [map()]
Gets all cached guilds.
@spec me() :: EDA.User.t() | map() | nil
Gets the current bot user as an %EDA.User{} struct.
Returns nil if the bot hasn't connected yet.
The raw map is also stored for internal callers that need string-key access.
@spec me_raw() :: map() | nil
Returns the raw map of the current bot user (string keys).
Used internally by code that expects me["id"] string-key access
(e.g., app_id/0, voice event routing).
@spec member_count() :: non_neg_integer()
Returns the total number of cached members.
Gets all members for a guild.
Gets all presences for a guild.
@spec put_me(map()) :: :ok
Stores the current bot user. Saves both the parsed %EDA.User{} struct
and the raw map for backward compatibility.
@spec role_count() :: non_neg_integer()
Returns the total number of cached roles.
Gets all roles for a guild.
@spec user_count() :: non_neg_integer()
Returns the number of cached users.
@spec users() :: [map()]
Gets all cached users.
Gets all voice states for a specific channel in a guild.
Gets all voice states for a guild.