diff --git a/assets/propaganda/democracy_manifest.png b/assets/propaganda/democracy_manifest.png new file mode 100644 index 000000000..76aaf53d6 Binary files /dev/null and b/assets/propaganda/democracy_manifest.png differ diff --git a/guides/intro/gateway_intents.md b/guides/intro/gateway_intents.md index 0f28e7419..028f665b9 100644 --- a/guides/intro/gateway_intents.md +++ b/guides/intro/gateway_intents.md @@ -90,15 +90,31 @@ direct_message_reactions: direct_message_typing: - TYPING_START -GUILD_SCHEDULED_EVENTS: +message_content*: + - MESSAGE_CONTENT + +guild_scheduled_events: - GUILD_SCHEDULED_EVENT_CREATE - GUILD_SCHEDULED_EVENT_UPDATE - GUILD_SCHEDULED_EVENT_DELETE - GUILD_SCHEDULED_EVENT_USER_ADD - GUILD_SCHEDULED_EVENT_USER_REMOVE -message_content*: - - MESSAGE_CONTENT +auto_moderation_configuration: + - AUTO_MODERATION_RULE_CREATE + - AUTO_MODERATION_RULE_DELETE + - AUTO_MODERATION_RULE_UPDATE + +auto_moderation_execution: + - AUTO_MODERATION_RULE_EXECUTION + +guild_message_polls: + - MESSAGE_POLL_VOTE_ADD + - MESSAGE_POLL_VOTE_REMOVE + +direct_message_polls: + - MESSAGE_POLL_VOTE_ADD + - MESSAGE_POLL_VOTE_REMOVE ``` Besides an explicit list of atoms, acceptable configuration values are `:all` and `:nonprivileged`. diff --git a/lib/nostrum/api.ex b/lib/nostrum/api.ex index 1bda61c04..a8d4d69df 100644 --- a/lib/nostrum/api.ex +++ b/lib/nostrum/api.ex @@ -57,6 +57,7 @@ defmodule Nostrum.Api do Interaction, Invite, Message, + Message.Poll, ThreadMember, User, Webhook @@ -219,8 +220,9 @@ defmodule Nostrum.Api do * `:embeds` (`t:Nostrum.Struct.Embed.t/0`) - a list of embedded rich content * `:allowed_mentions` (`t:allowed_mentions/0`) - see the allowed mentions type documentation * `:message_reference` (`map`) - See "Message references" below + * `:poll` (`t:Nostrum.Struct.Message.Poll.t/0`) - A poll object to send with the message - At least one of the following is required: `:content`, `:file`, `:embeds`. + At least one of the following is required: `:content`, `:file`, `:embeds`, `:poll`. ### Message reference @@ -617,6 +619,57 @@ defmodule Nostrum.Api do |> bangify end + @doc ~S""" + Get voters for the provided answer on the poll attached to the provided message. + + If successful, returns `{:ok, users}`. Otherwise, returns `t:Nostrum.Api.error/0`. + + The optional `params` are `after`, the user ID to query after, absent by default, + and `limit`, the max number of users to return, 1-100, 25 by default. Results are + sorted by Discord user snowflake (ID) in ascending order. + """ + @spec get_poll_answer_voters(Channel.id(), Message.id(), Poll.Answer.answer_id()) :: + error | {:ok, [User.t()]} + def get_poll_answer_voters(channel_id, message_id, answer_id, params \\ []) do + result = + request(:get, Constants.poll_answer_voters(channel_id, message_id, answer_id), "", params) + |> handle_request_with_decode() + + case result do + {:ok, %{users: users}} -> {:ok, Util.cast(users, {:list, {:struct, User}})} + _ -> result + end + end + + @doc ~S""" + Same as `get_poll_answer_voters/4`, but raises `Nostrum.Error.ApiError` in case of failure. + """ + @spec get_poll_answer_voters!(Channel.id(), Message.id(), Poll.Answer.answer_id()) :: [User.t()] + def get_poll_answer_voters!(channel_id, message_id, answer_id, params \\ []) do + get_poll_answer_voters(channel_id, message_id, answer_id, params) + |> bangify + end + + @doc ~S""" + Expire (close voting on) a poll before the scheduled end time. + + Returns the original message containing the poll. + """ + @spec expire_poll(Channel.id(), Message.id()) :: error | {:ok, Message.t()} + def expire_poll(channel_id, message_id) do + request(:post, Constants.poll_expire(channel_id, message_id)) + |> handle_request_with_decode({:struct, Message}) + end + + @doc ~S""" + Same as `expire_poll/2`, but raises `Nostrum.Error.ApiError` in case of failure. + """ + @spec expire_poll!(Channel.id(), Message.id()) :: Message.t() + def expire_poll!(channel_id, message_id) do + expire_poll(channel_id, message_id) + |> bangify + end + @doc ~S""" Gets a channel. diff --git a/lib/nostrum/constants.ex b/lib/nostrum/constants.ex index d1e0c374d..851ddcba6 100644 --- a/lib/nostrum/constants.ex +++ b/lib/nostrum/constants.ex @@ -174,6 +174,12 @@ defmodule Nostrum.Constants do def guild_auto_moderation_rule(guild_id, rule_id), do: "/guilds/#{guild_id}/auto-moderation/rules/#{rule_id}" + def poll_answer_voters(channel_id, message_id, answer_id), + do: "/channels/#{channel_id}/polls/#{message_id}/answers/#{answer_id}" + + def poll_expire(channel_id, message_id), + do: "/channels/#{channel_id}/polls/#{message_id}/expire" + def discord_epoch, do: 1_420_070_400_000 def opcodes do diff --git a/lib/nostrum/consumer.ex b/lib/nostrum/consumer.ex index 78f45429c..a1181e866 100644 --- a/lib/nostrum/consumer.ex +++ b/lib/nostrum/consumer.ex @@ -83,6 +83,7 @@ defmodule Nostrum.Consumer do MessageReactionRemove, MessageReactionRemoveAll, MessageReactionRemoveEmoji, + PollVoteChange, Ready, SpeakingUpdate, ThreadListSync, @@ -325,6 +326,17 @@ defmodule Nostrum.Consumer do Dispatched when member(s) are added or removed from a thread """ @type thread_members_update :: {:THREAD_MEMBERS_UPDATE, ThreadMembersUpdate.t(), WSState.t()} + + @typedoc """ + Dispatched when a user adds a vote to a poll. + """ + @type message_poll_vote_add :: {:MESSAGE_POLL_VOTE_ADD, PollVoteChange.t(), WSState.t()} + + @typedoc """ + Dispatched when a user removes a vote from a poll. + """ + @type message_poll_vote_remove :: {:MESSAGE_POLL_VOTE_REMVE, PollVoteChange.t(), WSState.t()} + @type event :: auto_moderation_rule_create | auto_moderation_rule_delete @@ -364,6 +376,8 @@ defmodule Nostrum.Consumer do | message_reaction_remove | message_reaction_remove_all | message_ack + | message_poll_vote_add + | message_poll_vote_remove | presence_update | ready | resumed diff --git a/lib/nostrum/permission.ex b/lib/nostrum/permission.ex index e7b21639b..6aa23c2de 100644 --- a/lib/nostrum/permission.ex +++ b/lib/nostrum/permission.ex @@ -48,6 +48,7 @@ defmodule Nostrum.Permission do | :view_guild_insights | :use_application_commands | :moderate_members + | :send_polls @type text_permission :: :add_reactions @@ -124,7 +125,8 @@ defmodule Nostrum.Permission do use_external_stickers: 1 <<< 37, send_messages_in_threads: 1 <<< 38, use_embedded_activities: 1 <<< 39, - moderate_members: 1 <<< 40 + moderate_members: 1 <<< 40, + send_polls: 1 <<< 49 } @bit_to_permission_map Map.new(@permission_to_bit_map, fn {k, v} -> {v, k} end) diff --git a/lib/nostrum/shard/dispatch.ex b/lib/nostrum/shard/dispatch.ex index c51a1e260..a43606f18 100644 --- a/lib/nostrum/shard/dispatch.ex +++ b/lib/nostrum/shard/dispatch.ex @@ -32,6 +32,7 @@ defmodule Nostrum.Shard.Dispatch do MessageReactionRemove, MessageReactionRemoveAll, MessageReactionRemoveEmoji, + PollVoteChange, Ready, SpeakingUpdate, ThreadListSync, @@ -404,6 +405,14 @@ defmodule Nostrum.Shard.Dispatch do {event, Interaction.to_struct(p), state} end + def handle_event(:MESSAGE_POLL_VOTE_ADD = event, p, state) do + {event, PollVoteChange.to_struct(Map.merge(p, %{type: :add})), state} + end + + def handle_event(:MESSAGE_POLL_VOTE_REMOVE = event, p, state) do + {event, PollVoteChange.to_struct(Map.merge(p, %{type: :remove})), state} + end + def handle_event(event, p, state) do Logger.warning("UNHANDLED GATEWAY DISPATCH EVENT TYPE: #{event}, #{inspect(p)}") {event, p, state} diff --git a/lib/nostrum/shard/intents.ex b/lib/nostrum/shard/intents.ex index 3dbe07333..51a23b5d2 100644 --- a/lib/nostrum/shard/intents.ex +++ b/lib/nostrum/shard/intents.ex @@ -32,7 +32,9 @@ defmodule Nostrum.Shard.Intents do message_content: 1 <<< 15, guild_scheduled_events: 1 <<< 16, auto_moderation_configuration: 1 <<< 20, - auto_moderation_execution: 1 <<< 21 + auto_moderation_execution: 1 <<< 21, + guild_message_polls: 1 <<< 24, + direct_message_polls: 1 <<< 25 ] end diff --git a/lib/nostrum/struct/event/poll_vote_change.ex b/lib/nostrum/struct/event/poll_vote_change.ex new file mode 100644 index 000000000..453a1a07a --- /dev/null +++ b/lib/nostrum/struct/event/poll_vote_change.ex @@ -0,0 +1,47 @@ +defmodule Nostrum.Struct.Event.PollVoteChange do + @moduledoc """ + Represents an addition or removal of a vote from a Discord poll. + + For polls where multiple answers were selected, one of these events will be fired for each vote. + """ + alias Nostrum.Util + + alias Nostrum.Struct.{Channel, Guild, Message, User} + + defstruct [:user_id, :channel_id, :message_id, :guild_id, :answer_id, :type] + + @typedoc "ID of the user that has voted" + @type user_id :: User.id() + + @typedoc "ID of the channel the vote took place in" + @type channel_id :: Channel.id() + + @typedoc "ID of the message the poll was attached to" + @type message_id :: Message.id() + + @typedoc "ID of the guild the poll is in (unless it is a private channel)" + @type guild_id :: Guild.id() + + @typedoc "ID corresponding to the answer_id in the `t:Nostrum.Struct.Message.Poll.answers/0` list" + @type answer_id :: integer + + @typedoc "Whether the vote was an addition or removal for a vote of the option" + @type type :: :add | :remove + + @typedoc "Event representing a addition or removal of a vote from a poll" + @type t :: %__MODULE__{ + user_id: user_id, + channel_id: channel_id, + message_id: message_id, + guild_id: guild_id, + answer_id: answer_id, + type: type + } + + @doc false + def to_struct(map) do + new = Map.new(map, fn {k, v} -> {Util.maybe_to_atom(k), v} end) + + struct(__MODULE__, new) + end +end diff --git a/lib/nostrum/struct/message.ex b/lib/nostrum/struct/message.ex index 645b3e17d..00f52139b 100644 --- a/lib/nostrum/struct/message.ex +++ b/lib/nostrum/struct/message.ex @@ -14,6 +14,7 @@ defmodule Nostrum.Struct.Message do Application, Attachment, Component, + Poll, Reaction, Reference, Sticker @@ -43,6 +44,7 @@ defmodule Nostrum.Struct.Message do :message_reference, :nonce, :pinned, + :poll, :reactions, :referenced_message, :sticker_items, @@ -111,7 +113,10 @@ defmodule Nostrum.Struct.Message do @typedoc """ Message interaction object """ - @type interaction :: Interaction.t() + @type interaction :: Interaction.t() | nil + + @typedoc "The poll object attached to the message" + @type poll :: Poll.t() | nil @typedoc "List of embedded content in the message" @type embeds :: [Embed.t()] @@ -217,6 +222,7 @@ defmodule Nostrum.Struct.Message do message_reference: message_reference, nonce: nonce, pinned: pinned, + poll: poll, reactions: reactions, referenced_message: referenced_message, sticker_items: sticker_items, @@ -250,6 +256,7 @@ defmodule Nostrum.Struct.Message do |> Map.update(:mentions, nil, &Util.cast(&1, {:list, {:struct, User}})) |> Map.update(:message_reference, nil, &Util.cast(&1, {:struct, Reference})) |> Map.update(:nonce, nil, &Util.cast(&1, Snowflake)) + |> Map.update(:poll, nil, &Util.cast(&1, {:struct, Poll})) |> Map.update(:reactions, nil, &Util.cast(&1, {:list, {:struct, Reaction}})) |> Map.update(:referenced_message, nil, &Util.cast(&1, {:struct, __MODULE__})) |> Map.update(:sticker_items, nil, &Util.cast(&1, {:list, {:struct, Sticker}})) diff --git a/lib/nostrum/struct/message/poll.ex b/lib/nostrum/struct/message/poll.ex new file mode 100644 index 000000000..1cc5c7fa9 --- /dev/null +++ b/lib/nostrum/struct/message/poll.ex @@ -0,0 +1,191 @@ +defmodule Nostrum.Struct.Message.Poll do + @moduledoc """ + Struct representing a poll in a Discord chat. + + There are various helper methods on this structure to create new poll, see `create_poll/2` and `put_answer/2` & `put_answer/3` for code samples. + """ + + alias Nostrum.Util + + alias Nostrum.Snowflake + alias Nostrum.Struct.Message.Poll.{Answer, MediaObject, Results} + + @derive Jason.Encoder + defstruct [ + :question, + :answers, + :expiry, + :duration, + :allow_multiselect, + :layout_type, + :results + ] + + @typedoc """ + Question for the poll + """ + @type question :: MediaObject.t() + + @typedoc """ + List of potential answers for the poll + """ + @type answers :: [Answer.t()] + + @typedoc """ + Expiry time of the poll + """ + @type expiry :: DateTime.t() | nil + + @typedoc """ + Duration of poll in hours + """ + @type duration :: integer | nil + + @typedoc """ + Whether the poll allows selection of multiple answers + """ + @type allow_multiselect :: boolean + + @typedoc """ + Layout type for the poll, currently only 1 (`DEFAULT`) is supported here. + + If set to `nil`, the value will default to `1` at the Discord API. + """ + @type layout_type :: 1 | nil + + @typedoc """ + Result counts of a poll that has been voted on. + + This field is only present for poll objects received over the gateway or Discord API. + + As mentioned in the `Nostrum.Struct.Message.Poll.Results` documentation, if an answer has not been voted on it + will not be in this object. + """ + @type results :: Results.t() | nil + + @type t :: %__MODULE__{ + question: question, + answers: answers, + expiry: expiry, + duration: duration, + allow_multiselect: allow_multiselect, + layout_type: layout_type, + results: results + } + + @doc false + def to_struct(map) do + new = + map + |> Map.new(fn {k, v} -> {Util.maybe_to_atom(k), v} end) + |> Map.update(:expiry, nil, &Util.maybe_to_datetime/1) + |> Map.update(:question, nil, &Util.cast(&1, {:struct, MediaObject})) + |> Map.update(:answers, nil, &Util.cast(&1, {:list, {:struct, Answer}})) + |> Map.update(:results, nil, &Util.cast(&1, {:struct, Results})) + + struct(__MODULE__, new) + end + + @doc ~S""" + Create a new poll struct. + + Use `Nostrum.Api.create_message` to send it once you've populated it. + + Accepts a `question_text` parameter which is the string to use as the poll title. + + Keyword arguments: + - `duration`: duration (in hours) the poll should be open for + - `allow_multiselect`: whether users should be able to select multiple answers + + You can also pass an `answers` key with answers, though `put_answer/2` and `put_answer/3` are advised. + + ## Examples + + ```elixir + poll = Poll.create_poll("Do you enjoy pineapple on pizza?", duration: 2, allow_multiselect: false) + |> Poll.put_answer("Yes!", default_emoji: "\u2705") # check mark emoji + |> Poll.put_answer("No!", default_emoji: "\u274C") # cross emoji + + Api.create_message(channel_id, poll: poll) + ``` + """ + @spec create_poll(String.t(), duration: duration, allow_multiselect: allow_multiselect) :: t() + def create_poll(question_text, duration: duration, allow_multiselect: allow_multiselect) do + %__MODULE__{ + question: %MediaObject{ + text: question_text + }, + answers: [], + duration: duration, + allow_multiselect: allow_multiselect, + layout_type: 1 + } + end + + @spec create_poll(String.t(), + duration: duration, + allow_multiselect: allow_multiselect, + answers: [Answer.t()] + ) :: t() + def create_poll(question_text, + duration: duration, + allow_multiselect: allow_multiselect, + answers: answers + ) do + poll = create_poll(question_text, duration: duration, allow_multiselect: allow_multiselect) + + Map.put(poll, :answers, answers) + end + + defp add_answer(poll, answer) do + Map.update(poll, :answers, [answer], fn answers -> answers ++ [answer] end) + end + + @doc ~S""" + Add an answer to the provided poll. + + See `create_poll/2` for a code sample of this function. + + Takes a required "answer" text field, as well as either of the optional arguments: + - `custom_emoji`: An integer representing the snowflake of an emoji to display with the option + - `default_emoji`: A default platform emoji represented as a unicode character + """ + @spec put_answer(t(), String.t()) :: t() + def put_answer(poll, answer) do + new_answer = %Answer{ + poll_media: %MediaObject{ + text: answer + } + } + + add_answer(poll, new_answer) + end + + @spec put_answer(t(), String.t(), custom_emoji: Snowflake.t()) :: t() + def put_answer(poll, answer, custom_emoji: custom_emoji) do + new_answer = %Answer{ + poll_media: %MediaObject{ + text: answer, + emoji: %{ + id: custom_emoji + } + } + } + + add_answer(poll, new_answer) + end + + @spec put_answer(t(), String.t(), default_emoji: String.t()) :: t() + def put_answer(poll, answer, default_emoji: default_emoji) do + new_answer = %Answer{ + poll_media: %MediaObject{ + text: answer, + emoji: %{ + name: default_emoji + } + } + } + + add_answer(poll, new_answer) + end +end diff --git a/lib/nostrum/struct/message/poll/answer.ex b/lib/nostrum/struct/message/poll/answer.ex new file mode 100644 index 000000000..11e07236b --- /dev/null +++ b/lib/nostrum/struct/message/poll/answer.ex @@ -0,0 +1,40 @@ +defmodule Nostrum.Struct.Message.Poll.Answer do + @moduledoc """ + A struct representing a poll answer. + """ + + alias Nostrum.Util + + alias Nostrum.Struct.Message.Poll.MediaObject + + @derive Jason.Encoder + defstruct [ + :answer_id, + :poll_media + ] + + @typedoc """ + ID of the answer, this is only sent *from* the gateway, you do not need to send this to the gateway. + """ + @type answer_id :: integer | nil + + @typedoc """ + Object representing how the answer is displayed visually, with the text and optional emojis. + """ + @type poll_media :: MediaObject.t() + + @type t :: %__MODULE__{ + answer_id: answer_id, + poll_media: poll_media + } + + @doc false + def to_struct(map) do + new = + map + |> Map.new(fn {k, v} -> {Util.maybe_to_atom(k), v} end) + |> Map.update(:poll_media, nil, &Util.cast(&1, {:struct, MediaObject})) + + struct(__MODULE__, new) + end +end diff --git a/lib/nostrum/struct/message/poll/media_object.ex b/lib/nostrum/struct/message/poll/media_object.ex new file mode 100644 index 000000000..f59c029e0 --- /dev/null +++ b/lib/nostrum/struct/message/poll/media_object.ex @@ -0,0 +1,38 @@ +defmodule Nostrum.Struct.Message.Poll.MediaObject do + @moduledoc """ + A struct representing a media item of a poll (e.g. a question or answer) + """ + + alias Nostrum.Util + + @derive Jason.Encoder + defstruct [ + :text, + :emoji + ] + + @typedoc """ + Text of the poll media object, either the question or answer text. + """ + @type text :: String.t() | nil + + @typedoc """ + A partial emoji (only supported for answers). + + For a custom emoji, only the `id` field needs to be sent, for a default emoji, only the + `name` field needs to be sent (with the Unicode emoji). + """ + @type emoji :: %{id: integer | nil, name: String.t() | nil} + + @type t :: %__MODULE__{ + text: text, + emoji: emoji + } + + @doc false + def to_struct(map) do + new = Map.new(map, fn {k, v} -> {Util.maybe_to_atom(k), v} end) + + struct(__MODULE__, new) + end +end diff --git a/lib/nostrum/struct/message/poll/results.ex b/lib/nostrum/struct/message/poll/results.ex new file mode 100644 index 000000000..11059ce32 --- /dev/null +++ b/lib/nostrum/struct/message/poll/results.ex @@ -0,0 +1,40 @@ +defmodule Nostrum.Struct.Message.Poll.Results do + @moduledoc """ + A struct representing the results of a poll. + """ + + alias Nostrum.Util + + defstruct [ + :is_finalized, + :answer_counts + ] + + @typedoc """ + A flag on whether the poll has finished counting. + + If this is set to true, the counts are guaranteed to be accurate from Discord. + """ + @type is_finalized :: boolean + + @typedoc """ + A list of objects representing the counts for each of the potential answers in the poll. + + The `id` property of each item corresponds to the `answer_id` + of the poll answers. If an option is not present in this list, then + there were no votes for that answer. + """ + @type answer_counts :: [%{id: integer, count: integer, me_voted: boolean}] + + @type t :: %__MODULE__{ + is_finalized: is_finalized, + answer_counts: answer_counts + } + + @doc false + def to_struct(map) do + new = Map.new(map, fn {k, v} -> {Util.maybe_to_atom(k), v} end) + + struct(__MODULE__, new) + end +end diff --git a/mix.exs b/mix.exs index b6052aa4a..d4284edbd 100644 --- a/mix.exs +++ b/mix.exs @@ -157,7 +157,7 @@ defmodule Nostrum.Mixfile do {:mime, "~> 1.6 or ~> 2.0"}, {:castle, "~> 0.3.0", runtime: false}, {:ex_doc, "~> 0.28", only: :dev, runtime: false}, - {:credo, "~> 1.4", only: [:dev, :test], runtime: false}, + {:credo, "~> 1.7.5", only: [:dev, :test], runtime: false}, {:dialyxir, "~> 1.1", only: [:dev], runtime: false}, {:benchee, "~> 1.1", only: :dev, runtime: false}, {:recon, "~> 2.3", only: :dev, optional: true} diff --git a/mix.lock b/mix.lock index 5e9d07163..f6f065a10 100644 --- a/mix.lock +++ b/mix.lock @@ -1,12 +1,12 @@ %{ "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "benchee_json": {:hex, :benchee_json, "1.0.0", "cc661f4454d5995c08fe10dd1f2f72f229c8f0fb1c96f6b327a8c8fc96a91fe5", [:mix], [{:benchee, ">= 0.99.0 and < 2.0.0", [hex: :benchee, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "da05d813f9123505f870344d68fb7c86a4f0f9074df7d7b7e2bb011a63ec231c"}, - "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, + "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "castle": {:hex, :castle, "0.3.0", "47b1a550b2348a6d7e60e43ded1df19dca601ed21ef6f267c3dbb1b3a301fbf5", [:mix], [{:forecastle, "~> 0.1.0", [hex: :forecastle, repo: "hexpm", optional: false]}], "hexpm", "dbdc1c171520c4591101938a3d342dec70d36b7f5b102a5c138098581e35fcef"}, "certifi": {:hex, :certifi, "2.11.0", "5adfe37ceb8569d019f836944aeaf27f8ac391dacaf3707f570c155b7e03aaa8", [:rebar3], [], "hexpm", "9e37e0542ec3fabaa19a0734b3900dc095797fac48c40a2a9741d8ad5e3c9bb7"}, "chacha20": {:hex, :chacha20, "1.0.4", "0359d8f9a32269271044c1b471d5cf69660c362a7c61a98f73a05ef0b5d9eb9e", [:mix], [], "hexpm", "2027f5d321ae9903f1f0da7f51b0635ad6b8819bc7fe397837930a2011bc2349"}, "cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"}, - "credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"}, + "credo": {:hex, :credo, "1.7.5", "643213503b1c766ec0496d828c90c424471ea54da77c8a168c725686377b9545", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "f799e9b5cd1891577d8c773d245668aa74a2fcd15eb277f51a0131690ebfb3fd"}, "curve25519": {:hex, :curve25519, "1.0.5", "f801179424e4012049fcfcfcda74ac04f65d0ffceeb80e7ef1d3352deb09f5bb", [:mix], [], "hexpm", "0fba3ad55bf1154d4d5fc3ae5fb91b912b77b13f0def6ccb3a5d58168ff4192d"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.3.0", "fd1672f0922b7648ff9ce7b1b26fcf0ef56dda964a459892ad15f6b4410b5284", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "00b2a4bcd6aa8db9dcb0b38c1225b7277dca9bc370b6438715667071a304696f"}, @@ -15,10 +15,10 @@ "equivalex": {:hex, :equivalex, "1.0.3", "170d9a82ae066e0020dfe1cf7811381669565922eb3359f6c91d7e9a1124ff74", [:mix], [], "hexpm", "46fa311adb855117d36e461b9c0ad2598f72110ad17ad73d7533c78020e045fc"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"}, - "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, + "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, "forecastle": {:hex, :forecastle, "0.1.1", "89dcfaccbfffe866cbd8a4c41ade55f62f00f1b5d0528bec787b1e6631004b98", [:mix], [], "hexpm", "f6f4d297224a22ac4387d305249aed7b8b02e85b4a03e83225af4536812c4079"}, "gun": {:hex, :gun, "2.0.1", "160a9a5394800fcba41bc7e6d421295cf9a7894c2252c0678244948e3336ad73", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "a10bc8d6096b9502205022334f719cc9a08d9adcfbfc0dbee9ef31b56274a20b"}, - "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, + "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "kcl": {:hex, :kcl, "1.4.2", "8b73a55a14899dc172fcb05a13a754ac171c8165c14f65043382d567922f44ab", [:mix], [{:curve25519, ">= 1.0.4", [hex: :curve25519, repo: "hexpm", optional: false]}, {:ed25519, "~> 1.3", [hex: :ed25519, repo: "hexpm", optional: false]}, {:poly1305, "~> 1.0", [hex: :poly1305, repo: "hexpm", optional: false]}, {:salsa20, "~> 1.0", [hex: :salsa20, repo: "hexpm", optional: false]}], "hexpm", "9f083dd3844d902df6834b258564a82b21a15eb9f6acdc98e8df0c10feeabf05"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, diff --git a/test/nostrum/shard/intents_test.exs b/test/nostrum/shard/intents_test.exs index 3febeb5a1..cd693e300 100644 --- a/test/nostrum/shard/intents_test.exs +++ b/test/nostrum/shard/intents_test.exs @@ -10,7 +10,7 @@ defmodule Nostrum.Shard.IntentsTest do result = Intents.get_enabled_intents() # Value of all intents - expected = 3_276_799 + expected = 53_608_447 assert(^result = expected) end @@ -21,7 +21,7 @@ defmodule Nostrum.Shard.IntentsTest do result = Intents.get_enabled_intents() # Value of all non-privileged intents - expected = 3_276_799 - (2 + 256 + 32_768) + expected = 53_608_447 - (2 + 256 + 32_768) assert(^result = expected) end diff --git a/test/nostrum/struct/message/poll_test.exs b/test/nostrum/struct/message/poll_test.exs new file mode 100644 index 000000000..b48706aba --- /dev/null +++ b/test/nostrum/struct/message/poll_test.exs @@ -0,0 +1,40 @@ +defmodule Nostrum.Struct.Message.PollTest do + use ExUnit.Case, async: true + + alias Nostrum.Struct.Message.Poll + + doctest Poll + + test "Poll.create_poll/2" do + assert Poll.create_poll("Craigs Cats!", duration: 1, allow_multiselect: true) == + %Nostrum.Struct.Message.Poll{ + question: %Nostrum.Struct.Message.Poll.MediaObject{ + text: "Craigs Cats!", + emoji: nil + }, + answers: [], + expiry: nil, + duration: 1, + allow_multiselect: true, + layout_type: 1, + results: nil + } + end + + test "Poll.put_answer/2" do + poll = + Poll.create_poll("Craigs Cats!", duration: 1, allow_multiselect: true) + |> Poll.put_answer("Yes!") + + assert Enum.at(poll.answers, 0).poll_media.text == "Yes!" + end + + test "Poll.put_answer/3" do + poll = + Poll.create_poll("Craigs Cats!", duration: 1, allow_multiselect: true) + |> Poll.put_answer("Yes!", custom_emoji: 112_233_445_566_778_899) + + assert Enum.at(poll.answers, 0).poll_media.text == "Yes!" + assert Enum.at(poll.answers, 0).poll_media.emoji.id == 112_233_445_566_778_899 + end +end