api-surface #1
4 changed files with 215 additions and 1 deletions
feat(model): Member/PartialMember via shared field group, thread + forum satellites
commit
c9c4a3c445
49
lib/dexcord/model/channel_parts.ex
Normal file
49
lib/dexcord/model/channel_parts.ex
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
defmodule Dexcord.ThreadMetadata do
|
||||
@moduledoc false
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :archived, :boolean
|
||||
field :auto_archive_duration, :integer
|
||||
field :archive_timestamp, :datetime
|
||||
field :locked, :boolean
|
||||
field :invitable, :boolean
|
||||
field :create_timestamp, :datetime
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.ThreadMember do
|
||||
@moduledoc false
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :id, :snowflake
|
||||
field :user_id, :snowflake
|
||||
field :join_timestamp, :datetime
|
||||
field :flags, :integer
|
||||
field :member, {:struct, Dexcord.Member}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.ForumTag do
|
||||
@moduledoc false
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :id, :snowflake
|
||||
field :name, :string
|
||||
field :moderated, :boolean
|
||||
field :emoji_id, :snowflake
|
||||
field :emoji_name, :string
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.DefaultReaction do
|
||||
@moduledoc false
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :emoji_id, :snowflake
|
||||
field :emoji_name, :string
|
||||
end
|
||||
end
|
||||
45
lib/dexcord/model/member.ex
Normal file
45
lib/dexcord/model/member.ex
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
defmodule Dexcord.Model.MemberShared do
|
||||
@moduledoc false
|
||||
# Field group shared by Dexcord.Member and Dexcord.PartialMember.
|
||||
|
||||
def __included_fields__ do
|
||||
[
|
||||
{:nick, :string, []},
|
||||
{:avatar, :string, []},
|
||||
{:banner, :string, []},
|
||||
{:roles, {:list, :snowflake}, [default: []]},
|
||||
{:joined_at, :datetime, []},
|
||||
{:premium_since, :datetime, []},
|
||||
{:deaf, :boolean, []},
|
||||
{:mute, :boolean, []},
|
||||
{:flags, {:flags, Dexcord.GuildMemberFlags}, []},
|
||||
{:pending, :boolean, []},
|
||||
{:permissions, {:flags, Dexcord.Permissions}, [wire_string: true]},
|
||||
{:communication_disabled_until, :datetime, []},
|
||||
{:avatar_decoration_data, {:struct, Dexcord.AvatarDecorationData}, []},
|
||||
{:collectibles, :raw, []}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.Member do
|
||||
@moduledoc "A guild member (with its user). https://docs.discord.com/developers/resources/guild"
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :user, {:struct, Dexcord.User}
|
||||
include_fields Dexcord.Model.MemberShared
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.PartialMember do
|
||||
@moduledoc """
|
||||
A guild member without the nested user — the shape Discord sends inside
|
||||
MESSAGE_CREATE/UPDATE (`message.member`) and interaction resolved data.
|
||||
"""
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
include_fields Dexcord.Model.MemberShared
|
||||
end
|
||||
end
|
||||
118
test/dexcord/model_member_test.exs
Normal file
118
test/dexcord/model_member_test.exs
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
defmodule Dexcord.ModelMemberTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
@member_map %{
|
||||
"user" => %{"id" => "80351110224678912", "username" => "Nelly", "discriminator" => "1337"},
|
||||
"nick" => "NOT nelly",
|
||||
"roles" => ["11111111111111111", "22222222222222222"],
|
||||
"joined_at" => "2015-04-26T06:26:56.936000+00:00",
|
||||
"premium_since" => "2016-05-15T06:26:56.936000+00:00",
|
||||
"deaf" => false,
|
||||
"mute" => false,
|
||||
"flags" => 0,
|
||||
"pending" => false,
|
||||
"permissions" => "2147483648"
|
||||
}
|
||||
|
||||
describe "Member.from_map/1" do
|
||||
test "full member decodes nested user, snowflake roles, and datetime joined_at" do
|
||||
member = Dexcord.Member.from_map(@member_map)
|
||||
|
||||
assert %Dexcord.User{} = member.user
|
||||
assert member.user.id == 80_351_110_224_678_912
|
||||
assert member.nick == "NOT nelly"
|
||||
assert member.roles == [11_111_111_111_111_111, 22_222_222_222_222_222]
|
||||
assert %DateTime{} = member.joined_at
|
||||
assert %DateTime{} = member.premium_since
|
||||
assert member.deaf == false
|
||||
assert member.flags == 0
|
||||
assert member.permissions == 2_147_483_648
|
||||
end
|
||||
end
|
||||
|
||||
describe "PartialMember.from_map/1" do
|
||||
test "a member map without user decodes and the struct has no :user key" do
|
||||
map = Map.delete(@member_map, "user")
|
||||
pm = Dexcord.PartialMember.from_map(map)
|
||||
|
||||
assert %Dexcord.PartialMember{} = pm
|
||||
assert pm.nick == "NOT nelly"
|
||||
assert pm.roles == [11_111_111_111_111_111, 22_222_222_222_222_222]
|
||||
refute Map.has_key?(Map.from_struct(pm), :user)
|
||||
end
|
||||
end
|
||||
|
||||
describe "shared field group parity" do
|
||||
test "Member and PartialMember __fields__ differ by exactly the :user entry" do
|
||||
member_names = Enum.map(Dexcord.Member.__fields__(), & &1.name)
|
||||
partial_names = Enum.map(Dexcord.PartialMember.__fields__(), & &1.name)
|
||||
|
||||
assert member_names -- partial_names == [:user]
|
||||
assert partial_names -- member_names == []
|
||||
end
|
||||
end
|
||||
|
||||
describe "ThreadMember.from_map/1" do
|
||||
test "thread member with nested member decodes" do
|
||||
map = %{
|
||||
"id" => "1",
|
||||
"user_id" => "2",
|
||||
"join_timestamp" => "2021-01-01T00:00:00.000000+00:00",
|
||||
"flags" => 1,
|
||||
"member" => @member_map
|
||||
}
|
||||
|
||||
tm = Dexcord.ThreadMember.from_map(map)
|
||||
|
||||
assert tm.id == 1
|
||||
assert tm.user_id == 2
|
||||
assert %DateTime{} = tm.join_timestamp
|
||||
assert tm.flags == 1
|
||||
assert %Dexcord.Member{} = tm.member
|
||||
assert %Dexcord.User{} = tm.member.user
|
||||
end
|
||||
end
|
||||
|
||||
describe "ThreadMetadata / ForumTag / DefaultReaction" do
|
||||
test "thread metadata decodes timestamps and booleans" do
|
||||
map = %{
|
||||
"archived" => true,
|
||||
"auto_archive_duration" => 1440,
|
||||
"archive_timestamp" => "2021-01-01T00:00:00.000000+00:00",
|
||||
"locked" => false,
|
||||
"invitable" => true,
|
||||
"create_timestamp" => "2021-01-01T00:00:00.000000+00:00"
|
||||
}
|
||||
|
||||
tmeta = Dexcord.ThreadMetadata.from_map(map)
|
||||
assert tmeta.archived == true
|
||||
assert tmeta.auto_archive_duration == 1440
|
||||
assert %DateTime{} = tmeta.archive_timestamp
|
||||
assert tmeta.locked == false
|
||||
assert tmeta.invitable == true
|
||||
end
|
||||
|
||||
test "forum tag decodes emoji fields" do
|
||||
tag =
|
||||
Dexcord.ForumTag.from_map(%{
|
||||
"id" => "1",
|
||||
"name" => "help",
|
||||
"moderated" => true,
|
||||
"emoji_id" => "2",
|
||||
"emoji_name" => nil
|
||||
})
|
||||
|
||||
assert tag.id == 1
|
||||
assert tag.name == "help"
|
||||
assert tag.moderated == true
|
||||
assert tag.emoji_id == 2
|
||||
assert tag.emoji_name == nil
|
||||
end
|
||||
|
||||
test "default reaction decodes" do
|
||||
dr = Dexcord.DefaultReaction.from_map(%{"emoji_id" => nil, "emoji_name" => "🔥"})
|
||||
assert dr.emoji_id == nil
|
||||
assert dr.emoji_name == "🔥"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -104,7 +104,9 @@ defmodule Dexcord.ModelUserTest do
|
|||
end
|
||||
|
||||
test "custom emoji decodes id, name, and animated" do
|
||||
emoji = Dexcord.PartialEmoji.from_map(%{"id" => "123", "name" => "blob", "animated" => true})
|
||||
emoji =
|
||||
Dexcord.PartialEmoji.from_map(%{"id" => "123", "name" => "blob", "animated" => true})
|
||||
|
||||
assert emoji.id == 123
|
||||
assert emoji.name == "blob"
|
||||
assert emoji.animated == true
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue