From a587875a4ce9e80e4747dd27bf58b1589fcf16e5 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 22 Apr 2024 19:43:42 +0200 Subject: [PATCH] Add polls (#346) * Add polls * fix AnswerID type * remove omitempty for now * add current intent requirement * add PollCreate * fix ExpirePoll endpoint route * add intents * split poll events into DM and guild * consistent wording * add answer votes paging * make Expiry a pointer * add permission * fix Results type * add PollCreateBuilder * address review * you might wanna build the builder idk * add ClearPoll instead of accepting a pointer * Update discord/poll_create_builder.go --- discord/emoji.go | 6 +- discord/message.go | 1 + discord/message_create.go | 1 + discord/message_create_builder.go | 12 ++++ discord/permissions.go | 7 +- discord/poll.go | 70 +++++++++++++++++++ discord/poll_create_builder.go | 66 ++++++++++++++++++ discord/webhook_message_create.go | 1 + discord/webhook_message_create_builder.go | 12 ++++ events/dm_message_poll_events.go | 24 +++++++ events/guild_message_poll_events.go | 36 ++++++++++ events/listener_adapter.go | 38 +++++++++++ events/message_poll_events.go | 34 ++++++++++ gateway/gateway_event_type.go | 2 + gateway/gateway_events.go | 22 ++++++ gateway/gateway_intents.go | 17 ++++- handlers/all_handlers.go | 3 + handlers/message_poll_handler.go | 83 +++++++++++++++++++++++ rest/channels.go | 30 ++++++++ rest/page.go | 25 +++++++ rest/rest_endpoints.go | 3 + 21 files changed, 486 insertions(+), 7 deletions(-) create mode 100644 discord/poll.go create mode 100644 discord/poll_create_builder.go create mode 100644 events/dm_message_poll_events.go create mode 100644 events/guild_message_poll_events.go create mode 100644 events/message_poll_events.go create mode 100644 handlers/message_poll_handler.go diff --git a/discord/emoji.go b/discord/emoji.go index 94d7fcefd..896efd112 100644 --- a/discord/emoji.go +++ b/discord/emoji.go @@ -66,9 +66,9 @@ type EmojiUpdate struct { } type PartialEmoji struct { - ID *snowflake.ID `json:"id"` - Name *string `json:"name"` - Animated bool `json:"animated"` + ID *snowflake.ID `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Animated bool `json:"animated,omitempty"` } // Reaction returns a string used for manipulating with reactions. May be empty if the Name is nil diff --git a/discord/message.go b/discord/message.go index b107c45b1..cf47706b1 100644 --- a/discord/message.go +++ b/discord/message.go @@ -115,6 +115,7 @@ type Message struct { RoleSubscriptionData *RoleSubscriptionData `json:"role_subscription_data,omitempty"` InteractionMetadata *InteractionMetadata `json:"interaction_metadata,omitempty"` Resolved *ResolvedData `json:"resolved,omitempty"` + Poll *Poll `json:"poll,omitempty"` } func (m *Message) UnmarshalJSON(data []byte) error { diff --git a/discord/message_create.go b/discord/message_create.go index 8e192cb7a..e21cc4cba 100644 --- a/discord/message_create.go +++ b/discord/message_create.go @@ -18,6 +18,7 @@ type MessageCreate struct { MessageReference *MessageReference `json:"message_reference,omitempty"` Flags MessageFlags `json:"flags,omitempty"` EnforceNonce bool `json:"enforce_nonce,omitempty"` + Poll *PollCreate `json:"poll,omitempty"` } func (MessageCreate) interactionCallbackData() {} diff --git a/discord/message_create_builder.go b/discord/message_create_builder.go index bba78c692..cf1178c17 100644 --- a/discord/message_create_builder.go +++ b/discord/message_create_builder.go @@ -251,6 +251,18 @@ func (b *MessageCreateBuilder) SetSuppressEmbeds(suppressEmbeds bool) *MessageCr return b } +// SetPoll sets the Poll of the Message +func (b *MessageCreateBuilder) SetPoll(poll PollCreate) *MessageCreateBuilder { + b.Poll = &poll + return b +} + +// ClearPoll clears the Poll of the Message +func (b *MessageCreateBuilder) ClearPoll() *MessageCreateBuilder { + b.Poll = nil + return b +} + // Build builds the MessageCreateBuilder to a MessageCreate struct func (b *MessageCreateBuilder) Build() MessageCreate { return b.MessageCreate diff --git a/discord/permissions.go b/discord/permissions.go index 7eaafd4fe..96c18a93d 100644 --- a/discord/permissions.go +++ b/discord/permissions.go @@ -63,6 +63,9 @@ const ( PermissionCreateEvents PermissionUseExternalSounds PermissionSendVoiceMessages + _ + _ + PermissionSendPolls PermissionsAllText = PermissionViewChannel | PermissionSendMessages | @@ -72,7 +75,8 @@ const ( PermissionAttachFiles | PermissionReadMessageHistory | PermissionMentionEveryone | - PermissionSendVoiceMessages + PermissionSendVoiceMessages | + PermissionSendPolls PermissionsAllThread = PermissionManageThreads | PermissionCreatePublicThreads | @@ -170,6 +174,7 @@ var permissions = map[Permissions]string{ PermissionStream: "Video", PermissionViewGuildInsights: "View Server Insights", PermissionSendVoiceMessages: "Send Voice Messages", + PermissionSendPolls: "Create Polls", } func (p Permissions) String() string { diff --git a/discord/poll.go b/discord/poll.go new file mode 100644 index 000000000..03f1e019a --- /dev/null +++ b/discord/poll.go @@ -0,0 +1,70 @@ +package discord + +import ( + "time" + + "github.com/disgoorg/json" + "github.com/disgoorg/snowflake/v2" +) + +type Poll struct { + Question PollMedia `json:"question"` + Answers []PollAnswer `json:"answers"` + Expiry *time.Time `json:"expiry"` + AllowMultiselect bool `json:"allow_multiselect"` + LayoutType PollLayoutType `json:"layout_type"` + Results *PollResults `json:"results"` +} + +type PollCreate struct { + Question PollMedia `json:"question"` + Answers []PollMedia `json:"-"` + Duration int `json:"duration"` + AllowMultiselect bool `json:"allow_multiselect"` + LayoutType PollLayoutType `json:"layout_type,omitempty"` +} + +func (p PollCreate) MarshalJSON() ([]byte, error) { + type pollCreate PollCreate + + answers := make([]PollAnswer, 0, len(p.Answers)) + for _, answer := range p.Answers { + answers = append(answers, PollAnswer{ + PollMedia: answer, + }) + } + return json.Marshal(struct { + Answers []PollAnswer `json:"answers"` + pollCreate + }{ + Answers: answers, + pollCreate: pollCreate(p), + }) +} + +type PollMedia struct { + Text *string `json:"text"` + Emoji *PartialEmoji `json:"emoji,omitempty"` +} + +type PollAnswer struct { + AnswerID *int `json:"answer_id,omitempty"` + PollMedia PollMedia `json:"poll_media"` +} + +type PollResults struct { + IsFinalized bool `json:"is_finalized"` + AnswerCounts []PollAnswerCount `json:"answer_counts"` +} + +type PollAnswerCount struct { + ID snowflake.ID `json:"id"` + Count int `json:"count"` + MeVoted bool `json:"me_voted"` +} + +type PollLayoutType int + +const ( + PollLayoutTypeDefault PollLayoutType = iota + 1 +) diff --git a/discord/poll_create_builder.go b/discord/poll_create_builder.go new file mode 100644 index 000000000..3b436ecbf --- /dev/null +++ b/discord/poll_create_builder.go @@ -0,0 +1,66 @@ +package discord + +// PollCreateBuilder helps create PollCreate structs easier +type PollCreateBuilder struct { + PollCreate +} + +// SetQuestion sets the question of the Poll +func (b *PollCreateBuilder) SetQuestion(text string) *PollCreateBuilder { + b.Question = PollMedia{ + Text: &text, + } + return b +} + +// SetPollAnswers sets the answers of the Poll +func (b *PollCreateBuilder) SetPollAnswers(answers ...PollMedia) *PollCreateBuilder { + b.Answers = answers + return b +} + +// AddPollAnswer adds an answer to the Poll +func (b *PollCreateBuilder) AddPollAnswer(text string, emoji *PartialEmoji) *PollCreateBuilder { + b.Answers = append(b.Answers, PollMedia{ + Text: &text, + Emoji: emoji, + }) + return b +} + +// RemoveAnswer removes an answer from the Poll +func (b *PollCreateBuilder) RemoveAnswer(i int) *PollCreateBuilder { + if len(b.Answers) > i { + b.Answers = append(b.Answers[:i], b.Answers[i+1:]...) + } + return b +} + +// ClearAnswers removes all answers of the Poll +func (b *PollCreateBuilder) ClearAnswers() *PollCreateBuilder { + b.Answers = []PollMedia{} + return b +} + +// SetDuration sets the duration of the Poll (in hours) +func (b *PollCreateBuilder) SetDuration(duration int) *PollCreateBuilder { + b.Duration = duration + return b +} + +// SetAllowMultiselect sets whether users will be able to vote for more than one answer of the Poll +func (b *PollCreateBuilder) SetAllowMultiselect(multiselect bool) *PollCreateBuilder { + b.AllowMultiselect = multiselect + return b +} + +// SetLayoutType sets the layout of the Poll +func (b *PollCreateBuilder) SetLayoutType(layout PollLayoutType) *PollCreateBuilder { + b.LayoutType = layout + return b +} + +// Build builds the PollCreateBuilder to a PollCreate struct +func (b *PollCreateBuilder) Build() PollCreate { + return b.PollCreate +} diff --git a/discord/webhook_message_create.go b/discord/webhook_message_create.go index 05b2d8165..f84e4ea8d 100644 --- a/discord/webhook_message_create.go +++ b/discord/webhook_message_create.go @@ -15,6 +15,7 @@ type WebhookMessageCreate struct { Flags MessageFlags `json:"flags,omitempty"` ThreadName string `json:"thread_name,omitempty"` AppliedTags []snowflake.ID `json:"applied_tags,omitempty"` + Poll *PollCreate `json:"poll,omitempty"` } // ToBody returns the MessageCreate ready for body diff --git a/discord/webhook_message_create_builder.go b/discord/webhook_message_create_builder.go index e82bbb958..4c8666959 100644 --- a/discord/webhook_message_create_builder.go +++ b/discord/webhook_message_create_builder.go @@ -210,6 +210,18 @@ func (b *WebhookMessageCreateBuilder) SetThreadName(threadName string) *WebhookM return b } +// SetPoll sets the Poll of the webhook Message +func (b *WebhookMessageCreateBuilder) SetPoll(poll PollCreate) *WebhookMessageCreateBuilder { + b.Poll = &poll + return b +} + +// ClearPoll clears the Poll of the webhook Message +func (b *WebhookMessageCreateBuilder) ClearPoll() *WebhookMessageCreateBuilder { + b.Poll = nil + return b +} + // Build builds the WebhookMessageCreateBuilder to a MessageCreate struct func (b *WebhookMessageCreateBuilder) Build() WebhookMessageCreate { b.WebhookMessageCreate.Components = b.Components diff --git a/events/dm_message_poll_events.go b/events/dm_message_poll_events.go new file mode 100644 index 000000000..a80db4a6a --- /dev/null +++ b/events/dm_message_poll_events.go @@ -0,0 +1,24 @@ +package events + +import ( + "github.com/disgoorg/snowflake/v2" +) + +// GenericDMMessagePollVote is called upon receiving DMMessagePollVoteAdd or DMMessagePollVoteRemove (requires gateway.IntentDirectMessagePolls) +type GenericDMMessagePollVote struct { + *GenericEvent + UserID snowflake.ID + ChannelID snowflake.ID + MessageID snowflake.ID + AnswerID int +} + +// DMMessagePollVoteAdd indicates that a discord.User voted on a discord.Poll in a DM (requires gateway.IntentDirectMessagePolls) +type DMMessagePollVoteAdd struct { + *GenericDMMessagePollVote +} + +// DMMessagePollVoteRemove indicates that a discord.User removed their vote on a discord.Poll in a DM (requires gateway.IntentDirectMessagePolls) +type DMMessagePollVoteRemove struct { + *GenericDMMessagePollVote +} diff --git a/events/guild_message_poll_events.go b/events/guild_message_poll_events.go new file mode 100644 index 000000000..9c9829ceb --- /dev/null +++ b/events/guild_message_poll_events.go @@ -0,0 +1,36 @@ +package events + +import ( + "github.com/disgoorg/disgo/discord" + "github.com/disgoorg/snowflake/v2" +) + +// GenericGuildMessagePollVote is called upon receiving GuildMessagePollVoteAdd or GuildMessagePollVoteRemove (requires gateway.IntentGuildMessagePolls) +type GenericGuildMessagePollVote struct { + *GenericEvent + UserID snowflake.ID + ChannelID snowflake.ID + MessageID snowflake.ID + GuildID snowflake.ID + AnswerID int +} + +// Guild returns the discord.Guild where the GenericGuildMessagePollVote happened +func (e *GenericGuildMessagePollVote) Guild() (discord.Guild, bool) { + return e.Client().Caches().Guild(e.GuildID) +} + +// Channel returns the discord.GuildMessageChannel where the GenericGuildMessagePollVote happened +func (e *GenericGuildMessagePollVote) Channel() (discord.GuildMessageChannel, bool) { + return e.Client().Caches().GuildMessageChannel(e.ChannelID) +} + +// GuildMessagePollVoteAdd indicates that a discord.User voted on a discord.Poll in a discord.Guild (requires gateway.IntentGuildMessagePolls) +type GuildMessagePollVoteAdd struct { + *GenericGuildMessagePollVote +} + +// GuildMessagePollVoteRemove indicates that a discord.User removed their vote on a discord.Poll in a discord.Guild (requires gateway.IntentGuildMessagePolls) +type GuildMessagePollVoteRemove struct { + *GenericGuildMessagePollVote +} diff --git a/events/listener_adapter.go b/events/listener_adapter.go index 9f38bc815..62e30c213 100644 --- a/events/listener_adapter.go +++ b/events/listener_adapter.go @@ -147,6 +147,18 @@ type ListenerAdapter struct { OnMessageUpdate func(event *MessageUpdate) OnMessageDelete func(event *MessageDelete) + // Message Poll Events + OnMessagePollVoteAdd func(event *MessagePollVoteAdd) + OnMessagePollVoteRemove func(event *MessagePollVoteRemove) + + // DM Message Poll Events + OnDMMessagePollVoteAdd func(event *DMMessagePollVoteAdd) + OnDMMessagePollVoteRemove func(event *DMMessagePollVoteRemove) + + // Guild Message Poll Events + OnGuildMessagePollVoteAdd func(event *GuildMessagePollVoteAdd) + OnGuildMessagePollVoteRemove func(event *GuildMessagePollVoteRemove) + // Message Reaction Events OnMessageReactionAdd func(event *MessageReactionAdd) OnMessageReactionRemove func(event *MessageReactionRemove) @@ -574,6 +586,32 @@ func (l *ListenerAdapter) OnEvent(event bot.Event) { listener(e) } + // Message Poll Events + case *MessagePollVoteAdd: + if listener := l.OnMessagePollVoteAdd; listener != nil { + listener(e) + } + case *MessagePollVoteRemove: + if listener := l.OnMessagePollVoteRemove; listener != nil { + listener(e) + } + case *DMMessagePollVoteAdd: + if listener := l.OnDMMessagePollVoteAdd; listener != nil { + listener(e) + } + case *DMMessagePollVoteRemove: + if listener := l.OnDMMessagePollVoteRemove; listener != nil { + listener(e) + } + case *GuildMessagePollVoteAdd: + if listener := l.OnGuildMessagePollVoteAdd; listener != nil { + listener(e) + } + case *GuildMessagePollVoteRemove: + if listener := l.OnGuildMessagePollVoteRemove; listener != nil { + listener(e) + } + // Message Reaction Events case *MessageReactionAdd: if listener := l.OnMessageReactionAdd; listener != nil { diff --git a/events/message_poll_events.go b/events/message_poll_events.go new file mode 100644 index 000000000..e6dd72757 --- /dev/null +++ b/events/message_poll_events.go @@ -0,0 +1,34 @@ +package events + +import ( + "github.com/disgoorg/disgo/discord" + "github.com/disgoorg/snowflake/v2" +) + +// GenericMessagePollVote is a generic poll vote event (requires gateway.IntentGuildMessagePolls and/or gateway.IntentDirectMessagePolls) +type GenericMessagePollVote struct { + *GenericEvent + UserID snowflake.ID + ChannelID snowflake.ID + MessageID snowflake.ID + GuildID *snowflake.ID + AnswerID int +} + +// Guild returns the discord.Guild where the GenericMessagePollVote happened or empty if it happened in DMs +func (e *GenericMessagePollVote) Guild() (discord.Guild, bool) { + if e.GuildID == nil { + return discord.Guild{}, false + } + return e.Client().Caches().Guild(*e.GuildID) +} + +// MessagePollVoteAdd indicates that a discord.User voted on a discord.Poll (requires gateway.IntentGuildMessagePolls and/or gateway.IntentDirectMessagePolls) +type MessagePollVoteAdd struct { + *GenericMessagePollVote +} + +// MessagePollVoteRemove indicates that a discord.User removed their vote on a discord.Poll (requires gateway.IntentGuildMessagePolls and/or gateway.IntentDirectMessagePolls) +type MessagePollVoteRemove struct { + *GenericMessagePollVote +} diff --git a/gateway/gateway_event_type.go b/gateway/gateway_event_type.go index 0cb54fd01..1b4e28b40 100644 --- a/gateway/gateway_event_type.go +++ b/gateway/gateway_event_type.go @@ -59,6 +59,8 @@ const ( EventTypeMessageUpdate EventType = "MESSAGE_UPDATE" EventTypeMessageDelete EventType = "MESSAGE_DELETE" EventTypeMessageDeleteBulk EventType = "MESSAGE_DELETE_BULK" + EventTypeMessagePollVoteAdd EventType = "MESSAGE_POLL_VOTE_ADD" + EventTypeMessagePollVoteRemove EventType = "MESSAGE_POLL_VOTE_REMOVE" EventTypeMessageReactionAdd EventType = "MESSAGE_REACTION_ADD" EventTypeMessageReactionRemove EventType = "MESSAGE_REACTION_REMOVE" EventTypeMessageReactionRemoveAll EventType = "MESSAGE_REACTION_REMOVE_ALL" diff --git a/gateway/gateway_events.go b/gateway/gateway_events.go index a2f717d58..bfdf410d4 100644 --- a/gateway/gateway_events.go +++ b/gateway/gateway_events.go @@ -507,6 +507,28 @@ type EventMessageDeleteBulk struct { func (EventMessageDeleteBulk) messageData() {} func (EventMessageDeleteBulk) eventData() {} +type EventMessagePollVoteAdd struct { + UserID snowflake.ID `json:"user_id"` + ChannelID snowflake.ID `json:"channel_id"` + MessageID snowflake.ID `json:"message_id"` + GuildID *snowflake.ID `json:"guild_id"` + AnswerID int `json:"answer_id"` +} + +func (EventMessagePollVoteAdd) messageData() {} +func (EventMessagePollVoteAdd) eventData() {} + +type EventMessagePollVoteRemove struct { + UserID snowflake.ID `json:"user_id"` + ChannelID snowflake.ID `json:"channel_id"` + MessageID snowflake.ID `json:"message_id"` + GuildID *snowflake.ID `json:"guild_id"` + AnswerID int `json:"answer_id"` +} + +func (EventMessagePollVoteRemove) messageData() {} +func (EventMessagePollVoteRemove) eventData() {} + type EventPresenceUpdate struct { discord.Presence } diff --git a/gateway/gateway_intents.go b/gateway/gateway_intents.go index f9033c80e..68f6c4ec2 100644 --- a/gateway/gateway_intents.go +++ b/gateway/gateway_intents.go @@ -29,6 +29,10 @@ const ( _ IntentAutoModerationConfiguration IntentAutoModerationExecution + _ + _ + IntentGuildMessagePolls + IntentDirectMessagePolls IntentsGuild = IntentGuilds | IntentGuildMembers | @@ -42,11 +46,16 @@ const ( IntentGuildMessages | IntentGuildMessageReactions | IntentGuildMessageTyping | - IntentGuildScheduledEvents + IntentGuildScheduledEvents | + IntentGuildMessagePolls IntentsDirectMessage = IntentDirectMessages | IntentDirectMessageReactions | - IntentDirectMessageTyping + IntentDirectMessageTyping | + IntentDirectMessagePolls + + IntentsMessagePolls = IntentGuildMessagePolls | + IntentDirectMessagePolls IntentsNonPrivileged = IntentGuilds | IntentGuildModeration | @@ -63,7 +72,9 @@ const ( IntentDirectMessageTyping | IntentGuildScheduledEvents | IntentAutoModerationConfiguration | - IntentAutoModerationExecution + IntentAutoModerationExecution | + IntentGuildMessagePolls | + IntentDirectMessagePolls IntentsPrivileged = IntentGuildMembers | IntentGuildPresences | IntentMessageContent diff --git a/handlers/all_handlers.go b/handlers/all_handlers.go index 51a6466a1..81ea0234f 100644 --- a/handlers/all_handlers.go +++ b/handlers/all_handlers.go @@ -101,6 +101,9 @@ var allEventHandlers = []bot.GatewayEventHandler{ bot.NewGatewayEventHandler(gateway.EventTypeMessageDelete, gatewayHandlerMessageDelete), bot.NewGatewayEventHandler(gateway.EventTypeMessageDeleteBulk, gatewayHandlerMessageDeleteBulk), + bot.NewGatewayEventHandler(gateway.EventTypeMessagePollVoteAdd, gatewayHandlerMessagePollVoteAdd), + bot.NewGatewayEventHandler(gateway.EventTypeMessagePollVoteRemove, gatewayHandlerMessagePollVoteRemove), + bot.NewGatewayEventHandler(gateway.EventTypeMessageReactionAdd, gatewayHandlerMessageReactionAdd), bot.NewGatewayEventHandler(gateway.EventTypeMessageReactionRemove, gatewayHandlerMessageReactionRemove), bot.NewGatewayEventHandler(gateway.EventTypeMessageReactionRemoveAll, gatewayHandlerMessageReactionRemoveAll), diff --git a/handlers/message_poll_handler.go b/handlers/message_poll_handler.go new file mode 100644 index 000000000..affdecc0a --- /dev/null +++ b/handlers/message_poll_handler.go @@ -0,0 +1,83 @@ +package handlers + +import ( + "github.com/disgoorg/disgo/bot" + "github.com/disgoorg/disgo/events" + "github.com/disgoorg/disgo/gateway" +) + +func gatewayHandlerMessagePollVoteAdd(client bot.Client, sequenceNumber int, shardID int, event gateway.EventMessagePollVoteAdd) { + genericEvent := events.NewGenericEvent(client, sequenceNumber, shardID) + + client.EventManager().DispatchEvent(&events.MessagePollVoteAdd{ + GenericMessagePollVote: &events.GenericMessagePollVote{ + GenericEvent: genericEvent, + UserID: event.UserID, + ChannelID: event.ChannelID, + MessageID: event.MessageID, + GuildID: event.GuildID, + AnswerID: event.AnswerID, + }, + }) + + if event.GuildID == nil { + client.EventManager().DispatchEvent(&events.DMMessagePollVoteAdd{ + GenericDMMessagePollVote: &events.GenericDMMessagePollVote{ + GenericEvent: genericEvent, + UserID: event.UserID, + ChannelID: event.ChannelID, + MessageID: event.MessageID, + AnswerID: event.AnswerID, + }, + }) + } else { + client.EventManager().DispatchEvent(&events.GuildMessagePollVoteAdd{ + GenericGuildMessagePollVote: &events.GenericGuildMessagePollVote{ + GenericEvent: genericEvent, + UserID: event.UserID, + ChannelID: event.ChannelID, + MessageID: event.MessageID, + GuildID: *event.GuildID, + AnswerID: event.AnswerID, + }, + }) + } +} + +func gatewayHandlerMessagePollVoteRemove(client bot.Client, sequenceNumber int, shardID int, event gateway.EventMessagePollVoteRemove) { + genericEvent := events.NewGenericEvent(client, sequenceNumber, shardID) + + client.EventManager().DispatchEvent(&events.MessagePollVoteRemove{ + GenericMessagePollVote: &events.GenericMessagePollVote{ + GenericEvent: genericEvent, + UserID: event.UserID, + ChannelID: event.ChannelID, + MessageID: event.MessageID, + GuildID: event.GuildID, + AnswerID: event.AnswerID, + }, + }) + + if event.GuildID == nil { + client.EventManager().DispatchEvent(&events.DMMessagePollVoteRemove{ + GenericDMMessagePollVote: &events.GenericDMMessagePollVote{ + GenericEvent: genericEvent, + UserID: event.UserID, + ChannelID: event.ChannelID, + MessageID: event.MessageID, + AnswerID: event.AnswerID, + }, + }) + } else { + client.EventManager().DispatchEvent(&events.GuildMessagePollVoteRemove{ + GenericGuildMessagePollVote: &events.GenericGuildMessagePollVote{ + GenericEvent: genericEvent, + UserID: event.UserID, + ChannelID: event.ChannelID, + MessageID: event.MessageID, + GuildID: *event.GuildID, + AnswerID: event.AnswerID, + }, + }) + } +} diff --git a/rest/channels.go b/rest/channels.go index 3afd45565..c189adf63 100644 --- a/rest/channels.go +++ b/rest/channels.go @@ -47,6 +47,10 @@ type Channels interface { PinMessage(channelID snowflake.ID, messageID snowflake.ID, opts ...RequestOpt) error UnpinMessage(channelID snowflake.ID, messageID snowflake.ID, opts ...RequestOpt) error Follow(channelID snowflake.ID, targetChannelID snowflake.ID, opts ...RequestOpt) (*discord.FollowedChannel, error) + + GetPollAnswerVotes(channelID snowflake.ID, messageID snowflake.ID, answerID int, after snowflake.ID, limit int, opts ...RequestOpt) ([]discord.User, error) + GetPollAnswerVotesPage(channelID snowflake.ID, messageID snowflake.ID, answerID int, startID snowflake.ID, limit int, opts ...RequestOpt) PollAnswerVotesPage + ExpirePoll(channelID snowflake.ID, messageID snowflake.ID, opts ...RequestOpt) (*discord.Message, error) } type channelImpl struct { @@ -222,3 +226,29 @@ func (s *channelImpl) Follow(channelID snowflake.ID, targetChannelID snowflake.I err = s.client.Do(FollowChannel.Compile(nil, channelID), discord.FollowChannel{ChannelID: targetChannelID}, &followedChannel, opts...) return } + +func (s *channelImpl) GetPollAnswerVotes(channelID snowflake.ID, messageID snowflake.ID, answerID int, after snowflake.ID, limit int, opts ...RequestOpt) (users []discord.User, err error) { + values := discord.QueryValues{} + if after != 0 { + values["after"] = after + } + if limit != 0 { + values["limit"] = limit + } + err = s.client.Do(GetPollAnswerVotes.Compile(values, channelID, messageID, answerID), nil, &users, opts...) + return +} + +func (s *channelImpl) GetPollAnswerVotesPage(channelID snowflake.ID, messageID snowflake.ID, answerID int, startID snowflake.ID, limit int, opts ...RequestOpt) PollAnswerVotesPage { + return PollAnswerVotesPage{ + getItems: func(after snowflake.ID) ([]discord.User, error) { + return s.GetPollAnswerVotes(channelID, messageID, answerID, after, limit, opts...) + }, + ID: startID, + } +} + +func (s *channelImpl) ExpirePoll(channelID snowflake.ID, messageID snowflake.ID, opts ...RequestOpt) (message *discord.Message, err error) { + err = s.client.Do(ExpirePoll.Compile(nil, channelID, messageID), nil, &message, opts...) + return +} diff --git a/rest/page.go b/rest/page.go index a3d7b76be..6065b1793 100644 --- a/rest/page.go +++ b/rest/page.go @@ -117,3 +117,28 @@ func (p *ThreadMemberPage) Next() bool { } return p.Err == nil } + +type PollAnswerVotesPage struct { + getItems func(after snowflake.ID) ([]discord.User, error) + + Items []discord.User + Err error + + ID snowflake.ID +} + +func (p *PollAnswerVotesPage) Next() bool { + if p.Err != nil { + return false + } + + if len(p.Items) > 0 { + p.ID = p.Items[0].ID + } + + p.Items, p.Err = p.getItems(p.ID) + if p.Err == nil && len(p.Items) == 0 { + p.Err = ErrNoMorePages + } + return p.Err == nil +} diff --git a/rest/rest_endpoints.go b/rest/rest_endpoints.go index faa92815d..5cb758ba2 100644 --- a/rest/rest_endpoints.go +++ b/rest/rest_endpoints.go @@ -171,6 +171,9 @@ var ( SendTyping = NewEndpoint(http.MethodPost, "/channels/{channel.id}/typing") FollowChannel = NewEndpoint(http.MethodPost, "/channels/{channel.id}/followers") + + GetPollAnswerVotes = NewEndpoint(http.MethodGet, "/channels/{channel.id}/polls/{message.id}/answers/{answer.id}") + ExpirePoll = NewEndpoint(http.MethodPost, "/channels/{channel.id}/polls/{message.id}/expire") ) // Threads