Browse Source

Add Guild Scheduled Event Support (#1032)

* Add Guild Scheduled Events support

* Add missing Indents for Guild Scheduled Events

* fix: Do update from new schedules updates and repository updates

* doc: Add missing documentation on const

* doc: Add missing documentation on events struct

* tests: Add a Skip condition when dgBot is not set to prevent segfault

* fix: Somes changes following the last review steps

* docs: Add an example to manipulate GuildScheduledEvent

* clean: Remove useless pointers on struct used to retrieve data

* tests: Test extra query params on GuildScheduledEventUsers requests

* clean: Remove unused variables

* feat: Add nullable types to provide null value to Discord API when is necessary

* feat: Use NullableString in ScheduledEvents

* docs: Add example for usage of NullableString

* Update structs.go

Co-authored-by: Fedor Lapshin <fe.lap.prog@gmail.com>

* Update restapi.go

Co-authored-by: Fedor Lapshin <fe.lap.prog@gmail.com>

* fix: Review changes to move back nullable string into a simple MarshalJSON

* fix: Remove NullString on tests and examples

* doc: Add missing doc

* Update structs.go

Co-authored-by: Fedor Lapshin <fe.lap.prog@gmail.com>

* fix: misunderstood MarhsalJSON

* fix: Follow the convention of discordgo on url.Values

* Update examples/scheduled_events/main.go

Co-authored-by: Fedor Lapshin <fe.lap.prog@gmail.com>

* changes: use conditional instead on Sprintf

* fix: Add missing status on Params

* Update structs.go

Co-authored-by: Fedor Lapshin <fe.lap.prog@gmail.com>

* Update structs.go

Co-authored-by: Fedor Lapshin <fe.lap.prog@gmail.com>

* changes: Move flag.Parse inside the init function

* fix: remove null statement of test suite

* fix: Rewrite Marshal of GuildScheduledEventParams to prevent a stack overflow on marshall same type

* clean: Remove unused Intents

* Update restapi.go

Co-authored-by: Fedor Lapshin <fe.lap.prog@gmail.com>

* Update restapi.go

Co-authored-by: Fedor Lapshin <fe.lap.prog@gmail.com>

* Update restapi.go

Co-authored-by: Fedor Lapshin <fe.lap.prog@gmail.com>

* doc: polish the documentation

* clean: Final polish code

* doc: Add information about 1:1 usage

* Update discord_test.go

Co-authored-by: Fedor Lapshin <fe.lap.prog@gmail.com>

* doc: remove unnecessary additional infos

* Update structs.go

Co-authored-by: Fedor Lapshin <fe.lap.prog@gmail.com>

* Update discord_test.go

Co-authored-by: Fedor Lapshin <fe.lap.prog@gmail.com>

* Update restapi.go

Co-authored-by: Fedor Lapshin <fe.lap.prog@gmail.com>

* chore(examples/scheduled_events): removed NullString comment

* fix(structs): grammar in comment to EntityType

* fix: run gofmt

Co-authored-by: Fedor Lapshin <fe.lap.prog@gmail.com>
42Atomys 2 years ago
parent
commit
9448b0eb96
8 changed files with 640 additions and 92 deletions
  1. 122 5
      discord_test.go
  2. 35 32
      endpoints.go
  3. 125 53
      eventhandlers.go
  4. 15 0
      events.go
  5. 84 0
      examples/scheduled_events/main.go
  6. 2 1
      examples/threads/main.go
  7. 112 0
      restapi.go
  8. 145 1
      structs.go

+ 122 - 5
discord_test.go

@@ -15,11 +15,12 @@ var (
 	dg    *Session // Stores a global discordgo user session
 	dgBot *Session // Stores a global discordgo bot session
 
-	envOAuth2Token = os.Getenv("DG_OAUTH2_TOKEN") // Token to use when authenticating using OAuth2 token
-	envBotToken    = os.Getenv("DGB_TOKEN")       // Token to use when authenticating the bot account
-	envGuild       = os.Getenv("DG_GUILD")        // Guild ID to use for tests
-	envChannel     = os.Getenv("DG_CHANNEL")      // Channel ID to use for tests
-	envAdmin       = os.Getenv("DG_ADMIN")        // User ID of admin user to use for tests
+	envOAuth2Token  = os.Getenv("DG_OAUTH2_TOKEN")  // Token to use when authenticating using OAuth2 token
+	envBotToken     = os.Getenv("DGB_TOKEN")        // Token to use when authenticating the bot account
+	envGuild        = os.Getenv("DG_GUILD")         // Guild ID to use for tests
+	envChannel      = os.Getenv("DG_CHANNEL")       // Channel ID to use for tests
+	envVoiceChannel = os.Getenv("DG_VOICE_CHANNEL") // Channel ID to use for tests
+	envAdmin        = os.Getenv("DG_ADMIN")         // User ID of admin user to use for tests
 )
 
 func TestMain(m *testing.M) {
@@ -183,3 +184,119 @@ func TestRemoveHandler(t *testing.T) {
 		t.Fatalf("testHandler was not called once.")
 	}
 }
+
+func TestScheduledEvents(t *testing.T) {
+	if dgBot == nil {
+		t.Skip("Skipping, dgBot not set.")
+	}
+
+	beginAt := time.Now().Add(1 * time.Hour)
+	endAt := time.Now().Add(2 * time.Hour)
+	event, err := dgBot.GuildScheduledEventCreate(envGuild, &GuildScheduledEventParams{
+		Name:               "Test Event",
+		PrivacyLevel:       GuildScheduledEventPrivacyLevelGuildOnly,
+		ScheduledStartTime: &beginAt,
+		ScheduledEndTime:   &endAt,
+		Description:        "Awesome Test Event created on livestream",
+		EntityType:         GuildScheduledEventEntityTypeExternal,
+		EntityMetadata: &GuildScheduledEventEntityMetadata{
+			Location: "https://discord.com",
+		},
+	})
+	defer dgBot.GuildScheduledEventDelete(envGuild, event.ID)
+
+	if err != nil || event.Name != "Test Event" {
+		t.Fatal(err)
+	}
+
+	events, err := dgBot.GuildScheduledEvents(envGuild, true)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var foundEvent *GuildScheduledEvent
+	for _, e := range events {
+		if e.ID == event.ID {
+			foundEvent = e
+			break
+		}
+	}
+	if foundEvent.Name != event.Name {
+		t.Fatal("err on GuildScheduledEvents endpoint. Missing Scheduled Event")
+	}
+
+	getEvent, err := dgBot.GuildScheduledEvent(envGuild, event.ID, true)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if getEvent.Name != event.Name {
+		t.Fatal("err on GuildScheduledEvent endpoint. Mismatched Scheduled Event")
+	}
+
+	eventUpdated, err := dgBot.GuildScheduledEventEdit(envGuild, event.ID, &GuildScheduledEventParams{Name: "Test Event Updated"})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if eventUpdated.Name != "Test Event Updated" {
+		t.Fatal("err on GuildScheduledEventUpdate endpoint. Scheduled Event Name mismatch")
+	}
+
+	// Usage of 1 and 1 is just the pseudo data with the purpose to run all branches in the function without crashes.
+	// see https://github.com/bwmarrin/discordgo/pull/1032#discussion_r815438303 for more details.
+	users, err := dgBot.GuildScheduledEventUsers(envGuild, event.ID, 1, true, "1", "1")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(users) != 0 {
+		t.Fatal("err on GuildScheduledEventUsers. Mismatch of event maybe occured")
+	}
+
+	err = dgBot.GuildScheduledEventDelete(envGuild, event.ID)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestComplexScheduledEvents(t *testing.T) {
+	if dgBot == nil {
+		t.Skip("Skipping, dgBot not set.")
+	}
+
+	beginAt := time.Now().Add(1 * time.Hour)
+	endAt := time.Now().Add(2 * time.Hour)
+	event, err := dgBot.GuildScheduledEventCreate(envGuild, &GuildScheduledEventParams{
+		Name:               "Test Voice Event",
+		PrivacyLevel:       GuildScheduledEventPrivacyLevelGuildOnly,
+		ScheduledStartTime: &beginAt,
+		ScheduledEndTime:   &endAt,
+		Description:        "Test event on voice channel",
+		EntityType:         GuildScheduledEventEntityTypeVoice,
+		ChannelID:          envVoiceChannel,
+	})
+	if err != nil || event.Name != "Test Voice Event" {
+		t.Fatal(err)
+	}
+	defer dgBot.GuildScheduledEventDelete(envGuild, event.ID)
+
+	_, err = dgBot.GuildScheduledEventEdit(envGuild, event.ID, &GuildScheduledEventParams{
+		EntityType: GuildScheduledEventEntityTypeExternal,
+		EntityMetadata: &GuildScheduledEventEntityMetadata{
+			Location: "https://discord.com",
+		},
+	})
+
+	if err != nil {
+		t.Fatal("err on GuildScheduledEventEdit. Change of entity type to external failed")
+	}
+
+	_, err = dgBot.GuildScheduledEventEdit(envGuild, event.ID, &GuildScheduledEventParams{
+		ChannelID:      envVoiceChannel,
+		EntityType:     GuildScheduledEventEntityTypeVoice,
+		EntityMetadata: nil,
+	})
+
+	if err != nil {
+		t.Fatal("err on GuildScheduledEventEdit. Change of entity type to voice failed")
+	}
+}

+ 35 - 32
endpoints.go

@@ -60,38 +60,41 @@ var (
 	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 }
-	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 }
-	EndpointGuildTemplate      = func(tID string) string { return EndpointGuilds + "/templates/" + tID }
-	EndpointGuildTemplates     = func(gID string) string { return EndpointGuilds + gID + "/templates" }
-	EndpointGuildTemplateSync  = func(gID, tID string) string { return EndpointGuilds + gID + "/templates/" + tID }
-	EndpointGuildMemberAvatar  = func(gId, uID, aID string) string {
+	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 }
+	EndpointGuildScheduledEvents     = func(gID string) string { return EndpointGuilds + gID + "/scheduled-events" }
+	EndpointGuildScheduledEvent      = func(gID, eID string) string { return EndpointGuilds + gID + "/scheduled-events/" + eID }
+	EndpointGuildScheduledEventUsers = func(gID, eID string) string { return EndpointGuildScheduledEvent(gID, eID) + "/users" }
+	EndpointGuildTemplate            = func(tID string) string { return EndpointGuilds + "/templates/" + tID }
+	EndpointGuildTemplates           = func(gID string) string { return EndpointGuilds + gID + "/templates" }
+	EndpointGuildTemplateSync        = func(gID, tID string) string { return EndpointGuilds + gID + "/templates/" + tID }
+	EndpointGuildMemberAvatar        = func(gId, uID, aID string) string {
 		return EndpointCDNGuilds + gId + "/users/" + uID + "/avatars/" + aID + ".png"
 	}
 	EndpointGuildMemberAvatarAnimated = func(gId, uID, aID string) string {

+ 125 - 53
eventhandlers.go

@@ -7,59 +7,62 @@ package discordgo
 // Event type values are used to match the events returned by Discord.
 // EventTypes surrounded by __ are synthetic and are internal to DiscordGo.
 const (
-	channelCreateEventType            = "CHANNEL_CREATE"
-	channelDeleteEventType            = "CHANNEL_DELETE"
-	channelPinsUpdateEventType        = "CHANNEL_PINS_UPDATE"
-	channelUpdateEventType            = "CHANNEL_UPDATE"
-	connectEventType                  = "__CONNECT__"
-	disconnectEventType               = "__DISCONNECT__"
-	eventEventType                    = "__EVENT__"
-	guildBanAddEventType              = "GUILD_BAN_ADD"
-	guildBanRemoveEventType           = "GUILD_BAN_REMOVE"
-	guildCreateEventType              = "GUILD_CREATE"
-	guildDeleteEventType              = "GUILD_DELETE"
-	guildEmojisUpdateEventType        = "GUILD_EMOJIS_UPDATE"
-	guildIntegrationsUpdateEventType  = "GUILD_INTEGRATIONS_UPDATE"
-	guildMemberAddEventType           = "GUILD_MEMBER_ADD"
-	guildMemberRemoveEventType        = "GUILD_MEMBER_REMOVE"
-	guildMemberUpdateEventType        = "GUILD_MEMBER_UPDATE"
-	guildMembersChunkEventType        = "GUILD_MEMBERS_CHUNK"
-	guildRoleCreateEventType          = "GUILD_ROLE_CREATE"
-	guildRoleDeleteEventType          = "GUILD_ROLE_DELETE"
-	guildRoleUpdateEventType          = "GUILD_ROLE_UPDATE"
-	guildUpdateEventType              = "GUILD_UPDATE"
-	interactionCreateEventType        = "INTERACTION_CREATE"
-	inviteCreateEventType             = "INVITE_CREATE"
-	inviteDeleteEventType             = "INVITE_DELETE"
-	messageAckEventType               = "MESSAGE_ACK"
-	messageCreateEventType            = "MESSAGE_CREATE"
-	messageDeleteEventType            = "MESSAGE_DELETE"
-	messageDeleteBulkEventType        = "MESSAGE_DELETE_BULK"
-	messageReactionAddEventType       = "MESSAGE_REACTION_ADD"
-	messageReactionRemoveEventType    = "MESSAGE_REACTION_REMOVE"
-	messageReactionRemoveAllEventType = "MESSAGE_REACTION_REMOVE_ALL"
-	messageUpdateEventType            = "MESSAGE_UPDATE"
-	presenceUpdateEventType           = "PRESENCE_UPDATE"
-	presencesReplaceEventType         = "PRESENCES_REPLACE"
-	rateLimitEventType                = "__RATE_LIMIT__"
-	readyEventType                    = "READY"
-	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"
-	userSettingsUpdateEventType       = "USER_SETTINGS_UPDATE"
-	userUpdateEventType               = "USER_UPDATE"
-	voiceServerUpdateEventType        = "VOICE_SERVER_UPDATE"
-	voiceStateUpdateEventType         = "VOICE_STATE_UPDATE"
-	webhooksUpdateEventType           = "WEBHOOKS_UPDATE"
+	channelCreateEventType             = "CHANNEL_CREATE"
+	channelDeleteEventType             = "CHANNEL_DELETE"
+	channelPinsUpdateEventType         = "CHANNEL_PINS_UPDATE"
+	channelUpdateEventType             = "CHANNEL_UPDATE"
+	connectEventType                   = "__CONNECT__"
+	disconnectEventType                = "__DISCONNECT__"
+	eventEventType                     = "__EVENT__"
+	guildBanAddEventType               = "GUILD_BAN_ADD"
+	guildBanRemoveEventType            = "GUILD_BAN_REMOVE"
+	guildCreateEventType               = "GUILD_CREATE"
+	guildDeleteEventType               = "GUILD_DELETE"
+	guildEmojisUpdateEventType         = "GUILD_EMOJIS_UPDATE"
+	guildIntegrationsUpdateEventType   = "GUILD_INTEGRATIONS_UPDATE"
+	guildMemberAddEventType            = "GUILD_MEMBER_ADD"
+	guildMemberRemoveEventType         = "GUILD_MEMBER_REMOVE"
+	guildMemberUpdateEventType         = "GUILD_MEMBER_UPDATE"
+	guildMembersChunkEventType         = "GUILD_MEMBERS_CHUNK"
+	guildRoleCreateEventType           = "GUILD_ROLE_CREATE"
+	guildRoleDeleteEventType           = "GUILD_ROLE_DELETE"
+	guildRoleUpdateEventType           = "GUILD_ROLE_UPDATE"
+	guildUpdateEventType               = "GUILD_UPDATE"
+	guildScheduledEventCreateEventType = "GUILD_SCHEDULED_EVENT_CREATE"
+	guildScheduledEventUpdateEventType = "GUILD_SCHEDULED_EVENT_UPDATE"
+	guildScheduledEventDeleteEventType = "GUILD_SCHEDULED_EVENT_DELETE"
+	interactionCreateEventType         = "INTERACTION_CREATE"
+	inviteCreateEventType              = "INVITE_CREATE"
+	inviteDeleteEventType              = "INVITE_DELETE"
+	messageAckEventType                = "MESSAGE_ACK"
+	messageCreateEventType             = "MESSAGE_CREATE"
+	messageDeleteEventType             = "MESSAGE_DELETE"
+	messageDeleteBulkEventType         = "MESSAGE_DELETE_BULK"
+	messageReactionAddEventType        = "MESSAGE_REACTION_ADD"
+	messageReactionRemoveEventType     = "MESSAGE_REACTION_REMOVE"
+	messageReactionRemoveAllEventType  = "MESSAGE_REACTION_REMOVE_ALL"
+	messageUpdateEventType             = "MESSAGE_UPDATE"
+	presenceUpdateEventType            = "PRESENCE_UPDATE"
+	presencesReplaceEventType          = "PRESENCES_REPLACE"
+	rateLimitEventType                 = "__RATE_LIMIT__"
+	readyEventType                     = "READY"
+	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"
+	userSettingsUpdateEventType        = "USER_SETTINGS_UPDATE"
+	userUpdateEventType                = "USER_UPDATE"
+	voiceServerUpdateEventType         = "VOICE_SERVER_UPDATE"
+	voiceStateUpdateEventType          = "VOICE_STATE_UPDATE"
+	webhooksUpdateEventType            = "WEBHOOKS_UPDATE"
 )
 
 // channelCreateEventHandler is an event handler for ChannelCreate events.
@@ -307,6 +310,66 @@ func (eh guildIntegrationsUpdateEventHandler) Handle(s *Session, i interface{})
 	}
 }
 
+// guildScheduledEventCreateEventHandler is an event handler for GuildScheduledEventCreate events.
+type guildScheduledEventCreateEventHandler func(*Session, *GuildScheduledEventCreate)
+
+// Type returns the event type for GuildScheduledEventCreate events.
+func (eh guildScheduledEventCreateEventHandler) Type() string {
+	return guildScheduledEventCreateEventType
+}
+
+// New returns a new instance of GuildScheduledEventCreate.
+func (eh guildScheduledEventCreateEventHandler) New() interface{} {
+	return &GuildScheduledEventCreate{}
+}
+
+// Handle is the handler for GuildScheduledEventCreate events.
+func (eh guildScheduledEventCreateEventHandler) Handle(s *Session, i interface{}) {
+	if t, ok := i.(*GuildScheduledEventCreate); ok {
+		eh(s, t)
+	}
+}
+
+// guildScheduledEventUpdateEventHandler is an event handler for GuildScheduledEventUpdate events.
+type guildScheduledEventUpdateEventHandler func(*Session, *GuildScheduledEventUpdate)
+
+// Type returns the event type for GuildScheduledEventUpdate events.
+func (eh guildScheduledEventUpdateEventHandler) Type() string {
+	return guildScheduledEventUpdateEventType
+}
+
+// New returns a new instance of GuildScheduledEventUpdate.
+func (eh guildScheduledEventUpdateEventHandler) New() interface{} {
+	return &GuildScheduledEventUpdate{}
+}
+
+// Handle is the handler for GuildScheduledEventUpdate events.
+func (eh guildScheduledEventUpdateEventHandler) Handle(s *Session, i interface{}) {
+	if t, ok := i.(*GuildScheduledEventUpdate); ok {
+		eh(s, t)
+	}
+}
+
+// guildScheduledEventDeleteEventHandler is an event handler for GuildScheduledEventDelete events.
+type guildScheduledEventDeleteEventHandler func(*Session, *GuildScheduledEventDelete)
+
+// Type returns the event type for GuildScheduledEventDelete events.
+func (eh guildScheduledEventDeleteEventHandler) Type() string {
+	return guildScheduledEventDeleteEventType
+}
+
+// New returns a new instance of GuildScheduledEventDelete.
+func (eh guildScheduledEventDeleteEventHandler) New() interface{} {
+	return &GuildScheduledEventDelete{}
+}
+
+// Handle is the handler for GuildScheduledEventDelete events.
+func (eh guildScheduledEventDeleteEventHandler) Handle(s *Session, i interface{}) {
+	if t, ok := i.(*GuildScheduledEventDelete); ok {
+		eh(s, t)
+	}
+}
+
 // guildMemberAddEventHandler is an event handler for GuildMemberAdd events.
 type guildMemberAddEventHandler func(*Session, *GuildMemberAdd)
 
@@ -1132,6 +1195,12 @@ func handlerForInterface(handler interface{}) EventHandler {
 		return guildEmojisUpdateEventHandler(v)
 	case func(*Session, *GuildIntegrationsUpdate):
 		return guildIntegrationsUpdateEventHandler(v)
+	case func(*Session, *GuildScheduledEventCreate):
+		return guildScheduledEventCreateEventHandler(v)
+	case func(*Session, *GuildScheduledEventUpdate):
+		return guildScheduledEventUpdateEventHandler(v)
+	case func(*Session, *GuildScheduledEventDelete):
+		return guildScheduledEventDeleteEventHandler(v)
 	case func(*Session, *GuildMemberAdd):
 		return guildMemberAddEventHandler(v)
 	case func(*Session, *GuildMemberRemove):
@@ -1228,6 +1297,9 @@ func init() {
 	registerInterfaceProvider(guildDeleteEventHandler(nil))
 	registerInterfaceProvider(guildEmojisUpdateEventHandler(nil))
 	registerInterfaceProvider(guildIntegrationsUpdateEventHandler(nil))
+	registerInterfaceProvider(guildScheduledEventCreateEventHandler(nil))
+	registerInterfaceProvider(guildScheduledEventUpdateEventHandler(nil))
+	registerInterfaceProvider(guildScheduledEventDeleteEventHandler(nil))
 	registerInterfaceProvider(guildMemberAddEventHandler(nil))
 	registerInterfaceProvider(guildMemberRemoveEventHandler(nil))
 	registerInterfaceProvider(guildMemberUpdateEventHandler(nil))

+ 15 - 0
events.go

@@ -199,6 +199,21 @@ type GuildIntegrationsUpdate struct {
 	GuildID string `json:"guild_id"`
 }
 
+// GuildScheduledEventCreate is the data for a GuildScheduledEventCreate event.
+type GuildScheduledEventCreate struct {
+	*GuildScheduledEvent
+}
+
+// GuildScheduledEventUpdate is the data for a GuildScheduledEventUpdate event.
+type GuildScheduledEventUpdate struct {
+	*GuildScheduledEvent
+}
+
+// GuildScheduledEventDelete is the data for a GuildScheduledEventDelete event.
+type GuildScheduledEventDelete struct {
+	*GuildScheduledEvent
+}
+
 // MessageAck is the data for a MessageAck event.
 type MessageAck struct {
 	MessageID string `json:"message_id"`

+ 84 - 0
examples/scheduled_events/main.go

@@ -0,0 +1,84 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"log"
+	"os"
+	"os/signal"
+	"time"
+
+	"github.com/bwmarrin/discordgo"
+)
+
+// Flags
+var (
+	GuildID        = flag.String("guild", "", "Test guild ID")
+	VoiceChannelID = flag.String("voice", "", "Test voice channel ID")
+	BotToken       = flag.String("token", "", "Bot token")
+)
+
+func init() { flag.Parse() }
+
+func main() {
+	s, _ := discordgo.New("Bot " + *BotToken)
+	s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) {
+		fmt.Println("Bot is ready")
+	})
+
+	err := s.Open()
+	if err != nil {
+		log.Fatalf("Cannot open the session: %v", err)
+	}
+	defer s.Close()
+
+	event := createAmazingEvent(s)
+	transformEventToExternalEvent(s, event)
+
+	stop := make(chan os.Signal, 1)
+	signal.Notify(stop, os.Interrupt)
+	<-stop
+	log.Println("Graceful shutdown")
+
+}
+
+// Create a new event on guild
+func createAmazingEvent(s *discordgo.Session) *discordgo.GuildScheduledEvent {
+	// Define the starting time (must be in future)
+	startingTime := time.Now().Add(1 * time.Hour)
+	// Define the ending time (must be after starting time)
+	endingTime := startingTime.Add(30 * time.Minute)
+	// Create the event
+	scheduledEvent, err := s.GuildScheduledEventCreate(*GuildID, &discordgo.GuildScheduledEventParams{
+		Name:               "Amazing Event",
+		Description:        "This event will start in 1 hour and last 30 minutes",
+		ScheduledStartTime: &startingTime,
+		ScheduledEndTime:   &endingTime,
+		EntityType:         discordgo.GuildScheduledEventEntityTypeVoice,
+		ChannelID:          *VoiceChannelID,
+		PrivacyLevel:       discordgo.GuildScheduledEventPrivacyLevelGuildOnly,
+	})
+	if err != nil {
+		log.Printf("Error creating scheduled event: %v", err)
+		return nil
+	}
+
+	fmt.Println("Created scheduled event:", scheduledEvent.Name)
+	return scheduledEvent
+}
+
+func transformEventToExternalEvent(s *discordgo.Session, event *discordgo.GuildScheduledEvent) {
+	scheduledEvent, err := s.GuildScheduledEventEdit(*GuildID, event.ID, &discordgo.GuildScheduledEventParams{
+		Name:       "Amazing Event @ Discord Website",
+		EntityType: discordgo.GuildScheduledEventEntityTypeExternal,
+		EntityMetadata: &discordgo.GuildScheduledEventEntityMetadata{
+			Location: "https://discord.com",
+		},
+	})
+	if err != nil {
+		log.Printf("Error during transformation of scheduled voice event into external event: %v", err)
+		return
+	}
+
+	fmt.Println("Created scheduled event:", scheduledEvent.Name)
+}

+ 2 - 1
examples/threads/main.go

@@ -21,8 +21,9 @@ const timeout time.Duration = time.Second * 10
 
 var games map[string]time.Time = make(map[string]time.Time)
 
+func init() { flag.Parse() }
+
 func main() {
-	flag.Parse()
 	s, _ := discordgo.New("Bot " + *BotToken)
 	s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) {
 		fmt.Println("Bot is ready")

+ 112 - 0
restapi.go

@@ -2757,3 +2757,115 @@ func (s *Session) FollowupMessageEdit(appID string, interaction *Interaction, me
 func (s *Session) FollowupMessageDelete(appID string, interaction *Interaction, messageID string) error {
 	return s.WebhookMessageDelete(appID, interaction.Token, messageID)
 }
+
+// ------------------------------------------------------------------------------------------------
+// Functions specific to guilds scheduled events
+// ------------------------------------------------------------------------------------------------
+
+// GuildScheduledEvents returns an array of GuildScheduledEvent for a guild
+// guildID        : The ID of a Guild
+// userCount      : Whether to include the user count in the response
+func (s *Session) GuildScheduledEvents(guildID string, userCount bool) (st []*GuildScheduledEvent, err error) {
+	uri := EndpointGuildScheduledEvents(guildID)
+	if userCount {
+		uri += "?with_user_count=true"
+	}
+
+	body, err := s.RequestWithBucketID("GET", uri, nil, EndpointGuildScheduledEvents(guildID))
+	if err != nil {
+		return
+	}
+
+	err = unmarshal(body, &st)
+	return
+}
+
+// GuildScheduledEvent returns a specific GuildScheduledEvent in a guild
+// guildID        : The ID of a Guild
+// eventID        : The ID of the event
+// userCount      : Whether to include the user count in the response
+func (s *Session) GuildScheduledEvent(guildID, eventID string, userCount bool) (st *GuildScheduledEvent, err error) {
+	uri := EndpointGuildScheduledEvent(guildID, eventID)
+	if userCount {
+		uri += "?with_user_count=true"
+	}
+
+	body, err := s.RequestWithBucketID("GET", uri, nil, EndpointGuildScheduledEvent(guildID, eventID))
+	if err != nil {
+		return
+	}
+
+	err = unmarshal(body, &st)
+	return
+}
+
+// GuildScheduledEventCreate creates a GuildScheduledEvent for a guild and returns it
+// guildID   : The ID of a Guild
+// eventID   : The ID of the event
+func (s *Session) GuildScheduledEventCreate(guildID string, event *GuildScheduledEventParams) (st *GuildScheduledEvent, err error) {
+	body, err := s.RequestWithBucketID("POST", EndpointGuildScheduledEvents(guildID), event, EndpointGuildScheduledEvents(guildID))
+	if err != nil {
+		return
+	}
+
+	err = unmarshal(body, &st)
+	return
+}
+
+// GuildScheduledEventEdit updates a specific event for a guild and returns it.
+// guildID   : The ID of a Guild
+// eventID   : The ID of the event
+func (s *Session) GuildScheduledEventEdit(guildID, eventID string, event *GuildScheduledEventParams) (st *GuildScheduledEvent, err error) {
+	body, err := s.RequestWithBucketID("PATCH", EndpointGuildScheduledEvent(guildID, eventID), event, EndpointGuildScheduledEvent(guildID, eventID))
+	if err != nil {
+		return
+	}
+
+	err = unmarshal(body, &st)
+	return
+}
+
+// GuildScheduledEventDelete deletes a specific GuildScheduledEvent in a guild
+// guildID   : The ID of a Guild
+// eventID   : The ID of the event
+func (s *Session) GuildScheduledEventDelete(guildID, eventID string) (err error) {
+	_, err = s.RequestWithBucketID("DELETE", EndpointGuildScheduledEvent(guildID, eventID), nil, EndpointGuildScheduledEvent(guildID, eventID))
+	return
+}
+
+// GuildScheduledEventUsers returns an array of GuildScheduledEventUser for a particular event in a guild
+// guildID    : The ID of a Guild
+// eventID    : The ID of the event
+// limit      : The maximum number of users to return (Max 100)
+// withMember : Whether to include the member object in the response
+// beforeID   : If is not empty all returned users entries will be before the given ID
+// afterID    : If is not empty all returned users entries will be after the given ID
+func (s *Session) GuildScheduledEventUsers(guildID, eventID string, limit int, withMember bool, beforeID, afterID string) (st []*GuildScheduledEventUser, err error) {
+	uri := EndpointGuildScheduledEventUsers(guildID, eventID)
+
+	queryParams := url.Values{}
+	if withMember {
+		queryParams.Set("with_member", "true")
+	}
+	if limit > 0 {
+		queryParams.Set("limit", strconv.Itoa(limit))
+	}
+	if beforeID != "" {
+		queryParams.Set("before", beforeID)
+	}
+	if afterID != "" {
+		queryParams.Set("after", afterID)
+	}
+
+	if len(queryParams) > 0 {
+		uri += "?" + queryParams.Encode()
+	}
+
+	body, err := s.RequestWithBucketID("GET", uri, nil, EndpointGuildScheduledEventUsers(guildID, eventID))
+	if err != nil {
+		return
+	}
+
+	err = unmarshal(body, &st)
+	return
+}

+ 145 - 1
structs.go

@@ -767,6 +767,149 @@ type GuildPreview struct {
 	Description string `json:"description"`
 }
 
+// GuildScheduledEvent is a representation of a scheduled event in a guild. Only for retrieval of the data.
+// https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event
+type GuildScheduledEvent struct {
+	// The ID of the scheduled event
+	ID string `json:"id"`
+	// The guild id which the scheduled event belongs to
+	GuildID string `json:"guild_id"`
+	// The channel id in which the scheduled event will be hosted, or null if scheduled entity type is EXTERNAL
+	ChannelID string `json:"channel_id"`
+	// The id of the user that created the scheduled event
+	CreatorID string `json:"creator_id"`
+	// The name of the scheduled event (1-100 characters)
+	Name string `json:"name"`
+	// The description of the scheduled event (1-1000 characters)
+	Description string `json:"description"`
+	// The time the scheduled event will start
+	ScheduledStartTime time.Time `json:"scheduled_start_time"`
+	// The time the scheduled event will end, required only when entity_type is EXTERNAL
+	ScheduledEndTime *time.Time `json:"scheduled_end_time"`
+	// The privacy level of the scheduled event
+	PrivacyLevel GuildScheduledEventPrivacyLevel `json:"privacy_level"`
+	// The status of the scheduled event
+	Status GuildScheduledEventStatus `json:"status"`
+	// Type of the entity where event would be hosted
+	// See field requirements
+	// https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-field-requirements-by-entity-type
+	EntityType GuildScheduledEventEntityType `json:"entity_type"`
+	// The id of an entity associated with a guild scheduled event
+	EntityID string `json:"entity_id"`
+	// Additional metadata for the guild scheduled event
+	EntityMetadata GuildScheduledEventEntityMetadata `json:"entity_metadata"`
+	// The user that created the scheduled event
+	Creator *User `json:"creator"`
+	// The number of users subscribed to the scheduled event
+	UserCount int `json:"user_count"`
+	// The cover image hash of the scheduled event
+	// see https://discord.com/developers/docs/reference#image-formatting for more
+	// information about image formatting
+	Image string `json:"image"`
+}
+
+// GuildScheduledEventParams are the parameters allowed for creating or updating a scheduled event
+// https://discord.com/developers/docs/resources/guild-scheduled-event#create-guild-scheduled-event
+type GuildScheduledEventParams struct {
+	// The channel id in which the scheduled event will be hosted, or null if scheduled entity type is EXTERNAL
+	ChannelID string `json:"channel_id,omitempty"`
+	// The name of the scheduled event (1-100 characters)
+	Name string `json:"name,omitempty"`
+	// The description of the scheduled event (1-1000 characters)
+	Description string `json:"description,omitempty"`
+	// The time the scheduled event will start
+	ScheduledStartTime *time.Time `json:"scheduled_start_time,omitempty"`
+	// The time the scheduled event will end, required only when entity_type is EXTERNAL
+	ScheduledEndTime *time.Time `json:"scheduled_end_time,omitempty"`
+	// The privacy level of the scheduled event
+	PrivacyLevel GuildScheduledEventPrivacyLevel `json:"privacy_level,omitempty"`
+	// The status of the scheduled event
+	Status GuildScheduledEventStatus `json:"status,omitempty"`
+	// Type of the entity where event would be hosted
+	// See field requirements
+	// https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-field-requirements-by-entity-type
+	EntityType GuildScheduledEventEntityType `json:"entity_type,omitempty"`
+	// Additional metadata for the guild scheduled event
+	EntityMetadata *GuildScheduledEventEntityMetadata `json:"entity_metadata,omitempty"`
+	// The cover image hash of the scheduled event
+	// see https://discord.com/developers/docs/reference#image-formatting for more
+	// information about image formatting
+	Image string `json:"image,omitempty"`
+}
+
+// MarshalJSON is a helper function to marshal GuildScheduledEventParams
+func (p GuildScheduledEventParams) MarshalJSON() ([]byte, error) {
+	type guildScheduledEventParams GuildScheduledEventParams
+
+	if p.EntityType == GuildScheduledEventEntityTypeExternal && p.ChannelID == "" {
+		return json.Marshal(struct {
+			guildScheduledEventParams
+			ChannelID json.RawMessage `json:"channel_id"`
+		}{
+			guildScheduledEventParams: guildScheduledEventParams(p),
+			ChannelID:                 json.RawMessage("null"),
+		})
+	}
+
+	return json.Marshal(guildScheduledEventParams(p))
+}
+
+// GuildScheduledEventEntityMetadata holds additional metadata for guild scheduled event.
+type GuildScheduledEventEntityMetadata struct {
+	// location of the event (1-100 characters)
+	// required for events with 'entity_type': EXTERNAL
+	Location string `json:"location"`
+}
+
+// GuildScheduledEventPrivacyLevel is the privacy level of a scheduled event.
+// https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-privacy-level
+type GuildScheduledEventPrivacyLevel int
+
+const (
+	// GuildScheduledEventPrivacyLevelGuildOnly makes the scheduled
+	// event is only accessible to guild members
+	GuildScheduledEventPrivacyLevelGuildOnly GuildScheduledEventPrivacyLevel = 2
+)
+
+// GuildScheduledEventStatus is the status of a scheduled event
+// Valid Guild Scheduled Event Status Transitions :
+// SCHEDULED --> ACTIVE --> COMPLETED
+// SCHEDULED --> CANCELED
+// https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-status
+type GuildScheduledEventStatus int
+
+const (
+	// GuildScheduledEventStatusScheduled represents the current event is in scheduled state
+	GuildScheduledEventStatusScheduled = 1
+	// GuildScheduledEventStatusActive represents the current event is in active state
+	GuildScheduledEventStatusActive = 2
+	// GuildScheduledEventStatusCompleted represents the current event is in completed state
+	GuildScheduledEventStatusCompleted = 3
+	// GuildScheduledEventStatusCanceled represents the current event is in canceled state
+	GuildScheduledEventStatusCanceled = 4
+)
+
+// GuildScheduledEventEntityType is the type of entity associated with a guild scheduled event.
+// https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-entity-types
+type GuildScheduledEventEntityType int
+
+const (
+	// GuildScheduledEventEntityTypeStageInstance represents a stage channel
+	GuildScheduledEventEntityTypeStageInstance = 1
+	// GuildScheduledEventEntityTypeVoice represents a voice channel
+	GuildScheduledEventEntityTypeVoice = 2
+	// GuildScheduledEventEntityTypeExternal represents an external event
+	GuildScheduledEventEntityTypeExternal = 3
+)
+
+// GuildScheduledEventUser is a user subscribed to a scheduled event.
+// https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-user-object
+type GuildScheduledEventUser struct {
+	GuildScheduledEventID string  `json:"guild_scheduled_event_id"`
+	User                  *User   `json:"user"`
+	Member                *Member `json:"member"`
+}
+
 // A GuildTemplate represents
 type GuildTemplate struct {
 	// The unique code for the guild template
@@ -1876,7 +2019,8 @@ const (
 		IntentDirectMessages |
 		IntentDirectMessageReactions |
 		IntentDirectMessageTyping |
-		IntentGuildScheduledEvents
+		IntentGuildScheduledEvents |
+		IntentsGuildScheduledEvents
 
 	IntentsAll = IntentsAllWithoutPrivileged |
 		IntentGuildMembers |