api-surface #1

Merged
luna merged 51 commits from api-surface into mistress 2026-07-05 14:43:13 +00:00
6 changed files with 729 additions and 0 deletions
Showing only changes of commit 576bfa1205 - Show all commits

feat(events): envelope structs with hydrate slots for all non-full-object events

Luna 2026-07-05 04:51:12 -03:00

View file

@ -0,0 +1,50 @@
defmodule Dexcord.Events.ChannelPinsUpdate do
@moduledoc "CHANNEL_PINS_UPDATE. https://docs.discord.com/developers/events/gateway-events#channel-pins-update"
use Dexcord.Struct
discord_struct do
field :guild_id, :snowflake
field :channel_id, :snowflake
field :last_pin_timestamp, :datetime, tristate: true
hydrate :channel, from: :channel_id, type: :channel
hydrate :guild, from: :guild_id, type: Dexcord.Guild
end
end
defmodule Dexcord.Events.ThreadDelete do
@moduledoc "THREAD_DELETE. https://docs.discord.com/developers/events/gateway-events#thread-delete"
use Dexcord.Struct
discord_struct do
field :id, :snowflake
field :guild_id, :snowflake
field :parent_id, :snowflake
field :type, {:enum, Dexcord.ChannelType}
hydrate :guild, from: :guild_id, type: Dexcord.Guild
end
end
defmodule Dexcord.Events.ThreadListSync do
@moduledoc "THREAD_LIST_SYNC. https://docs.discord.com/developers/events/gateway-events#thread-list-sync"
use Dexcord.Struct
discord_struct do
field :guild_id, :snowflake
field :channel_ids, {:list, :snowflake}, default: []
field :threads, {:list, {:struct, Dexcord.Channel}}, default: []
field :members, {:list, {:struct, Dexcord.ThreadMember}}, default: []
end
end
defmodule Dexcord.Events.ThreadMembersUpdate do
@moduledoc "THREAD_MEMBERS_UPDATE. https://docs.discord.com/developers/events/gateway-events#thread-members-update"
use Dexcord.Struct
discord_struct do
field :id, :snowflake
field :guild_id, :snowflake
field :member_count, :integer
field :added_members, {:list, {:struct, Dexcord.ThreadMember}}, default: []
field :removed_member_ids, {:list, :snowflake}, default: []
end
end

View file

@ -0,0 +1,166 @@
defmodule Dexcord.Events.GuildBanAdd do
@moduledoc "GUILD_BAN_ADD. https://docs.discord.com/developers/events/gateway-events#guild-ban-add"
use Dexcord.Struct
discord_struct do
field :guild_id, :snowflake
field :user, {:struct, Dexcord.User}
hydrate :guild, from: :guild_id, type: Dexcord.Guild
end
end
defmodule Dexcord.Events.GuildBanRemove do
@moduledoc "GUILD_BAN_REMOVE. https://docs.discord.com/developers/events/gateway-events#guild-ban-remove"
use Dexcord.Struct
discord_struct do
field :guild_id, :snowflake
field :user, {:struct, Dexcord.User}
hydrate :guild, from: :guild_id, type: Dexcord.Guild
end
end
defmodule Dexcord.Events.GuildEmojisUpdate do
@moduledoc "GUILD_EMOJIS_UPDATE. https://docs.discord.com/developers/events/gateway-events#guild-emojis-update"
use Dexcord.Struct
discord_struct do
field :guild_id, :snowflake
field :emojis, {:list, {:struct, Dexcord.Emoji}}, default: []
hydrate :guild, from: :guild_id, type: Dexcord.Guild
end
end
defmodule Dexcord.Events.GuildStickersUpdate do
@moduledoc "GUILD_STICKERS_UPDATE. https://docs.discord.com/developers/events/gateway-events#guild-stickers-update"
use Dexcord.Struct
discord_struct do
field :guild_id, :snowflake
field :stickers, {:list, {:struct, Dexcord.Sticker}}, default: []
end
end
defmodule Dexcord.Events.GuildIntegrationsUpdate do
@moduledoc "GUILD_INTEGRATIONS_UPDATE. https://docs.discord.com/developers/events/gateway-events#guild-integrations-update"
use Dexcord.Struct
discord_struct do
field :guild_id, :snowflake
end
end
defmodule Dexcord.Events.GuildMemberRemove do
@moduledoc "GUILD_MEMBER_REMOVE. https://docs.discord.com/developers/events/gateway-events#guild-member-remove"
use Dexcord.Struct
discord_struct do
field :guild_id, :snowflake
field :user, {:struct, Dexcord.User}
hydrate :guild, from: :guild_id, type: Dexcord.Guild
end
end
defmodule Dexcord.Events.GuildMemberUpdate do
@moduledoc """
GUILD_MEMBER_UPDATE.
This is NOT a `Dexcord.Member`: `joined_at` is nullable, there is no `flags`,
and several fields carry PATCH tristate semantics for the Phase 6 cache merge
(`:absent` means the key was not sent, distinct from an explicit `null`).
https://docs.discord.com/developers/events/gateway-events#guild-member-update
"""
use Dexcord.Struct
discord_struct do
field :guild_id, :snowflake
field :roles, {:list, :snowflake}, default: []
field :user, {:struct, Dexcord.User}
field :nick, :string, tristate: true
field :avatar, :string
field :banner, :string
field :joined_at, :datetime
field :premium_since, :datetime, tristate: true
field :deaf, :boolean
field :mute, :boolean
field :pending, :boolean
field :communication_disabled_until, :datetime, tristate: true
field :avatar_decoration_data, :raw, tristate: true
field :collectibles, :raw, tristate: true
end
end
defmodule Dexcord.Events.GuildMembersChunk do
@moduledoc "GUILD_MEMBERS_CHUNK. https://docs.discord.com/developers/events/gateway-events#guild-members-chunk"
use Dexcord.Struct
discord_struct do
field :guild_id, :snowflake
field :members, {:list, {:struct, Dexcord.Member}}, default: []
field :chunk_index, :integer
field :chunk_count, :integer
field :not_found, {:list, :raw}, default: []
field :presences, {:list, {:struct, Dexcord.Presence}}, default: []
field :nonce, :string
end
end
defmodule Dexcord.Events.GuildRoleCreate do
@moduledoc "GUILD_ROLE_CREATE. https://docs.discord.com/developers/events/gateway-events#guild-role-create"
use Dexcord.Struct
discord_struct do
field :guild_id, :snowflake
field :role, {:struct, Dexcord.Role}
hydrate :guild, from: :guild_id, type: Dexcord.Guild
end
end
defmodule Dexcord.Events.GuildRoleUpdate do
@moduledoc "GUILD_ROLE_UPDATE. https://docs.discord.com/developers/events/gateway-events#guild-role-update"
use Dexcord.Struct
discord_struct do
field :guild_id, :snowflake
field :role, {:struct, Dexcord.Role}
hydrate :guild, from: :guild_id, type: Dexcord.Guild
end
end
defmodule Dexcord.Events.GuildRoleDelete do
@moduledoc "GUILD_ROLE_DELETE. https://docs.discord.com/developers/events/gateway-events#guild-role-delete"
use Dexcord.Struct
discord_struct do
field :guild_id, :snowflake
field :role_id, :snowflake
hydrate :guild, from: :guild_id, type: Dexcord.Guild
end
end
defmodule Dexcord.Events.GuildScheduledEventUserAdd do
@moduledoc "GUILD_SCHEDULED_EVENT_USER_ADD. https://docs.discord.com/developers/events/gateway-events#guild-scheduled-event-user-add"
use Dexcord.Struct
discord_struct do
field :guild_scheduled_event_id, :snowflake
field :user_id, :snowflake
field :guild_id, :snowflake
hydrate :user, from: :user_id, type: Dexcord.User
hydrate :guild, from: :guild_id, type: Dexcord.Guild
end
end
defmodule Dexcord.Events.GuildScheduledEventUserRemove do
@moduledoc "GUILD_SCHEDULED_EVENT_USER_REMOVE. https://docs.discord.com/developers/events/gateway-events#guild-scheduled-event-user-remove"
use Dexcord.Struct
discord_struct do
field :guild_scheduled_event_id, :snowflake
field :user_id, :snowflake
field :guild_id, :snowflake
hydrate :user, from: :user_id, type: Dexcord.User
hydrate :guild, from: :guild_id, type: Dexcord.Guild
end
end

View file

@ -0,0 +1,123 @@
defmodule Dexcord.Events.MessageDelete do
@moduledoc "MESSAGE_DELETE. https://docs.discord.com/developers/events/gateway-events#message-delete"
use Dexcord.Struct
discord_struct do
field :id, :snowflake
field :channel_id, :snowflake
field :guild_id, :snowflake
hydrate :channel, from: :channel_id, type: :channel
hydrate :guild, from: :guild_id, type: Dexcord.Guild
end
end
defmodule Dexcord.Events.MessageDeleteBulk do
@moduledoc "MESSAGE_DELETE_BULK. https://docs.discord.com/developers/events/gateway-events#message-delete-bulk"
use Dexcord.Struct
discord_struct do
field :ids, {:list, :snowflake}, default: []
field :channel_id, :snowflake
field :guild_id, :snowflake
hydrate :channel, from: :channel_id, type: :channel
hydrate :guild, from: :guild_id, type: Dexcord.Guild
end
end
defmodule Dexcord.Events.ReactionAdd do
@moduledoc "MESSAGE_REACTION_ADD. https://docs.discord.com/developers/events/gateway-events#message-reaction-add"
use Dexcord.Struct
discord_struct do
field :user_id, :snowflake
field :channel_id, :snowflake
field :message_id, :snowflake
field :guild_id, :snowflake
field :member, {:struct, Dexcord.Member}
field :emoji, {:struct, Dexcord.PartialEmoji}
field :message_author_id, :snowflake
field :burst, :boolean
field :burst_colors, {:list, :string}, default: []
field :type, {:enum, Dexcord.ReactionType}
hydrate :user, from: :user_id, type: Dexcord.User
hydrate :channel, from: :channel_id, type: :channel
hydrate :guild, from: :guild_id, type: Dexcord.Guild
end
end
defmodule Dexcord.Events.ReactionRemove do
@moduledoc "MESSAGE_REACTION_REMOVE. https://docs.discord.com/developers/events/gateway-events#message-reaction-remove"
use Dexcord.Struct
discord_struct do
field :user_id, :snowflake
field :channel_id, :snowflake
field :message_id, :snowflake
field :guild_id, :snowflake
field :emoji, {:struct, Dexcord.PartialEmoji}
field :burst, :boolean
field :type, {:enum, Dexcord.ReactionType}
hydrate :user, from: :user_id, type: Dexcord.User
hydrate :channel, from: :channel_id, type: :channel
hydrate :guild, from: :guild_id, type: Dexcord.Guild
end
end
defmodule Dexcord.Events.ReactionRemoveAll do
@moduledoc "MESSAGE_REACTION_REMOVE_ALL. https://docs.discord.com/developers/events/gateway-events#message-reaction-remove-all"
use Dexcord.Struct
discord_struct do
field :channel_id, :snowflake
field :message_id, :snowflake
field :guild_id, :snowflake
hydrate :channel, from: :channel_id, type: :channel
hydrate :guild, from: :guild_id, type: Dexcord.Guild
end
end
defmodule Dexcord.Events.ReactionRemoveEmoji do
@moduledoc "MESSAGE_REACTION_REMOVE_EMOJI. https://docs.discord.com/developers/events/gateway-events#message-reaction-remove-emoji"
use Dexcord.Struct
discord_struct do
field :channel_id, :snowflake
field :guild_id, :snowflake
field :message_id, :snowflake
field :emoji, {:struct, Dexcord.PartialEmoji}
hydrate :channel, from: :channel_id, type: :channel
hydrate :guild, from: :guild_id, type: Dexcord.Guild
end
end
defmodule Dexcord.Events.PollVoteAdd do
@moduledoc "MESSAGE_POLL_VOTE_ADD. https://docs.discord.com/developers/events/gateway-events#message-poll-vote-add"
use Dexcord.Struct
discord_struct do
field :user_id, :snowflake
field :channel_id, :snowflake
field :message_id, :snowflake
field :guild_id, :snowflake
field :answer_id, :integer
hydrate :user, from: :user_id, type: Dexcord.User
hydrate :channel, from: :channel_id, type: :channel
hydrate :guild, from: :guild_id, type: Dexcord.Guild
end
end
defmodule Dexcord.Events.PollVoteRemove do
@moduledoc "MESSAGE_POLL_VOTE_REMOVE. https://docs.discord.com/developers/events/gateway-events#message-poll-vote-remove"
use Dexcord.Struct
discord_struct do
field :user_id, :snowflake
field :channel_id, :snowflake
field :message_id, :snowflake
field :guild_id, :snowflake
field :answer_id, :integer
hydrate :user, from: :user_id, type: Dexcord.User
hydrate :channel, from: :channel_id, type: :channel
hydrate :guild, from: :guild_id, type: Dexcord.Guild
end
end

View file

@ -0,0 +1,124 @@
defmodule Dexcord.Events.AutoModerationActionExecution do
@moduledoc "AUTO_MODERATION_ACTION_EXECUTION. https://docs.discord.com/developers/events/gateway-events#auto-moderation-action-execution"
use Dexcord.Struct
discord_struct do
field :guild_id, :snowflake
field :action, {:struct, Dexcord.AutoModerationAction}
field :rule_id, :snowflake
field :rule_trigger_type, {:enum, Dexcord.AutoModerationTriggerType}
field :user_id, :snowflake
field :channel_id, :snowflake
field :message_id, :snowflake
field :alert_system_message_id, :snowflake
field :content, :string
field :matched_keyword, :string
field :matched_content, :string
hydrate :user, from: :user_id, type: Dexcord.User
hydrate :channel, from: :channel_id, type: :channel
hydrate :guild, from: :guild_id, type: Dexcord.Guild
end
end
defmodule Dexcord.Events.IntegrationDelete do
@moduledoc "INTEGRATION_DELETE. https://docs.discord.com/developers/events/gateway-events#integration-delete"
use Dexcord.Struct
discord_struct do
field :id, :snowflake
field :guild_id, :snowflake
field :application_id, :snowflake
end
end
defmodule Dexcord.Events.InviteCreate do
@moduledoc "INVITE_CREATE. https://docs.discord.com/developers/events/gateway-events#invite-create"
use Dexcord.Struct
discord_struct do
field :channel_id, :snowflake
field :code, :string
field :created_at, :datetime
field :guild_id, :snowflake
field :inviter, {:struct, Dexcord.User}
field :max_age, :integer
field :max_uses, :integer
field :target_type, {:enum, Dexcord.InviteTargetType}
field :target_user, {:struct, Dexcord.User}
field :target_application, :raw
field :temporary, :boolean
field :uses, :integer
field :expires_at, :datetime
field :role_ids, {:list, :snowflake}, default: []
hydrate :channel, from: :channel_id, type: :channel
hydrate :guild, from: :guild_id, type: Dexcord.Guild
end
end
defmodule Dexcord.Events.InviteDelete do
@moduledoc "INVITE_DELETE. https://docs.discord.com/developers/events/gateway-events#invite-delete"
use Dexcord.Struct
discord_struct do
field :channel_id, :snowflake
field :guild_id, :snowflake
field :code, :string
hydrate :channel, from: :channel_id, type: :channel
hydrate :guild, from: :guild_id, type: Dexcord.Guild
end
end
defmodule Dexcord.Events.TypingStart do
@moduledoc "TYPING_START. `timestamp` is unix SECONDS (integer), NOT a datetime. https://docs.discord.com/developers/events/gateway-events#typing-start"
use Dexcord.Struct
discord_struct do
field :channel_id, :snowflake
field :guild_id, :snowflake
field :user_id, :snowflake
field :timestamp, :integer
field :member, {:struct, Dexcord.Member}
hydrate :user, from: :user_id, type: Dexcord.User
hydrate :channel, from: :channel_id, type: :channel
hydrate :guild, from: :guild_id, type: Dexcord.Guild
end
end
defmodule Dexcord.Events.VoiceChannelEffectSend do
@moduledoc "VOICE_CHANNEL_EFFECT_SEND. https://docs.discord.com/developers/events/gateway-events#voice-channel-effect-send"
use Dexcord.Struct
discord_struct do
field :channel_id, :snowflake
field :guild_id, :snowflake
field :user_id, :snowflake
field :emoji, :raw
field :animation_type, {:enum, Dexcord.VoiceAnimationType}
field :animation_id, :integer
field :sound_id, :raw
field :sound_volume, :number
end
end
defmodule Dexcord.Events.VoiceServerUpdate do
@moduledoc "VOICE_SERVER_UPDATE. A null `endpoint` means the server was lost; await reallocation. https://docs.discord.com/developers/events/gateway-events#voice-server-update"
use Dexcord.Struct
discord_struct do
field :token, :string
field :guild_id, :snowflake
field :endpoint, :string
end
end
defmodule Dexcord.Events.WebhooksUpdate do
@moduledoc "WEBHOOKS_UPDATE. https://docs.discord.com/developers/events/gateway-events#webhooks-update"
use Dexcord.Struct
discord_struct do
field :guild_id, :snowflake
field :channel_id, :snowflake
hydrate :channel, from: :channel_id, type: :channel
hydrate :guild, from: :guild_id, type: Dexcord.Guild
end
end

View file

@ -0,0 +1,14 @@
defmodule Dexcord.Events.Ready do
@moduledoc "The READY dispatch payload. https://docs.discord.com/developers/events/gateway-events#ready"
use Dexcord.Struct
discord_struct do
field :v, :integer
field :user, {:struct, Dexcord.User}
field :guilds, {:list, {:struct, Dexcord.UnavailableGuild}}, default: []
field :session_id, :string
field :resume_gateway_url, :string
field :shard, {:list, :integer}
field :application, :raw
end
end

View file

@ -0,0 +1,252 @@
defmodule Dexcord.EventsEnvelopeTest do
use ExUnit.Case, async: true
alias Dexcord.Events
describe "message_events.ex" do
test "ReactionAdd decodes member, unicode emoji, burst; hydrate slots default nil" do
raw = %{
"user_id" => "111",
"channel_id" => "222",
"message_id" => "333",
"guild_id" => "444",
"member" => %{"nick" => "bee", "user" => %{"id" => "111", "username" => "bee"}},
"emoji" => %{"id" => nil, "name" => "🔥"},
"message_author_id" => "555",
"burst" => true,
"burst_colors" => ["#ffffff"],
"type" => 0
}
assert %Events.ReactionAdd{} = ev = Events.ReactionAdd.from_map(raw)
assert ev.user_id == 111
assert ev.channel_id == 222
assert ev.message_id == 333
assert ev.guild_id == 444
assert %Dexcord.Member{nick: "bee"} = ev.member
assert %Dexcord.User{username: "bee"} = ev.member.user
assert %Dexcord.PartialEmoji{name: "🔥", id: nil} = ev.emoji
assert ev.message_author_id == 555
assert ev.burst == true
assert ev.burst_colors == ["#ffffff"]
assert ev.type == :normal
# hydrate slots exist, default to nil, and are declared
assert ev.user == nil
assert ev.channel == nil
assert ev.guild == nil
slots = Enum.map(Events.ReactionAdd.__hydrations__(), & &1.name)
assert :user in slots
assert :channel in slots
assert :guild in slots
end
test "MessageDelete decodes minimal payload with hydrate slots" do
ev = Events.MessageDelete.from_map(%{"id" => "1", "channel_id" => "2", "guild_id" => "3"})
assert %Events.MessageDelete{id: 1, channel_id: 2, guild_id: 3} = ev
assert ev.channel == nil
assert ev.guild == nil
end
test "MessageDeleteBulk decodes ids list" do
ev = Events.MessageDeleteBulk.from_map(%{"ids" => ["1", "2"], "channel_id" => "9"})
assert ev.ids == [1, 2]
assert ev.channel_id == 9
end
test "PollVoteAdd decodes answer_id" do
ev =
Events.PollVoteAdd.from_map(%{
"user_id" => "1",
"channel_id" => "2",
"message_id" => "3",
"guild_id" => "4",
"answer_id" => 7
})
assert ev.answer_id == 7
assert ev.user == nil
end
end
describe "guild_events.ex" do
test "GuildMemberUpdate: nick present-null -> nil, absent premium_since -> :absent" do
raw = %{
"guild_id" => "1",
"roles" => ["10", "11"],
"user" => %{"id" => "2", "username" => "x"},
"nick" => nil,
"joined_at" => "2021-01-01T00:00:00.000000+00:00"
}
ev = Events.GuildMemberUpdate.from_map(raw)
assert ev.guild_id == 1
assert ev.roles == [10, 11]
assert %Dexcord.User{username: "x"} = ev.user
assert ev.nick == nil
assert ev.premium_since == :absent
assert ev.communication_disabled_until == :absent
end
test "GuildRoleCreate decodes nested role + guild hydrate" do
ev =
Events.GuildRoleCreate.from_map(%{
"guild_id" => "1",
"role" => %{"id" => "5", "name" => "admin"}
})
assert ev.guild_id == 1
assert %Dexcord.Role{name: "admin"} = ev.role
assert ev.guild == nil
end
test "GuildRoleDelete decodes role_id" do
ev = Events.GuildRoleDelete.from_map(%{"guild_id" => "1", "role_id" => "5"})
assert ev.role_id == 5
end
test "GuildMembersChunk decodes nested members" do
ev =
Events.GuildMembersChunk.from_map(%{
"guild_id" => "1",
"members" => [%{"user" => %{"id" => "2"}}],
"chunk_index" => 0,
"chunk_count" => 1
})
assert [%Dexcord.Member{}] = ev.members
assert ev.chunk_index == 0
end
end
describe "channel_events.ex" do
test "ThreadMembersUpdate: added_members with nested member" do
raw = %{
"id" => "1",
"guild_id" => "2",
"member_count" => 3,
"added_members" => [
%{"id" => "1", "user_id" => "9", "member" => %{"user" => %{"id" => "9"}}}
],
"removed_member_ids" => ["8"]
}
ev = Events.ThreadMembersUpdate.from_map(raw)
assert ev.member_count == 3
assert [%Dexcord.ThreadMember{} = tm] = ev.added_members
assert %Dexcord.Member{} = tm.member
assert ev.removed_member_ids == [8]
end
test "ChannelPinsUpdate tristate last_pin_timestamp absent -> :absent" do
ev = Events.ChannelPinsUpdate.from_map(%{"guild_id" => "1", "channel_id" => "2"})
assert ev.last_pin_timestamp == :absent
assert ev.channel == nil
end
test "ThreadDelete decodes type enum" do
ev =
Events.ThreadDelete.from_map(%{
"id" => "1",
"guild_id" => "2",
"parent_id" => "3",
"type" => 11
})
assert ev.id == 1
assert ev.type == :public_thread
end
end
describe "misc_events.ex" do
test "TypingStart: integer timestamp stays integer" do
raw = %{
"channel_id" => "1",
"guild_id" => "2",
"user_id" => "3",
"timestamp" => 1_700_000_000,
"member" => %{"user" => %{"id" => "3"}}
}
ev = Events.TypingStart.from_map(raw)
assert ev.timestamp == 1_700_000_000
assert is_integer(ev.timestamp)
assert %Dexcord.Member{} = ev.member
assert ev.user == nil
end
test "InviteCreate: expires_at null stays nil" do
raw = %{
"channel_id" => "1",
"code" => "abc",
"created_at" => "2021-01-01T00:00:00.000000+00:00",
"guild_id" => "2",
"max_age" => 3600,
"max_uses" => 0,
"temporary" => false,
"uses" => 0,
"expires_at" => nil
}
ev = Events.InviteCreate.from_map(raw)
assert ev.code == "abc"
assert ev.expires_at == nil
assert ev.channel == nil
end
test "VoiceServerUpdate decodes token/endpoint" do
ev =
Events.VoiceServerUpdate.from_map(%{
"token" => "t",
"guild_id" => "1",
"endpoint" => "smart.example"
})
assert ev.token == "t"
assert ev.endpoint == "smart.example"
end
test "AutoModerationActionExecution decodes nested action + enum" do
ev =
Events.AutoModerationActionExecution.from_map(%{
"guild_id" => "1",
"action" => %{"type" => 1, "metadata" => %{"channel_id" => "9"}},
"rule_id" => "2",
"rule_trigger_type" => 1,
"user_id" => "3",
"content" => "bad word"
})
assert %Dexcord.AutoModerationAction{type: :block_message} = ev.action
assert ev.rule_trigger_type == :keyword
assert ev.content == "bad word"
end
test "WebhooksUpdate decodes with channel/guild hydrates" do
ev = Events.WebhooksUpdate.from_map(%{"guild_id" => "1", "channel_id" => "2"})
assert ev.guild_id == 1
assert ev.channel_id == 2
assert ev.channel == nil
assert ev.guild == nil
end
end
describe "ready.ex" do
test "Ready decodes user + unavailable guilds" do
ev =
Events.Ready.from_map(%{
"v" => 10,
"user" => %{"id" => "1", "username" => "bot"},
"guilds" => [%{"id" => "9", "unavailable" => true}],
"session_id" => "sess",
"resume_gateway_url" => "wss://example"
})
assert ev.v == 10
assert %Dexcord.User{username: "bot"} = ev.user
assert [%Dexcord.UnavailableGuild{id: 9, unavailable: true}] = ev.guilds
assert ev.session_id == "sess"
end
end
end