EDA - Elixir Discord API

CI Hex.pm Hex Docs License: MIT

A complete, production-grade Discord library for Elixir. 24 API modules, 68+ event types, full voice with DAVE E2EE, automatic sharding, and 1300+ tests.

Why EDA?

Installation

def deps do
[
{:eda, "~> 0.3.0"}
]
end

Quick Start

1. Configure your bot

# config/config.exs
config :eda,
token: System.get_env("DISCORD_TOKEN"),
intents: [:guilds, :guild_messages, :message_content],
consumer: MyBot.Consumer

2. Create a consumer

defmodule MyBot.Consumer do
@behaviour EDA.Consumer
@impl true
def handle_event({:MESSAGE_CREATE, msg}) do
if msg.content == "!ping" do
EDA.API.Message.create(msg.channel_id, "Pong!")
end
end
@impl true
def handle_event({:READY, ready}) do
IO.puts("Online as #{ready.user.username}!")
end
@impl true
def handle_event(_event), do: :ok
end

3. Run

DISCORD_TOKEN="your_token" iex -S mix

REST API

# Messages
EDA.API.Message.create(channel_id, "Hello!")
EDA.API.Message.create(channel_id, content: "With embed", embeds: [%{title: "Hey", color: 0x5865F2}])
# Guilds & members
{:ok, guild} = EDA.API.Guild.get(guild_id)
{:ok, member} = EDA.API.Member.get(guild_id, user_id)
EDA.API.Role.add(guild_id, user_id, role_id)
# Slash commands
EDA.API.Command.create_global(app_id, %{name: "ping", description: "Pong!"})
# Reactions, threads, webhooks...
EDA.API.Reaction.create(channel_id, message_id, "🔥")
EDA.API.Thread.create(channel_id, %{name: "Discussion", auto_archive_duration: 1440})

DX Helpers

# Collectors — await events with filters
{:ok, reply} = EDA.await_message(fn msg ->
msg.channel_id == channel_id and msg.author["id"] == user_id
end, timeout: 30_000)
# Auto-delete messages after a delay
EDA.API.Message.create(channel_id, content: "Temporary!", delete_after: 10_000)
# Pre-styled embeds
EDA.Embed.error("Something went wrong")
EDA.Embed.success("User banned successfully")
# Interaction workflows
EDA.Interaction.delete_source(interaction) # Delete the button message
EDA.Interaction.defer_and_edit(interaction, fn -> do_work(); "Done!" end)
EDA.Interaction.respond(interaction, content: "Bye!", delete_after: 5_000)
# Disable all buttons after interaction
disabled = EDA.Component.disable_all(message["components"])
# Reply to a message
EDA.API.Message.reply(msg, "Got it!")
# Mentions & formatting
EDA.Mention.user("123") #=> "<@123>"
EDA.Mention.timestamp(unix, :R) #=> "<t:1700000000:R>"
# Colors
EDA.Color.random() #=> 0xA3F29C (crypto-random)
EDA.Embed.new() |> EDA.Embed.color(:random)

Cache

EDA.Cache.me() # Bot user
EDA.Cache.get_guild(guild_id) # Single guild
EDA.Cache.guilds() # All guilds
EDA.Cache.get_channel(channel_id) # Single channel
EDA.Cache.channels_for_guild(guild_id) # Guild channels
EDA.Cache.guild_count() # Stats

Configure cache admission per entity:

config :eda, :cache,
guilds: [],
users: [max_size: 100_000], # LRW eviction once the table exceeds this size
members: [policy: :none], # :all (default) | :none | MyPolicy | fn/3
presences: [policy: :none],
channels: [
policy: fn _entity, _key, ch ->
if ch["type"] in [0, 2, 5], do: :cache, else: :skip
end
]

Options are set per entity, not globally. The configurable caches are :guilds, :users, :channels, :members, :roles, :voice_states and :presences; each accepts :policy and :max_size. Caches left out use the defaults (policy: :all, no size limit). The eviction sweep interval is fixed and not configurable.

Events

Event Description
{:MESSAGE_CREATE, msg} Message created
{:INTERACTION_CREATE, interaction} Slash command / component / modal
{:GUILD_CREATE, guild} Guild available
{:GUILD_MEMBER_ADD, member} Member joined
{:VOICE_STATE_UPDATE, state} Voice state changed
{:CHANNEL_CREATE, channel} Channel created
{:THREAD_CREATE, thread} Thread created
{:AUTO_MODERATION_ACTION_EXECUTION, action} AutoMod triggered

Plus 60+ more — see HexDocs for the full list.

Gateway

config :eda,
intents: [:guilds, :guild_messages, :message_content],
# or :all, :nonprivileged
gateway_encoding: :etf # :etf (default, binary) or :json

zlib-stream transport compression is always enabled and has no configuration option.

Sharding is automatic. EDA fetches the recommended shard count from Discord, launches shards with staggered timing, and tracks per-shard readiness. Override with:

config :eda, shards: :auto # Discord's recommended count (default)
config :eda, shards: 4 # Fixed count — this node runs shards 0..3
config :eda, shards: {0..1, 4} # This node runs shards 0 and 1 out of 4 total

Architecture

EDA.Application
├── EDA.Cache.Supervisor
│ ├── EDA.Cache.Guild (ETS)
│ ├── EDA.Cache.Channel (ETS)
│ ├── EDA.Cache.User (ETS)
│ ├── EDA.Cache.Member (ETS)
│ ├── EDA.Cache.Role (ETS)
│ ├── EDA.Cache.Presence (ETS)
│ ├── EDA.Cache.VoiceState (ETS)
│ └── EDA.Cache.Evictor
├── EDA.HTTP.RateLimiter
├── EDA.Voice.Supervisor
├── EDA.Collector (event await patterns)
├── EDA.AutoDelete (timer-based message cleanup)
├── Task.Supervisor (async event dispatch)
├── EDA.Gateway.MemberChunker
├── EDA.Gateway.ReadyTracker
└── EDA.Gateway.ShardSupervisor
├── EDA.Gateway.ShardManager
└── EDA.Gateway.Connection (per shard)

Documentation

Full documentation is available on HexDocs.

License

MIT — see LICENSE for details.

Contributing

Contributions are welcome! Open an issue or submit a pull request.