Threads reloaded (#1058)

* feat(endpoints): bumped discord version to 9

* feat: threads barebones

* feat(threads): documentation

* feat(threads): membership caching

* feat(threads): added type to StartThread method

* fix: replaced missing Timestamp definitions with time.Time

* chore: removed debug logs

* chore: removed thread alias for channel type

* feat(webhooks): separated thread option into method

* fix(state): ThreadMembersUpdate member duplication bug

* fix: golint

* feat(threads): pr fixes and BeforeUpdate in ThreadUpdate

* feat: removed unnecessary todo

* feat(state): removed thread last message update in MessageAdd

* Revert "feat(state): removed thread last message update in MessageAdd"

This reverts commit 4ca359fd2cc304e5d0ec2937e25c0c487a1f2096.

* feat(state): update only last message id for thread update

Implements updating message id in MESSAGE_CREATE and MESSAGE_DELETE events. Refer to https://discord.com/developers/docs/topics/gateway#thread-update for more info.

* fix(restapi): passing threadID in WebhookThreadExecute

* feat(state): dropped last_message_id updates for threads

* fix: gofmt

* feat(events#ThreadCreate): added newly_created field

* feat(restapi)!: corrected names of thread functions
This commit is contained in:
Fedor Lapshin 2022-02-17 22:50:42 +03:00 committed by GitHub
parent 6015eed933
commit 992358e106
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 899 additions and 121 deletions

View file

@ -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 (
@ -53,50 +53,61 @@ var (
uDiscriminatorInt, _ := strconv.Atoi(uDiscriminator)
return EndpointCDN + "embed/avatars/" + strconv.Itoa(uDiscriminatorInt%5) + ".png"
}
EndpointUserGuilds = func(uID string) string { return EndpointUsers + uID + "/guilds" }
EndpointUserGuild = func(uID, gID string) string { return EndpointUsers + uID + "/guilds/" + gID }
EndpointUserChannels = func(uID string) string { return EndpointUsers + uID + "/channels" }
EndpointUserConnections = func(uID string) string { return EndpointUsers + uID + "/connections" }
EndpointGuild = func(gID string) string { return EndpointGuilds + gID }
EndpointGuildPreview = func(gID string) string { return EndpointGuilds + gID + "/preview" }
EndpointGuildChannels = func(gID string) string { return EndpointGuilds + gID + "/channels" }
EndpointGuildMembers = func(gID string) string { return EndpointGuilds + gID + "/members" }
EndpointGuildMember = func(gID, uID string) string { return EndpointGuilds + gID + "/members/" + uID }
EndpointGuildMemberRole = func(gID, uID, rID string) string { return EndpointGuilds + gID + "/members/" + uID + "/roles/" + rID }
EndpointGuildBans = func(gID string) string { return EndpointGuilds + gID + "/bans" }
EndpointGuildBan = func(gID, uID string) string { return EndpointGuilds + gID + "/bans/" + uID }
EndpointGuildIntegrations = func(gID string) string { return EndpointGuilds + gID + "/integrations" }
EndpointGuildIntegration = func(gID, iID string) string { return EndpointGuilds + gID + "/integrations/" + iID }
EndpointGuildRoles = func(gID string) string { return EndpointGuilds + gID + "/roles" }
EndpointGuildRole = func(gID, rID string) string { return EndpointGuilds + gID + "/roles/" + rID }
EndpointGuildInvites = func(gID string) string { return EndpointGuilds + gID + "/invites" }
EndpointGuildWidget = func(gID string) string { return EndpointGuilds + gID + "/widget" }
EndpointGuildEmbed = EndpointGuildWidget
EndpointGuildPrune = func(gID string) string { return EndpointGuilds + gID + "/prune" }
EndpointGuildIcon = func(gID, hash string) string { return EndpointCDNIcons + gID + "/" + hash + ".png" }
EndpointGuildIconAnimated = func(gID, hash string) string { return EndpointCDNIcons + gID + "/" + hash + ".gif" }
EndpointGuildSplash = func(gID, hash string) string { return EndpointCDNSplashes + gID + "/" + hash + ".png" }
EndpointGuildWebhooks = func(gID string) string { return EndpointGuilds + gID + "/webhooks" }
EndpointGuildAuditLogs = func(gID string) string { return EndpointGuilds + gID + "/audit-logs" }
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 }
EndpointGuild = func(gID string) string { return EndpointGuilds + gID }
EndpointGuildThreads = func(gID string) string { return EndpointGuild(gID) + "/threads" }
EndpointGuildActiveThreads = func(gID string) string { return EndpointGuildThreads(gID) + "/active" }
EndpointGuildPreview = func(gID string) string { return EndpointGuilds + gID + "/preview" }
EndpointGuildChannels = func(gID string) string { return EndpointGuilds + gID + "/channels" }
EndpointGuildMembers = func(gID string) string { return EndpointGuilds + gID + "/members" }
EndpointGuildMember = func(gID, uID string) string { return EndpointGuilds + gID + "/members/" + uID }
EndpointGuildMemberRole = func(gID, uID, rID string) string { return EndpointGuilds + gID + "/members/" + uID + "/roles/" + rID }
EndpointGuildBans = func(gID string) string { return EndpointGuilds + gID + "/bans" }
EndpointGuildBan = func(gID, uID string) string { return EndpointGuilds + gID + "/bans/" + uID }
EndpointGuildIntegrations = func(gID string) string { return EndpointGuilds + gID + "/integrations" }
EndpointGuildIntegration = func(gID, iID string) string { return EndpointGuilds + gID + "/integrations/" + iID }
EndpointGuildRoles = func(gID string) string { return EndpointGuilds + gID + "/roles" }
EndpointGuildRole = func(gID, rID string) string { return EndpointGuilds + gID + "/roles/" + rID }
EndpointGuildInvites = func(gID string) string { return EndpointGuilds + gID + "/invites" }
EndpointGuildWidget = func(gID string) string { return EndpointGuilds + gID + "/widget" }
EndpointGuildEmbed = EndpointGuildWidget
EndpointGuildPrune = func(gID string) string { return EndpointGuilds + gID + "/prune" }
EndpointGuildIcon = func(gID, hash string) string { return EndpointCDNIcons + gID + "/" + hash + ".png" }
EndpointGuildIconAnimated = func(gID, hash string) string { return EndpointCDNIcons + gID + "/" + hash + ".gif" }
EndpointGuildSplash = func(gID, hash string) string { return EndpointCDNSplashes + gID + "/" + hash + ".png" }
EndpointGuildWebhooks = func(gID string) string { return EndpointGuilds + gID + "/webhooks" }
EndpointGuildAuditLogs = func(gID string) string { return EndpointGuilds + gID + "/audit-logs" }
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 }
EndpointChannel = func(cID string) string { return EndpointChannels + cID }
EndpointChannelPermissions = func(cID string) string { return EndpointChannels + cID + "/permissions" }
EndpointChannelPermission = func(cID, tID string) string { return EndpointChannelPermissions(cID) + "/" + tID }
EndpointChannelInvites = func(cID string) string { return EndpointChannels + cID + "/invites" }
EndpointChannelTyping = func(cID string) string { return EndpointChannels + cID + "/typing" }
EndpointChannelMessages = func(cID string) string { return EndpointChannels + cID + "/messages" }
EndpointChannelMessage = func(cID, mID string) string { return EndpointChannels + cID + "/messages/" + mID }
EndpointChannelMessagesBulkDelete = func(cID string) string { return EndpointChannel(cID) + "/messages/bulk-delete" }
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" }
EndpointChannel = func(cID string) string { return EndpointChannels + cID }
EndpointChannelThreads = func(cID string) string { return EndpointChannel(cID) + "/threads" }
EndpointChannelActiveThreads = func(cID string) string { return EndpointChannelThreads(cID) + "/active" }
EndpointChannelPublicArchivedThreads = func(cID string) string { return EndpointChannelThreads(cID) + "/archived/public" }
EndpointChannelPrivateArchivedThreads = func(cID string) string { return EndpointChannelThreads(cID) + "/archived/private" }
EndpointChannelJoinedPrivateArchivedThreads = func(cID string) string { return EndpointChannel(cID) + "/users/@me/threads/archived/private" }
EndpointChannelPermissions = func(cID string) string { return EndpointChannels + cID + "/permissions" }
EndpointChannelPermission = func(cID, tID string) string { return EndpointChannels + cID + "/permissions/" + tID }
EndpointChannelInvites = func(cID string) string { return EndpointChannels + cID + "/invites" }
EndpointChannelTyping = func(cID string) string { return EndpointChannels + cID + "/typing" }
EndpointChannelMessages = func(cID string) string { return EndpointChannels + cID + "/messages" }
EndpointChannelMessage = func(cID, mID string) string { return EndpointChannels + cID + "/messages/" + mID }
EndpointChannelMessageThread = func(cID, mID string) string { return EndpointChannelMessage(cID, mID) + "/threads" }
EndpointChannelMessagesBulkDelete = func(cID string) string { return EndpointChannel(cID) + "/messages/bulk-delete" }
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" }
EndpointThreadMembers = func(tID string) string { return EndpointChannel(tID) + "/thread-members" }
EndpointThreadMember = func(tID, mID string) string { return EndpointThreadMembers(tID) + "/" + mID }
EndpointGroupIcon = func(cID, hash string) string { return EndpointCDNChannelIcons + cID + "/" + hash + ".png" }

View file

@ -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))

View file

@ -73,6 +73,53 @@ type ChannelPinsUpdate struct {
GuildID string `json:"guild_id,omitempty"`
}
// ThreadCreate is the data for a ThreadCreate event.
type ThreadCreate struct {
*Channel
NewlyCreated bool `json:"newly_created"`
}
// ThreadUpdate is the data for a ThreadUpdate event.
type ThreadUpdate struct {
*Channel
BeforeUpdate *Channel `json:"-"`
}
// ThreadDelete is the data for a ThreadDelete event.
type ThreadDelete struct {
*Channel
}
// ThreadListSync is the data for a ThreadListSync event.
type ThreadListSync struct {
// The id of the guild
GuildID string `json:"guild_id"`
// The parent channel ids whose threads are being synced.
// If omitted, then threads were synced for the entire guild.
// This array may contain channel_ids that have no active threads as well, so you know to clear that data.
ChannelIDs []string `json:"channel_ids"`
// All active threads in the given channels that the current user can access
Threads []*Channel `json:"threads"`
// All thread member objects from the synced threads for the current user,
// indicating which threads the current user has been added to
Members []*ThreadMember `json:"members"`
}
// ThreadMemberUpdate is the data for a ThreadMemberUpdate event.
type ThreadMemberUpdate struct {
*ThreadMember
GuildID string `json:"guild_id"`
}
// 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 []AddedThreadMember `json:"added_members"`
RemovedMembers []string `json:"removed_member_ids"`
}
// GuildCreate is the data for a GuildCreate event.
type GuildCreate struct {
*Guild

73
examples/threads/main.go Normal file
View file

@ -0,0 +1,73 @@
package main
import (
"flag"
"fmt"
"log"
"os"
"os/signal"
"strings"
"time"
"github.com/bwmarrin/discordgo"
)
// Flags
var (
BotToken = flag.String("token", "", "Bot token")
)
const timeout time.Duration = time.Second * 10
var games map[string]time.Time = make(map[string]time.Time)
func main() {
flag.Parse()
s, _ := discordgo.New("Bot " + *BotToken)
s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) {
fmt.Println("Bot is ready")
})
s.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) {
if strings.Contains(m.Content, "ping") {
if ch, err := s.State.Channel(m.ChannelID); err != nil || !ch.IsThread() {
thread, err := s.MessageThreadStartComplex(m.ChannelID, m.ID, &discordgo.ThreadStart{
Name: "Pong game with " + m.Author.Username,
AutoArchiveDuration: 60,
Invitable: false,
RateLimitPerUser: 10,
})
if err != nil {
panic(err)
}
_, _ = s.ChannelMessageSend(thread.ID, "pong")
m.ChannelID = thread.ID
} else {
_, _ = s.ChannelMessageSendReply(m.ChannelID, "pong", m.Reference())
}
games[m.ChannelID] = time.Now()
<-time.After(timeout)
if time.Since(games[m.ChannelID]) >= timeout {
_, err := s.ChannelEditComplex(m.ChannelID, &discordgo.ChannelEdit{
Archived: true,
Locked: true,
})
if err != nil {
panic(err)
}
}
}
})
s.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsAllWithoutPrivileged)
err := s.Open()
if err != nil {
log.Fatalf("Cannot open the session: %v", err)
}
defer s.Close()
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt)
<-stop
log.Println("Graceful shutdown")
}

View file

@ -38,8 +38,10 @@ const (
MessageTypeChannelFollowAdd MessageType = 12
MessageTypeGuildDiscoveryDisqualified MessageType = 14
MessageTypeGuildDiscoveryRequalified MessageType = 15
MessageTypeThreadCreated MessageType = 18
MessageTypeReply MessageType = 19
MessageTypeChatInputCommand MessageType = 20
MessageTypeThreadStarterMessage MessageType = 21
MessageTypeContextMenuCommand MessageType = 23
)
@ -126,11 +128,21 @@ type Message struct {
// To generate a reference to this message, use (*Message).Reference().
MessageReference *MessageReference `json:"message_reference"`
// The message associated with the message_reference
// NOTE: This field is only returned for messages with a type of 19 (REPLY) or 21 (THREAD_STARTER_MESSAGE).
// If the message is a reply but the referenced_message field is not present,
// the backend did not attempt to fetch the message that was being replied to, so its state is unknown.
// If the field exists but is null, the referenced message was deleted.
ReferencedMessage *Message `json:"referenced_message"`
// The flags of the message, which describe extra features of a message.
// 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"`
// The thread that was started from this message, includes thread member object
Thread *Channel `json:"thread,omitempty"`
// An array of Sticker objects, if any were sent.
StickerItems []*Sticker `json:"sticker_items"`
}

View file

@ -1988,15 +1988,19 @@ func (s *Session) WebhookDeleteWithToken(webhookID, token string) (st *Webhook,
return
}
// WebhookExecute executes a webhook.
// webhookID: The ID of a webhook.
// token : The auth token for the webhook
// wait : Waits for server confirmation of message send and ensures that the return struct is populated (it is nil otherwise)
func (s *Session) WebhookExecute(webhookID, token string, wait bool, data *WebhookParams) (st *Message, err error) {
func (s *Session) webhookExecute(webhookID, token string, wait bool, threadID string, data *WebhookParams) (st *Message, err error) {
uri := EndpointWebhookToken(webhookID, token)
v := url.Values{}
if wait {
uri += "?wait=true"
v.Set("wait", "true")
}
if threadID != "" {
v.Set("thread_id", threadID)
}
if len(v) != 0 {
uri += "?" + v.Encode()
}
var response []byte
@ -2018,6 +2022,23 @@ func (s *Session) WebhookExecute(webhookID, token string, wait bool, data *Webho
return
}
// WebhookExecute executes a webhook.
// webhookID: The ID of a webhook.
// token : The auth token for the webhook
// wait : Waits for server confirmation of message send and ensures that the return struct is populated (it is nil otherwise)
func (s *Session) WebhookExecute(webhookID, token string, wait bool, data *WebhookParams) (st *Message, err error) {
return s.webhookExecute(webhookID, token, wait, "", data)
}
// WebhookThreadExecute executes a webhook in a thread.
// webhookID: The ID of a webhook.
// token : The auth token for the webhook
// wait : Waits for server confirmation of message send and ensures that the return struct is populated (it is nil otherwise)
// threadID : Sends a message to the specified thread within a webhook's channel. The thread will automatically be unarchived.
func (s *Session) WebhookThreadExecute(webhookID, token string, wait bool, threadID string, data *WebhookParams) (st *Message, err error) {
return s.webhookExecute(webhookID, token, wait, threadID, data)
}
// WebhookMessage gets a webhook message.
// webhookID : The ID of a webhook
// token : The auth token for the webhook
@ -2164,6 +2185,226 @@ func (s *Session) MessageReactions(channelID, messageID, emojiID string, limit i
return
}
// ------------------------------------------------------------------------------------------------
// Functions specific to threads
// ------------------------------------------------------------------------------------------------
// MessageThreadStartComplex creates a new thread from an existing message.
// channelID : Channel to create thread in
// messageID : Message to start thread from
// data : Parameters of the thread
func (s *Session) MessageThreadStartComplex(channelID, messageID string, data *ThreadStart) (ch *Channel, err error) {
endpoint := EndpointChannelMessageThread(channelID, messageID)
var body []byte
body, err = s.RequestWithBucketID("POST", endpoint, data, endpoint)
if err != nil {
return
}
err = unmarshal(body, &ch)
return
}
// MessageThreadStart creates a new thread from an existing message.
// channelID : Channel to create thread in
// messageID : Message to start thread from
// name : Name of the thread
// archiveDuration : Auto archive duration (in minutes)
func (s *Session) MessageThreadStart(channelID, messageID string, name string, archiveDuration int) (ch *Channel, err error) {
return s.MessageThreadStartComplex(channelID, messageID, &ThreadStart{
Name: name,
AutoArchiveDuration: archiveDuration,
})
}
// ThreadStartComplex creates a new thread.
// channelID : Channel to create thread in
// data : Parameters of the thread
func (s *Session) ThreadStartComplex(channelID string, data *ThreadStart) (ch *Channel, err error) {
endpoint := EndpointChannelThreads(channelID)
var body []byte
body, err = s.RequestWithBucketID("POST", endpoint, data, endpoint)
if err != nil {
return
}
err = unmarshal(body, &ch)
return
}
// ThreadStart creates a new thread.
// channelID : Channel to create thread in
// name : Name of the thread
// archiveDuration : Auto archive duration (in minutes)
func (s *Session) ThreadStart(channelID, name string, typ ChannelType, archiveDuration int) (ch *Channel, err error) {
return s.ThreadStartComplex(channelID, &ThreadStart{
Name: name,
Type: typ,
AutoArchiveDuration: archiveDuration,
})
}
// ThreadJoin adds current user to a thread
func (s *Session) ThreadJoin(id string) error {
endpoint := EndpointThreadMember(id, "@me")
_, err := s.RequestWithBucketID("PUT", endpoint, nil, endpoint)
return err
}
// ThreadLeave removes current user to a thread
func (s *Session) ThreadLeave(id string) error {
endpoint := EndpointThreadMember(id, "@me")
_, err := s.RequestWithBucketID("DELETE", endpoint, nil, endpoint)
return err
}
// ThreadMemberAdd adds another member to a thread
func (s *Session) ThreadMemberAdd(threadID, memberID string) error {
endpoint := EndpointThreadMember(threadID, memberID)
_, err := s.RequestWithBucketID("PUT", endpoint, nil, endpoint)
return err
}
// ThreadMemberRemove removes another member from a thread
func (s *Session) ThreadMemberRemove(threadID, memberID string) error {
endpoint := EndpointThreadMember(threadID, memberID)
_, err := s.RequestWithBucketID("DELETE", endpoint, nil, endpoint)
return err
}
// ThreadMember returns thread member object for the specified member of a thread
func (s *Session) ThreadMember(threadID, memberID string) (member *ThreadMember, err error) {
endpoint := EndpointThreadMember(threadID, memberID)
var body []byte
body, err = s.RequestWithBucketID("GET", endpoint, nil, endpoint)
if err != nil {
return
}
err = unmarshal(body, &member)
return
}
// ThreadMembers returns all members of specified thread.
func (s *Session) ThreadMembers(threadID string) (members []*ThreadMember, err error) {
var body []byte
body, err = s.RequestWithBucketID("GET", EndpointThreadMembers(threadID), nil, EndpointThreadMembers(threadID))
if err != nil {
return
}
err = unmarshal(body, &members)
return
}
// ThreadsActive returns all active threads for specified channel.
func (s *Session) ThreadsActive(channelID string) (threads *ThreadsList, err error) {
var body []byte
body, err = s.RequestWithBucketID("GET", EndpointChannelActiveThreads(channelID), nil, EndpointChannelActiveThreads(channelID))
if err != nil {
return
}
err = unmarshal(body, &threads)
return
}
// GuildThreadsActive returns all active threads for specified guild.
func (s *Session) GuildThreadsActive(guildID string) (threads *ThreadsList, err error) {
var body []byte
body, err = s.RequestWithBucketID("GET", EndpointGuildActiveThreads(guildID), nil, EndpointGuildActiveThreads(guildID))
if err != nil {
return
}
err = unmarshal(body, &threads)
return
}
// ThreadsArchived returns archived threads for specified channel.
// before : If specified returns only threads before the timestamp
// limit : Optional maximum amount of threads to return.
func (s *Session) ThreadsArchived(channelID string, before *time.Time, limit int) (threads *ThreadsList, err error) {
endpoint := EndpointChannelPublicArchivedThreads(channelID)
v := url.Values{}
if before != nil {
v.Set("before", before.Format(time.RFC3339))
}
if limit > 0 {
v.Set("limit", strconv.Itoa(limit))
}
if len(v) > 0 {
endpoint += "?" + v.Encode()
}
var body []byte
body, err = s.RequestWithBucketID("GET", endpoint, nil, endpoint)
if err != nil {
return
}
err = unmarshal(body, &threads)
return
}
// ThreadsPrivateArchived returns archived private threads for specified channel.
// before : If specified returns only threads before the timestamp
// limit : Optional maximum amount of threads to return.
func (s *Session) ThreadsPrivateArchived(channelID string, before *time.Time, limit int) (threads *ThreadsList, err error) {
endpoint := EndpointChannelPrivateArchivedThreads(channelID)
v := url.Values{}
if before != nil {
v.Set("before", before.Format(time.RFC3339))
}
if limit > 0 {
v.Set("limit", strconv.Itoa(limit))
}
if len(v) > 0 {
endpoint += "?" + v.Encode()
}
var body []byte
body, err = s.RequestWithBucketID("GET", endpoint, nil, endpoint)
if err != nil {
return
}
err = unmarshal(body, &threads)
return
}
// ThreadsPrivateJoinedArchived returns archived joined private threads for specified channel.
// before : If specified returns only threads before the timestamp
// limit : Optional maximum amount of threads to return.
func (s *Session) ThreadsPrivateJoinedArchived(channelID string, before *time.Time, limit int) (threads *ThreadsList, err error) {
endpoint := EndpointChannelJoinedPrivateArchivedThreads(channelID)
v := url.Values{}
if before != nil {
v.Set("before", before.Format(time.RFC3339))
}
if limit > 0 {
v.Set("limit", strconv.Itoa(limit))
}
if len(v) > 0 {
endpoint += "?" + v.Encode()
}
var body []byte
body, err = s.RequestWithBucketID("GET", endpoint, nil, endpoint)
if err != nil {
return
}
err = unmarshal(body, &threads)
return
}
// ------------------------------------------------------------------------------------------------
// Functions specific to application (slash) commands
// ------------------------------------------------------------------------------------------------

275
state.go
View file

@ -38,13 +38,15 @@ type State struct {
Ready
// MaxMessageCount represents how many messages per channel the state will store.
MaxMessageCount int
TrackChannels bool
TrackEmojis bool
TrackMembers bool
TrackRoles bool
TrackVoice bool
TrackPresences bool
MaxMessageCount int
TrackChannels bool
TrackThreads bool
TrackEmojis bool
TrackMembers bool
TrackThreadMembers bool
TrackRoles bool
TrackVoice bool
TrackPresences bool
guildMap map[string]*Guild
channelMap map[string]*Channel
@ -58,15 +60,17 @@ func NewState() *State {
PrivateChannels: []*Channel{},
Guilds: []*Guild{},
},
TrackChannels: true,
TrackEmojis: true,
TrackMembers: true,
TrackRoles: true,
TrackVoice: true,
TrackPresences: true,
guildMap: make(map[string]*Guild),
channelMap: make(map[string]*Channel),
memberMap: make(map[string]map[string]*Member),
TrackChannels: true,
TrackThreads: true,
TrackEmojis: true,
TrackMembers: true,
TrackThreadMembers: true,
TrackRoles: true,
TrackVoice: true,
TrackPresences: true,
guildMap: make(map[string]*Guild),
channelMap: make(map[string]*Channel),
memberMap: make(map[string]map[string]*Member),
}
}
@ -93,6 +97,11 @@ func (s *State) GuildAdd(guild *Guild) error {
s.channelMap[c.ID] = c
}
// Add all the threads to the state in case of thread sync list.
for _, t := range guild.Threads {
s.channelMap[t.ID] = t
}
// If this guild contains a new member slice, we must regenerate the member map so the pointers stay valid
if guild.Members != nil {
s.createMemberMap(guild)
@ -122,6 +131,9 @@ func (s *State) GuildAdd(guild *Guild) error {
if guild.Channels == nil {
guild.Channels = g.Channels
}
if guild.Threads == nil {
guild.Threads = g.Threads
}
if guild.VoiceStates == nil {
guild.VoiceStates = g.VoiceStates
}
@ -180,21 +192,12 @@ func (s *State) Guild(guildID string) (*Guild, error) {
return nil, ErrStateNotFound
}
// PresenceAdd adds a presence to the current world state, or
// updates it if it already exists.
func (s *State) PresenceAdd(guildID string, presence *Presence) error {
if s == nil {
return ErrNilState
func (s *State) presenceAdd(guildID string, presence *Presence) error {
guild, ok := s.guildMap[guildID]
if !ok {
return ErrStateNotFound
}
guild, err := s.Guild(guildID)
if err != nil {
return err
}
s.Lock()
defer s.Unlock()
for i, p := range guild.Presences {
if p.User.ID == presence.User.ID {
//guild.Presences[i] = presence
@ -233,6 +236,19 @@ func (s *State) PresenceAdd(guildID string, presence *Presence) error {
return nil
}
// PresenceAdd adds a presence to the current world state, or
// updates it if it already exists.
func (s *State) PresenceAdd(guildID string, presence *Presence) error {
if s == nil {
return ErrNilState
}
s.Lock()
defer s.Unlock()
return s.presenceAdd(guildID, presence)
}
// PresenceRemove removes a presence from the current world state.
func (s *State) PresenceRemove(guildID string, presence *Presence) error {
if s == nil {
@ -279,21 +295,12 @@ func (s *State) Presence(guildID, userID string) (*Presence, error) {
// TODO: Consider moving Guild state update methods onto *Guild.
// MemberAdd adds a member to the current world state, or
// updates it if it already exists.
func (s *State) MemberAdd(member *Member) error {
if s == nil {
return ErrNilState
func (s *State) memberAdd(member *Member) error {
guild, ok := s.guildMap[member.GuildID]
if !ok {
return ErrStateNotFound
}
guild, err := s.Guild(member.GuildID)
if err != nil {
return err
}
s.Lock()
defer s.Unlock()
members, ok := s.memberMap[member.GuildID]
if !ok {
return ErrStateNotFound
@ -311,10 +318,22 @@ func (s *State) MemberAdd(member *Member) error {
}
*m = *member
}
return nil
}
// MemberAdd adds a member to the current world state, or
// updates it if it already exists.
func (s *State) MemberAdd(member *Member) error {
if s == nil {
return ErrNilState
}
s.Lock()
defer s.Unlock()
return s.memberAdd(member)
}
// MemberRemove removes a member from current world state.
func (s *State) MemberRemove(member *Member) error {
if s == nil {
@ -465,6 +484,9 @@ func (s *State) ChannelAdd(channel *Channel) error {
if channel.PermissionOverwrites == nil {
channel.PermissionOverwrites = c.PermissionOverwrites
}
if channel.ThreadMetadata == nil {
channel.ThreadMetadata = c.ThreadMetadata
}
*c = *channel
return nil
@ -472,12 +494,18 @@ func (s *State) ChannelAdd(channel *Channel) error {
if channel.Type == ChannelTypeDM || channel.Type == ChannelTypeGroupDM {
s.PrivateChannels = append(s.PrivateChannels, channel)
} else {
guild, ok := s.guildMap[channel.GuildID]
if !ok {
return ErrStateNotFound
}
s.channelMap[channel.ID] = channel
return nil
}
guild, ok := s.guildMap[channel.GuildID]
if !ok {
return ErrStateNotFound
}
if channel.IsThread() {
guild.Threads = append(guild.Threads, channel)
} else {
guild.Channels = append(guild.Channels, channel)
}
@ -507,15 +535,26 @@ func (s *State) ChannelRemove(channel *Channel) error {
break
}
}
} else {
guild, err := s.Guild(channel.GuildID)
if err != nil {
return err
delete(s.channelMap, channel.ID)
return nil
}
guild, err := s.Guild(channel.GuildID)
if err != nil {
return err
}
s.Lock()
defer s.Unlock()
if channel.IsThread() {
for i, t := range guild.Threads {
if t.ID == channel.ID {
guild.Threads = append(guild.Threads[:i], guild.Threads[i+1:]...)
break
}
}
s.Lock()
defer s.Unlock()
} else {
for i, c := range guild.Channels {
if c.ID == channel.ID {
guild.Channels = append(guild.Channels[:i], guild.Channels[i+1:]...)
@ -529,6 +568,99 @@ func (s *State) ChannelRemove(channel *Channel) error {
return nil
}
// ThreadListSync syncs guild threads with provided ones.
func (s *State) ThreadListSync(tls *ThreadListSync) error {
guild, err := s.Guild(tls.GuildID)
if err != nil {
return err
}
s.Lock()
defer s.Unlock()
// This algorithm filters out archived or
// threads which are children of channels in channelIDs
// and then it adds all synced threads to guild threads and cache
index := 0
outer:
for _, t := range guild.Threads {
if !t.ThreadMetadata.Archived && tls.ChannelIDs != nil {
for _, v := range tls.ChannelIDs {
if t.ParentID == v {
delete(s.channelMap, t.ID)
continue outer
}
}
guild.Threads[index] = t
index++
} else {
delete(s.channelMap, t.ID)
}
}
guild.Threads = guild.Threads[:index]
for _, t := range tls.Threads {
s.channelMap[t.ID] = t
guild.Threads = append(guild.Threads, t)
}
for _, m := range tls.Members {
if c, ok := s.channelMap[m.ID]; ok {
c.Member = m
}
}
return nil
}
// ThreadMembersUpdate updates thread members list
func (s *State) ThreadMembersUpdate(tmu *ThreadMembersUpdate) error {
thread, err := s.Channel(tmu.ID)
if err != nil {
return err
}
s.Lock()
defer s.Unlock()
for idx, member := range thread.Members {
for _, removedMember := range tmu.RemovedMembers {
if member.ID == removedMember {
thread.Members = append(thread.Members[:idx], thread.Members[idx+1:]...)
break
}
}
}
for _, addedMember := range tmu.AddedMembers {
thread.Members = append(thread.Members, addedMember.ThreadMember)
if addedMember.Member != nil {
err = s.memberAdd(addedMember.Member)
if err != nil {
return err
}
}
if addedMember.Presence != nil {
err = s.presenceAdd(tmu.GuildID, addedMember.Presence)
if err != nil {
return err
}
}
}
thread.MemberCount = tmu.MemberCount
return nil
}
// ThreadMemberUpdate sets or updates member data for the current user.
func (s *State) ThreadMemberUpdate(mu *ThreadMemberUpdate) error {
thread, err := s.Channel(mu.ID)
if err != nil {
return err
}
thread.Member = mu.ThreadMember
return nil
}
// GuildChannel gets a channel by ID from a guild.
// This method is Deprecated, use Channel(channelID)
func (s *State) GuildChannel(guildID, channelID string) (*Channel, error) {
@ -668,6 +800,7 @@ func (s *State) MessageAdd(message *Message) error {
if len(c.Messages) > s.MaxMessageCount {
c.Messages = c.Messages[len(c.Messages)-s.MaxMessageCount:]
}
return nil
}
@ -693,6 +826,7 @@ func (s *State) messageRemoveByID(channelID, messageID string) error {
for i, m := range c.Messages {
if m.ID == messageID {
c.Messages = append(c.Messages[:i], c.Messages[i+1:]...)
return nil
}
}
@ -913,6 +1047,35 @@ func (s *State) OnInterface(se *Session, i interface{}) (err error) {
if s.TrackChannels {
err = s.ChannelRemove(t.Channel)
}
case *ThreadCreate:
if s.TrackThreads {
err = s.ChannelAdd(t.Channel)
}
case *ThreadUpdate:
if s.TrackThreads {
old, err := s.Channel(t.ID)
if err == nil {
oldCopy := *old
t.BeforeUpdate = &oldCopy
}
err = s.ChannelAdd(t.Channel)
}
case *ThreadDelete:
if s.TrackThreads {
err = s.ChannelRemove(t.Channel)
}
case *ThreadMemberUpdate:
if s.TrackThreads {
err = s.ThreadMemberUpdate(t)
}
case *ThreadMembersUpdate:
if s.TrackThreadMembers {
err = s.ThreadMembersUpdate(t)
}
case *ThreadListSync:
if s.TrackThreads {
err = s.ThreadListSync(t)
}
case *MessageCreate:
if s.MaxMessageCount != 0 {
err = s.MessageAdd(t.Message)

View file

@ -226,13 +226,16 @@ 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
)
// A Channel holds all data related to an individual Discord channel.
@ -261,6 +264,11 @@ type Channel struct {
// nil if the channel has no pinned messages.
LastPinTimestamp *time.Time `json:"last_pin_timestamp"`
// An approximate count of messages in a thread, stops counting at 50
MessageCount int `json:"message_count"`
// An approximate count of users in a thread, stops counting at 50
MemberCount int `json:"member_count"`
// Whether the channel is marked as NSFW.
NSFW bool `json:"nsfw"`
@ -286,18 +294,26 @@ type Channel struct {
// The user limit of the voice channel.
UserLimit int `json:"user_limit"`
// The ID of the parent channel, if the channel is under a category
// The ID of the parent channel, if the channel is under a category. For threads - id of the channel thread was created in.
ParentID string `json:"parent_id"`
// Amount of seconds a user has to wait before sending another message (0-21600)
// Amount of seconds a user has to wait before sending another message or creating another thread (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 creator of the group DM or 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"`
// Thread-specific fields not needed by other channels
ThreadMetadata *ThreadMetadata `json:"thread_metadata,omitempty"`
// Thread member object for the current user, if they have joined the thread, only included on certain API endpoints
Member *ThreadMember `json:"thread_member"`
// All thread members. State channels only.
Members []*ThreadMember `json:"-"`
}
// Mention returns a string which mentions the channel
@ -305,6 +321,11 @@ func (c *Channel) Mention() string {
return fmt.Sprintf("<#%s>", c.ID)
}
// IsThread is a helper function to determine if channel is a thread or not
func (c *Channel) IsThread() bool {
return c.Type == ChannelTypeGuildPublicThread || c.Type == ChannelTypeGuildPrivateThread || c.Type == ChannelTypeGuildNewsThread
}
// A ChannelEdit holds Channel Field data for a channel edit.
type ChannelEdit struct {
Name string `json:"name,omitempty"`
@ -316,6 +337,13 @@ type ChannelEdit struct {
PermissionOverwrites []*PermissionOverwrite `json:"permission_overwrites,omitempty"`
ParentID string `json:"parent_id,omitempty"`
RateLimitPerUser int `json:"rate_limit_per_user,omitempty"`
// NOTE: threads only
Archived bool `json:"archived,omitempty"`
AutoArchiveDuration int `json:"auto_archive_duration,omitempty"`
Locked bool `json:"locked,bool"`
Invitable bool `json:"invitable,omitempty"`
}
// A ChannelFollow holds data returned after following a news channel
@ -342,6 +370,56 @@ type PermissionOverwrite struct {
Allow int64 `json:"allow,string"`
}
// ThreadStart stores all parameters you can use with MessageThreadStartComplex or ThreadStartComplex
type ThreadStart struct {
Name string `json:"name"`
AutoArchiveDuration int `json:"auto_archive_duration,omitempty"`
Type ChannelType `json:"type,omitempty"`
Invitable bool `json:"invitable"`
RateLimitPerUser int `json:"rate_limit_per_user,omitempty"`
}
// ThreadMetadata contains a number of thread-specific channel fields that are not needed by other channel types.
type ThreadMetadata struct {
// Whether the thread is archived
Archived bool `json:"archived"`
// Duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080
AutoArchiveDuration int `json:"auto_archive_duration"`
// Timestamp when the thread's archive status was last changed, used for calculating recent activity
ArchiveTimestamp time.Time `json:"archive_timestamp"`
// Whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it
Locked bool `json:"locked"`
// Whether non-moderators can add other non-moderators to a thread; only available on private threads
Invitable bool `json:"invitable"`
}
// ThreadMember is used to indicate whether a user has joined a thread or not.
// NOTE: ID and UserID are empty (omitted) on the member sent within each thread in the GUILD_CREATE event.
type ThreadMember struct {
// The id of the thread
ID string `json:"id,omitempty"`
// The id of the user
UserID string `json:"user_id,omitempty"`
// The time the current user last joined the thread
JoinTimestamp time.Time `json:"join_timestamp"`
// Any user-thread settings, currently only used for notifications
Flags int
}
// ThreadsList represents a list of threads alongisde with thread member objects for the current user.
type ThreadsList struct {
Threads []*Channel `json:"threads"`
Members []*ThreadMember `json:"members"`
HasMore bool `json:"has_more"`
}
// AddedThreadMember holds information about the user who was added to the thread
type AddedThreadMember struct {
*ThreadMember
Member *Member `json:"member"`
Presence *Presence `json:"presence"`
}
// Emoji struct holds data related to Emoji's
type Emoji struct {
ID string `json:"id"`
@ -507,6 +585,11 @@ type Guild struct {
// update events, and thus is only present in state-cached guilds.
Channels []*Channel `json:"channels"`
// A list of all active threads in the guild that current user has permission to view
// 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.
@ -1367,16 +1450,20 @@ type IdentifyProperties struct {
// Constants for the different bit offsets of text channel permissions
const (
// Deprecated: PermissionReadMessages has been replaced with PermissionViewChannel for text and voice channels
PermissionReadMessages = 0x0000000000000400
PermissionSendMessages = 0x0000000000000800
PermissionSendTTSMessages = 0x0000000000001000
PermissionManageMessages = 0x0000000000002000
PermissionEmbedLinks = 0x0000000000004000
PermissionAttachFiles = 0x0000000000008000
PermissionReadMessageHistory = 0x0000000000010000
PermissionMentionEveryone = 0x0000000000020000
PermissionUseExternalEmojis = 0x0000000000040000
PermissionUseSlashCommands = 0x0000000080000000
PermissionReadMessages = 0x0000000000000400
PermissionSendMessages = 0x0000000000000800
PermissionSendTTSMessages = 0x0000000000001000
PermissionManageMessages = 0x0000000000002000
PermissionEmbedLinks = 0x0000000000004000
PermissionAttachFiles = 0x0000000000008000
PermissionReadMessageHistory = 0x0000000000010000
PermissionMentionEveryone = 0x0000000000020000
PermissionUseExternalEmojis = 0x0000000000040000
PermissionUseSlashCommands = 0x0000000080000000
PermissionManageThreads = 0x0000000400000000
PermissionCreatePublicThreads = 0x0000000800000000
PermissionCreatePrivateThreads = 0x0000001000000000
PermissionSendMessagesInThreads = 0x0000004000000000
)
// Constants for the different bit offsets of voice permissions