Преглед на файлове

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
Fedor Lapshin преди 2 години
родител
ревизия
992358e106
променени са 8 файла, в които са добавени 899 реда и са изтрити 121 реда
  1. 51 40
      endpoints.go
  2. 144 0
      eventhandlers.go
  3. 47 0
      events.go
  4. 73 0
      examples/threads/main.go
  5. 12 0
      message.go
  6. 247 6
      restapi.go
  7. 218 55
      state.go
  8. 107 20
      structs.go

+ 51 - 40
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 (
@@ -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 }
-
-	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" }
+	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 }
+	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" }
 

+ 144 - 0
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))

+ 47 - 0
events.go

@@ -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 - 0
examples/threads/main.go

@@ -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")
+
+}

+ 12 - 0
message.go

@@ -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"`
 }

+ 247 - 6
restapi.go

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

+ 218 - 55
state.go

@@ -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
-	}
-
-	guild, err := s.Guild(guildID)
-	if err != nil {
-		return err
+func (s *State) presenceAdd(guildID string, presence *Presence) error {
+	guild, ok := s.guildMap[guildID]
+	if !ok {
+		return ErrStateNotFound
 	}
 
-	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
-	}
-
-	guild, err := s.Guild(member.GuildID)
-	if err != nil {
-		return err
+func (s *State) memberAdd(member *Member) error {
+	guild, ok := s.guildMap[member.GuildID]
+	if !ok {
+		return ErrStateNotFound
 	}
 
-	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
+	}
 
-		s.Lock()
-		defer s.Unlock()
+	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
+			}
+		}
+	} 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)

+ 107 - 20
structs.go

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