Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add support for polls #534

Merged
merged 35 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
425e660
Bump Credo to 1.7.5 to stop deprecation warnings
jb3 Apr 13, 2024
125bd92
Add structures for Discord polls
jb3 Apr 13, 2024
64f2848
Add the new `poll` attribute to Message types
jb3 Apr 13, 2024
d7a3f44
Meet Credo checks
jb3 Apr 13, 2024
7a14f9d
Add missing typespec to method Poll.create_poll/2
jb3 Apr 13, 2024
33fd626
Add democracy manifest propaganda
jb3 Apr 14, 2024
d346bbb
Add new gateway struct for poll vote changes
jb3 Apr 14, 2024
7414520
Add new dispatch logic for poll vote changes
jb3 Apr 14, 2024
11ba7e5
Add new gateway intents for poll vote changes
jb3 Apr 14, 2024
2ee7662
Update intents test with new values
jb3 Apr 14, 2024
a4ed73c
Add API route for fetching voters for a poll
jb3 Apr 14, 2024
594eb47
Add API route for expiring polls early
jb3 Apr 14, 2024
22ee041
Add new typing for new gateway events to consumer
jb3 Apr 14, 2024
09791c7
Add documentation for helper methods on Poll
jb3 Apr 14, 2024
c3e267e
Add tests for Poll helper methods
jb3 Apr 14, 2024
a6b897f
Update Polls API module naming and documentation
jb3 Apr 15, 2024
2774595
Documentation improvements in PollVoteChange
jb3 Apr 15, 2024
c79c6b8
Simplify Map creation in PollVoteChange
jb3 Apr 15, 2024
40c15c2
Documentation improvements in Poll struct
jb3 Apr 15, 2024
4528747
Further documentation improvements in Poll struct
jb3 Apr 15, 2024
109b0d4
Documentation and simplifications in MediaObject
jb3 Apr 15, 2024
83d1589
Documentation improvements in Poll Results struct
jb3 Apr 15, 2024
6c022f3
Update Api.get_poll_answer_voters!/4 with new name
jb3 Apr 15, 2024
29f11e9
mix format to remove whitespace
jb3 Apr 15, 2024
17049c8
Update documentation about pagination
jb3 Apr 15, 2024
abfed4b
Actually fix the get_poll_answer_voters!/4 method
jb3 Apr 15, 2024
ac62d3d
Replace types of PollVoteChange with ID types
jb3 Apr 15, 2024
879d5cb
Update PollVoteChange to link to Poll.answers type
jb3 Apr 15, 2024
a447c75
Correct typing in Message struct
jb3 Apr 15, 2024
f0fe367
Better document results attribute of Poll
jb3 Apr 15, 2024
4de7b2f
Simplify Map creation for Results struct
jb3 Apr 15, 2024
747119b
Stop Credo complaints about PollVoteChange
jb3 Apr 15, 2024
6733306
Add new `send_polls` permission
jb3 Apr 17, 2024
dd57fa1
Clarify pagination return value
jb3 Apr 17, 2024
688d558
Add clarification on layout_type attribute
jb3 Apr 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added assets/propaganda/democracy_manifest.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 19 additions & 3 deletions guides/intro/gateway_intents.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
55 changes: 54 additions & 1 deletion lib/nostrum/api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ defmodule Nostrum.Api do
Interaction,
Invite,
Message,
Message.Poll,
ThreadMember,
User,
Webhook
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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).
jb3 marked this conversation as resolved.
Show resolved Hide resolved
"""
@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.

Expand Down
6 changes: 6 additions & 0 deletions lib/nostrum/constants.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions lib/nostrum/consumer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ defmodule Nostrum.Consumer do
MessageReactionRemove,
MessageReactionRemoveAll,
MessageReactionRemoveEmoji,
PollVoteChange,
Ready,
SpeakingUpdate,
ThreadListSync,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions lib/nostrum/shard/dispatch.ex
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ defmodule Nostrum.Shard.Dispatch do
MessageReactionRemove,
MessageReactionRemoveAll,
MessageReactionRemoveEmoji,
PollVoteChange,
Ready,
SpeakingUpdate,
ThreadListSync,
Expand Down Expand Up @@ -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}
Expand Down
4 changes: 3 additions & 1 deletion lib/nostrum/shard/intents.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
47 changes: 47 additions & 0 deletions lib/nostrum/struct/event/poll_vote_change.ex
Original file line number Diff line number Diff line change
@@ -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
9 changes: 8 additions & 1 deletion lib/nostrum/struct/message.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ defmodule Nostrum.Struct.Message do
Application,
Attachment,
Component,
Poll,
Reaction,
Reference,
Sticker
Expand Down Expand Up @@ -43,6 +44,7 @@ defmodule Nostrum.Struct.Message do
:message_reference,
:nonce,
:pinned,
:poll,
:reactions,
:referenced_message,
:sticker_items,
Expand Down Expand Up @@ -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()]
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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}))
jchristgit marked this conversation as resolved.
Show resolved Hide resolved
|> 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}}))
Expand Down
Loading
Loading