Skip to content

Commit

Permalink
Add polls (#346)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
mlnrDev committed Apr 22, 2024
1 parent 1c2e34f commit a587875
Show file tree
Hide file tree
Showing 21 changed files with 486 additions and 7 deletions.
6 changes: 3 additions & 3 deletions discord/emoji.go
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions discord/message.go
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions discord/message_create.go
Expand Up @@ -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() {}
Expand Down
12 changes: 12 additions & 0 deletions discord/message_create_builder.go
Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion discord/permissions.go
Expand Up @@ -63,6 +63,9 @@ const (
PermissionCreateEvents
PermissionUseExternalSounds
PermissionSendVoiceMessages
_
_
PermissionSendPolls

PermissionsAllText = PermissionViewChannel |
PermissionSendMessages |
Expand All @@ -72,7 +75,8 @@ const (
PermissionAttachFiles |
PermissionReadMessageHistory |
PermissionMentionEveryone |
PermissionSendVoiceMessages
PermissionSendVoiceMessages |
PermissionSendPolls

PermissionsAllThread = PermissionManageThreads |
PermissionCreatePublicThreads |
Expand Down Expand Up @@ -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 {
Expand Down
70 changes: 70 additions & 0 deletions 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
)
66 changes: 66 additions & 0 deletions 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
}
1 change: 1 addition & 0 deletions discord/webhook_message_create.go
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions discord/webhook_message_create_builder.go
Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions 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
}
36 changes: 36 additions & 0 deletions 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
}
38 changes: 38 additions & 0 deletions events/listener_adapter.go
Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down
34 changes: 34 additions & 0 deletions 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
}
2 changes: 2 additions & 0 deletions gateway/gateway_event_type.go
Expand Up @@ -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"
Expand Down

0 comments on commit a587875

Please sign in to comment.