api-surface #1
6 changed files with 411 additions and 0 deletions
feat(model): Message + reference/snapshot/call satellites, golden message fixtures
commit
3f52d82e6c
91
lib/dexcord/model/message.ex
Normal file
91
lib/dexcord/model/message.ex
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
defmodule Dexcord.Message do
|
||||
@moduledoc "A Discord message. https://docs.discord.com/developers/resources/message"
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :id, :snowflake
|
||||
field :channel_id, :snowflake
|
||||
field :guild_id, :snowflake
|
||||
field :author, {:struct, Dexcord.User}
|
||||
field :member, {:struct, Dexcord.PartialMember}
|
||||
field :content, :string
|
||||
field :timestamp, :datetime
|
||||
field :edited_timestamp, :datetime
|
||||
field :tts, :boolean
|
||||
field :mention_everyone, :boolean
|
||||
field :mentions, {:list, {:struct, Dexcord.User}}, default: []
|
||||
field :mention_roles, {:list, :snowflake}, default: []
|
||||
field :mention_channels, {:list, {:struct, Dexcord.ChannelMention}}, default: []
|
||||
field :attachments, {:list, {:struct, Dexcord.Attachment}}, default: []
|
||||
field :embeds, {:list, {:struct, Dexcord.Embed}}, default: []
|
||||
field :reactions, {:list, {:struct, Dexcord.Reaction}}, default: []
|
||||
field :nonce, :raw
|
||||
field :pinned, :boolean
|
||||
field :webhook_id, :snowflake
|
||||
field :type, {:enum, Dexcord.MessageType}
|
||||
field :channel_type, :integer
|
||||
field :activity, :raw
|
||||
field :application, :raw
|
||||
field :application_id, :snowflake
|
||||
field :flags, {:flags, Dexcord.MessageFlags}
|
||||
field :message_reference, {:struct, Dexcord.MessageReference}
|
||||
field :message_snapshots, {:list, {:struct, Dexcord.MessageSnapshot}}
|
||||
field :referenced_message, {:struct, __MODULE__}, tristate: true
|
||||
field :interaction_metadata, :raw
|
||||
field :interaction, :raw
|
||||
field :thread, {:struct, Dexcord.Channel}
|
||||
field :components, :raw
|
||||
field :sticker_items, {:list, {:struct, Dexcord.StickerItem}}, default: []
|
||||
field :stickers, {:list, {:struct, Dexcord.Sticker}}
|
||||
field :position, :integer
|
||||
field :role_subscription_data, :raw
|
||||
field :resolved, :raw
|
||||
field :poll, :raw
|
||||
field :call, {:struct, Dexcord.MessageCall}
|
||||
field :shared_client_theme, :raw
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.MessageReference do
|
||||
@moduledoc false
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :type, {:enum, Dexcord.MessageReferenceType}
|
||||
field :message_id, :snowflake
|
||||
field :channel_id, :snowflake
|
||||
field :guild_id, :snowflake
|
||||
field :fail_if_not_exists, :boolean
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.MessageSnapshot do
|
||||
@moduledoc "A forwarded-message snapshot (partial message; no author)."
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :message, {:struct, Dexcord.Message}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.MessageCall do
|
||||
@moduledoc false
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :participants, {:list, :snowflake}, default: []
|
||||
field :ended_timestamp, :datetime
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.ChannelMention do
|
||||
@moduledoc false
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :id, :snowflake
|
||||
field :guild_id, :snowflake
|
||||
field :type, {:enum, Dexcord.ChannelType}
|
||||
field :name, :string
|
||||
end
|
||||
end
|
||||
168
test/dexcord/model_message_test.exs
Normal file
168
test/dexcord/model_message_test.exs
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
defmodule Dexcord.ModelMessageTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
describe "api-surface.AC2.11: MESSAGE_CREATE member-without-user + full author" do
|
||||
setup do
|
||||
{:ok, msg: Dexcord.Message.from_map(Dexcord.Fixtures.load!("message_create_guild.json"))}
|
||||
end
|
||||
|
||||
test "member decodes to a %PartialMember{} with nick/roles/joined_at", %{msg: msg} do
|
||||
assert %Dexcord.PartialMember{} = msg.member
|
||||
assert msg.member.nick == "The Big N"
|
||||
assert msg.member.roles == [197_038_439_483_310_087, 197_038_439_483_310_088]
|
||||
assert %DateTime{} = msg.member.joined_at
|
||||
# PartialMember genuinely has no :user field.
|
||||
refute Map.has_key?(Map.from_struct(msg.member), :user)
|
||||
end
|
||||
|
||||
test "author decodes to a full %User{}", %{msg: msg} do
|
||||
assert %Dexcord.User{} = msg.author
|
||||
assert msg.author.id == 80_351_110_224_678_912
|
||||
assert msg.author.username == "Nelly"
|
||||
end
|
||||
|
||||
test "mentions decode to %User{} list, ignoring the embedded member key", %{msg: msg} do
|
||||
assert [%Dexcord.User{} = mentioned] = msg.mentions
|
||||
assert mentioned.id == 104_937_225_384_493_056
|
||||
assert mentioned.username == "friend"
|
||||
# The per-mention embedded partial member is ignored (unknown-key tolerance).
|
||||
refute Map.has_key?(Map.from_struct(mentioned), :member)
|
||||
end
|
||||
|
||||
test "mention_roles decode to snowflake integers", %{msg: msg} do
|
||||
assert msg.mention_roles == [197_038_439_483_310_088]
|
||||
end
|
||||
|
||||
test "attachments and embeds decode to typed structs", %{msg: msg} do
|
||||
assert [%Dexcord.Attachment{filename: "cat.png"}] = msg.attachments
|
||||
assert [%Dexcord.Embed{title: "An Embed"}] = msg.embeds
|
||||
end
|
||||
end
|
||||
|
||||
describe "api-surface.AC2.12: webhook-authored message" do
|
||||
setup do
|
||||
{:ok, msg: Dexcord.Message.from_map(Dexcord.Fixtures.load!("message_webhook.json"))}
|
||||
end
|
||||
|
||||
test "author is a synthetic %User{} whose id equals webhook_id", %{msg: msg} do
|
||||
assert %Dexcord.User{} = msg.author
|
||||
assert msg.author.id == msg.webhook_id
|
||||
assert msg.webhook_id == 223_704_706_495_545_344
|
||||
end
|
||||
|
||||
test "there is no member", %{msg: msg} do
|
||||
assert msg.member == nil
|
||||
end
|
||||
|
||||
test "application_id decodes to a snowflake", %{msg: msg} do
|
||||
assert msg.application_id == 223_704_706_495_545_344
|
||||
end
|
||||
|
||||
test "decode does not raise", %{msg: msg} do
|
||||
assert %Dexcord.Message{} = msg
|
||||
end
|
||||
end
|
||||
|
||||
describe "api-surface.AC2.13: referenced_message tristate" do
|
||||
test "present-null referenced_message decodes to nil (deleted reply)" do
|
||||
map = Dexcord.Fixtures.load!("message_reply_deleted.json")
|
||||
msg = Dexcord.Message.from_map(map)
|
||||
|
||||
assert msg.referenced_message == nil
|
||||
assert msg.type == :reply
|
||||
assert %Dexcord.MessageReference{} = msg.message_reference
|
||||
assert msg.message_reference.message_id == 1_052_425_005_447_712_818
|
||||
end
|
||||
|
||||
test "absent referenced_message decodes to :absent" do
|
||||
map =
|
||||
"message_reply_deleted.json"
|
||||
|> Dexcord.Fixtures.load!()
|
||||
|> Map.delete("referenced_message")
|
||||
|
||||
msg = Dexcord.Message.from_map(map)
|
||||
assert msg.referenced_message == :absent
|
||||
end
|
||||
|
||||
test "present referenced_message decodes to a self-nested %Message{}" do
|
||||
map =
|
||||
"message_reply_deleted.json"
|
||||
|> Dexcord.Fixtures.load!()
|
||||
|> Map.put("referenced_message", %{
|
||||
"id" => "1052425005447712818",
|
||||
"channel_id" => "155361364909801472",
|
||||
"author" => %{"id" => "80351110224678912", "username" => "Nelly"},
|
||||
"content" => "the original message",
|
||||
"type" => 0
|
||||
})
|
||||
|
||||
msg = Dexcord.Message.from_map(map)
|
||||
assert %Dexcord.Message{} = msg.referenced_message
|
||||
assert msg.referenced_message.content == "the original message"
|
||||
assert %Dexcord.User{} = msg.referenced_message.author
|
||||
end
|
||||
end
|
||||
|
||||
describe "DM message" do
|
||||
setup do
|
||||
{:ok, msg: Dexcord.Message.from_map(Dexcord.Fixtures.load!("message_dm.json"))}
|
||||
end
|
||||
|
||||
test "has no guild_id and no member, channel_type 1", %{msg: msg} do
|
||||
assert msg.guild_id == nil
|
||||
assert msg.member == nil
|
||||
assert msg.channel_type == 1
|
||||
end
|
||||
end
|
||||
|
||||
describe "scalar decoding" do
|
||||
setup do
|
||||
{:ok, msg: Dexcord.Message.from_map(Dexcord.Fixtures.load!("message_create_guild.json"))}
|
||||
end
|
||||
|
||||
test "timestamp decodes to %DateTime{}", %{msg: msg} do
|
||||
assert %DateTime{} = msg.timestamp
|
||||
end
|
||||
|
||||
test "default-type message decodes type to :default atom", %{msg: msg} do
|
||||
assert msg.type == :default
|
||||
end
|
||||
end
|
||||
|
||||
describe "satellites" do
|
||||
test "MessageCall decodes participants and ended_timestamp" do
|
||||
call =
|
||||
Dexcord.MessageCall.from_map(%{
|
||||
"participants" => ["80351110224678912", "104937225384493056"],
|
||||
"ended_timestamp" => "2022-12-14T09:00:00.000000+00:00"
|
||||
})
|
||||
|
||||
assert call.participants == [80_351_110_224_678_912, 104_937_225_384_493_056]
|
||||
assert %DateTime{} = call.ended_timestamp
|
||||
end
|
||||
|
||||
test "MessageSnapshot wraps a partial (author-less) message" do
|
||||
snap =
|
||||
Dexcord.MessageSnapshot.from_map(%{
|
||||
"message" => %{"content" => "forwarded content", "type" => 0}
|
||||
})
|
||||
|
||||
assert %Dexcord.Message{} = snap.message
|
||||
assert snap.message.content == "forwarded content"
|
||||
assert snap.message.author == nil
|
||||
end
|
||||
|
||||
test "ChannelMention decodes type to the enum atom" do
|
||||
cm =
|
||||
Dexcord.ChannelMention.from_map(%{
|
||||
"id" => "155361364909801472",
|
||||
"guild_id" => "197038439483310086",
|
||||
"type" => 0,
|
||||
"name" => "general"
|
||||
})
|
||||
|
||||
assert cm.type == :guild_text
|
||||
assert cm.id == 155_361_364_909_801_472
|
||||
end
|
||||
end
|
||||
end
|
||||
70
test/fixtures/message_create_guild.json
vendored
Normal file
70
test/fixtures/message_create_guild.json
vendored
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
{
|
||||
"id": "1052425005447712818",
|
||||
"channel_id": "155361364909801472",
|
||||
"guild_id": "197038439483310086",
|
||||
"author": {
|
||||
"id": "80351110224678912",
|
||||
"username": "Nelly",
|
||||
"discriminator": "1337",
|
||||
"global_name": "Nelly",
|
||||
"avatar": "8342729096ea3675442027381ff50dfe",
|
||||
"bot": false,
|
||||
"public_flags": 131072
|
||||
},
|
||||
"member": {
|
||||
"nick": "The Big N",
|
||||
"roles": ["197038439483310087", "197038439483310088"],
|
||||
"joined_at": "2021-04-11T15:00:00.000000+00:00",
|
||||
"deaf": false,
|
||||
"mute": false,
|
||||
"flags": 0,
|
||||
"pending": false
|
||||
},
|
||||
"content": "Hey <@104937225384493056> check this out",
|
||||
"timestamp": "2022-12-14T05:06:00.000000+00:00",
|
||||
"edited_timestamp": null,
|
||||
"tts": false,
|
||||
"mention_everyone": false,
|
||||
"mentions": [
|
||||
{
|
||||
"id": "104937225384493056",
|
||||
"username": "friend",
|
||||
"discriminator": "0001",
|
||||
"global_name": "Friend",
|
||||
"avatar": null,
|
||||
"member": {
|
||||
"nick": "buddy",
|
||||
"roles": ["197038439483310087"],
|
||||
"joined_at": "2021-05-01T00:00:00.000000+00:00"
|
||||
}
|
||||
}
|
||||
],
|
||||
"mention_roles": ["197038439483310088"],
|
||||
"attachments": [
|
||||
{
|
||||
"id": "1052425005447712819",
|
||||
"filename": "cat.png",
|
||||
"size": 45678,
|
||||
"url": "https://cdn.discordapp.com/attachments/1/2/cat.png",
|
||||
"proxy_url": "https://media.discordapp.net/attachments/1/2/cat.png",
|
||||
"height": 512,
|
||||
"width": 512,
|
||||
"content_type": "image/png"
|
||||
}
|
||||
],
|
||||
"embeds": [
|
||||
{
|
||||
"title": "An Embed",
|
||||
"type": "rich",
|
||||
"description": "A described embed",
|
||||
"color": 5814783,
|
||||
"timestamp": "2022-12-14T05:00:00.000000+00:00"
|
||||
}
|
||||
],
|
||||
"reactions": [],
|
||||
"nonce": "1052425005447712818",
|
||||
"pinned": false,
|
||||
"type": 0,
|
||||
"channel_type": 0,
|
||||
"flags": 0
|
||||
}
|
||||
24
test/fixtures/message_dm.json
vendored
Normal file
24
test/fixtures/message_dm.json
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"id": "1052425005447713100",
|
||||
"channel_id": "319674150115823165",
|
||||
"author": {
|
||||
"id": "80351110224678912",
|
||||
"username": "Nelly",
|
||||
"discriminator": "1337",
|
||||
"global_name": "Nelly",
|
||||
"avatar": "8342729096ea3675442027381ff50dfe"
|
||||
},
|
||||
"content": "a private message",
|
||||
"timestamp": "2022-12-14T08:00:00.000000+00:00",
|
||||
"edited_timestamp": null,
|
||||
"tts": false,
|
||||
"mention_everyone": false,
|
||||
"mentions": [],
|
||||
"mention_roles": [],
|
||||
"attachments": [],
|
||||
"embeds": [],
|
||||
"pinned": false,
|
||||
"type": 0,
|
||||
"channel_type": 1,
|
||||
"flags": 0
|
||||
}
|
||||
31
test/fixtures/message_reply_deleted.json
vendored
Normal file
31
test/fixtures/message_reply_deleted.json
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"id": "1052425005447713000",
|
||||
"channel_id": "155361364909801472",
|
||||
"guild_id": "197038439483310086",
|
||||
"author": {
|
||||
"id": "80351110224678912",
|
||||
"username": "Nelly",
|
||||
"discriminator": "1337",
|
||||
"global_name": "Nelly",
|
||||
"avatar": "8342729096ea3675442027381ff50dfe"
|
||||
},
|
||||
"content": "replying to a message that got deleted",
|
||||
"timestamp": "2022-12-14T07:00:00.000000+00:00",
|
||||
"edited_timestamp": null,
|
||||
"tts": false,
|
||||
"mention_everyone": false,
|
||||
"mentions": [],
|
||||
"mention_roles": [],
|
||||
"attachments": [],
|
||||
"embeds": [],
|
||||
"pinned": false,
|
||||
"type": 19,
|
||||
"flags": 0,
|
||||
"message_reference": {
|
||||
"type": 0,
|
||||
"message_id": "1052425005447712818",
|
||||
"channel_id": "155361364909801472",
|
||||
"guild_id": "197038439483310086"
|
||||
},
|
||||
"referenced_message": null
|
||||
}
|
||||
27
test/fixtures/message_webhook.json
vendored
Normal file
27
test/fixtures/message_webhook.json
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"id": "1052425005447712900",
|
||||
"channel_id": "155361364909801472",
|
||||
"guild_id": "197038439483310086",
|
||||
"webhook_id": "223704706495545344",
|
||||
"author": {
|
||||
"id": "223704706495545344",
|
||||
"username": "GitHub",
|
||||
"discriminator": "0000",
|
||||
"avatar": null,
|
||||
"bot": true
|
||||
},
|
||||
"content": "A new commit was pushed",
|
||||
"timestamp": "2022-12-14T06:00:00.000000+00:00",
|
||||
"edited_timestamp": null,
|
||||
"tts": false,
|
||||
"mention_everyone": false,
|
||||
"mentions": [],
|
||||
"mention_roles": [],
|
||||
"attachments": [],
|
||||
"embeds": [],
|
||||
"reactions": [],
|
||||
"pinned": false,
|
||||
"type": 0,
|
||||
"application_id": "223704706495545344",
|
||||
"flags": 0
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue