diff --git a/components.go b/components.go index 124e95e5c..4622323e4 100644 --- a/components.go +++ b/components.go @@ -9,9 +9,9 @@ type ComponentType uint // MessageComponent types. const ( - ActionsRowComponent ComponentType = 1 - ButtonComponent ComponentType = 2 - SelectMenuComponent ComponentType = 3 + ComponentActionRow ComponentType = 1 + ComponentButton ComponentType = 2 + ComponentSelectMenu ComponentType = 3 ) // MessageComponent is a base interface for all message components. @@ -36,11 +36,11 @@ func (umc *unmarshalableMessageComponent) UnmarshalJSON(src []byte) error { var data MessageComponent switch v.Type { - case ActionsRowComponent: + case ComponentActionRow: v := ActionsRow{} err = json.Unmarshal(src, &v) data = v - case ButtonComponent: + case ComponentButton: v := Button{} err = json.Unmarshal(src, &v) data = v @@ -89,7 +89,7 @@ func (r *ActionsRow) UnmarshalJSON(data []byte) error { // Type is a method to get the type of a component. func (r ActionsRow) Type() ComponentType { - return ActionsRowComponent + return ComponentActionRow } // ButtonStyle is style of button. @@ -97,19 +97,19 @@ type ButtonStyle uint // Button styles. const ( - // PrimaryButton is a button with blurple color. - PrimaryButton ButtonStyle = 1 - // SecondaryButton is a button with grey color. - SecondaryButton ButtonStyle = 2 - // SuccessButton is a button with green color. - SuccessButton ButtonStyle = 3 - // DangerButton is a button with red color. - DangerButton ButtonStyle = 4 - // LinkButton is a special type of button which navigates to a URL. Has grey color. - LinkButton ButtonStyle = 5 + // ButtonPrimary is a button with blurple color. + ButtonPrimary ButtonStyle = 1 + // ButtonSecondary is a button with grey color. + ButtonSecondary ButtonStyle = 2 + // ButtonSuccess is a button with green color. + ButtonSuccess ButtonStyle = 3 + // ButtonDanger is a button with red color. + ButtonDanger ButtonStyle = 4 + // ButtonLink is a special type of button which navigates to a URL. Has grey color. + ButtonLink ButtonStyle = 5 ) -// ComponentEmoji represents button emoji, if it does have one. +// ComponentEmoji represents a button's emoji, if it has one. type ComponentEmoji struct { Name string `json:"name,omitempty"` ID string `json:"id,omitempty"` @@ -123,7 +123,7 @@ type Button struct { Disabled bool `json:"disabled"` Emoji ComponentEmoji `json:"emoji"` - // NOTE: Only button with LinkButton style can have link. Also, URL is mutually exclusive with CustomID. + // NOTE: Only button with ButtonLink style can have link. Also, URL is mutually exclusive with CustomID. URL string `json:"url,omitempty"` CustomID string `json:"custom_id,omitempty"` } @@ -133,7 +133,7 @@ func (b Button) MarshalJSON() ([]byte, error) { type button Button if b.Style == 0 { - b.Style = PrimaryButton + b.Style = ButtonPrimary } return json.Marshal(struct { @@ -146,8 +146,8 @@ func (b Button) MarshalJSON() ([]byte, error) { } // Type is a method to get the type of a component. -func (Button) Type() ComponentType { - return ButtonComponent +func (b Button) Type() ComponentType { + return ComponentButton } // SelectMenuOption represents an option for a select menu. @@ -175,7 +175,7 @@ type SelectMenu struct { // Type is a method to get the type of a component. func (SelectMenu) Type() ComponentType { - return SelectMenuComponent + return ComponentSelectMenu } // MarshalJSON is a method for marshaling SelectMenu to a JSON object. diff --git a/endpoints.go b/endpoints.go index e5d7d9de5..e5eb00252 100644 --- a/endpoints.go +++ b/endpoints.go @@ -14,7 +14,7 @@ package discordgo import "strconv" // APIVersion is the Discord API version used for the REST and Websocket API. -var APIVersion = "8" +var APIVersion = "9" // Known Discord API Endpoints. var ( @@ -23,14 +23,16 @@ var ( EndpointSmActive = EndpointSm + "active.json" EndpointSmUpcoming = EndpointSm + "upcoming.json" - EndpointDiscord = "https://discord.com/" - EndpointAPI = EndpointDiscord + "api/v" + APIVersion + "/" - EndpointGuilds = EndpointAPI + "guilds/" - EndpointChannels = EndpointAPI + "channels/" - EndpointUsers = EndpointAPI + "users/" - EndpointGateway = EndpointAPI + "gateway" - EndpointGatewayBot = EndpointGateway + "/bot" - EndpointWebhooks = EndpointAPI + "webhooks/" + EndpointDiscord = "https://discord.com/" + EndpointAPI = EndpointDiscord + "api/v" + APIVersion + "/" + EndpointGuilds = EndpointAPI + "guilds/" + EndpointChannels = EndpointAPI + "channels/" + EndpointUsers = EndpointAPI + "users/" + EndpointGateway = EndpointAPI + "gateway" + EndpointGatewayBot = EndpointGateway + "/bot" + EndpointWebhooks = EndpointAPI + "webhooks/" + EndpointStickers = EndpointAPI + "stickers/" + EndpointStageInstances = EndpointAPI + "stage-instances/" EndpointCDN = "https://cdn.discordapp.com/" EndpointCDNAttachments = EndpointCDN + "attachments/" @@ -102,6 +104,9 @@ var ( EndpointGuildEmojis = func(gID string) string { return EndpointGuilds + gID + "/emojis" } EndpointGuildEmoji = func(gID, eID string) string { return EndpointGuilds + gID + "/emojis/" + eID } EndpointGuildBanner = func(gID, hash string) string { return EndpointCDNBanners + gID + "/" + hash + ".png" } + EndpointGuildStickers = func(gID string) string { return EndpointGuilds + gID + "/stickers" } + EndpointGuildSticker = func(gID, sID string) string { return EndpointGuilds + gID + "/stickers/" + sID } + EndpointGuildActiveThreads = func(gID string) string { return EndpointGuilds + gID + "/threads/active" } EndpointChannel = func(cID string) string { return EndpointChannels + cID } EndpointChannelPermissions = func(cID string) string { return EndpointChannels + cID + "/permissions" } @@ -115,10 +120,28 @@ var ( EndpointChannelMessagesPins = func(cID string) string { return EndpointChannel(cID) + "/pins" } EndpointChannelMessagePin = func(cID, mID string) string { return EndpointChannel(cID) + "/pins/" + mID } EndpointChannelMessageCrosspost = func(cID, mID string) string { return EndpointChannel(cID) + "/messages/" + mID + "/crosspost" } - EndpointChannelFollow = func(cID string) string { return EndpointChannel(cID) + "/followers" } + EndpointChannelUsers = func(cID string) string { return EndpointChannel(cID) + "/users" } + EndpointChannelMessageThreads = func(cID, mID string) string { + return EndpointChannels + cID + "/messages/" + mID + "/threads" + } + + EndpointThreads = func(cID string) string { return EndpointChannel(cID) + "/threads" } + EndpointThreadMembers = func(cID string) string { return EndpointChannel(cID) + "/thread-members" } + EndpointThreadMember = func(cID, uID string) string { return EndpointChannel(cID) + "/thread-members/" + uID } + EndpointThreadsArchived = func(cID string) string { return EndpointThreads(cID) + "/archived" } + EndpointThreadsPublicArchived = func(cID string) string { return EndpointThreadsArchived(cID) + "/public" } + EndpointThreadsPrivateArchived = func(cID string) string { return EndpointThreadsArchived(cID) + "/private" } + EndpointThreadsJoinedPrivateArchived = func(cID string) string { return EndpointChannelUsers(cID) + "/@me/threads/archived/private" } + + EndpointChannelFollow = func(cID string) string { return EndpointChannel(cID) + "/followers" } EndpointGroupIcon = func(cID, hash string) string { return EndpointCDNChannelIcons + cID + "/" + hash + ".png" } + EndpointSticker = func(sID string) string { return EndpointStickers + sID } + EndpointNitroStickerPacks = EndpointAPI + "/sticker-packs" + + EndpointStageInstance = func(cID string) string { return EndpointStageInstances + cID } + EndpointChannelWebhooks = func(cID string) string { return EndpointChannel(cID) + "/webhooks" } EndpointWebhook = func(wID string) string { return EndpointWebhooks + wID } EndpointWebhookToken = func(wID, token string) string { return EndpointWebhooks + wID + "/" + token } diff --git a/eventhandlers.go b/eventhandlers.go index 4f5c4d92e..29565a7a0 100644 --- a/eventhandlers.go +++ b/eventhandlers.go @@ -44,6 +44,12 @@ const ( relationshipAddEventType = "RELATIONSHIP_ADD" relationshipRemoveEventType = "RELATIONSHIP_REMOVE" resumedEventType = "RESUMED" + threadCreateEventType = "THREAD_CREATE" + threadDeleteEventType = "THREAD_DELETE" + threadListSyncEventType = "THREAD_LIST_SYNC" + threadMemberUpdateEventType = "THREAD_MEMBER_UPDATE" + threadMembersUpdateEventType = "THREAD_MEMBERS_UPDATE" + threadUpdateEventType = "THREAD_UPDATE" typingStartEventType = "TYPING_START" userGuildSettingsUpdateEventType = "USER_GUILD_SETTINGS_UPDATE" userNoteUpdateEventType = "USER_NOTE_UPDATE" @@ -774,6 +780,126 @@ func (eh resumedEventHandler) Handle(s *Session, i interface{}) { } } +// threadCreateEventHandler is an event handler for ThreadCreate events. +type threadCreateEventHandler func(*Session, *ThreadCreate) + +// Type returns the event type for ThreadCreate events. +func (eh threadCreateEventHandler) Type() string { + return threadCreateEventType +} + +// New returns a new instance of ThreadCreate. +func (eh threadCreateEventHandler) New() interface{} { + return &ThreadCreate{} +} + +// Handle is the handler for ThreadCreate events. +func (eh threadCreateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*ThreadCreate); ok { + eh(s, t) + } +} + +// threadDeleteEventHandler is an event handler for ThreadDelete events. +type threadDeleteEventHandler func(*Session, *ThreadDelete) + +// Type returns the event type for ThreadDelete events. +func (eh threadDeleteEventHandler) Type() string { + return threadDeleteEventType +} + +// New returns a new instance of ThreadDelete. +func (eh threadDeleteEventHandler) New() interface{} { + return &ThreadDelete{} +} + +// Handle is the handler for ThreadDelete events. +func (eh threadDeleteEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*ThreadDelete); ok { + eh(s, t) + } +} + +// threadListSyncEventHandler is an event handler for ThreadListSync events. +type threadListSyncEventHandler func(*Session, *ThreadListSync) + +// Type returns the event type for ThreadListSync events. +func (eh threadListSyncEventHandler) Type() string { + return threadListSyncEventType +} + +// New returns a new instance of ThreadListSync. +func (eh threadListSyncEventHandler) New() interface{} { + return &ThreadListSync{} +} + +// Handle is the handler for ThreadListSync events. +func (eh threadListSyncEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*ThreadListSync); ok { + eh(s, t) + } +} + +// threadMemberUpdateEventHandler is an event handler for ThreadMemberUpdate events. +type threadMemberUpdateEventHandler func(*Session, *ThreadMemberUpdate) + +// Type returns the event type for ThreadMemberUpdate events. +func (eh threadMemberUpdateEventHandler) Type() string { + return threadMemberUpdateEventType +} + +// New returns a new instance of ThreadMemberUpdate. +func (eh threadMemberUpdateEventHandler) New() interface{} { + return &ThreadMemberUpdate{} +} + +// Handle is the handler for ThreadMemberUpdate events. +func (eh threadMemberUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*ThreadMemberUpdate); ok { + eh(s, t) + } +} + +// threadMembersUpdateEventHandler is an event handler for ThreadMembersUpdate events. +type threadMembersUpdateEventHandler func(*Session, *ThreadMembersUpdate) + +// Type returns the event type for ThreadMembersUpdate events. +func (eh threadMembersUpdateEventHandler) Type() string { + return threadMembersUpdateEventType +} + +// New returns a new instance of ThreadMembersUpdate. +func (eh threadMembersUpdateEventHandler) New() interface{} { + return &ThreadMembersUpdate{} +} + +// Handle is the handler for ThreadMembersUpdate events. +func (eh threadMembersUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*ThreadMembersUpdate); ok { + eh(s, t) + } +} + +// threadUpdateEventHandler is an event handler for ThreadUpdate events. +type threadUpdateEventHandler func(*Session, *ThreadUpdate) + +// Type returns the event type for ThreadUpdate events. +func (eh threadUpdateEventHandler) Type() string { + return threadUpdateEventType +} + +// New returns a new instance of ThreadUpdate. +func (eh threadUpdateEventHandler) New() interface{} { + return &ThreadUpdate{} +} + +// Handle is the handler for ThreadUpdate events. +func (eh threadUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*ThreadUpdate); ok { + eh(s, t) + } +} + // typingStartEventHandler is an event handler for TypingStart events. type typingStartEventHandler func(*Session, *TypingStart) @@ -1012,6 +1138,18 @@ func handlerForInterface(handler interface{}) EventHandler { return relationshipRemoveEventHandler(v) case func(*Session, *Resumed): return resumedEventHandler(v) + case func(*Session, *ThreadCreate): + return threadCreateEventHandler(v) + case func(*Session, *ThreadDelete): + return threadDeleteEventHandler(v) + case func(*Session, *ThreadListSync): + return threadListSyncEventHandler(v) + case func(*Session, *ThreadMemberUpdate): + return threadMemberUpdateEventHandler(v) + case func(*Session, *ThreadMembersUpdate): + return threadMembersUpdateEventHandler(v) + case func(*Session, *ThreadUpdate): + return threadUpdateEventHandler(v) case func(*Session, *TypingStart): return typingStartEventHandler(v) case func(*Session, *UserGuildSettingsUpdate): @@ -1067,6 +1205,12 @@ func init() { registerInterfaceProvider(relationshipAddEventHandler(nil)) registerInterfaceProvider(relationshipRemoveEventHandler(nil)) registerInterfaceProvider(resumedEventHandler(nil)) + registerInterfaceProvider(threadCreateEventHandler(nil)) + registerInterfaceProvider(threadDeleteEventHandler(nil)) + registerInterfaceProvider(threadListSyncEventHandler(nil)) + registerInterfaceProvider(threadMemberUpdateEventHandler(nil)) + registerInterfaceProvider(threadMembersUpdateEventHandler(nil)) + registerInterfaceProvider(threadUpdateEventHandler(nil)) registerInterfaceProvider(typingStartEventHandler(nil)) registerInterfaceProvider(userGuildSettingsUpdateEventHandler(nil)) registerInterfaceProvider(userNoteUpdateEventHandler(nil)) diff --git a/events.go b/events.go index 3000509ba..fd2b17879 100644 --- a/events.go +++ b/events.go @@ -289,6 +289,43 @@ type InteractionCreate struct { *Interaction } +// ThreadCreate is the data for a ThreadCreate event +type ThreadCreate struct { + *Channel +} + +// ThreadUpdate is the data for a ThreadUpdate event +type ThreadUpdate struct { + *Channel +} + +// ThreadDelete is the data for a ThreadDelete event +type ThreadDelete struct { + *Channel +} + +// ThreadMemberUpdate is the data for a ThreadMemberUpdate event +type ThreadMemberUpdate struct { + *ThreadMember +} + +// ThreadMembersUpdate is the data for a ThreadMembersUpdate event +type ThreadMembersUpdate struct { + ID string `json:"id"` + GuildID string `json:"guild_id"` + MemberCount int `json:"member_count"` + AddedMembers []*ThreadMember `json:"added_members"` + RemovedMemberIDs []string `json:"removed_member_ids"` +} + +// ThreadListSync is the data for a ThreadListSync event +type ThreadListSync struct { + GuildID string `json:"guild_id"` + ChannelIDs []string `json:"channel_ids"` + Threads []*Channel `json:"threads"` + Members []*ThreadMember `json:"members"` +} + // UnmarshalJSON is a helper function to unmarshal Interaction object. func (i *InteractionCreate) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &i.Interaction) diff --git a/examples/components/main.go b/examples/components/main.go index 2e80c6411..c1f1d2f26 100644 --- a/examples/components/main.go +++ b/examples/components/main.go @@ -49,7 +49,7 @@ var ( Name: "📜", }, Label: "Documentation", - Style: discordgo.LinkButton, + Style: discordgo.ButtonLink, URL: "https://discord.com/developers/docs/interactions/message-components#buttons", }, discordgo.Button{ @@ -57,7 +57,7 @@ var ( Name: "🔧", }, Label: "Discord developers", - Style: discordgo.LinkButton, + Style: discordgo.ButtonLink, URL: "https://discord.gg/discord-developers", }, discordgo.Button{ @@ -65,7 +65,7 @@ var ( Name: "đŸĻĢ", }, Label: "Discord Gophers", - Style: discordgo.LinkButton, + Style: discordgo.ButtonLink, URL: "https://discord.gg/7RuRrVHyXF", }, }, @@ -92,7 +92,7 @@ var ( Name: "🔧", }, Label: "Discord developers", - Style: discordgo.LinkButton, + Style: discordgo.ButtonLink, URL: "https://discord.gg/discord-developers", }, discordgo.Button{ @@ -100,7 +100,7 @@ var ( Name: "đŸĻĢ", }, Label: "Discord Gophers", - Style: discordgo.LinkButton, + Style: discordgo.ButtonLink, URL: "https://discord.gg/7RuRrVHyXF", }, }, @@ -174,7 +174,7 @@ var ( Name: "📜", }, Label: "Documentation", - Style: discordgo.LinkButton, + Style: discordgo.ButtonLink, URL: "https://discord.com/developers/docs/interactions/message-components#select-menus", }, discordgo.Button{ @@ -182,7 +182,7 @@ var ( Name: "🔧", }, Label: "Discord developers", - Style: discordgo.LinkButton, + Style: discordgo.ButtonLink, URL: "https://discord.gg/discord-developers", }, discordgo.Button{ @@ -190,7 +190,7 @@ var ( Name: "đŸĻĢ", }, Label: "Discord Gophers", - Style: discordgo.LinkButton, + Style: discordgo.ButtonLink, URL: "https://discord.gg/7RuRrVHyXF", }, }, @@ -219,7 +219,7 @@ var ( // Label is what the user will see on the button. Label: "Yes", // Style provides coloring of the button. There are not so many styles tho. - Style: discordgo.SuccessButton, + Style: discordgo.ButtonSuccess, // Disabled allows bot to disable some buttons for users. Disabled: false, // CustomID is a thing telling Discord which data to send when this button will be pressed. @@ -227,13 +227,13 @@ var ( }, discordgo.Button{ Label: "No", - Style: discordgo.DangerButton, + Style: discordgo.ButtonDanger, Disabled: false, CustomID: "fd_no", }, discordgo.Button{ Label: "I don't know", - Style: discordgo.LinkButton, + Style: discordgo.ButtonLink, Disabled: false, // Link buttons don't require CustomID and do not trigger the gateway/HTTP event URL: "https://www.youtube.com/watch?v=dQw4w9WgXcQ", @@ -248,7 +248,7 @@ var ( Components: []discordgo.MessageComponent{ discordgo.Button{ Label: "Discord Developers server", - Style: discordgo.LinkButton, + Style: discordgo.ButtonLink, Disabled: false, URL: "https://discord.gg/discord-developers", }, diff --git a/interactions.go b/interactions.go index 6f1891792..e62ac0f84 100644 --- a/interactions.go +++ b/interactions.go @@ -129,11 +129,12 @@ func (t InteractionType) String() string { // Interaction represents data of an interaction. type Interaction struct { - ID string `json:"id"` - Type InteractionType `json:"type"` - Data InteractionData `json:"-"` - GuildID string `json:"guild_id"` - ChannelID string `json:"channel_id"` + ID string `json:"id"` + ApplicationID string `json:"application_id"` + Type InteractionType `json:"type"` + Data InteractionData `json:"-"` + GuildID string `json:"guild_id"` + ChannelID string `json:"channel_id"` // The message on which interaction was used. // NOTE: this field is only filled when a button click triggered the interaction. Otherwise it will be nil. @@ -406,6 +407,15 @@ type InteractionResponse struct { Data *InteractionResponseData `json:"data,omitempty"` } +// InteractionResponseDataFlag are the flags for InteractionResponseData +type InteractionResponseDataFlag uint64 + +// All known flags for InteractionResponseData +const ( + // InteractionResponseDataEphemeral means only the user receiving the message can see it. + InteractionResponseDataEphemeral InteractionResponseDataFlag = 1 << 6 +) + // InteractionResponseData is response data for an interaction. type InteractionResponseData struct { TTS bool `json:"tts"` @@ -413,11 +423,19 @@ type InteractionResponseData struct { Components []MessageComponent `json:"components"` Embeds []*MessageEmbed `json:"embeds,omitempty"` AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"` - Flags uint64 `json:"flags,omitempty"` Files []*File `json:"-"` // NOTE: autocomplete interaction only. Choices []*ApplicationCommandOptionChoice `json:"choices,omitempty"` + Flags InteractionResponseDataFlag `json:"flags,omitempty"` +} + +// MessageInteraction is the data for an Interaction, when in a Message object. +type MessageInteraction struct { + ID string `json:"id"` + Type InteractionType `json:"type"` + Name string `json:"name"` + User User `json:"user"` } // VerifyInteraction implements message verification of the discord interactions api diff --git a/message.go b/message.go index 402ab1f45..f464915c7 100644 --- a/message.go +++ b/message.go @@ -22,24 +22,29 @@ type MessageType int // Block contains the valid known MessageType values const ( - MessageTypeDefault MessageType = 0 - MessageTypeRecipientAdd MessageType = 1 - MessageTypeRecipientRemove MessageType = 2 - MessageTypeCall MessageType = 3 - MessageTypeChannelNameChange MessageType = 4 - MessageTypeChannelIconChange MessageType = 5 - MessageTypeChannelPinnedMessage MessageType = 6 - MessageTypeGuildMemberJoin MessageType = 7 - MessageTypeUserPremiumGuildSubscription MessageType = 8 - MessageTypeUserPremiumGuildSubscriptionTierOne MessageType = 9 - MessageTypeUserPremiumGuildSubscriptionTierTwo MessageType = 10 - MessageTypeUserPremiumGuildSubscriptionTierThree MessageType = 11 - MessageTypeChannelFollowAdd MessageType = 12 - MessageTypeGuildDiscoveryDisqualified MessageType = 14 - MessageTypeGuildDiscoveryRequalified MessageType = 15 - MessageTypeReply MessageType = 19 - MessageTypeChatInputCommand MessageType = 20 - MessageTypeContextMenuCommand MessageType = 23 + MessageTypeDefault MessageType = 0 + MessageTypeRecipientAdd MessageType = 1 + MessageTypeRecipientRemove MessageType = 2 + MessageTypeCall MessageType = 3 + MessageTypeChannelNameChange MessageType = 4 + MessageTypeChannelIconChange MessageType = 5 + MessageTypeChannelPinnedMessage MessageType = 6 + MessageTypeGuildMemberJoin MessageType = 7 + MessageTypeUserPremiumGuildSubscription MessageType = 8 + MessageTypeUserPremiumGuildSubscriptionTierOne MessageType = 9 + MessageTypeUserPremiumGuildSubscriptionTierTwo MessageType = 10 + MessageTypeUserPremiumGuildSubscriptionTierThree MessageType = 11 + MessageTypeChannelFollowAdd MessageType = 12 + MessageTypeGuildDiscoveryDisqualified MessageType = 14 + MessageTypeGuildDiscoveryRequalified MessageType = 15 + MessageTypeGuildDiscoveryGracePeriodInitialWarning MessageType = 16 + MessageTypeGuildDiscoveryGracePeriodFinalWarning MessageType = 17 + MessageTypeThreadCreated MessageType = 18 + MessageTypeReply MessageType = 19 + MessageTypeChatInputCommand MessageType = 20 + MessageTypeThreadStarterMessage MessageType = 21 + MessageTypeGuildInviteReminder MessageType = 22 + MessageTypeContextMenuCommand MessageType = 23 ) // A Message stores all data related to a specific Discord message. @@ -120,6 +125,9 @@ type Message struct { // Is sent with Rich Presence-related chat embeds Application *MessageApplication `json:"application"` + // If the message is a response to an Interaction, the interaction's application ID + ApplicationID string `json:"application_id"` + // MessageReference contains reference data sent with crossposted or reply messages. // This does not contain the reference *to* this message; this is for when *this* message references another. // To generate a reference to this message, use (*Message).Reference(). @@ -129,6 +137,20 @@ type Message struct { // This is a combination of bit masks; the presence of a certain permission can // be checked by performing a bitwise AND between this int and the flag. Flags MessageFlags `json:"flags"` + + // Available only for messages that are a reply or thread starter message. + // This is the message that is being referenced. Null if the originating message + // was deleted or if it wasn't loaded. + ReferencedMessage *Message `json:"referenced_message"` + + // The message's response to an Interaction. + Interaction *MessageInteraction `json:"interaction"` + + // The thread that was started from this message. Includes the thread member object. + Thread *Channel `json:"thread"` + + // An array of Sticker objects, if any were sent. + StickerItems []*Sticker `json:"sticker_items"` } // UnmarshalJSON is a helper function to unmarshal the Message. @@ -176,9 +198,12 @@ type MessageFlags int const ( MessageFlagsCrossPosted MessageFlags = 1 << 0 MessageFlagsIsCrossPosted MessageFlags = 1 << 1 - MessageFlagsSupressEmbeds MessageFlags = 1 << 2 + MessageFlagsSuppressEmbeds MessageFlags = 1 << 2 MessageFlagsSourceMessageDeleted MessageFlags = 1 << 3 MessageFlagsUrgent MessageFlags = 1 << 4 + MessageFlagsHasThread MessageFlags = 1 << 5 + MessageFlagsEphemeral MessageFlags = 1 << 6 + MessageFlagsLoading MessageFlags = 1 << 7 ) // File stores info about files you e.g. send in messages. @@ -188,6 +213,50 @@ type File struct { Reader io.Reader } +// StickerFormat is the file format of the Sticker. +type StickerFormat int + +// Defines all known Sticker types. +const ( + StickerFormatTypePNG StickerFormat = 1 + StickerFormatTypeAPNG StickerFormat = 2 + StickerFormatTypeLottie StickerFormat = 3 +) + +// StickerType is the type of sticker. +type StickerType int + +// Defines Sticker types. +const ( + StickerTypeStandard StickerType = 1 + StickerTypeGuild StickerType = 2 +) + +// Sticker represents a sticker object that can be sent in a Message. +type Sticker struct { + ID string `json:"id"` + PackID string `json:"pack_id"` + Name string `json:"name"` + Description string `json:"description"` + Tags string `json:"tags"` + FormatType StickerFormat `json:"format_type"` + Available bool `json:"available"` + GuildID string `json:"guild_id"` + User *User `json:"user"` + SortValue int `json:"sort_value"` +} + +// StickerPack represents a pack of standard stickers. +type StickerPack struct { + ID string `json:"id"` + Stickers []Sticker `json:"stickers"` + Name string `json:"name"` + SKUID string `json:"sku_id"` + CoverStickerID string `json:"cover_sticker_id"` + Description string `json:"description"` + BannerAssetID string `json:"banner_asset_id"` +} + // MessageSend stores all parameters you can send with ChannelMessageSendComplex. type MessageSend struct { Content string `json:"content,omitempty"` diff --git a/restapi.go b/restapi.go index e3817d233..df38941a7 100644 --- a/restapi.go +++ b/restapi.go @@ -1872,6 +1872,402 @@ func (s *Session) ChannelNewsFollow(channelID, targetID string) (st *ChannelFoll return } +// ------------------------------------------------------------------------------------------------ +// Functions specific to Threads +// ------------------------------------------------------------------------------------------------ + +// ThreadListFilter is the struct that can be passed to buildFilter. +type ThreadListFilter struct { + // Threads before this timestamp + Before *time.Time + + // Optional; limit the number of threads returned + Limit int +} + +// buildFilter puts together a query string for use when getting a thread list. +func buildFilter(f *ThreadListFilter) (q string) { + if f == nil { + return + } + query := url.Values{} + if f.Before != nil { + query.Set("before", f.Before.Format(time.RFC3339)) + } + if f.Limit != 0 { + query.Add("limit", strconv.Itoa(f.Limit)) + } + if len(query) > 0 { + return "?" + query.Encode() + } + + return +} + +// ThreadJoin joins the session user to a thread +// PUT/channels/{channel.id}/thread-members/@me +func (s *Session) ThreadJoin(channelID string) (err error) { + _, err = s.RequestWithBucketID("PUT", EndpointThreadMember(channelID, "@me"), nil, EndpointChannel(channelID)) + return +} + +// ThreadMemberAdd adds another user to a thread +// PUT /channels/{channel.id}/thread-members/{user.id} +func (s *Session) ThreadMemberAdd(channelID, uID string) (err error) { + _, err = s.RequestWithBucketID("PUT", EndpointThreadMember(channelID, uID), nil, EndpointChannel(channelID)) + return +} + +// ThreadLeave removes the session user from a thread +// DELETE /channels/{channel.id}/thread-members/@me +func (s *Session) ThreadLeave(channelID string) (err error) { + _, err = s.RequestWithBucketID("DELETE", EndpointThreadMember(channelID, "@me"), nil, EndpointChannel(channelID)) + return +} + +// ThreadMemberRemove removes another user from a thread +// PUT /channels/{channel.id}/thread-members/{user.id} +func (s *Session) ThreadMemberRemove(channelID, uID string) (err error) { + _, err = s.RequestWithBucketID("DELETE", EndpointThreadMember(channelID, uID), nil, EndpointChannel(channelID)) + return +} + +// ThreadStartWithMessage creates a new public thread from an existing message. +// When called on a ChannelTypeGuildText channel, creates a ChannelTypeGuildPublicThread. +// When called on a ChannelTypeGuildNews channel, creates a ChannelTypeGuildNewsThread. +// The id of the created thread will be the same as the id of the message, +// and as such a message can only have a single thread created from it. +// +// POST /channels/{channel.id}/messages/{message.id}/threads +func (s *Session) ThreadStartWithMessage(channelID, messageID string, data *ThreadCreateData) (st *Channel, err error) { + body, err := s.RequestWithBucketID("POST", EndpointChannelMessageThreads(channelID, messageID), data, EndpointChannel(channelID)) + + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// ThreadStartWithoutMessage creates a new thread that is not connected +// to an existing message. +// The created thread is always a ChannelTypeGuildPrivateThread +// +// POST /channels/{channel.id}/threads +func (s *Session) ThreadStartWithoutMessage(channelID string, data *ThreadCreateData) (st *Channel, err error) { + body, err := s.RequestWithBucketID("POST", EndpointThreads(channelID), data, EndpointChannel(channelID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// ThreadEditComplex edits an existing thread, replacing the parameters entirely with ThreadEdit struct +// threadID : The ID of a Thread +// data : The thread struct to send +func (s *Session) ThreadEditComplex(threadID string, data *ThreadEditData) (st *Channel, err error) { + body, err := s.RequestWithBucketID("PATCH", EndpointChannel(threadID), data, EndpointChannel(threadID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// ThreadMembers lists the members of a thread +// GET /channels/{channel.id}/thread-members +func (s *Session) ThreadMembers(channelID string) (st []*ThreadMember, err error) { + body, err := s.RequestWithBucketID("GET", EndpointThreadMembers(channelID), nil, EndpointChannel(channelID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// ThreadsResponseBody is the returned data from the discord gateway +// of some specific threads requests +type ThreadsResponseBody struct { + Threads []*Channel `json:"threads"` + Members []*ThreadMember `json:"members"` + + // Whether there are potentially additional threads + // that could be returned on a subsequent call + HasMore bool `json:"has_more"` +} + +// listActiveThreadsResponseBody is the returned data from ListActiveThreads +type listActiveThreadsResponseBody struct { + Threads []*Channel `json:"threads"` + Members []*ThreadMember `json:"members"` +} + +// ThreadsListActive returns all active threads in the guild, +// including public and private threads. +// Threads are ordered by their `id`, in descending order. +func (s *Session) ThreadsListActive(guildID string) (t *listActiveThreadsResponseBody, err error) { + body, err := s.RequestWithBucketID("GET", EndpointGuildActiveThreads(guildID), nil, EndpointGuild(guildID)) + if err != nil { + return + } + + err = unmarshal(body, &t) + return +} + +// ThreadsListPublicArchived returns archived threads in the channel that are public +// GET /channels/{channel.id}/threads/archived/public +func (s *Session) ThreadsListPublicArchived(channelID string, filter *ThreadListFilter) (st *ThreadsResponseBody, err error) { + uri := EndpointThreadsPublicArchived(channelID) + buildFilter(filter) + + body, err := s.RequestWithBucketID("GET", uri, nil, EndpointChannel(channelID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// ThreadsListPrivateArchived returns archived threads in the channel that are private +// GET /channels/{channel.id}/threads/archived/private +func (s *Session) ThreadsListPrivateArchived(channelID string, filter *ThreadListFilter) (st *ThreadsResponseBody, err error) { + uri := EndpointThreadsPrivateArchived(channelID) + buildFilter(filter) + + body, err := s.RequestWithBucketID("GET", uri, nil, EndpointChannel(channelID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// ThreadsListJoinedPrivateArchived returns archived threads in the channel +// that are of type GUILD_PRIVATE_THREAD, and the user has joined +// GET /channels/{channel.id}/users/@me/threads/archived/private +func (s *Session) ThreadsListJoinedPrivateArchived(channelID string, filter *ThreadListFilter) (st *ThreadsResponseBody, err error) { + uri := EndpointThreadsPrivateArchived(channelID) + buildFilter(filter) + + body, err := s.RequestWithBucketID("GET", uri, nil, EndpointChannel(channelID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// ------------------------------------------------------------------------------------------------ +// Functions specific to Stickers +// ------------------------------------------------------------------------------------------------ + +// Sticker returns a Sticker based on its id. +func (s *Session) Sticker(sID string) (a *Sticker, err error) { + + endpoint := EndpointSticker(sID) + + b, err := s.RequestWithBucketID("GET", endpoint, nil, EndpointSticker(sID)) + if err != nil { + return nil, err + } + + err = unmarshal(b, &a) + return +} + +// StickerStandardNitroPacks gets the list of sticker packs available to premium users. +func (s *Session) StickerStandardNitroPacks() (a *struct { + StickerPacks []StickerPack `json:"sticker_packs"` +}, err error) { + + endpoint := EndpointNitroStickerPacks + + b, err := s.RequestWithBucketID("GET", endpoint, nil, EndpointStickers) + if err != nil { + return nil, err + } + + err = unmarshal(b, &a) + return +} + +// GuildStickers gets all stickers in a guild. +func (s *Session) GuildStickers(gID string) (a *[]Sticker, err error) { + + endpoint := EndpointGuildStickers(gID) + + b, err := s.RequestWithBucketID("GET", endpoint, nil, EndpointGuildStickers(gID)) + if err != nil { + return nil, err + } + + err = unmarshal(b, &a) + return +} + +// GuildSticker gets a sticker in a guild. +func (s *Session) GuildSticker(gID, sID string) (a *Sticker, err error) { + + endpoint := EndpointGuildSticker(gID, sID) + + b, err := s.RequestWithBucketID("GET", endpoint, nil, EndpointGuildStickers(gID)) + if err != nil { + return nil, err + } + + err = unmarshal(b, &a) + return +} + +// GuildStickerCreate creates a guild sticker. Image to use should be encoded as base64. +func (s *Session) GuildStickerCreate(gID, image, name, description, tags string) (a *Sticker, err error) { + + endpoint := EndpointGuildStickers(gID) + + data := struct { + Name string `json:"name"` + Description string `json:"description"` + + // The Discord name of an emoji. This will represent the sticker. + Tags string `json:"tags"` + + File string `json:"file"` + }{ + name, + description, + tags, + image, + } + + b, err := s.RequestWithBucketID("POST", endpoint, data, EndpointGuildStickers(gID)) + if err != nil { + return nil, err + } + + err = unmarshal(b, &a) + return +} + +// GuildStickerModify updates a guild sticker's information. Every field is optional. +func (s *Session) GuildStickerModify(gID, sID, name, description, tags string) (a *Sticker, err error) { + + endpoint := EndpointGuildSticker(gID, sID) + + data := struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + + // The Discord name of an emoji. This will represent the sticker. + Tags string `json:"tags,omitempty"` + }{ + name, + description, + tags, + } + + b, err := s.RequestWithBucketID("PATCH", endpoint, data, EndpointGuildStickers(gID)) + if err != nil { + return nil, err + } + + err = unmarshal(b, &a) + return +} + +// GuildStickerDelete removes a sticker from a guild. +func (s *Session) GuildStickerDelete(gID, sID string) (err error) { + + endpoint := EndpointGuildSticker(gID, sID) + + _, err = s.RequestWithBucketID("DELETE", endpoint, nil, EndpointGuildStickers(gID)) + if err != nil { + return err + } + + return +} + +// ------------------------------------------------------------------------------------------------ +// Functions specific to Stage Instances +// ------------------------------------------------------------------------------------------------ + +// StageInstance gets a stage instance. +func (s *Session) StageInstance(cID string) (st *StageInstance, err error) { + + endpoint := EndpointStageInstance(cID) + + _, err = s.RequestWithBucketID("GET", endpoint, nil, EndpointStageInstances) + if err != nil { + return nil, err + } + + return +} + +// StageInstanceCreate creates a stage instance in the channel ID given. +// It is not clear what this endpoint returns because its return value is not specified. +func (s *Session) StageInstanceCreate(cID, topic string, privacyLevel StagePrivacyLevel) (err error) { + + endpoint := EndpointStageInstances + + data := struct { + ChannelID string `json:"channel_id"` + Topic string `json:"topic"` + PrivacyLevel StagePrivacyLevel `json:"privacy_level,omitempty"` + }{ + cID, + topic, + privacyLevel, + } + + _, err = s.RequestWithBucketID("POST", endpoint, data, EndpointStageInstances) + if err != nil { + return err + } + + return +} + +// StageInstanceModify modifies the properties of a stage channel. +func (s *Session) StageInstanceModify(cID string, topic string, privacyLevel StagePrivacyLevel) (st *StageInstance, err error) { + + endpoint := EndpointStageInstance(cID) + + data := struct { + Topic string `json:"topic"` + PrivacyLevel StagePrivacyLevel `json:"privacy_level,omitempty"` + }{ + topic, + privacyLevel, + } + + _, err = s.RequestWithBucketID("PATCH", endpoint, data, endpoint) + if err != nil { + return nil, err + } + + return +} + +// StageInstanceDelete removes the stage channel. +func (s *Session) StageInstanceDelete(cID string) (err error) { + + endpoint := EndpointStageInstance(cID) + + _, err = s.RequestWithBucketID("DELETE", endpoint, nil, endpoint) + if err != nil { + return err + } + + return +} + // ------------------------------------------------------------------------------------------------ // Functions specific to Discord Invites // ------------------------------------------------------------------------------------------------ @@ -1902,6 +2298,32 @@ func (s *Session) InviteWithCounts(inviteID string) (st *Invite, err error) { return } +// InviteComplex returns an Invite, with the option to also include +// the number of times it has been used and when it expires. +func (s *Session) InviteComplex(inviteID string, withCounts, withExpiration bool) (st *Invite, err error) { + + endpoint := EndpointInvite(inviteID) + + v := url.Values{} + if withCounts { + v.Add("with_counts", "true") + } + if withExpiration { + v.Add("with_expiration", "true") + } + if len(v) > 0 { + endpoint = fmt.Sprintf("%s?%s", endpoint, v.Encode()) + } + + body, err := s.RequestWithBucketID("GET", endpoint, nil, EndpointInvite("")) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + // InviteDelete deletes an existing invite // inviteID : the code of an invite func (s *Session) InviteDelete(inviteID string) (st *Invite, err error) { @@ -2165,8 +2587,18 @@ func (s *Session) WebhookDeleteWithToken(webhookID, token string) (st *Webhook, func (s *Session) WebhookExecute(webhookID, token string, wait bool, data *WebhookParams) (st *Message, err error) { uri := EndpointWebhookToken(webhookID, token) + v := url.Values{} + if wait { - uri += "?wait=true" + v.Set("wait", "true") + } + + if data.ThreadID != "" { + v.Set("thread_id", data.ThreadID) + } + + if len(v) > 0 { + uri += "?" + v.Encode() } var response []byte diff --git a/state.go b/state.go index 2c21b19cd..9b9d2aca4 100644 --- a/state.go +++ b/state.go @@ -125,6 +125,10 @@ func (s *State) GuildAdd(guild *Guild) error { if guild.VoiceStates == nil { guild.VoiceStates = g.VoiceStates } + if guild.Threads == nil { + guild.Threads = g.Threads + } + *g = *guild return nil } @@ -557,6 +561,119 @@ func (s *State) Channel(channelID string) (*Channel, error) { return nil, ErrStateNotFound } +// ThreadMemberUpdate updates the ThreadMember object for the user, +// in a specific thread. +func (s *State) ThreadMemberUpdate(t *ThreadMember) error { + if s == nil { + return ErrNilState + } + + s.Lock() + defer s.Unlock() + + channel, ok := s.channelMap[t.ID] + if !ok { + return ErrStateNotFound + } + + channel.Member = t + return nil +} + +// ThreadMembersUpdate updates the member object of the thread. +// If the user was removed, the ThreadMember object in the state +// will be set to nil. +func (s *State) ThreadMembersUpdate(t *ThreadMembersUpdate) error { + if s == nil { + return ErrNilState + } + + s.Lock() + defer s.Unlock() + + thread, ok := s.channelMap[t.ID] + if !ok { + return ErrStateNotFound + } + + for _, memberID := range t.RemovedMemberIDs { + if memberID == s.User.ID { + thread.Member = nil + break + } + } + + for _, member := range t.AddedMembers { + if member.UserID == s.User.ID { + thread.Member = member + break + } + } + + return nil +} + +// ThreadListSync syncs new threads and threads member that +// the bot gained access to. +// https://discord.com/developers/docs/topics/gateway#thread-list-sync +func (s *State) ThreadListSync(t *ThreadListSync) error { + if s == nil { + return ErrNilState + } + + g, err := s.Guild(t.GuildID) + if err != nil { + return err + } + + s.Lock() + defer s.Unlock() + + var index int + +OUTER: + for _, c := range g.Channels { + if !c.IsThread() || c.ThreadMetadata.Archived { + g.Channels[index] = c + index++ + continue + } + + // If len(t.ChannelIDs) == 0, this means this event + // is happening for the whole guild. + // In this case we can remove all threads unconditionally. + if len(t.ChannelIDs) == 0 { + delete(s.channelMap, c.ID) + continue + } + + for _, parentChannelID := range t.ChannelIDs { + if c.ParentID == parentChannelID { + delete(s.channelMap, c.ID) + continue OUTER + } + } + + g.Channels[index] = c + index++ + } + g.Channels = g.Channels[:index] + + for _, channel := range t.Threads { + s.channelMap[channel.ID] = channel + g.Channels = append(g.Channels, channel) + } + + for _, member := range t.Members { + channel, ok := s.channelMap[member.ID] + if ok { + channel.Member = member + } + } + + return nil +} + // Emoji returns an emoji for a guild and emoji id. func (s *State) Emoji(guildID, emojiID string) (*Emoji, error) { if s == nil { @@ -913,6 +1030,30 @@ func (s *State) OnInterface(se *Session, i interface{}) (err error) { if s.TrackChannels { err = s.ChannelRemove(t.Channel) } + case *ThreadCreate: + if s.TrackChannels { + err = s.ChannelAdd(t.Channel) + } + case *ThreadUpdate: + if s.TrackChannels { + err = s.ChannelAdd(t.Channel) + } + case *ThreadDelete: + if s.TrackChannels { + err = s.ChannelRemove(t.Channel) + } + case *ThreadMemberUpdate: + if s.TrackChannels { + err = s.ThreadMemberUpdate(t.ThreadMember) + } + case *ThreadMembersUpdate: + if s.TrackChannels { + err = s.ThreadMembersUpdate(t) + } + case *ThreadListSync: + if s.TrackChannels { + err = s.ThreadListSync(t) + } case *MessageCreate: if s.MaxMessageCount != 0 { err = s.MessageAdd(t.Message) diff --git a/structs.go b/structs.go index 88ec3f735..f5caf9dc1 100644 --- a/structs.go +++ b/structs.go @@ -172,10 +172,14 @@ type IntegrationAccount struct { // A VoiceRegion stores data for a specific voice region server. type VoiceRegion struct { - ID string `json:"id"` - Name string `json:"name"` - Hostname string `json:"sample_hostname"` - Port int `json:"sample_port"` + ID string `json:"id"` + Name string `json:"name"` + Hostname string `json:"sample_hostname"` + Port int `json:"sample_port"` + VIP bool `json:"vip"` + Optimal bool `json:"optimal"` + Deprecated bool `json:"deprecated"` + Custom bool `json:"custom"` } // A VoiceICE stores data for voice ICE servers. @@ -193,21 +197,26 @@ type ICEServer struct { // A Invite stores all data related to a specific Discord Guild or Channel invite. type Invite struct { - Guild *Guild `json:"guild"` - Channel *Channel `json:"channel"` - Inviter *User `json:"inviter"` - Code string `json:"code"` - CreatedAt Timestamp `json:"created_at"` - MaxAge int `json:"max_age"` - Uses int `json:"uses"` - MaxUses int `json:"max_uses"` - Revoked bool `json:"revoked"` - Temporary bool `json:"temporary"` - Unique bool `json:"unique"` - TargetUser *User `json:"target_user"` - TargetUserType TargetUserType `json:"target_user_type"` - - // will only be filled when using InviteWithCounts + Guild *Guild `json:"guild"` + Channel *Channel `json:"channel"` + Inviter *User `json:"inviter"` + Code string `json:"code"` + CreatedAt Timestamp `json:"created_at"` + MaxAge int `json:"max_age"` + Uses int `json:"uses"` + MaxUses int `json:"max_uses"` + Revoked bool `json:"revoked"` + Temporary bool `json:"temporary"` + Unique bool `json:"unique"` + TargetUser *User `json:"target_user"` + TargetUserType TargetUserType `json:"target_user_type"` + TargetApplication *Application `json:"target_application"` + StageInstance *InviteStageInstance `json:"stage_instance"` + + // with_expiration only + ExpiresAt Timestamp `json:"expires_at"` + + // with_counts only ApproximatePresenceCount int `json:"approximate_presence_count"` ApproximateMemberCount int `json:"approximate_member_count"` } @@ -226,13 +235,17 @@ type ChannelType int // Block contains known ChannelType values const ( - ChannelTypeGuildText ChannelType = 0 - ChannelTypeDM ChannelType = 1 - ChannelTypeGuildVoice ChannelType = 2 - ChannelTypeGroupDM ChannelType = 3 - ChannelTypeGuildCategory ChannelType = 4 - ChannelTypeGuildNews ChannelType = 5 - ChannelTypeGuildStore ChannelType = 6 + ChannelTypeGuildText ChannelType = 0 + ChannelTypeDM ChannelType = 1 + ChannelTypeGuildVoice ChannelType = 2 + ChannelTypeGroupDM ChannelType = 3 + ChannelTypeGuildCategory ChannelType = 4 + ChannelTypeGuildNews ChannelType = 5 + ChannelTypeGuildStore ChannelType = 6 + ChannelTypeGuildNewsThread ChannelType = 10 + ChannelTypeGuildPublicThread ChannelType = 11 + ChannelTypeGuildPrivateThread ChannelType = 12 + ChannelTypeGuildStageVoice ChannelType = 13 ) // A Channel holds all data related to an individual Discord channel. @@ -287,17 +300,44 @@ type Channel struct { UserLimit int `json:"user_limit"` // The ID of the parent channel, if the channel is under a category + // If this channel is a thread, this will be the ID of the channel it was created from. ParentID string `json:"parent_id"` // Amount of seconds a user has to wait before sending another message (0-21600) // bots, as well as users with the permission manage_messages or manage_channel, are unaffected RateLimitPerUser int `json:"rate_limit_per_user"` - // ID of the DM creator Zeroed if guild channel + // ID of the DM creator. Will be zero if this is a guild channel. + // If this channel is a thread, it will be the ID of the user who started the thread. OwnerID string `json:"owner_id"` // ApplicationID of the DM creator Zeroed if guild channel or not a bot user ApplicationID string `json:"application_id"` + + // Voice region ID for the voice channel, automatic when set to null + RTCRegion *string `json:"rtc_region"` + + // Only applicable to the thread channel type. + // MessageCount is an approximation of how many messages are in the thread, and stops counting at 50. + MessageCount int `json:"message_count,omitempty"` + + // Only applicable to the thread channel type. + // MemberCount is an approximation of how many members are in the thread, and stops counting at 50. + MemberCount int `json:"member_count,omitempty"` + + // Only applicable to the thread channel type. + // ThreadMetadata contains information about the thread. + ThreadMetadata *ThreadMetadata `json:"thread_metadata,omitempty"` + + // Default duration for new threads, in minutes, before they are archived due to inactivity. + DefaultAutoArchiveDuration int `json:"default_auto_archive_duration,omitempty"` + + // Only applicable to the thread channel type. + // ThreadMember is the information for the current user, if they joined the thread. + Member *ThreadMember `json:"member,omitempty"` + + // The camera video quality mode of the voice channel, 1 when not present + VideoQualityMode VideoQualityMode `json:"video_quality_mode,omitempty"` } // Mention returns a string which mentions the channel @@ -305,6 +345,11 @@ func (c *Channel) Mention() string { return fmt.Sprintf("<#%s>", c.ID) } +// IsThread returns wether the specific channel is a thread +func (c *Channel) IsThread() bool { + return c.Type == ChannelTypeGuildNewsThread || c.Type == ChannelTypeGuildPrivateThread || c.Type == ChannelTypeGuildPublicThread +} + // A ChannelEdit holds Channel Field data for a channel edit. type ChannelEdit struct { Name string `json:"name,omitempty"` @@ -324,6 +369,87 @@ type ChannelFollow struct { WebhookID string `json:"webhook_id"` } +// ThreadMetadata holds information about a thread. +type ThreadMetadata struct { + // Whether the channel is archived. + Archived bool `json:"archived"` + + // The period of inactivity allowed before the channel automatically archives. + AutoArchiveDuration ArchiveDuration `json:"auto_archive_duration"` + + // A timestamp of when the thread's archive status was last changed. + ArchiveTimestamp Timestamp `json:"archive_timestamp"` + + // Whether the thread is locked. + Locked bool `json:"locked,omitempty"` +} + +// ThreadMember is used to determine whether a user is in a thread or not. +// ID and UserID are omitted in the member sent within each thread in the Guild Create event +type ThreadMember struct { + ID string `json:"id,omitempty"` + + UserID string `json:"userid,omitempty"` + + // JoinTimestamp is a timestamp of when the user last joined the thread. + JoinTimestamp Timestamp `json:"join_timestamp"` + + // Currently only used for notifications. + Flags int `json:"flags"` +} + +// ArchiveDuration represents the increments of time at which a thread auto-archives, in minutes. +type ArchiveDuration int + +// Defines the increments at which a thread archives due to inactivity. +const ( + ArchiveDurationOneHour ArchiveDuration = 60 + ArchiveDurationOneDay ArchiveDuration = ArchiveDurationOneHour * 24 + ArchiveDurationThreeDays ArchiveDuration = ArchiveDurationOneDay * 3 + ArchiveDurationOneWeek ArchiveDuration = ArchiveDurationOneDay * 7 +) + +// VideoQualityMode of the voice channel +// 1 when not present +type VideoQualityMode int + +// Constants for the Video Quality Modes of a channel +const ( + VideoQualityModeAuto VideoQualityMode = 1 + VideoQualityModeFull VideoQualityMode = 2 +) + +// ThreadCreateData is the data used to create threads +type ThreadCreateData struct { + // 2-100 character channel name + Name string `json:"name"` + + // Duration in minutes to automatically archive the thread + // after recent activity. + AutoArchiveDuration ArchiveDuration `json:"auto_archive_duration"` + + Type ChannelType `json:"type"` +} + +// ThreadEditData is the data used to edit threads +type ThreadEditData struct { + Name string `json:"name"` + Archived bool `json:"archived"` + Locked bool `json:"locked"` + RateLimitPerUser *int `json:"rate_limit_per_user,omitempty"` + + // Duration in minutes to automatically archive the thread + // after recent activity. + AutoArchiveDuration ArchiveDuration `json:"auto_archive_duration"` +} + +// ThreadListResponse is the response when getting a list of threads. +type ThreadListResponse struct { + Threads []Channel `json:"threads"` + Members []ThreadMember `json:"members"` + HasMore bool `json:"has_more"` +} + // PermissionOverwriteType represents the type of resource on which // a permission overwrite acts. type PermissionOverwriteType int @@ -450,7 +576,8 @@ type Guild struct { Icon string `json:"icon"` // The voice region of the guild. - Region string `json:"region"` + // Changed from region -> rtc_reg + RTCRegion string `json:"rtc_region"` // The ID of the AFK voice channel. AfkChannelID string `json:"afk_channel_id"` @@ -518,6 +645,11 @@ type Guild struct { // update events, and thus is only present in state-cached guilds. Channels []*Channel `json:"channels"` + // A list of threads in the guild. + // This field is only present in GUILD_CREATE events and websocket + // update events, and thus is only present in state-cached guilds. + Threads []*Channel `json:"threads"` + // A list of voice states for the guild. // This field is only present in GUILD_CREATE events and websocket // update events, and thus is only present in state-cached guilds. @@ -590,6 +722,9 @@ type Guild struct { // Permissions of our user Permissions int64 `json:"permissions,string"` + + // A list of stickers present in the guild. + Stickers []*Sticker `json:"stickers"` } // A GuildPreview holds data related to a specific public Discord Guild, even if the user is not in the guild. @@ -939,6 +1074,7 @@ type GuildAuditLog struct { Users []*User `json:"users,omitempty"` AuditLogEntries []*AuditLogEntry `json:"audit_log_entries"` Integrations []*Integration `json:"integrations"` + Threads []*Channel `json:"threads"` } // AuditLogEntry for a GuildAuditLog @@ -967,12 +1103,18 @@ type AuditLogChangeKey string // Block of valid AuditLogChangeKey const ( AuditLogChangeKeyName AuditLogChangeKey = "name" + AuditLogChangeKeyDescription AuditLogChangeKey = "description" AuditLogChangeKeyIconHash AuditLogChangeKey = "icon_hash" AuditLogChangeKeySplashHash AuditLogChangeKey = "splash_hash" + AuditLogChangeKeyDiscoverySplashHash AuditLogChangeKey = "discovery_splash_hash" + AuditLogChangeKeyBannerHash AuditLogChangeKey = "banner_hash" AuditLogChangeKeyOwnerID AuditLogChangeKey = "owner_id" AuditLogChangeKeyRegion AuditLogChangeKey = "region" + AuditLogChangeKeyPreferredLocale AuditLogChangeKey = "preferred_locale" AuditLogChangeKeyAfkChannelID AuditLogChangeKey = "afk_channel_id" AuditLogChangeKeyAfkTimeout AuditLogChangeKey = "afk_timeout" + AuditLogChangeKeyRulesChannelID AuditLogChangeKey = "rules_channel_id" + AuditLogChangeKeyPublicUpdatesChannelID AuditLogChangeKey = "public_updates_channel_id" AuditLogChangeKeyMfaLevel AuditLogChangeKey = "mfa_level" AuditLogChangeKeyVerificationLevel AuditLogChangeKey = "verification_level" AuditLogChangeKeyExplicitContentFilter AuditLogChangeKey = "explicit_content_filter" @@ -1003,7 +1145,7 @@ const ( AuditLogChangeKeyMaxUses AuditLogChangeKey = "max_uses" AuditLogChangeKeyUses AuditLogChangeKey = "uses" AuditLogChangeKeyMaxAge AuditLogChangeKey = "max_age" - AuditLogChangeKeyTempoary AuditLogChangeKey = "temporary" + AuditLogChangeKeyTemporary AuditLogChangeKey = "temporary" AuditLogChangeKeyDeaf AuditLogChangeKey = "deaf" AuditLogChangeKeyMute AuditLogChangeKey = "mute" AuditLogChangeKeyNick AuditLogChangeKey = "nick" @@ -1013,6 +1155,17 @@ const ( AuditLogChangeKeyEnableEmoticons AuditLogChangeKey = "enable_emoticons" AuditLogChangeKeyExpireBehavior AuditLogChangeKey = "expire_behavior" AuditLogChangeKeyExpireGracePeriod AuditLogChangeKey = "expire_grace_period" + AuditLogChangeKeyUserLimit AuditLogChangeKey = "user_limit" + AuditLogChangeKeyPrivacyLevel AuditLogChangeKey = "privacy_level" + AuditLogChangeKeyTags AuditLogChangeKey = "tags" + AuditLogChangeKeyFormatType AuditLogChangeKey = "format_type" + AuditLogChangeKeyAsset AuditLogChangeKey = "asset" + AuditLogChangeKeyAvailable AuditLogChangeKey = "available" + AuditLogChangeKeyGuildID AuditLogChangeKey = "guild_id" + AuditLogChangeKeyArchived AuditLogChangeKey = "archived" + AuditLogChangeKeyLocked AuditLogChangeKey = "locked" + AuditLogChangeKeyAutoArchiveDuration AuditLogChangeKey = "auto_archive_duration" + AuditLogChangeKeyDefaultAutoArchiveDuration AuditLogChangeKey = "default_auto_archive_duration" ) // AuditLogOptions optional data for the AuditLog @@ -1059,6 +1212,10 @@ const ( AuditLogActionMemberBanRemove AuditLogAction = 23 AuditLogActionMemberUpdate AuditLogAction = 24 AuditLogActionMemberRoleUpdate AuditLogAction = 25 + AuditLogActionMemberMove AuditLogAction = 26 + AuditLogActionMemberDisconnect AuditLogAction = 27 + + AuditLogActionBotAdd AuditLogAction = 28 AuditLogActionRoleCreate AuditLogAction = 30 AuditLogActionRoleUpdate AuditLogAction = 31 @@ -1084,6 +1241,18 @@ const ( AuditLogActionIntegrationCreate AuditLogAction = 80 AuditLogActionIntegrationUpdate AuditLogAction = 81 AuditLogActionIntegrationDelete AuditLogAction = 82 + + AuditLogActionStageInstanceCreate AuditLogAction = 83 + AuditLogActionStageInstanceUpdate AuditLogAction = 84 + AuditLogActionStageInstanceDelete AuditLogAction = 85 + + AuditLogActionStickerCreate AuditLogAction = 90 + AuditLogActionStickerUpdate AuditLogAction = 91 + AuditLogActionStickerDelete AuditLogAction = 92 + + AuditLogActionThreadCreate AuditLogAction = 110 + AuditLogActionThreadUpdate AuditLogAction = 111 + AuditLogActionThreadDelete AuditLogAction = 112 ) // A UserGuildSettingsChannelOverride stores data for a channel override for a users guild settings. @@ -1127,6 +1296,35 @@ type MessageReaction struct { GuildID string `json:"guild_id,omitempty"` } +// StagePrivacyLevel is the privacy level of the stage. +type StagePrivacyLevel int + +// Contains all known values for stage privacy level. +const ( + // StagePrivacyPublic means the stage can be seen in stage discovery. + StagePrivacyPublic StagePrivacyLevel = 1 + // StagePrivacyGuildOnly means the stage can only be seen by guild members. + StagePrivacyGuildOnly StagePrivacyLevel = 2 +) + +// StageInstance holds information about a live stage. +type StageInstance struct { + ID string `json:"id"` + GuildID string `json:"guild_id"` + ChannelID string `json:"channel_id"` + Topic string `json:"topic"` + PrivacyLevel StagePrivacyLevel `json:"privacy_level"` + DiscoveryDisabled bool `json:"discovery_disabled"` +} + +// InviteStageInstance is used in an Invite to a stage instance. +type InviteStageInstance struct { + Members []*Member `json:"members"` + ParticipantCount int `json:"participant_count"` + SpeakerCount int `json:"speaker_count"` + Topic string `json:"topic"` +} + // GatewayBotResponse stores the data for the gateway/bot response type GatewayBotResponse struct { URL string `json:"url"` @@ -1310,6 +1508,9 @@ const ( PermissionViewAuditLogs = 0x0000000000000080 PermissionViewChannel = 0x0000000000000400 PermissionViewGuildInsights = 0x0000000000080000 + PermissionManageThreads = 0x0000000400000000 + PermissionUsePublicThreads = 0x0000000800000000 + PermissionUsePrivateThreads = 0x0000001000000000 PermissionAllText = PermissionViewChannel | PermissionSendMessages | @@ -1318,7 +1519,10 @@ const ( PermissionEmbedLinks | PermissionAttachFiles | PermissionReadMessageHistory | - PermissionMentionEveryone + PermissionMentionEveryone | + PermissionUsePublicThreads | + PermissionUsePrivateThreads | + PermissionManageThreads PermissionAllVoice = PermissionViewChannel | PermissionVoiceConnect | PermissionVoiceSpeak | @@ -1365,37 +1569,46 @@ const ( ErrCodeBotsCannotUseEndpoint = 20001 ErrCodeOnlyBotsCanUseEndpoint = 20002 - ErrCodeMaximumGuildsReached = 30001 - ErrCodeMaximumFriendsReached = 30002 - ErrCodeMaximumPinsReached = 30003 - ErrCodeMaximumGuildRolesReached = 30005 - ErrCodeTooManyReactions = 30010 + ErrCodeMaximumGuildsReached = 30001 + ErrCodeMaximumFriendsReached = 30002 + ErrCodeMaximumPinsReached = 30003 + ErrCodeMaximumGuildRolesReached = 30005 + ErrCodeTooManyReactions = 30010 + ErrCodeMaximumNumberOfThreadParticipantsReached = 30033 ErrCodeUnauthorized = 40001 - ErrCodeMissingAccess = 50001 - ErrCodeInvalidAccountType = 50002 - ErrCodeCannotExecuteActionOnDMChannel = 50003 - ErrCodeEmbedDisabled = 50004 - ErrCodeCannotEditFromAnotherUser = 50005 - ErrCodeCannotSendEmptyMessage = 50006 - ErrCodeCannotSendMessagesToThisUser = 50007 - ErrCodeCannotSendMessagesInVoiceChannel = 50008 - ErrCodeChannelVerificationLevelTooHigh = 50009 - ErrCodeOAuth2ApplicationDoesNotHaveBot = 50010 - ErrCodeOAuth2ApplicationLimitReached = 50011 - ErrCodeInvalidOAuthState = 50012 - ErrCodeMissingPermissions = 50013 - ErrCodeInvalidAuthenticationToken = 50014 - ErrCodeNoteTooLong = 50015 - ErrCodeTooFewOrTooManyMessagesToDelete = 50016 - ErrCodeCanOnlyPinMessageToOriginatingChannel = 50019 - ErrCodeCannotExecuteActionOnSystemMessage = 50021 - ErrCodeMessageProvidedTooOldForBulkDelete = 50034 - ErrCodeInvalidFormBody = 50035 - ErrCodeInviteAcceptedToGuildApplicationsBotNotIn = 50036 + ErrCodeMissingAccess = 50001 + ErrCodeInvalidAccountType = 50002 + ErrCodeCannotExecuteActionOnDMChannel = 50003 + ErrCodeEmbedDisabled = 50004 + ErrCodeCannotEditFromAnotherUser = 50005 + ErrCodeCannotSendEmptyMessage = 50006 + ErrCodeCannotSendMessagesToThisUser = 50007 + ErrCodeCannotSendMessagesInVoiceChannel = 50008 + ErrCodeChannelVerificationLevelTooHigh = 50009 + ErrCodeOAuth2ApplicationDoesNotHaveBot = 50010 + ErrCodeOAuth2ApplicationLimitReached = 50011 + ErrCodeInvalidOAuthState = 50012 + ErrCodeMissingPermissions = 50013 + ErrCodeInvalidAuthenticationToken = 50014 + ErrCodeNoteTooLong = 50015 + ErrCodeTooFewOrTooManyMessagesToDelete = 50016 + ErrCodeCanOnlyPinMessageToOriginatingChannel = 50019 + ErrCodeCannotExecuteActionOnSystemMessage = 50021 + ErrCodeMessageProvidedTooOldForBulkDelete = 50034 + ErrCodeInvalidFormBody = 50035 + ErrCodeInviteAcceptedToGuildApplicationsBotNotIn = 50036 + ErrCodePerformedOperationOnArchivedThread = 50083 + ErrCodeInvalidThreadNotificationSettings = 50084 + ErrCodeBeforeValueIsEarlierThanThreadCreationDate = 50085 ErrCodeReactionBlocked = 90001 + + ErrCodeThreadAlreadyCreatedForThisMessage = 160004 + ErrCodeThreadIsLocked = 160005 + ErrCodeMaximumNumberOfActiveThreadsReached = 160006 + ErrCodeMaximumNumberOfActiveAnnouncementThreadsReached = 160007 ) // Intent is the type of a Gateway Intent diff --git a/webhook.go b/webhook.go index f54a45ce1..02bc3d1be 100644 --- a/webhook.go +++ b/webhook.go @@ -13,6 +13,10 @@ type Webhook struct { // ApplicationID is the bot/OAuth2 application that created this webhook ApplicationID string `json:"application_id,omitempty"` + + SourceGuild *Guild `json:"source_guild"` + SourceChannel *Channel `json:"source_channel"` + URL string `json:"url"` } // WebhookType is the type of Webhook (see WebhookType* consts) in the Webhook struct @@ -23,6 +27,7 @@ type WebhookType int const ( WebhookTypeIncoming WebhookType = 1 WebhookTypeChannelFollower WebhookType = 2 + WebhookTypeApplication WebhookType = 3 ) // WebhookParams is a struct for webhook params, used in the WebhookExecute command. @@ -36,7 +41,8 @@ type WebhookParams struct { Embeds []*MessageEmbed `json:"embeds,omitempty"` AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"` // NOTE: Works only for followup messages. - Flags uint64 `json:"flags,omitempty"` + Flags uint64 `json:"flags,omitempty"` + ThreadID string `json:"thread_id,omitempty"` } // WebhookEdit stores data for editing of a webhook message.