api-surface #1

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

feat(model): User, AvatarDecorationData, PrimaryGuild, Emoji, PartialEmoji, Sticker, StickerItem

Luna 2026-07-05 03:52:52 -03:00

View file

@ -0,0 +1,30 @@
defmodule Dexcord.Emoji do
@moduledoc "A guild emoji. https://docs.discord.com/developers/resources/emoji"
use Dexcord.Struct
discord_struct do
field :id, :snowflake
field :name, :string
field :roles, {:list, :snowflake}, default: []
field :user, {:struct, Dexcord.User}
field :require_colons, :boolean
field :managed, :boolean
field :animated, :boolean, default: false
field :available, :boolean
end
end
defmodule Dexcord.PartialEmoji do
@moduledoc """
The minimal emoji shape used in reactions, components, and poll media.
Unicode emoji: `id` nil, `name` the character. Custom: `id` set; `name`
may be nil when the emoji's data is unavailable (deleted custom emoji).
"""
use Dexcord.Struct
discord_struct do
field :id, :snowflake
field :name, :string
field :animated, :boolean, default: false
end
end

View file

@ -0,0 +1,29 @@
defmodule Dexcord.Sticker do
@moduledoc "A message sticker. https://docs.discord.com/developers/resources/sticker"
use Dexcord.Struct
discord_struct do
field :id, :snowflake
field :pack_id, :snowflake
field :name, :string
field :description, :string
field :tags, :string
field :type, {:enum, Dexcord.StickerType}
field :format_type, {:enum, Dexcord.StickerFormatType}
field :available, :boolean
field :guild_id, :snowflake
field :user, {:struct, Dexcord.User}
field :sort_value, :integer
end
end
defmodule Dexcord.StickerItem do
@moduledoc "The smallest amount of data required to render a sticker."
use Dexcord.Struct
discord_struct do
field :id, :snowflake
field :name, :string
field :format_type, {:enum, Dexcord.StickerFormatType}
end
end

48
lib/dexcord/model/user.ex Normal file
View file

@ -0,0 +1,48 @@
defmodule Dexcord.User do
@moduledoc "A Discord user. https://docs.discord.com/developers/resources/user"
use Dexcord.Struct
discord_struct do
field :id, :snowflake
field :username, :string
field :discriminator, :string
field :global_name, :string
field :avatar, :string
field :bot, :boolean, default: false
field :system, :boolean, default: false
field :mfa_enabled, :boolean
field :banner, :string
field :accent_color, :integer
field :locale, :string
field :verified, :boolean
field :email, :string
field :flags, {:flags, Dexcord.UserFlags}
field :premium_type, {:enum, Dexcord.PremiumType}
field :public_flags, {:flags, Dexcord.UserFlags}
field :avatar_decoration_data, {:struct, Dexcord.AvatarDecorationData}
field :collectibles, :raw
field :primary_guild, {:struct, Dexcord.PrimaryGuild}
end
end
defmodule Dexcord.AvatarDecorationData do
@moduledoc false
use Dexcord.Struct
discord_struct do
field :asset, :string
field :sku_id, :snowflake
end
end
defmodule Dexcord.PrimaryGuild do
@moduledoc "A user's primary-guild (server tag) identity."
use Dexcord.Struct
discord_struct do
field :identity_guild_id, :snowflake
field :identity_enabled, :boolean
field :tag, :string
field :badge, :string
end
end

View file

@ -0,0 +1,186 @@
defmodule Dexcord.ModelUserTest do
use ExUnit.Case, async: true
describe "User.from_map/1" do
test "full user map decodes field-correct with snowflake normalized to integer" do
map = %{
"id" => "80351110224678912",
"username" => "Nelly",
"discriminator" => "1337",
"global_name" => "Nelly",
"avatar" => "8342729096ea3675442027381ff50dfe",
"bot" => false,
"system" => false,
"mfa_enabled" => true,
"banner" => "06c16474723fe537c283b8efa61a30c8",
"accent_color" => 16_711_680,
"locale" => "en-US",
"verified" => true,
"email" => "nelly@discord.com",
"flags" => 64,
"premium_type" => 1,
"public_flags" => 64,
"avatar_decoration_data" => %{
"asset" => "a_fb5bc2b3625390fd53a5e6ecb268e5ba",
"sku_id" => "1144058844004233369"
},
"collectibles" => %{"nameplate" => %{"sku_id" => "x"}},
"primary_guild" => %{
"identity_guild_id" => "123456789",
"identity_enabled" => true,
"tag" => "PONY",
"badge" => "abc123"
}
}
user = Dexcord.User.from_map(map)
assert user.id == 80_351_110_224_678_912
assert user.username == "Nelly"
assert user.discriminator == "1337"
assert user.global_name == "Nelly"
assert user.avatar == "8342729096ea3675442027381ff50dfe"
assert user.bot == false
assert user.system == false
assert user.mfa_enabled == true
assert user.banner == "06c16474723fe537c283b8efa61a30c8"
assert user.accent_color == 16_711_680
assert user.locale == "en-US"
assert user.verified == true
assert user.email == "nelly@discord.com"
assert user.flags == 64
assert user.premium_type == :nitro_classic
assert user.public_flags == 64
assert user.collectibles == %{"nameplate" => %{"sku_id" => "x"}}
assert %Dexcord.AvatarDecorationData{} = user.avatar_decoration_data
assert user.avatar_decoration_data.asset == "a_fb5bc2b3625390fd53a5e6ecb268e5ba"
assert user.avatar_decoration_data.sku_id == 1_144_058_844_004_233_369
assert %Dexcord.PrimaryGuild{} = user.primary_guild
assert user.primary_guild.identity_guild_id == 123_456_789
assert user.primary_guild.identity_enabled == true
assert user.primary_guild.tag == "PONY"
assert user.primary_guild.badge == "abc123"
end
test "minimal user map defaults other fields to nil and bot to false" do
map = %{
"id" => "1",
"username" => "min",
"discriminator" => "0",
"global_name" => "min",
"avatar" => nil
}
user = Dexcord.User.from_map(map)
assert user.id == 1
assert user.username == "min"
assert user.avatar == nil
assert user.bot == false
assert user.system == false
assert user.mfa_enabled == nil
assert user.flags == nil
assert user.premium_type == nil
assert user.avatar_decoration_data == nil
assert user.primary_guild == nil
end
test "round-trip re-encodes id as a string" do
map = %{"id" => "80351110224678912", "username" => "Nelly", "discriminator" => "1337"}
out = map |> Dexcord.User.from_map() |> Dexcord.User.to_map()
assert out["id"] == "80351110224678912"
assert out["username"] == "Nelly"
end
end
describe "PartialEmoji.from_map/1" do
test "unicode emoji decodes with nil id and the character as name" do
emoji = Dexcord.PartialEmoji.from_map(%{"id" => nil, "name" => "🔥"})
assert emoji.id == nil
assert emoji.name == "🔥"
assert emoji.animated == false
end
test "custom emoji decodes id, name, and animated" do
emoji = Dexcord.PartialEmoji.from_map(%{"id" => "123", "name" => "blob", "animated" => true})
assert emoji.id == 123
assert emoji.name == "blob"
assert emoji.animated == true
end
test "deleted custom emoji with null name decodes name to nil" do
emoji = Dexcord.PartialEmoji.from_map(%{"id" => "123", "name" => nil})
assert emoji.id == 123
assert emoji.name == nil
end
end
describe "Emoji.from_map/1" do
test "guild emoji decodes with roles and nested user" do
map = %{
"id" => "41771983429993937",
"name" => "LUL",
"roles" => ["41771983429993000"],
"user" => %{"id" => "96008815106887111", "username" => "Cool", "discriminator" => "0001"},
"require_colons" => true,
"managed" => false,
"animated" => false,
"available" => true
}
emoji = Dexcord.Emoji.from_map(map)
assert emoji.id == 41_771_983_429_993_937
assert emoji.name == "LUL"
assert emoji.roles == [41_771_983_429_993_000]
assert %Dexcord.User{} = emoji.user
assert emoji.user.id == 96_008_815_106_887_111
assert emoji.require_colons == true
assert emoji.animated == false
assert emoji.available == true
end
test "roles defaults to empty list when absent" do
emoji = Dexcord.Emoji.from_map(%{"id" => "1", "name" => "x"})
assert emoji.roles == []
end
end
describe "Sticker.from_map/1 and StickerItem.from_map/1" do
test "sticker decodes type and format_type enums and nested user" do
map = %{
"id" => "749054660769218631",
"pack_id" => "847199849233514549",
"name" => "Wave",
"description" => "Wumpus waves hello",
"tags" => "wumpus, hello, sup, hi, oh, greeting, wave, welcome",
"type" => 1,
"format_type" => 3,
"available" => true,
"guild_id" => "1",
"user" => %{"id" => "2", "username" => "u", "discriminator" => "0"},
"sort_value" => 12
}
sticker = Dexcord.Sticker.from_map(map)
assert sticker.id == 749_054_660_769_218_631
assert sticker.pack_id == 847_199_849_233_514_549
assert sticker.name == "Wave"
assert sticker.type == :standard
assert sticker.format_type == :lottie
assert sticker.available == true
assert %Dexcord.User{} = sticker.user
assert sticker.sort_value == 12
end
test "sticker item decodes minimal shape" do
item = Dexcord.StickerItem.from_map(%{"id" => "1", "name" => "Wave", "format_type" => 1})
assert item.id == 1
assert item.name == "Wave"
assert item.format_type == :png
end
end
end