From 9e0783c37f5d254d9eb0139f5054db484f13e26a Mon Sep 17 00:00:00 2001 From: Fedor Lapshin Date: Fri, 29 Apr 2022 00:23:07 +0300 Subject: [PATCH 01/33] Application commands: Permissions v2 (#1075) * feat(interactions): default command permissions * feat: add ApplicationCommandPermissionTypeChannel * feat: add deprecation and oauth2 scope comments * feat(interactions): add GuildAllChannelsID function * feat(examples/slash_commands): application command permissions * feat(events): add ApplicationCommandPermissionsUpdate * feat: add AuditLogActionApplicationCommandPermissionUpdate * feat(interactions#GuildAllChannelsID): use strconv instead of math/big * feat(interactions#GuildAllChannelsID): error handling * feat: cosmetic changes * fix(examples/slash_commands): handle error returned by GuildAllChannelsID * fix: typo * fix: typo --- eventhandlers.go | 275 ++++++++++++++++++-------------- events.go | 5 + examples/slash_commands/main.go | 89 ++++++++++- interactions.go | 23 ++- restapi.go | 4 + structs.go | 2 + 6 files changed, 273 insertions(+), 125 deletions(-) diff --git a/eventhandlers.go b/eventhandlers.go index d0e382f8a..f2ec080c3 100644 --- a/eventhandlers.go +++ b/eventhandlers.go @@ -7,69 +7,90 @@ 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" - guildStageInstanceCreateEventType = "STAGE_INSTANCE_CREATE" - guildStageInstanceUpdateEventType = "STAGE_INSTANCE_UPDATE" - guildStageInstanceDeleteEventType = "STAGE_INSTANCE_DELETE" - guildScheduledEventCreateEventType = "GUILD_SCHEDULED_EVENT_CREATE" - guildScheduledEventDeleteEventType = "GUILD_SCHEDULED_EVENT_DELETE" - guildScheduledEventUpdateEventType = "GUILD_SCHEDULED_EVENT_UPDATE" - guildScheduledEventUserAddEventType = "GUILD_SCHEDULED_EVENT_USER_ADD" - guildScheduledEventUserRemoveEventType = "GUILD_SCHEDULED_EVENT_USER_REMOVE" - 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" + applicationCommandPermissionsUpdateEventType = "APPLICATION_COMMAND_PERMISSIONS_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" + guildScheduledEventCreateEventType = "GUILD_SCHEDULED_EVENT_CREATE" + guildScheduledEventDeleteEventType = "GUILD_SCHEDULED_EVENT_DELETE" + guildScheduledEventUpdateEventType = "GUILD_SCHEDULED_EVENT_UPDATE" + guildScheduledEventUserAddEventType = "GUILD_SCHEDULED_EVENT_USER_ADD" + guildScheduledEventUserRemoveEventType = "GUILD_SCHEDULED_EVENT_USER_REMOVE" + 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" + stageInstanceEventCreateEventType = "STAGE_INSTANCE_EVENT_CREATE" + stageInstanceEventDeleteEventType = "STAGE_INSTANCE_EVENT_DELETE" + stageInstanceEventUpdateEventType = "STAGE_INSTANCE_EVENT_UPDATE" + 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" ) +// applicationCommandPermissionsUpdateEventHandler is an event handler for ApplicationCommandPermissionsUpdate events. +type applicationCommandPermissionsUpdateEventHandler func(*Session, *ApplicationCommandPermissionsUpdate) + +// Type returns the event type for ApplicationCommandPermissionsUpdate events. +func (eh applicationCommandPermissionsUpdateEventHandler) Type() string { + return applicationCommandPermissionsUpdateEventType +} + +// New returns a new instance of ApplicationCommandPermissionsUpdate. +func (eh applicationCommandPermissionsUpdateEventHandler) New() interface{} { + return &ApplicationCommandPermissionsUpdate{} +} + +// Handle is the handler for ApplicationCommandPermissionsUpdate events. +func (eh applicationCommandPermissionsUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*ApplicationCommandPermissionsUpdate); ok { + eh(s, t) + } +} + // channelCreateEventHandler is an event handler for ChannelCreate events. type channelCreateEventHandler func(*Session, *ChannelCreate) @@ -455,66 +476,6 @@ func (eh guildRoleUpdateEventHandler) Handle(s *Session, i interface{}) { } } -// guildStageInstanceEventCreateHandler is an event handler for StageInstanceEventCreate events. -type guildStageInstanceEventCreateHandler func(*Session, *StageInstanceEventCreate) - -// Type returns the event type for StageInstanceEventCreate events. -func (eh guildStageInstanceEventCreateHandler) Type() string { - return guildStageInstanceCreateEventType -} - -// New returns a new instance of StageInstanceEventCreate. -func (eh guildStageInstanceEventCreateHandler) New() interface{} { - return &StageInstanceEventCreate{} -} - -// Handle is the handler for StageInstanceEventCreate events. -func (eh guildStageInstanceEventCreateHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*StageInstanceEventCreate); ok { - eh(s, t) - } -} - -// guildStageInstanceEventUpdateHandler is an event handler for StageInstanceEventUpdate events. -type guildStageInstanceEventUpdateHandler func(*Session, *StageInstanceEventUpdate) - -// Type returns the event type for StageInstanceEventUpdate events. -func (eh guildStageInstanceEventUpdateHandler) Type() string { - return guildStageInstanceCreateEventType -} - -// New returns a new instance of StageInstanceEventUpdate. -func (eh guildStageInstanceEventUpdateHandler) New() interface{} { - return &StageInstanceEventUpdate{} -} - -// Handle is the handler for StageInstanceEventUpdate events. -func (eh guildStageInstanceEventUpdateHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*StageInstanceEventUpdate); ok { - eh(s, t) - } -} - -// guildStageInstanceEventDeleteHandler is an event handler for StageInstanceEventDelete events. -type guildStageInstanceEventDeleteHandler func(*Session, *StageInstanceEventDelete) - -// Type returns the event type for StageInstanceEventDelete events. -func (eh guildStageInstanceEventDeleteHandler) Type() string { - return guildStageInstanceCreateEventType -} - -// New returns a new instance of StageInstanceEventDelete. -func (eh guildStageInstanceEventDeleteHandler) New() interface{} { - return &StageInstanceEventDelete{} -} - -// Handle is the handler for StageInstanceEventDelete events. -func (eh guildStageInstanceEventDeleteHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*StageInstanceEventDelete); ok { - eh(s, t) - } -} - // guildScheduledEventCreateEventHandler is an event handler for GuildScheduledEventCreate events. type guildScheduledEventCreateEventHandler func(*Session, *GuildScheduledEventCreate) @@ -990,6 +951,66 @@ func (eh resumedEventHandler) Handle(s *Session, i interface{}) { } } +// stageInstanceEventCreateEventHandler is an event handler for StageInstanceEventCreate events. +type stageInstanceEventCreateEventHandler func(*Session, *StageInstanceEventCreate) + +// Type returns the event type for StageInstanceEventCreate events. +func (eh stageInstanceEventCreateEventHandler) Type() string { + return stageInstanceEventCreateEventType +} + +// New returns a new instance of StageInstanceEventCreate. +func (eh stageInstanceEventCreateEventHandler) New() interface{} { + return &StageInstanceEventCreate{} +} + +// Handle is the handler for StageInstanceEventCreate events. +func (eh stageInstanceEventCreateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*StageInstanceEventCreate); ok { + eh(s, t) + } +} + +// stageInstanceEventDeleteEventHandler is an event handler for StageInstanceEventDelete events. +type stageInstanceEventDeleteEventHandler func(*Session, *StageInstanceEventDelete) + +// Type returns the event type for StageInstanceEventDelete events. +func (eh stageInstanceEventDeleteEventHandler) Type() string { + return stageInstanceEventDeleteEventType +} + +// New returns a new instance of StageInstanceEventDelete. +func (eh stageInstanceEventDeleteEventHandler) New() interface{} { + return &StageInstanceEventDelete{} +} + +// Handle is the handler for StageInstanceEventDelete events. +func (eh stageInstanceEventDeleteEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*StageInstanceEventDelete); ok { + eh(s, t) + } +} + +// stageInstanceEventUpdateEventHandler is an event handler for StageInstanceEventUpdate events. +type stageInstanceEventUpdateEventHandler func(*Session, *StageInstanceEventUpdate) + +// Type returns the event type for StageInstanceEventUpdate events. +func (eh stageInstanceEventUpdateEventHandler) Type() string { + return stageInstanceEventUpdateEventType +} + +// New returns a new instance of StageInstanceEventUpdate. +func (eh stageInstanceEventUpdateEventHandler) New() interface{} { + return &StageInstanceEventUpdate{} +} + +// Handle is the handler for StageInstanceEventUpdate events. +func (eh stageInstanceEventUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*StageInstanceEventUpdate); ok { + eh(s, t) + } +} + // threadCreateEventHandler is an event handler for ThreadCreate events. type threadCreateEventHandler func(*Session, *ThreadCreate) @@ -1274,6 +1295,8 @@ func handlerForInterface(handler interface{}) EventHandler { switch v := handler.(type) { case func(*Session, interface{}): return interfaceEventHandler(v) + case func(*Session, *ApplicationCommandPermissionsUpdate): + return applicationCommandPermissionsUpdateEventHandler(v) case func(*Session, *ChannelCreate): return channelCreateEventHandler(v) case func(*Session, *ChannelDelete): @@ -1362,6 +1385,12 @@ func handlerForInterface(handler interface{}) EventHandler { return relationshipRemoveEventHandler(v) case func(*Session, *Resumed): return resumedEventHandler(v) + case func(*Session, *StageInstanceEventCreate): + return stageInstanceEventCreateEventHandler(v) + case func(*Session, *StageInstanceEventDelete): + return stageInstanceEventDeleteEventHandler(v) + case func(*Session, *StageInstanceEventUpdate): + return stageInstanceEventUpdateEventHandler(v) case func(*Session, *ThreadCreate): return threadCreateEventHandler(v) case func(*Session, *ThreadDelete): @@ -1396,6 +1425,7 @@ func handlerForInterface(handler interface{}) EventHandler { } func init() { + registerInterfaceProvider(applicationCommandPermissionsUpdateEventHandler(nil)) registerInterfaceProvider(channelCreateEventHandler(nil)) registerInterfaceProvider(channelDeleteEventHandler(nil)) registerInterfaceProvider(channelPinsUpdateEventHandler(nil)) @@ -1436,6 +1466,9 @@ func init() { registerInterfaceProvider(relationshipAddEventHandler(nil)) registerInterfaceProvider(relationshipRemoveEventHandler(nil)) registerInterfaceProvider(resumedEventHandler(nil)) + registerInterfaceProvider(stageInstanceEventCreateEventHandler(nil)) + registerInterfaceProvider(stageInstanceEventDeleteEventHandler(nil)) + registerInterfaceProvider(stageInstanceEventUpdateEventHandler(nil)) registerInterfaceProvider(threadCreateEventHandler(nil)) registerInterfaceProvider(threadDeleteEventHandler(nil)) registerInterfaceProvider(threadListSyncEventHandler(nil)) diff --git a/events.go b/events.go index c90aede1e..17c78281f 100644 --- a/events.go +++ b/events.go @@ -401,3 +401,8 @@ type InviteDelete struct { GuildID string `json:"guild_id"` Code string `json:"code"` } + +// ApplicationCommandPermissionsUpdate is the data for an ApplicationCommandPermissionsUpdate event +type ApplicationCommandPermissionsUpdate struct { + *GuildApplicationCommandPermissions +} diff --git a/examples/slash_commands/main.go b/examples/slash_commands/main.go index b464a4795..9f9a6ce38 100644 --- a/examples/slash_commands/main.go +++ b/examples/slash_commands/main.go @@ -1,6 +1,7 @@ package main import ( + "errors" "flag" "fmt" "log" @@ -32,7 +33,9 @@ func init() { } var ( - integerOptionMinValue = 1.0 + integerOptionMinValue = 1.0 + dmPermission = false + defaultMemberPermissions int64 = discordgo.PermissionManageServer commands = []*discordgo.ApplicationCommand{ { @@ -42,6 +45,12 @@ var ( // of the command. Description: "Basic command", }, + { + Name: "permission-overview", + Description: "Command for demonstration of default command permissions", + DefaultMemberPermissions: &defaultMemberPermissions, + DMPermission: &dmPermission, + }, { Name: "basic-command-with-files", Description: "Basic command with files", @@ -320,6 +329,84 @@ var ( }, }) }, + "permission-overview": func(s *discordgo.Session, i *discordgo.InteractionCreate) { + perms, err := s.ApplicationCommandPermissions(s.State.User.ID, i.GuildID, i.ApplicationCommandData().ID) + + var restError *discordgo.RESTError + if errors.As(err, &restError) && restError.Message != nil && restError.Message.Code == discordgo.ErrCodeUnknownApplicationCommandPermissions { + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: ":x: No permission overwrites", + }, + }) + return + } else if err != nil { + panic(err) + } + + if err != nil { + panic(err) + } + format := "- %s %s\n" + + channels := "" + users := "" + roles := "" + + for _, o := range perms.Permissions { + emoji := "❌" + if o.Permission { + emoji = "☑" + } + + switch o.Type { + case discordgo.ApplicationCommandPermissionTypeUser: + users += fmt.Sprintf(format, emoji, "<@!"+o.ID+">") + case discordgo.ApplicationCommandPermissionTypeChannel: + allChannels, _ := discordgo.GuildAllChannelsID(i.GuildID) + + if o.ID == allChannels { + channels += fmt.Sprintf(format, emoji, "All channels") + } else { + channels += fmt.Sprintf(format, emoji, "<#"+o.ID+">") + } + case discordgo.ApplicationCommandPermissionTypeRole: + if o.ID == i.GuildID { + roles += fmt.Sprintf(format, emoji, "@everyone") + } else { + roles += fmt.Sprintf(format, emoji, "<@&"+o.ID+">") + } + } + } + + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Embeds: []*discordgo.MessageEmbed{ + { + Title: "Permissions overview", + Description: "Overview of permissions for this command", + Fields: []*discordgo.MessageEmbedField{ + { + Name: "Users", + Value: users, + }, + { + Name: "Channels", + Value: channels, + }, + { + Name: "Roles", + Value: roles, + }, + }, + }, + }, + AllowedMentions: &discordgo.MessageAllowedMentions{}, + }, + }) + }, "subcommands": func(s *discordgo.Session, i *discordgo.InteractionCreate) { options := i.ApplicationCommandData().Options content := "" diff --git a/interactions.go b/interactions.go index 7164f65ee..7c2f824a5 100644 --- a/interactions.go +++ b/interactions.go @@ -9,6 +9,7 @@ import ( "io" "io/ioutil" "net/http" + "strconv" "time" ) @@ -36,7 +37,10 @@ type ApplicationCommand struct { Type ApplicationCommandType `json:"type,omitempty"` Name string `json:"name"` NameLocalizations *map[Locale]string `json:"name_localizations,omitempty"` - DefaultPermission *bool `json:"default_permission,omitempty"` + // NOTE: DefaultPermission will be soon deprecated. Use DefaultMemberPermissions and DMPermission instead. + DefaultPermission *bool `json:"default_permission,omitempty"` + DefaultMemberPermissions *int64 `json:"default_member_permissions,string,omitempty"` + DMPermission *bool `json:"dm_permission,omitempty"` // NOTE: Chat commands only. Otherwise it mustn't be set. @@ -129,6 +133,18 @@ type ApplicationCommandPermissions struct { Permission bool `json:"permission"` } +// GuildAllChannelsID is a helper function which returns guild_id-1. +// It is used in ApplicationCommandPermissions to target all the channels within a guild. +func GuildAllChannelsID(guild string) (id string, err error) { + var v uint64 + v, err = strconv.ParseUint(guild, 10, 64) + if err != nil { + return + } + + return strconv.FormatUint(v-1, 10), nil +} + // ApplicationCommandPermissionsList represents a list of ApplicationCommandPermissions, needed for serializing to JSON. type ApplicationCommandPermissionsList struct { Permissions []*ApplicationCommandPermissions `json:"permissions"` @@ -147,8 +163,9 @@ type ApplicationCommandPermissionType uint8 // Application command permission types. const ( - ApplicationCommandPermissionTypeRole ApplicationCommandPermissionType = 1 - ApplicationCommandPermissionTypeUser ApplicationCommandPermissionType = 2 + ApplicationCommandPermissionTypeRole ApplicationCommandPermissionType = 1 + ApplicationCommandPermissionTypeUser ApplicationCommandPermissionType = 2 + ApplicationCommandPermissionTypeChannel ApplicationCommandPermissionType = 3 ) // InteractionType indicates the type of an interaction event. diff --git a/restapi.go b/restapi.go index bb21ef21f..1eee79c69 100644 --- a/restapi.go +++ b/restapi.go @@ -2840,6 +2840,8 @@ func (s *Session) ApplicationCommandPermissions(appID, guildID, cmdID string) (p // guildID : The guild ID containing the application command // cmdID : The command ID to edit the permissions of // permissions : An object containing a list of permissions for the application command +// +// NOTE: Requires OAuth2 token with applications.commands.permissions.update scope func (s *Session) ApplicationCommandPermissionsEdit(appID, guildID, cmdID string, permissions *ApplicationCommandPermissionsList) (err error) { endpoint := EndpointApplicationCommandPermissions(appID, guildID, cmdID) @@ -2851,6 +2853,8 @@ func (s *Session) ApplicationCommandPermissionsEdit(appID, guildID, cmdID string // appID : The Application ID // guildID : The guild ID to batch edit commands of // permissions : A list of permissions paired with a command ID, guild ID, and application ID per application command +// +// NOTE: This endpoint has been disabled with updates to command permissions (Permissions v2). Please use ApplicationCommandPermissionsEdit instead. func (s *Session) ApplicationCommandPermissionsBatchEdit(appID, guildID string, permissions []*GuildApplicationCommandPermissions) (err error) { endpoint := EndpointApplicationCommandsGuildPermissions(appID, guildID) diff --git a/structs.go b/structs.go index a1e0232c2..9c4d0a470 100644 --- a/structs.go +++ b/structs.go @@ -1574,6 +1574,8 @@ const ( AuditLogActionThreadCreate AuditLogAction = 110 AuditLogActionThreadUpdate AuditLogAction = 111 AuditLogActionThreadDelete AuditLogAction = 112 + + AuditLogActionApplicationCommandPermissionUpdate = 121 ) // A UserGuildSettingsChannelOverride stores data for a channel override for a users guild settings. From 466321bed66e56e397c542b4396feb473b829fa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=A4rtner?= Date: Fri, 6 May 2022 22:25:57 +0200 Subject: [PATCH 02/33] fix: type of AuditLogActionApplicationCommandPermissionUpdate (#1177) --- structs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/structs.go b/structs.go index 9c4d0a470..268933d6c 100644 --- a/structs.go +++ b/structs.go @@ -1575,7 +1575,7 @@ const ( AuditLogActionThreadUpdate AuditLogAction = 111 AuditLogActionThreadDelete AuditLogAction = 112 - AuditLogActionApplicationCommandPermissionUpdate = 121 + AuditLogActionApplicationCommandPermissionUpdate AuditLogAction = 121 ) // A UserGuildSettingsChannelOverride stores data for a channel override for a users guild settings. From e1f154bfe70ea03ecd9e35a4656e8ea569d2389a Mon Sep 17 00:00:00 2001 From: ozraru <34647405+ozraru@users.noreply.github.com> Date: Sat, 7 May 2022 05:30:13 +0900 Subject: [PATCH 03/33] Add replied_user to MessageAllowedMentions (#1175) --- message.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/message.go b/message.go index 22d5f7403..ce7c55f45 100644 --- a/message.go +++ b/message.go @@ -321,6 +321,9 @@ type MessageAllowedMentions struct { // A list of user IDs to allow. This cannot be used when specifying // AllowedMentionTypeUsers in the Parse slice. Users []string `json:"users,omitempty"` + + // For replies, whether to mention the author of the message being replied to + RepliedUser bool `json:"replied_user"` } // A MessageAttachment stores data for message attachments. From eab9aa521898cc2040e65d8f7c3caa13d0099c80 Mon Sep 17 00:00:00 2001 From: Andres Perez <1676612+andresperezl@users.noreply.github.com> Date: Sat, 14 May 2022 15:21:51 -0400 Subject: [PATCH 04/33] Remove broken links from README (#1180) * remove broken links from README * Restore Go Doc notice in README Co-authored-by: Fedor Lapshin Co-authored-by: Fedor Lapshin --- README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2bcd43b81..d6ee0cc79 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # DiscordGo -[![Go Reference](https://pkg.go.dev/badge/github.com/bwmarrin/discordgo.svg)](https://pkg.go.dev/github.com/bwmarrin/discordgo) [![Go Report Card](https://goreportcard.com/badge/github.com/bwmarrin/discordgo)](https://goreportcard.com/report/github.com/bwmarrin/discordgo) [![Build Status](https://travis-ci.com/bwmarrin/discordgo.svg?branch=master)](https://travis-ci.com/bwmarrin/discordgo) [![Discord Gophers](https://img.shields.io/badge/Discord%20Gophers-%23discordgo-blue.svg)](https://discord.gg/golang) [![Discord API](https://img.shields.io/badge/Discord%20API-%23go_discordgo-blue.svg)](https://discord.com/invite/discord-api) +[![Go Reference](https://pkg.go.dev/badge/github.com/bwmarrin/discordgo.svg)](https://pkg.go.dev/github.com/bwmarrin/discordgo) [![Go Report Card](https://goreportcard.com/badge/github.com/bwmarrin/discordgo)](https://goreportcard.com/report/github.com/bwmarrin/discordgo) [![CI](https://github.com/bwmarrin/discordgo/actions/workflows/ci.yml/badge.svg)](https://github.com/bwmarrin/discordgo/actions/workflows/ci.yml) [![Discord Gophers](https://img.shields.io/badge/Discord%20Gophers-%23discordgo-blue.svg)](https://discord.gg/golang) [![Discord API](https://img.shields.io/badge/Discord%20API-%23go_discordgo-blue.svg)](https://discord.com/invite/discord-api) DiscordGo logo @@ -61,11 +61,9 @@ See Documentation and Examples below for more detailed information. Because of that there may be major changes to library in the future. The DiscordGo code is fairly well documented at this point and is currently -the only documentation available. Both GoDoc and GoWalker (below) present -that information in a nice format. +the only documentation available. Go reference (below) presents that information in a nice format. -- [![Go Reference](https://pkg.go.dev/badge/github.com/bwmarrin/discordgo.svg)](https://pkg.go.dev/github.com/bwmarrin/discordgo) -- [![Go Walker](https://gowalker.org/api/v1/badge)](https://gowalker.org/github.com/bwmarrin/discordgo) +- [![Go Reference](https://pkg.go.dev/badge/github.com/bwmarrin/discordgo.svg)](https://pkg.go.dev/github.com/bwmarrin/discordgo) - Hand crafted documentation coming eventually. From c29e0d740f8b38fbafe36d5903d1b567c92fccef Mon Sep 17 00:00:00 2001 From: plally Date: Sat, 14 May 2022 15:43:50 -0400 Subject: [PATCH 05/33] make WebhookEdit fields pointers (#1174) * make WebhookEdit fields pointers * fix examples --- examples/slash_commands/main.go | 10 ++++++---- webhook.go | 6 +++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/examples/slash_commands/main.go b/examples/slash_commands/main.go index 9f9a6ce38..f9dd53fef 100644 --- a/examples/slash_commands/main.go +++ b/examples/slash_commands/main.go @@ -476,10 +476,11 @@ var ( return } time.AfterFunc(time.Second*5, func() { + content := content + "\n\nWell, now you know how to create and edit responses. " + + "But you still don't know how to delete them... so... wait 10 seconds and this " + + "message will be deleted." _, err = s.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{ - Content: content + "\n\nWell, now you know how to create and edit responses. " + - "But you still don't know how to delete them... so... wait 10 seconds and this " + - "message will be deleted.", + Content: &content, }) if err != nil { s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{ @@ -517,8 +518,9 @@ var ( } time.Sleep(time.Second * 5) + content := "Now the original message is gone and after 10 seconds this message will ~~self-destruct~~ be deleted." s.FollowupMessageEdit(i.Interaction, msg.ID, &discordgo.WebhookEdit{ - Content: "Now the original message is gone and after 10 seconds this message will ~~self-destruct~~ be deleted.", + Content: &content, }) time.Sleep(time.Second * 10) diff --git a/webhook.go b/webhook.go index f54a45ce1..fd815caf8 100644 --- a/webhook.go +++ b/webhook.go @@ -41,9 +41,9 @@ type WebhookParams struct { // WebhookEdit stores data for editing of a webhook message. type WebhookEdit struct { - Content string `json:"content,omitempty"` - Components []MessageComponent `json:"components"` - Embeds []*MessageEmbed `json:"embeds,omitempty"` + Content *string `json:"content,omitempty"` + Components *[]MessageComponent `json:"components,omitempty"` + Embeds *[]*MessageEmbed `json:"embeds,omitempty"` Files []*File `json:"-"` AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"` } From fa14e19ad51d120705d1853f4c918c9510562fb4 Mon Sep 17 00:00:00 2001 From: Andrei Khodko Date: Sat, 14 May 2022 22:52:33 +0300 Subject: [PATCH 06/33] fix: check if opus created (#1166) * fix: check is opus created * fix: careful concurrency fixes --- voice.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/voice.go b/voice.go index aedb87904..a4c3bf558 100644 --- a/voice.go +++ b/voice.go @@ -120,9 +120,9 @@ func (v *VoiceConnection) ChangeChannel(channelID string, mute, deaf bool) (err v.log(LogInformational, "called") data := voiceChannelJoinOp{4, voiceChannelJoinData{&v.GuildID, &channelID, mute, deaf}} - v.wsMutex.Lock() + v.session.wsMutex.Lock() err = v.session.wsConn.WriteJSON(data) - v.wsMutex.Unlock() + v.session.wsMutex.Unlock() if err != nil { return } @@ -323,7 +323,9 @@ func (v *VoiceConnection) open() (err error) { } data := voiceHandshakeOp{0, voiceHandshakeData{v.GuildID, v.UserID, v.sessionID, v.token}} + v.wsMutex.Lock() err = v.wsConn.WriteJSON(data) + v.wsMutex.Unlock() if err != nil { v.log(LogWarning, "error sending init packet, %s", err) return @@ -829,7 +831,12 @@ func (v *VoiceConnection) opusReceiver(udpConn *net.UDPConn, close <-chan struct p.SSRC = binary.BigEndian.Uint32(recvbuf[8:12]) // decrypt opus data copy(nonce[:], recvbuf[0:12]) - p.Opus, _ = secretbox.Open(nil, recvbuf[12:rlen], &nonce, &v.op4.SecretKey) + + if opus, ok := secretbox.Open(nil, recvbuf[12:rlen], &nonce, &v.op4.SecretKey); ok { + p.Opus = opus + } else { + return + } // extension bit set, and not a RTCP packet if ((recvbuf[0] & 0x10) == 0x10) && ((recvbuf[1] & 0x80) == 0) { @@ -870,7 +877,11 @@ func (v *VoiceConnection) reconnect() { v.reconnecting = true v.Unlock() - defer func() { v.reconnecting = false }() + defer func() { + v.Lock() + v.reconnecting = false + v.Unlock() + }() // Close any currently open connections v.Close() From e45536276050ae548cdaa84a73dbdbc4645f7938 Mon Sep 17 00:00:00 2001 From: Andrey Dudin Date: Sat, 14 May 2022 22:56:30 +0300 Subject: [PATCH 07/33] Add ability to reply with embeds (#1160) * Add ability to reply with embeds * Fix typo in method comment --- restapi.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/restapi.go b/restapi.go index 1eee79c69..8c700bed8 100644 --- a/restapi.go +++ b/restapi.go @@ -1729,6 +1729,28 @@ func (s *Session) ChannelMessageSendReply(channelID string, content string, refe }) } +// ChannelMessageSendEmbedReply sends a message to the given channel with reference data and embedded data. +// channelID : The ID of a Channel. +// embed : The embed data to send. +// reference : The message reference to send. +func (s *Session) ChannelMessageSendEmbedReply(channelID string, embed *MessageEmbed, reference *MessageReference) (*Message, error) { + return s.ChannelMessageSendEmbedsReply(channelID, []*MessageEmbed{embed}, reference) +} + +// ChannelMessageSendEmbedsReply sends a message to the given channel with reference data and multiple embedded data. +// channelID : The ID of a Channel. +// embeds : The embeds data to send. +// reference : The message reference to send. +func (s *Session) ChannelMessageSendEmbedsReply(channelID string, embeds []*MessageEmbed, reference *MessageReference) (*Message, error) { + if reference == nil { + return nil, fmt.Errorf("reply attempted with nil message reference") + } + return s.ChannelMessageSendComplex(channelID, &MessageSend{ + Embeds: embeds, + Reference: reference, + }) +} + // ChannelMessageEdit edits an existing message, replacing it entirely with // the given content. // channelID : The ID of a Channel From d7b4a482b5ca31fbf591d1853c46230deaaf8df8 Mon Sep 17 00:00:00 2001 From: Andres Perez <1676612+andresperezl@users.noreply.github.com> Date: Sun, 22 May 2022 17:32:52 -0400 Subject: [PATCH 08/33] Use MessageFlags in InteractionResponseData and WebhookParams (#1178) --- examples/components/main.go | 20 ++++++++++---------- examples/context_menus/main.go | 14 +++++++------- examples/modals/main.go | 2 +- examples/slash_commands/main.go | 2 +- interactions.go | 4 +++- webhook.go | 5 +++-- 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/examples/components/main.go b/examples/components/main.go index 45b580333..a8bb3413d 100644 --- a/examples/components/main.go +++ b/examples/components/main.go @@ -40,7 +40,7 @@ var ( Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: "Huh. I see, maybe some of these resources might help you?", - Flags: 1 << 6, + Flags: discordgo.MessageFlagsEphemeral, Components: []discordgo.MessageComponent{ discordgo.ActionsRow{ Components: []discordgo.MessageComponent{ @@ -83,7 +83,7 @@ var ( Data: &discordgo.InteractionResponseData{ Content: "Great! If you wanna know more or just have questions, feel free to visit Discord Devs and Discord Gophers server. " + "But now, when you know how buttons work, let's move onto select menus (execute `/selects single`)", - Flags: 1 << 6, + Flags: discordgo.MessageFlagsEphemeral, Components: []discordgo.MessageComponent{ discordgo.ActionsRow{ Components: []discordgo.MessageComponent{ @@ -122,7 +122,7 @@ var ( Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: "This is the way.", - Flags: 1 << 6, + Flags: discordgo.MessageFlagsEphemeral, }, } default: @@ -130,7 +130,7 @@ var ( Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: "It is not the way to go.", - Flags: 1 << 6, + Flags: discordgo.MessageFlagsEphemeral, }, } } @@ -142,7 +142,7 @@ var ( _, err = s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{ Content: "Anyways, now when you know how to use single select menus, let's see how multi select menus work. " + "Try calling `/selects multi` command.", - Flags: 1 << 6, + Flags: discordgo.MessageFlagsEphemeral, }) if err != nil { panic(err) @@ -157,7 +157,7 @@ var ( Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: "Here is your stackoverflow URL: " + fmt.Sprintf(stackoverflowFormat, strings.Join(data.Values, "+")), - Flags: 1 << 6, + Flags: discordgo.MessageFlagsEphemeral, }, }) if err != nil { @@ -196,7 +196,7 @@ var ( }, }, }, - Flags: 1 << 6, + Flags: discordgo.MessageFlagsEphemeral, }) if err != nil { panic(err) @@ -209,7 +209,7 @@ var ( Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: "Are you comfortable with buttons and other message components?", - Flags: 1 << 6, + Flags: discordgo.MessageFlagsEphemeral, // Buttons and other components are specified in Components field. Components: []discordgo.MessageComponent{ // ActionRow is a container of all buttons within the same row. @@ -269,7 +269,7 @@ var ( Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: "Now let's take a look on selects. This is single item select menu.", - Flags: 1 << 6, + Flags: discordgo.MessageFlagsEphemeral, Components: []discordgo.MessageComponent{ discordgo.ActionsRow{ Components: []discordgo.MessageComponent{ @@ -320,7 +320,7 @@ var ( Data: &discordgo.InteractionResponseData{ Content: "The tastiest things are left for the end. Let's see how the multi-item select menu works: " + "try generating your own stackoverflow search link", - Flags: 1 << 6, + Flags: discordgo.MessageFlagsEphemeral, Components: []discordgo.MessageComponent{ discordgo.ActionsRow{ Components: []discordgo.MessageComponent{ diff --git a/examples/context_menus/main.go b/examples/context_menus/main.go index f5d4b92ef..fda7996cd 100644 --- a/examples/context_menus/main.go +++ b/examples/context_menus/main.go @@ -74,7 +74,7 @@ var ( Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: "Operation rickroll has begun", - Flags: 1 << 6, + Flags: discordgo.MessageFlagsEphemeral, }, }) if err != nil { @@ -87,7 +87,7 @@ var ( if err != nil { _, err = s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{ Content: fmt.Sprintf("Mission failed. Cannot send a message to this user: %q", err.Error()), - Flags: 1 << 6, + Flags: discordgo.MessageFlagsEphemeral, }) if err != nil { panic(err) @@ -108,7 +108,7 @@ var ( Content: searchLink( i.ApplicationCommandData().Resolved.Messages[i.ApplicationCommandData().TargetID].Content, "https://google.com/search?q=%s", "+"), - Flags: 1 << 6, + Flags: discordgo.MessageFlagsEphemeral, }, }) if err != nil { @@ -122,7 +122,7 @@ var ( Content: searchLink( i.ApplicationCommandData().Resolved.Messages[i.ApplicationCommandData().TargetID].Content, "https://stackoverflow.com/search?q=%s", "+"), - Flags: 1 << 6, + Flags: discordgo.MessageFlagsEphemeral, }, }) if err != nil { @@ -136,7 +136,7 @@ var ( Content: searchLink( i.ApplicationCommandData().Resolved.Messages[i.ApplicationCommandData().TargetID].Content, "https://pkg.go.dev/search?q=%s", "+"), - Flags: 1 << 6, + Flags: discordgo.MessageFlagsEphemeral, }, }) if err != nil { @@ -150,7 +150,7 @@ var ( Content: searchLink( i.ApplicationCommandData().Resolved.Messages[i.ApplicationCommandData().TargetID].Content, "https://discord.js.org/#/docs/main/stable/search?query=%s", "+"), - Flags: 1 << 6, + Flags: discordgo.MessageFlagsEphemeral, }, }) if err != nil { @@ -164,7 +164,7 @@ var ( Content: searchLink( i.ApplicationCommandData().Resolved.Messages[i.ApplicationCommandData().TargetID].Content, "https://discordpy.readthedocs.io/en/stable/search.html?q=%s", "+"), - Flags: 1 << 6, + Flags: discordgo.MessageFlagsEphemeral, }, }) if err != nil { diff --git a/examples/modals/main.go b/examples/modals/main.go index effd8f1a3..6a48cdcae 100644 --- a/examples/modals/main.go +++ b/examples/modals/main.go @@ -99,7 +99,7 @@ func main() { Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: "Thank you for taking your time to fill this survey", - Flags: 1 << 6, + Flags: discordgo.MessageFlagsEphemeral, }, }) if err != nil { diff --git a/examples/slash_commands/main.go b/examples/slash_commands/main.go index f9dd53fef..a70cd4455 100644 --- a/examples/slash_commands/main.go +++ b/examples/slash_commands/main.go @@ -503,7 +503,7 @@ var ( // Note: this isn't documented, but you can use that if you want to. // This flag just allows you to create messages visible only for the caller of the command // (user who triggered the command) - Flags: 1 << 6, + Flags: discordgo.MessageFlagsEphemeral, Content: "Surprise!", }, }) diff --git a/interactions.go b/interactions.go index 7c2f824a5..28454a1df 100644 --- a/interactions.go +++ b/interactions.go @@ -534,9 +534,11 @@ type InteractionResponseData struct { Components []MessageComponent `json:"components"` Embeds []*MessageEmbed `json:"embeds"` AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"` - Flags uint64 `json:"flags,omitempty"` Files []*File `json:"-"` + // NOTE: only MessageFlagsSuppressEmbeds and MessageFlagsEphemeral can be set. + Flags MessageFlags `json:"flags,omitempty"` + // NOTE: autocomplete interaction only. Choices []*ApplicationCommandOptionChoice `json:"choices,omitempty"` diff --git a/webhook.go b/webhook.go index fd815caf8..9209b7095 100644 --- a/webhook.go +++ b/webhook.go @@ -35,8 +35,9 @@ type WebhookParams struct { Components []MessageComponent `json:"components"` Embeds []*MessageEmbed `json:"embeds,omitempty"` AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"` - // NOTE: Works only for followup messages. - Flags uint64 `json:"flags,omitempty"` + // Only MessageFlagsSuppressEmbeds and MessageFlagsEphemeral can be set. + // MessageFlagsEphemeral can only be set when using Followup Message Create endpoint. + Flags MessageFlags `json:"flags,omitempty"` } // WebhookEdit stores data for editing of a webhook message. From 414e505c5647165a88db0c3c5af13c0a96dca375 Mon Sep 17 00:00:00 2001 From: wass88 Date: Tue, 24 May 2022 03:23:09 +0900 Subject: [PATCH 09/33] Use MakeIntent in voice_recieve example (#1182) --- examples/voice_receive/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/voice_receive/main.go b/examples/voice_receive/main.go index c480b9e2e..e5a925294 100644 --- a/examples/voice_receive/main.go +++ b/examples/voice_receive/main.go @@ -75,7 +75,7 @@ func main() { defer s.Close() // We only really care about receiving voice state updates. - s.Identify.Intents = discordgo.IntentsGuildVoiceStates + s.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsGuildVoiceStates) err = s.Open() if err != nil { From c8c153872c36279e63107bc3d085de8d38e6c77f Mon Sep 17 00:00:00 2001 From: Andre Cesar Gomes Date: Fri, 3 Jun 2022 09:58:53 -0300 Subject: [PATCH 10/33] feat: add missing fields to VoiceState struct (#1190) --- structs.go | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/structs.go b/structs.go index 268933d6c..cddc5d9d6 100644 --- a/structs.go +++ b/structs.go @@ -1098,15 +1098,19 @@ func (r Roles) Swap(i, j int) { // A VoiceState stores the voice states of Guilds type VoiceState struct { - UserID string `json:"user_id"` - SessionID string `json:"session_id"` - ChannelID string `json:"channel_id"` - GuildID string `json:"guild_id"` - Suppress bool `json:"suppress"` - SelfMute bool `json:"self_mute"` - SelfDeaf bool `json:"self_deaf"` - Mute bool `json:"mute"` - Deaf bool `json:"deaf"` + GuildID string `json:"guild_id"` + ChannelID string `json:"channel_id"` + UserID string `json:"user_id"` + Member *Member `json:"member"` + SessionID string `json:"session_id"` + Deaf bool `json:"deaf"` + Mute bool `json:"mute"` + SelfDeaf bool `json:"self_deaf"` + SelfMute bool `json:"self_mute"` + SelfStream bool `json:"self_stream"` + SelfVideo bool `json:"self_video"` + Suppress bool `json:"suppress"` + RequestToSpeakTimestamp *time.Time `json:"request_to_speak_timestamp"` } // A Presence stores the online, offline, or idle and game status of Guild members. From e030c5fff83aa31d27f417f740b1da149fb9eddf Mon Sep 17 00:00:00 2001 From: Aderlx <34983999+Aderlx@users.noreply.github.com> Date: Wed, 15 Jun 2022 04:23:32 +0800 Subject: [PATCH 11/33] Add Invite ExpiresAt field (#1198) --- structs.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/structs.go b/structs.go index cddc5d9d6..0b6b6a675 100644 --- a/structs.go +++ b/structs.go @@ -246,6 +246,8 @@ type Invite struct { // will only be filled when using InviteWithCounts ApproximatePresenceCount int `json:"approximate_presence_count"` ApproximateMemberCount int `json:"approximate_member_count"` + + ExpiresAt *time.Time `json:"expires_at"` } // ChannelType is the type of a Channel From a7b7eaa2af2310a12f1d8969f0f419e61495dea1 Mon Sep 17 00:00:00 2001 From: Elias* <31409841+EliasStar@users.noreply.github.com> Date: Sun, 19 Jun 2022 17:43:00 +0200 Subject: [PATCH 12/33] Use `with_localizations` flag (#1196) * Improve support for application command localizations Signed-off-by: GitHub * Remove query string from bucket name Co-authored-by: Fedor Lapshin * Fix formatting Co-authored-by: Fedor Lapshin --- restapi.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/restapi.go b/restapi.go index 8c700bed8..16d2fb06a 100644 --- a/restapi.go +++ b/restapi.go @@ -2814,7 +2814,7 @@ func (s *Session) ApplicationCommands(appID, guildID string) (cmd []*Application endpoint = EndpointApplicationGuildCommands(appID, guildID) } - body, err := s.RequestWithBucketID("GET", endpoint, nil, endpoint) + body, err := s.RequestWithBucketID("GET", endpoint+"?with_localizations=true", nil, endpoint) if err != nil { return } From 5da28ed4b999452c7b2921b1f971667ecb324939 Mon Sep 17 00:00:00 2001 From: Riley Date: Sun, 19 Jun 2022 08:47:03 -0700 Subject: [PATCH 13/33] Fix GuildTemplate struct and documentation (#1191) * fix(structs.go): fix GuildTemplate struct and documentation * revert(structs.go): change description from a string ptr to a string * fix(structs.go): make name and description a pointer and omitempty * fix(structs.go): made Name field not a string pointer. removed extra files * fix(structs.go): fix Name field not having a type --- structs.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/structs.go b/structs.go index 0b6b6a675..98ad75829 100644 --- a/structs.go +++ b/structs.go @@ -948,19 +948,19 @@ type GuildScheduledEventUser struct { Member *Member `json:"member"` } -// A GuildTemplate represents +// A GuildTemplate represents a replicable template for guild creation type GuildTemplate struct { // The unique code for the guild template Code string `json:"code"` // The name of the template - Name string `json:"name"` + Name string `json:"name,omitempty"` // The description for the template - Description string `json:"description"` + Description *string `json:"description,omitempty"` // The number of times this template has been used - UsageCount string `json:"usage_count"` + UsageCount int `json:"usage_count"` // The ID of the user who created the template CreatorID string `json:"creator_id"` From a7f037862343f3ed29da9543b9ea4f8c57810620 Mon Sep 17 00:00:00 2001 From: plally Date: Sun, 19 Jun 2022 14:20:11 -0400 Subject: [PATCH 14/33] add GET prefix for ApplicationCommands bucketID (#1193) * add GET prefix for ApplicationCommands bucketID * dont use local variable for bucketID Co-authored-by: Fedor Lapshin * format code * remove newline Co-authored-by: Fedor Lapshin --- restapi.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/restapi.go b/restapi.go index 16d2fb06a..61a566d8e 100644 --- a/restapi.go +++ b/restapi.go @@ -2814,7 +2814,7 @@ func (s *Session) ApplicationCommands(appID, guildID string) (cmd []*Application endpoint = EndpointApplicationGuildCommands(appID, guildID) } - body, err := s.RequestWithBucketID("GET", endpoint+"?with_localizations=true", nil, endpoint) + body, err := s.RequestWithBucketID("GET", endpoint+"?with_localizations=true", nil, "GET "+endpoint) if err != nil { return } From f446e069a1d9f3f9fa4c7a60b51734e00f1bc7dd Mon Sep 17 00:00:00 2001 From: Zachinquarantine Date: Thu, 30 Jun 2022 17:49:29 -0400 Subject: [PATCH 15/33] Remove deprecated GuildChannel and PrivateChannel methods (#1203) First deprecated in 2016 with #163 --- state.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/state.go b/state.go index a25d55f50..eb87529e0 100644 --- a/state.go +++ b/state.go @@ -661,18 +661,6 @@ func (s *State) ThreadMemberUpdate(mu *ThreadMemberUpdate) error { 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) { - return s.Channel(channelID) -} - -// PrivateChannel gets a private channel by ID. -// This method is Deprecated, use Channel(channelID) -func (s *State) PrivateChannel(channelID string) (*Channel, error) { - return s.Channel(channelID) -} - // Channel gets a channel by ID, it will look in all guilds and private channels. func (s *State) Channel(channelID string) (*Channel, error) { if s == nil { From 033c07d89d5824c0fba4f85471b29d578ccb7f30 Mon Sep 17 00:00:00 2001 From: Fedor Lapshin Date: Sat, 2 Jul 2022 23:24:07 +0300 Subject: [PATCH 16/33] feat(interactions): application permissions (#1206) --- interactions.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/interactions.go b/interactions.go index 28454a1df..21df6eb28 100644 --- a/interactions.go +++ b/interactions.go @@ -207,6 +207,9 @@ type Interaction struct { // NOTE: this field is only filled when a button click triggered the interaction. Otherwise it will be nil. Message *Message `json:"message"` + // Bitwise set of permissions the app or bot has within the channel the interaction was sent from + AppPermissions int64 `json:"app_permissions,string"` + // The member who invoked this interaction. // NOTE: this field is only filled when the slash command was invoked in a guild; // if it was invoked in a DM, the `User` field will be filled instead. From f697ccae8cf714950a634bed0ca1c69d93787dcb Mon Sep 17 00:00:00 2001 From: Fedor Lapshin Date: Sat, 2 Jul 2022 23:27:27 +0300 Subject: [PATCH 17/33] feat(ApplicationCommand): guild id (#1207) Add missing guild_id field to ApplicationCommand structure --- interactions.go | 1 + 1 file changed, 1 insertion(+) diff --git a/interactions.go b/interactions.go index 21df6eb28..70509cde1 100644 --- a/interactions.go +++ b/interactions.go @@ -33,6 +33,7 @@ const ( type ApplicationCommand struct { ID string `json:"id,omitempty"` ApplicationID string `json:"application_id,omitempty"` + GuildID string `json:"guild_id,omitempty"` Version string `json:"version,omitempty"` Type ApplicationCommandType `json:"type,omitempty"` Name string `json:"name"` From 4e021d914065322339fbb0603bb5004601a0fec6 Mon Sep 17 00:00:00 2001 From: Fedor Lapshin Date: Sun, 3 Jul 2022 21:51:15 +0300 Subject: [PATCH 18/33] Auto moderation (#1201) * feat: auto moderation * feat(examples/automod): add message content intent * style(examples/automod): newline between sections * feat(AutoModerationActionExecution): add user id Add user_id field to AutoModerationActionExecution event. * refactor(events): remove todos Remove TODO comments for AutoModerationRuleUpdate and AutoModerationRuleDelete. * feat(AutoModerationEventMessageSend): doc comment Add documentation comment to AutoModerationEventMessageSend constant. --- endpoints.go | 3 + eventhandlers.go | 96 ++++++++++++++++++++++++ events.go | 30 ++++++++ examples/auto_moderation/main.go | 117 +++++++++++++++++++++++++++++ restapi.go | 77 +++++++++++++++++++ structs.go | 122 ++++++++++++++++++++++++++----- 6 files changed, 427 insertions(+), 18 deletions(-) create mode 100644 examples/auto_moderation/main.go diff --git a/endpoints.go b/endpoints.go index f5822da64..b0215fceb 100644 --- a/endpoints.go +++ b/endpoints.go @@ -68,6 +68,9 @@ var ( EndpointUserConnections = func(uID string) string { return EndpointUsers + uID + "/connections" } EndpointGuild = func(gID string) string { return EndpointGuilds + gID } + EndpointGuildAutoModeration = func(gID string) string { return EndpointGuild(gID) + "/auto-moderation" } + EndpointGuildAutoModerationRules = func(gID string) string { return EndpointGuildAutoModeration(gID) + "/rules" } + EndpointGuildAutoModerationRule = func(gID, rID string) string { return EndpointGuildAutoModerationRules(gID) + "/" + rID } 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" } diff --git a/eventhandlers.go b/eventhandlers.go index f2ec080c3..36beefdf4 100644 --- a/eventhandlers.go +++ b/eventhandlers.go @@ -8,6 +8,10 @@ package discordgo // EventTypes surrounded by __ are synthetic and are internal to DiscordGo. const ( applicationCommandPermissionsUpdateEventType = "APPLICATION_COMMAND_PERMISSIONS_UPDATE" + autoModerationActionExecutionEventType = "AUTO_MODERATION_ACTION_EXECUTION" + autoModerationRuleCreateEventType = "AUTO_MODERATION_RULE_CREATE" + autoModerationRuleDeleteEventType = "AUTO_MODERATION_RULE_DELETE" + autoModerationRuleUpdateEventType = "AUTO_MODERATION_RULE_UPDATE" channelCreateEventType = "CHANNEL_CREATE" channelDeleteEventType = "CHANNEL_DELETE" channelPinsUpdateEventType = "CHANNEL_PINS_UPDATE" @@ -91,6 +95,86 @@ func (eh applicationCommandPermissionsUpdateEventHandler) Handle(s *Session, i i } } +// autoModerationActionExecutionEventHandler is an event handler for AutoModerationActionExecution events. +type autoModerationActionExecutionEventHandler func(*Session, *AutoModerationActionExecution) + +// Type returns the event type for AutoModerationActionExecution events. +func (eh autoModerationActionExecutionEventHandler) Type() string { + return autoModerationActionExecutionEventType +} + +// New returns a new instance of AutoModerationActionExecution. +func (eh autoModerationActionExecutionEventHandler) New() interface{} { + return &AutoModerationActionExecution{} +} + +// Handle is the handler for AutoModerationActionExecution events. +func (eh autoModerationActionExecutionEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*AutoModerationActionExecution); ok { + eh(s, t) + } +} + +// autoModerationRuleCreateEventHandler is an event handler for AutoModerationRuleCreate events. +type autoModerationRuleCreateEventHandler func(*Session, *AutoModerationRuleCreate) + +// Type returns the event type for AutoModerationRuleCreate events. +func (eh autoModerationRuleCreateEventHandler) Type() string { + return autoModerationRuleCreateEventType +} + +// New returns a new instance of AutoModerationRuleCreate. +func (eh autoModerationRuleCreateEventHandler) New() interface{} { + return &AutoModerationRuleCreate{} +} + +// Handle is the handler for AutoModerationRuleCreate events. +func (eh autoModerationRuleCreateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*AutoModerationRuleCreate); ok { + eh(s, t) + } +} + +// autoModerationRuleDeleteEventHandler is an event handler for AutoModerationRuleDelete events. +type autoModerationRuleDeleteEventHandler func(*Session, *AutoModerationRuleDelete) + +// Type returns the event type for AutoModerationRuleDelete events. +func (eh autoModerationRuleDeleteEventHandler) Type() string { + return autoModerationRuleDeleteEventType +} + +// New returns a new instance of AutoModerationRuleDelete. +func (eh autoModerationRuleDeleteEventHandler) New() interface{} { + return &AutoModerationRuleDelete{} +} + +// Handle is the handler for AutoModerationRuleDelete events. +func (eh autoModerationRuleDeleteEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*AutoModerationRuleDelete); ok { + eh(s, t) + } +} + +// autoModerationRuleUpdateEventHandler is an event handler for AutoModerationRuleUpdate events. +type autoModerationRuleUpdateEventHandler func(*Session, *AutoModerationRuleUpdate) + +// Type returns the event type for AutoModerationRuleUpdate events. +func (eh autoModerationRuleUpdateEventHandler) Type() string { + return autoModerationRuleUpdateEventType +} + +// New returns a new instance of AutoModerationRuleUpdate. +func (eh autoModerationRuleUpdateEventHandler) New() interface{} { + return &AutoModerationRuleUpdate{} +} + +// Handle is the handler for AutoModerationRuleUpdate events. +func (eh autoModerationRuleUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*AutoModerationRuleUpdate); ok { + eh(s, t) + } +} + // channelCreateEventHandler is an event handler for ChannelCreate events. type channelCreateEventHandler func(*Session, *ChannelCreate) @@ -1297,6 +1381,14 @@ func handlerForInterface(handler interface{}) EventHandler { return interfaceEventHandler(v) case func(*Session, *ApplicationCommandPermissionsUpdate): return applicationCommandPermissionsUpdateEventHandler(v) + case func(*Session, *AutoModerationActionExecution): + return autoModerationActionExecutionEventHandler(v) + case func(*Session, *AutoModerationRuleCreate): + return autoModerationRuleCreateEventHandler(v) + case func(*Session, *AutoModerationRuleDelete): + return autoModerationRuleDeleteEventHandler(v) + case func(*Session, *AutoModerationRuleUpdate): + return autoModerationRuleUpdateEventHandler(v) case func(*Session, *ChannelCreate): return channelCreateEventHandler(v) case func(*Session, *ChannelDelete): @@ -1426,6 +1518,10 @@ func handlerForInterface(handler interface{}) EventHandler { func init() { registerInterfaceProvider(applicationCommandPermissionsUpdateEventHandler(nil)) + registerInterfaceProvider(autoModerationActionExecutionEventHandler(nil)) + registerInterfaceProvider(autoModerationRuleCreateEventHandler(nil)) + registerInterfaceProvider(autoModerationRuleDeleteEventHandler(nil)) + registerInterfaceProvider(autoModerationRuleUpdateEventHandler(nil)) registerInterfaceProvider(channelCreateEventHandler(nil)) registerInterfaceProvider(channelDeleteEventHandler(nil)) registerInterfaceProvider(channelPinsUpdateEventHandler(nil)) diff --git a/events.go b/events.go index 17c78281f..5cb7622d7 100644 --- a/events.go +++ b/events.go @@ -406,3 +406,33 @@ type InviteDelete struct { type ApplicationCommandPermissionsUpdate struct { *GuildApplicationCommandPermissions } + +// AutoModerationRuleCreate is the data for an AutoModerationRuleCreate event. +type AutoModerationRuleCreate struct { + *AutoModerationRule +} + +// AutoModerationRuleUpdate is the data for an AutoModerationRuleUpdate event. +type AutoModerationRuleUpdate struct { + *AutoModerationRule +} + +// AutoModerationRuleDelete is the data for an AutoModerationRuleDelete event. +type AutoModerationRuleDelete struct { + *AutoModerationRule +} + +// AutoModerationActionExecution is the data for an AutoModerationActionExecution event. +type AutoModerationActionExecution struct { + GuildID string `json:"guild_id"` + Action AutoModerationAction `json:"action"` + RuleID string `json:"rule_id"` + RuleTriggerType AutoModerationRuleTriggerType `json:"rule_trigger_type"` + UserID string `json:"user_id"` + ChannelID string `json:"channel_id"` + MessageID string `json:"message_id"` + AlertSystemMessageID string `json:"alert_system_message_id"` + Content string `json:"content"` + MatchedKeyword string `json:"matched_keyword"` + MatchedContent string `json:"matched_content"` +} diff --git a/examples/auto_moderation/main.go b/examples/auto_moderation/main.go new file mode 100644 index 000000000..66fbbcbdc --- /dev/null +++ b/examples/auto_moderation/main.go @@ -0,0 +1,117 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + "os/signal" + "sync" + + "github.com/bwmarrin/discordgo" +) + +// Command line flags +var ( + BotToken = flag.String("token", "", "Bot authorization token") + GuildID = flag.String("guild", "", "ID of the testing guild") + ChannelID = flag.String("channel", "", "ID of the testing channel") +) + +func init() { flag.Parse() } + +func main() { + session, _ := discordgo.New("Bot " + *BotToken) + session.Identify.Intents |= discordgo.IntentAutoModerationExecution + session.Identify.Intents |= discordgo.IntentMessageContent + + enabled := true + rule, err := session.AutoModerationRuleCreate(*GuildID, &discordgo.AutoModerationRule{ + Name: "Auto Moderation example", + EventType: discordgo.AutoModerationEventMessageSend, + TriggerType: discordgo.AutoModerationEventTriggerKeyword, + TriggerMetadata: &discordgo.AutoModerationTriggerMetadata{ + KeywordFilter: []string{"*cat*"}, + }, + + Enabled: &enabled, + Actions: []discordgo.AutoModerationAction{ + {Type: discordgo.AutoModerationRuleActionBlockMessage}, + }, + }) + if err != nil { + panic(err) + } + + fmt.Println("Successfully created the rule") + defer session.AutoModerationRuleDelete(*GuildID, rule.ID) + + session.AddHandlerOnce(func(s *discordgo.Session, e *discordgo.AutoModerationActionExecution) { + _, err = session.AutoModerationRuleEdit(*GuildID, rule.ID, &discordgo.AutoModerationRule{ + TriggerMetadata: &discordgo.AutoModerationTriggerMetadata{ + KeywordFilter: []string{"cat"}, + }, + Actions: []discordgo.AutoModerationAction{ + {Type: discordgo.AutoModerationRuleActionTimeout, Metadata: &discordgo.AutoModerationActionMetadata{Duration: 60}}, + {Type: discordgo.AutoModerationRuleActionSendAlertMessage, Metadata: &discordgo.AutoModerationActionMetadata{ + ChannelID: e.ChannelID, + }}, + }, + }) + if err != nil { + session.AutoModerationRuleDelete(*GuildID, rule.ID) + panic(err) + } + + s.ChannelMessageSend(e.ChannelID, "Congratulations! You have just triggered an auto moderation rule.\n"+ + "The current trigger can match anywhere in the word, so even if you write the trigger word as a part of another word, it will still match.\n"+ + "The rule has now been changed, now the trigger matches only in the full words.\n"+ + "Additionally, when you send a message, an alert will be sent to this channel and you will be **timed out** for a minute.\n") + + var counter int + var counterMutex sync.Mutex + session.AddHandler(func(s *discordgo.Session, e *discordgo.AutoModerationActionExecution) { + action := "unknown" + switch e.Action.Type { + case discordgo.AutoModerationRuleActionBlockMessage: + action = "block message" + case discordgo.AutoModerationRuleActionSendAlertMessage: + action = "send alert message into <#" + e.Action.Metadata.ChannelID + ">" + case discordgo.AutoModerationRuleActionTimeout: + action = "timeout" + } + + counterMutex.Lock() + counter++ + if counter == 1 { + counterMutex.Unlock() + s.ChannelMessageSend(e.ChannelID, "Nothing has changed, right? "+ + "Well, since separate gateway events are fired per each action (current is "+action+"), "+ + "you'll see a second message about an action pop up soon") + } else if counter == 2 { + counterMutex.Unlock() + s.ChannelMessageSend(e.ChannelID, "Now the second ("+action+") action got executed.") + s.ChannelMessageSend(e.ChannelID, "And... you've made it! That's the end of the example.\n"+ + "For more information about the automod and how to use it, "+ + "you can visit the official Discord docs: https://discord.dev/resources/auto-moderation or ask in our server: https://discord.gg/6dzbuDpSWY", + ) + + session.Close() + session.AutoModerationRuleDelete(*GuildID, rule.ID) + os.Exit(0) + } + }) + }) + + err = session.Open() + if err != nil { + log.Fatalf("Cannot open the session: %v", err) + } + defer session.Close() + + stop := make(chan os.Signal, 1) + signal.Notify(stop, os.Interrupt) + <-stop + log.Println("Graceful shutdown") + +} diff --git a/restapi.go b/restapi.go index 61a566d8e..7dc8d1196 100644 --- a/restapi.go +++ b/restapi.go @@ -3111,3 +3111,80 @@ func (s *Session) GuildScheduledEventUsers(guildID, eventID string, limit int, w err = unmarshal(body, &st) return } + +// ---------------------------------------------------------------------- +// Functions specific to auto moderation +// ---------------------------------------------------------------------- + +// AutoModerationRules returns a list of auto moderation rules. +// guildID : ID of the guild +func (s *Session) AutoModerationRules(guildID string) (st []*AutoModerationRule, err error) { + endpoint := EndpointGuildAutoModerationRules(guildID) + + var body []byte + body, err = s.RequestWithBucketID("GET", endpoint, nil, endpoint) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// AutoModerationRule returns an auto moderation rule. +// guildID : ID of the guild +// ruleID : ID of the auto moderation rule +func (s *Session) AutoModerationRule(guildID, ruleID string) (st *AutoModerationRule, err error) { + endpoint := EndpointGuildAutoModerationRule(guildID, ruleID) + + var body []byte + body, err = s.RequestWithBucketID("GET", endpoint, nil, endpoint) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// AutoModerationRuleCreate creates an auto moderation rule with the given data and returns it. +// guildID : ID of the guild +// rule : Rule data +func (s *Session) AutoModerationRuleCreate(guildID string, rule *AutoModerationRule) (st *AutoModerationRule, err error) { + endpoint := EndpointGuildAutoModerationRules(guildID) + + var body []byte + body, err = s.RequestWithBucketID("POST", endpoint, rule, endpoint) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// AutoModerationRuleEdit edits and returns the updated auto moderation rule. +// guildID : ID of the guild +// ruleID : ID of the auto moderation rule +// rule : New rule data +func (s *Session) AutoModerationRuleEdit(guildID, ruleID string, rule *AutoModerationRule) (st *AutoModerationRule, err error) { + endpoint := EndpointGuildAutoModerationRule(guildID, ruleID) + + var body []byte + body, err = s.RequestWithBucketID("PATCH", endpoint, rule, endpoint) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// AutoModerationRuleDelete deletes an auto moderation rule. +// guildID : ID of the guild +// ruleID : ID of the auto moderation rule +func (s *Session) AutoModerationRuleDelete(guildID, ruleID string) (err error) { + endpoint := EndpointGuildAutoModerationRule(guildID, ruleID) + _, err = s.RequestWithBucketID("DELETE", endpoint, nil, endpoint) + return +} diff --git a/structs.go b/structs.go index 98ad75829..f6b0b50e8 100644 --- a/structs.go +++ b/structs.go @@ -1309,6 +1309,88 @@ type GuildBan struct { User *User `json:"user"` } +// AutoModerationRule stores data for an auto moderation rule. +type AutoModerationRule struct { + ID string `json:"id,omitempty"` + GuildID string `json:"guild_id,omitempty"` + Name string `json:"name,omitempty"` + CreatorID string `json:"creator_id,omitempty"` + EventType AutoModerationRuleEventType `json:"event_type,omitempty"` + TriggerType AutoModerationRuleTriggerType `json:"trigger_type,omitempty"` + TriggerMetadata *AutoModerationTriggerMetadata `json:"trigger_metadata,omitempty"` + Actions []AutoModerationAction `json:"actions,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + ExemptRoles *[]string `json:"exempt_roles,omitempty"` + ExemptChannels *[]string `json:"exempt_channels,omitempty"` +} + +// AutoModerationRuleEventType indicates in what event context a rule should be checked. +type AutoModerationRuleEventType int + +// Auto moderation rule event types. +const ( + // AutoModerationEventMessageSend is checked when a member sends or edits a message in the guild + AutoModerationEventMessageSend AutoModerationRuleEventType = 1 +) + +// AutoModerationRuleTriggerType represents the type of content which can trigger the rule. +type AutoModerationRuleTriggerType int + +// Auto moderation rule trigger types. +const ( + AutoModerationEventTriggerKeyword AutoModerationRuleTriggerType = 1 + AutoModerationEventTriggerHarmfulLink AutoModerationRuleTriggerType = 2 + AutoModerationEventTriggerSpam AutoModerationRuleTriggerType = 3 + AutoModerationEventTriggerKeywordPreset AutoModerationRuleTriggerType = 4 +) + +// AutoModerationKeywordPreset represents an internally pre-defined wordset. +type AutoModerationKeywordPreset uint + +// Auto moderation keyword presets. +const ( + AutoModerationKeywordPresetProfanity AutoModerationKeywordPreset = 1 + AutoModerationKeywordPresetSexualContent AutoModerationKeywordPreset = 2 + AutoModerationKeywordPresetSlurs AutoModerationKeywordPreset = 3 +) + +// AutoModerationTriggerMetadata represents additional metadata used to determine whether rule should be triggered. +type AutoModerationTriggerMetadata struct { + // Substrings which will be searched for in content. + // NOTE: should be only used with keyword trigger type. + KeywordFilter []string `json:"keyword_filter,omitempty"` + // Internally pre-defined wordsets which will be searched for in content. + // NOTE: should be only used with keyword preset trigger type. + Presets []AutoModerationKeywordPreset `json:"presets,omitempty"` +} + +// AutoModerationActionType represents an action which will execute whenever a rule is triggered. +type AutoModerationActionType int + +// Auto moderation actions types. +const ( + AutoModerationRuleActionBlockMessage AutoModerationActionType = 1 + AutoModerationRuleActionSendAlertMessage AutoModerationActionType = 2 + AutoModerationRuleActionTimeout AutoModerationActionType = 3 +) + +// AutoModerationActionMetadata represents additional metadata needed during execution for a specific action type. +type AutoModerationActionMetadata struct { + // Channel to which user content should be logged. + // NOTE: should be only used with send alert message action type. + ChannelID string `json:"channel_id,omitempty"` + + // Timeout duration in seconds (maximum of 2419200 - 4 weeks). + // NOTE: should be only used with timeout action type. + Duration int `json:"duration_seconds,omitempty"` +} + +// AutoModerationAction stores data for an auto moderation action. +type AutoModerationAction struct { + Type AutoModerationActionType `json:"type"` + Metadata *AutoModerationActionMetadata `json:"metadata,omitempty"` +} + // A GuildEmbed stores data for a guild embed. type GuildEmbed struct { Enabled bool `json:"enabled"` @@ -2067,23 +2149,25 @@ type Intent int // Constants for the different bit offsets of intents const ( - IntentGuilds Intent = 1 << 0 - IntentGuildMembers Intent = 1 << 1 - IntentGuildBans Intent = 1 << 2 - IntentGuildEmojis Intent = 1 << 3 - IntentGuildIntegrations Intent = 1 << 4 - IntentGuildWebhooks Intent = 1 << 5 - IntentGuildInvites Intent = 1 << 6 - IntentGuildVoiceStates Intent = 1 << 7 - IntentGuildPresences Intent = 1 << 8 - IntentGuildMessages Intent = 1 << 9 - IntentGuildMessageReactions Intent = 1 << 10 - IntentGuildMessageTyping Intent = 1 << 11 - IntentDirectMessages Intent = 1 << 12 - IntentDirectMessageReactions Intent = 1 << 13 - IntentDirectMessageTyping Intent = 1 << 14 - IntentMessageContent Intent = 1 << 15 - IntentGuildScheduledEvents Intent = 1 << 16 + IntentGuilds Intent = 1 << 0 + IntentGuildMembers Intent = 1 << 1 + IntentGuildBans Intent = 1 << 2 + IntentGuildEmojis Intent = 1 << 3 + IntentGuildIntegrations Intent = 1 << 4 + IntentGuildWebhooks Intent = 1 << 5 + IntentGuildInvites Intent = 1 << 6 + IntentGuildVoiceStates Intent = 1 << 7 + IntentGuildPresences Intent = 1 << 8 + IntentGuildMessages Intent = 1 << 9 + IntentGuildMessageReactions Intent = 1 << 10 + IntentGuildMessageTyping Intent = 1 << 11 + IntentDirectMessages Intent = 1 << 12 + IntentDirectMessageReactions Intent = 1 << 13 + IntentDirectMessageTyping Intent = 1 << 14 + IntentMessageContent Intent = 1 << 15 + IntentGuildScheduledEvents Intent = 1 << 16 + IntentAutoModerationConfiguration Intent = 1 << 20 + IntentAutoModerationExecution Intent = 1 << 21 // TODO: remove when compatibility is not needed @@ -2118,7 +2202,9 @@ const ( IntentDirectMessages | IntentDirectMessageReactions | IntentDirectMessageTyping | - IntentGuildScheduledEvents + IntentGuildScheduledEvents | + IntentAutoModerationConfiguration | + IntentAutoModerationExecution IntentsAll = IntentsAllWithoutPrivileged | IntentGuildMembers | From 0feaae8f1b39cef894a8eab82aaeee6733eb0434 Mon Sep 17 00:00:00 2001 From: Fedor Lapshin Date: Fri, 15 Jul 2022 00:40:21 +0300 Subject: [PATCH 19/33] feat(ApplicationCommandOption): min and max length (#1208) Implement min_length and max_length for string option type. --- interactions.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/interactions.go b/interactions.go index 70509cde1..61a4e9920 100644 --- a/interactions.go +++ b/interactions.go @@ -118,6 +118,10 @@ type ApplicationCommandOption struct { MinValue *float64 `json:"min_value,omitempty"` // Maximum value of number/integer option. MaxValue float64 `json:"max_value,omitempty"` + // Minimum length of string option. + MinLength *int `json:"min_length,omitempty"` + // Maximum length of string option. + MaxLength int `json:"max_length,omitempty"` } // ApplicationCommandOptionChoice represents a slash command option choice. From 039e6855ede965be2285c5c1f4480b64a80ad806 Mon Sep 17 00:00:00 2001 From: plally Date: Fri, 29 Jul 2022 11:42:40 -0400 Subject: [PATCH 20/33] fix typo in InviteTargetEmbeddedApplication and in some comments (#1211) --- discord.go | 2 +- logging.go | 2 +- state.go | 2 +- structs.go | 10 +++++----- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/discord.go b/discord.go index db8535498..61d4e3339 100644 --- a/discord.go +++ b/discord.go @@ -46,7 +46,7 @@ func New(token string) (s *Session, err error) { LastHeartbeatAck: time.Now().UTC(), } - // Initilize the Identify Package with defaults + // Initialize the Identify Package with defaults // These can be modified prior to calling Open() s.Identify.Compress = true s.Identify.LargeThreshold = 250 diff --git a/logging.go b/logging.go index 41f0481f3..b798d3e87 100644 --- a/logging.go +++ b/logging.go @@ -90,7 +90,7 @@ func (v *VoiceConnection) log(msgL int, format string, a ...interface{}) { msglog(msgL, 2, format, a...) } -// printJSON is a helper function to display JSON data in a easy to read format. +// printJSON is a helper function to display JSON data in an easy to read format. /* NOT USED ATM func printJSON(body []byte) { var prettyJSON bytes.Buffer diff --git a/state.go b/state.go index eb87529e0..6404b71d3 100644 --- a/state.go +++ b/state.go @@ -7,7 +7,7 @@ // This file contains code related to state tracking. If enabled, state // tracking will capture the initial READY packet and many other websocket -// events and maintain an in-memory state of of guilds, channels, users, and +// events and maintain an in-memory state of guilds, channels, users, and // so forth. This information can be accessed through the Session.State struct. package discordgo diff --git a/structs.go b/structs.go index f6b0b50e8..3ea4b15fd 100644 --- a/structs.go +++ b/structs.go @@ -59,12 +59,12 @@ type Session struct { ShardCount int // Should state tracking be enabled. - // State tracking is the best way for getting the the users + // State tracking is the best way for getting the users // active guilds and the members of the guilds. StateEnabled bool // Whether or not to call event handlers synchronously. - // e.g false = launch event handlers in their own goroutines. + // e.g. false = launch event handlers in their own goroutines. SyncEvents bool // Exposed but should not be modified by User. @@ -75,7 +75,7 @@ type Session struct { // Max number of REST API retries MaxRestRetries int - // Status stores the currect status of the websocket connection + // Status stores the current status of the websocket connection // this is being tested, may stay, may go away. status int32 @@ -222,8 +222,8 @@ type InviteTargetType uint8 // Invite target types const ( - InviteTargetStream InviteTargetType = 1 - InviteTargetEmbeddedAppliction InviteTargetType = 2 + InviteTargetStream InviteTargetType = 1 + InviteTargetEmbeddedApplication InviteTargetType = 2 ) // A Invite stores all data related to a specific Discord Guild or Channel invite. From 8b18bf4dc7c9aef86fd3c8f706a11b28d064c859 Mon Sep 17 00:00:00 2001 From: David G Date: Fri, 29 Jul 2022 17:45:18 +0200 Subject: [PATCH 21/33] Allow setting bool properties of ChannelEdit to false (#1199) Setting e.g. ChannelEdit.Archived = false is currently not possible as go will treat a false as empty and will omit the property from the JSON object. --- examples/threads/main.go | 6 ++++-- structs.go | 10 +++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/examples/threads/main.go b/examples/threads/main.go index 9b6179184..5ea5f8c9c 100644 --- a/examples/threads/main.go +++ b/examples/threads/main.go @@ -48,9 +48,11 @@ func main() { games[m.ChannelID] = time.Now() <-time.After(timeout) if time.Since(games[m.ChannelID]) >= timeout { + archived := true + locked := true _, err := s.ChannelEditComplex(m.ChannelID, &discordgo.ChannelEdit{ - Archived: true, - Locked: true, + Archived: &archived, + Locked: &locked, }) if err != nil { panic(err) diff --git a/structs.go b/structs.go index 3ea4b15fd..781dff173 100644 --- a/structs.go +++ b/structs.go @@ -360,7 +360,7 @@ func (c *Channel) IsThread() bool { type ChannelEdit struct { Name string `json:"name,omitempty"` Topic string `json:"topic,omitempty"` - NSFW bool `json:"nsfw,omitempty"` + NSFW *bool `json:"nsfw,omitempty"` Position int `json:"position"` Bitrate int `json:"bitrate,omitempty"` UserLimit int `json:"user_limit,omitempty"` @@ -370,10 +370,10 @@ type ChannelEdit struct { // 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"` + Archived *bool `json:"archived,omitempty"` + AutoArchiveDuration int `json:"auto_archive_duration,omitempty"` + Locked *bool `json:"locked,omitempty"` + Invitable *bool `json:"invitable,omitempty"` } // A ChannelFollow holds data returned after following a news channel From f60fb54c52b630316b0948f9e8c8e8d6ff1f9eb3 Mon Sep 17 00:00:00 2001 From: Fedor Lapshin Date: Mon, 1 Aug 2022 18:50:50 +0300 Subject: [PATCH 22/33] feat: add contributing.md Add CONTRIBUTING.md doc to set guidelines for new contributions. --- CONTRIBUTING.md | 87 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..85e9680cd --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,87 @@ +# Getting started + +To start off you can check out existing Pull Requests and Issues to get a gasp of what problems we’re currently solving and what features you can implement. + +## Issues + +Our issues are mostly used for bugs, however we welcome refactoring and conceptual issues. + +Any other conversation would belong and would be moved into “Discussions”. + +## Discussions + +We use discussions for ideas, polls, announcements and help questions. + +Don’t hesitate to ask, we always would try to help. + +## Pull Requests + +If you want to help us by improving existing or adding new features, you create what’s called a Pull Request (aka PR). It allows us to review your code, suggest changes and merge it. + +Here are some tips on how to make a good first PR: + +- When creating a PR, please consider a distinctive name and description for it, so the maintainers can understand what your PR changes / adds / removes. +- It’s always a good idea to link documentation when implementing a new feature / endpoint +- If you’re resolving an issue, don’t forget to [link it](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) in the description. +- Enable the checkbox to allow maintainers to edit your PR and make commits in the PR branch when necessary. +- We may ask for changes, usually through suggestions or pull request comments. You can apply suggestions right in the UI. Any other change needs to be done manually. +- Don’t forget to mark PR comments resolved when you’re done applying the changes. +- Be patient and don’t close and reopen your PR when no one responds, sometimes it might be held for a while. There might be a lot of reasons: release preparation, the feature is not significant, maintainers are busy, etc. + + +When your changes are still incomplete (i.e. in Work In Progress state), you can still create a PR, but consider making it a draft. +To make a draft PR, you can change the type of PR by clicking to a triangle next to the “Create Pull Request” button. + +Once you’re done, you can mark it as “Ready for review”, and we’ll get right on it. + + +# Code style + +To standardize and make things less messy we have a certain code style, that is persistent throughout the codebase. + +## Naming + +### REST methods + +When naming a REST method, while it might seem counterintuitive, we specify the entity before the action verb (for GET endpoints we don’t specify one however). Here’s an example: + +> Endpoint name: Get Channel Message +> +> Method name: `ChannelMessage` + +> Endpoint name: Edit Channel Message +> +> Method name: `ChannelMessageEdit` + +### Parameter structures + +When making a complex REST endpoint, sometimes you might need to implement a `Param` structure. This structure contains parameters for certain endpoint/set of endpoints. + +- If an endpoint/set of endpoints have mostly same parameters, it’s a good idea to use a single `Param` structure for them. Here’s an example: + + > Endpoint: `GuildMemberEdit` + > + > `Param` structure: `GuildMemberParams` +- If an endpoint/set of endpoints have differentiating parameters, `Param` structure can be named after the endpoint’s verb. Here’s an example: + + > Endpoint: `ChannelMessageSendComplex` + > + > `Param` structure: `MessageSend` + + > Endpoint: `ChannelMessageEditComplex` + > + > `Param` structure: `MessageEdit` + +### Events + +When naming an event, we follow gateway’s internal naming (which often matches with the official event name in the docs). Here’s an example: + +> Event name: Interaction Create (`INTERACTION_CREATE`) +> +> Structure name: `InteractionCreate` + +## Returns + +In our REST functions we usually favor named returns instead of regular anonymous returns. This helps readability. + +Additionally we try to avoid naked return statements for functions with a long body. Since it’s easier to loose track of the return result. From c0803d021f34d62843a51c17984bac72bf1e9182 Mon Sep 17 00:00:00 2001 From: Corbett Date: Thu, 4 Aug 2022 11:51:19 -0700 Subject: [PATCH 23/33] Add omitempty to channel_id in MessageReference struct (#1002) --- message.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/message.go b/message.go index ce7c55f45..1ba6e4454 100644 --- a/message.go +++ b/message.go @@ -457,7 +457,7 @@ type MessageApplication struct { // MessageReference contains reference data sent with crossposted messages type MessageReference struct { MessageID string `json:"message_id"` - ChannelID string `json:"channel_id"` + ChannelID string `json:"channel_id,omitempty"` GuildID string `json:"guild_id,omitempty"` } From 262e8ba52b48ae1fcebd8b6a595631e8ce9f746c Mon Sep 17 00:00:00 2001 From: Wynell <65278392+uWynell@users.noreply.github.com> Date: Sat, 13 Aug 2022 23:42:42 +0300 Subject: [PATCH 24/33] Add dialer property in config (#1179) Co-authored-by: eeWynell --- discord.go | 3 +++ structs.go | 3 +++ wsapi.go | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/discord.go b/discord.go index 61d4e3339..336b3d225 100644 --- a/discord.go +++ b/discord.go @@ -17,6 +17,8 @@ import ( "net/http" "runtime" "time" + + "github.com/gorilla/websocket" ) // VERSION of DiscordGo, follows Semantic Versioning. (http://semver.org/) @@ -41,6 +43,7 @@ func New(token string) (s *Session, err error) { ShardCount: 1, MaxRestRetries: 3, Client: &http.Client{Timeout: (20 * time.Second)}, + Dialer: websocket.DefaultDialer, UserAgent: "DiscordBot (https://github.com/bwmarrin/discordgo, v" + VERSION + ")", sequence: new(int64), LastHeartbeatAck: time.Now().UTC(), diff --git a/structs.go b/structs.go index 781dff173..c8e476c23 100644 --- a/structs.go +++ b/structs.go @@ -95,6 +95,9 @@ type Session struct { // The http client used for REST requests Client *http.Client + // The dialer used for WebSocket connection + Dialer *websocket.Dialer + // The user agent used for REST APIs UserAgent string diff --git a/wsapi.go b/wsapi.go index dad3fb989..2579ee42c 100644 --- a/wsapi.go +++ b/wsapi.go @@ -77,7 +77,7 @@ func (s *Session) Open() error { s.log(LogInformational, "connecting to gateway %s", s.gateway) header := http.Header{} header.Add("accept-encoding", "zlib") - s.wsConn, _, err = websocket.DefaultDialer.Dial(s.gateway, header) + s.wsConn, _, err = s.Dialer.Dial(s.gateway, header) if err != nil { s.log(LogError, "error connecting to gateway %s, %s", s.gateway, err) s.gateway = "" // clear cached gateway From f7309cdb9ea616abb9483969a81d2e7d677007ff Mon Sep 17 00:00:00 2001 From: Dylan Lau Date: Sun, 14 Aug 2022 05:59:17 +0900 Subject: [PATCH 25/33] feat: add UserGuildMember (#1210) * feat(UserGuildMember): add UserGuildMember * Update restapi.go Co-authored-by: Fedor Lapshin Co-authored-by: Fedor Lapshin --- endpoints.go | 3 +-- restapi.go | 12 ++++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/endpoints.go b/endpoints.go index b0215fceb..375b75b58 100644 --- a/endpoints.go +++ b/endpoints.go @@ -46,8 +46,6 @@ var ( EndpointVoice = EndpointAPI + "/voice/" EndpointVoiceRegions = EndpointVoice + "regions" - // TODO: EndpointUserGuildMember - EndpointUser = func(uID string) string { return EndpointUsers + uID } EndpointUserAvatar = func(uID, aID string) string { return EndpointCDNAvatars + uID + "/" + aID + ".png" } EndpointUserAvatarAnimated = func(uID, aID string) string { return EndpointCDNAvatars + uID + "/" + aID + ".gif" } @@ -64,6 +62,7 @@ var ( EndpointUserGuilds = func(uID string) string { return EndpointUsers + uID + "/guilds" } EndpointUserGuild = func(uID, gID string) string { return EndpointUsers + uID + "/guilds/" + gID } + EndpointUserGuildMember = func(uID, gID string) string { return EndpointUserGuild(uID, gID) + "/member" } EndpointUserChannels = func(uID string) string { return EndpointUsers + uID + "/channels" } EndpointUserConnections = func(uID string) string { return EndpointUsers + uID + "/connections" } diff --git a/restapi.go b/restapi.go index 7dc8d1196..cf660495e 100644 --- a/restapi.go +++ b/restapi.go @@ -334,6 +334,18 @@ func (s *Session) UserChannelCreate(recipientID string) (st *Channel, err error) return } +// UserGuildMember returns a guild member object for the current user in the given Guild. +// guildID : ID of the guild +func (s *Session) UserGuildMember(guildID string) (st *Member, err error) { + body, err := s.RequestWithBucketID("GET", EndpointUserGuildMember("@me", guildID), nil, EndpointUserGuildMember("@me", guildID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + // UserGuilds returns an array of UserGuild structures for all guilds. // limit : The number guilds that can be returned. (max 100) // beforeID : If provided all guilds returned will be before given ID. From 7c87035a71787c6fcb562fd96a9a9847c10e00a9 Mon Sep 17 00:00:00 2001 From: Fedor Lapshin Date: Sun, 14 Aug 2022 00:04:52 +0300 Subject: [PATCH 26/33] feat(voice): use Dialer for connections (#1220) Use Session.Dialer for WebSocket connections. --- voice.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/voice.go b/voice.go index a4c3bf558..efd880900 100644 --- a/voice.go +++ b/voice.go @@ -304,7 +304,7 @@ func (v *VoiceConnection) open() (err error) { // Connect to VoiceConnection Websocket vg := "wss://" + strings.TrimSuffix(v.endpoint, ":80") v.log(LogInformational, "connecting to voice endpoint %s", vg) - v.wsConn, _, err = websocket.DefaultDialer.Dial(vg, nil) + v.wsConn, _, err = v.session.Dialer.Dial(vg, nil) if err != nil { v.log(LogWarning, "error connecting to voice endpoint %s, %s", vg, err) v.log(LogDebug, "voice struct: %#v\n", v) From 73ebf6733577dae9825615dd1933b967c7a0e6b4 Mon Sep 17 00:00:00 2001 From: Alfonso <47463368+Alfonsohh99@users.noreply.github.com> Date: Mon, 15 Aug 2022 19:52:49 +0200 Subject: [PATCH 27/33] GuildFeatures added to UserGuild (#1223) * GuildFeatures added to UserGuild * Better comment phrasing * Added GuildFeature type to Guild --- structs.go | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/structs.go b/structs.go index c8e476c23..9ad9c906e 100644 --- a/structs.go +++ b/structs.go @@ -699,7 +699,7 @@ type Guild struct { NSFWLevel GuildNSFWLevel `json:"nsfw_level"` // The list of enabled guild features - Features []string `json:"features"` + Features []GuildFeature `json:"features"` // Required MFA level for the guild MfaLevel MfaLevel `json:"mfa_level"` @@ -1030,13 +1030,42 @@ func (g *Guild) BannerURL() string { // A UserGuild holds a brief version of a Guild type UserGuild struct { - ID string `json:"id"` - Name string `json:"name"` - Icon string `json:"icon"` - Owner bool `json:"owner"` - Permissions int64 `json:"permissions,string"` + ID string `json:"id"` + Name string `json:"name"` + Icon string `json:"icon"` + Owner bool `json:"owner"` + Permissions int64 `json:"permissions,string"` + Features []GuildFeature `json:"features"` } +// GuildFeature indicates the presence of a feature in a guild +type GuildFeature string + +// Constants for GuildFeature +const ( + GuildFeatureAnimatedBanner GuildFeature = "ANIMATED_BANNER" + GuildFeatureAnimatedIcon GuildFeature = "ANIMATED_ICON" + GuildFeatureAutoModeration GuildFeature = "AUTO_MODERATION" + GuildFeatureBanner GuildFeature = "BANNER" + GuildFeatureCommunity GuildFeature = "COMMUNITY" + GuildFeatureDiscoverable GuildFeature = "DISCOVERABLE" + GuildFeatureFeaturable GuildFeature = "FEATURABLE" + GuildFeatureInviteSplash GuildFeature = "INVITE_SPLASH" + GuildFeatureMemberVerificationGateEnabled GuildFeature = "MEMBER_VERIFICATION_GATE_ENABLED" + GuildFeatureMonetizationEnabled GuildFeature = "MONETIZATION_ENABLED" + GuildFeatureMoreStickers GuildFeature = "MORE_STICKERS" + GuildFeatureNews GuildFeature = "NEWS" + GuildFeaturePartnered GuildFeature = "PARTNERED" + GuildFeaturePreviewEnabled GuildFeature = "PREVIEW_ENABLED" + GuildFeaturePrivateThreads GuildFeature = "PRIVATE_THREADS" + GuildFeatureRoleIcons GuildFeature = "ROLE_ICONS" + GuildFeatureTicketedEventsEnabled GuildFeature = "TICKETED_EVENTS_ENABLED" + GuildFeatureVanityURL GuildFeature = "VANITY_URL" + GuildFeatureVerified GuildFeature = "VERIFIED" + GuildFeatureVipRegions GuildFeature = "VIP_REGIONS" + GuildFeatureWelcomeScreenEnabled GuildFeature = "WELCOME_SCREEN_ENABLED" +) + // A GuildParams stores all the data needed to update discord guild settings type GuildParams struct { Name string `json:"name,omitempty"` From 3cee831e8b0ec7a93ce29ba4823979cc132d89f9 Mon Sep 17 00:00:00 2001 From: Fedor Lapshin Date: Wed, 17 Aug 2022 17:17:30 +0300 Subject: [PATCH 28/33] Add missing fields to GuildMemberParams (#1226) * feat(GuildMemberParams): add missing fields Add the following fields to GuildMemberParams * channel_id * mute * deaf * communication_disabled_until * fix(GuildMemberParams): null values Fix null values on omitted fields --- structs.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/structs.go b/structs.go index 9ad9c906e..416d5dd28 100644 --- a/structs.go +++ b/structs.go @@ -1727,10 +1727,57 @@ type UserGuildSettingsEdit struct { // GuildMemberParams stores data needed to update a member // https://discord.com/developers/docs/resources/guild#modify-guild-member type GuildMemberParams struct { - // Value to set user's nickname to + // Value to set user's nickname to. Nick string `json:"nick,omitempty"` - // Array of role ids the member is assigned + // Array of role ids the member is assigned. Roles *[]string `json:"roles,omitempty"` + // ID of channel to move user to (if they are connected to voice). + // Set to "" to remove user from a voice channel. + ChannelID *string `json:"channel_id,omitempty"` + // Whether the user is muted in voice channels. + Mute *bool `json:"mute,omitempty"` + // Whether the user is deafened in voice channels. + Deaf *bool `json:"deaf,omitempty"` + // When the user's timeout will expire and the user will be able + // to communicate in the guild again (up to 28 days in the future). + // Set to time.Time{} to remove timeout. + CommunicationDisabledUntil *time.Time `json:"communication_disabled_until,omitempty"` +} + +// MarshalJSON is a helper function to marshal GuildMemberParams. +func (p GuildMemberParams) MarshalJSON() (res []byte, err error) { + type guildMemberParams GuildMemberParams + v := struct { + guildMemberParams + ChannelID json.RawMessage `json:"channel_id,omitempty"` + CommunicationDisabledUntil json.RawMessage `json:"communication_disabled_until,omitempty"` + }{guildMemberParams: guildMemberParams(p)} + + if p.ChannelID != nil { + if *p.ChannelID == "" { + v.ChannelID = json.RawMessage(`null`) + } else { + res, err = json.Marshal(p.ChannelID) + if err != nil { + return + } + v.ChannelID = res + } + } + + if p.CommunicationDisabledUntil != nil { + if p.CommunicationDisabledUntil.IsZero() { + v.CommunicationDisabledUntil = json.RawMessage(`null`) + } else { + res, err = json.Marshal(p.CommunicationDisabledUntil) + if err != nil { + return + } + v.CommunicationDisabledUntil = res + } + } + + return json.Marshal(v) } // An APIErrorMessage is an api error message returned from discord From eee9bcb196b50ee94eafefa2dff43645839e7a96 Mon Sep 17 00:00:00 2001 From: Fedor Lapshin Date: Wed, 17 Aug 2022 17:50:21 +0300 Subject: [PATCH 29/33] Remove remaining undocumented structs and fields (#1225) * feat(events)!: drop undocumented structs and fields * feat(structs)!: drop undocumented structs and fields * fix(eventhandlers): regenerate event handling code * fix(Ready): add PrivateChannels back --- eventhandlers.go | 144 ----------------------------------------------- events.go | 48 ++-------------- structs.go | 83 +-------------------------- 3 files changed, 8 insertions(+), 267 deletions(-) diff --git a/eventhandlers.go b/eventhandlers.go index 36beefdf4..9f69a607c 100644 --- a/eventhandlers.go +++ b/eventhandlers.go @@ -41,7 +41,6 @@ const ( interactionCreateEventType = "INTERACTION_CREATE" inviteCreateEventType = "INVITE_CREATE" inviteDeleteEventType = "INVITE_DELETE" - messageAckEventType = "MESSAGE_ACK" messageCreateEventType = "MESSAGE_CREATE" messageDeleteEventType = "MESSAGE_DELETE" messageDeleteBulkEventType = "MESSAGE_DELETE_BULK" @@ -53,8 +52,6 @@ const ( presencesReplaceEventType = "PRESENCES_REPLACE" rateLimitEventType = "__RATE_LIMIT__" readyEventType = "READY" - relationshipAddEventType = "RELATIONSHIP_ADD" - relationshipRemoveEventType = "RELATIONSHIP_REMOVE" resumedEventType = "RESUMED" stageInstanceEventCreateEventType = "STAGE_INSTANCE_EVENT_CREATE" stageInstanceEventDeleteEventType = "STAGE_INSTANCE_EVENT_DELETE" @@ -66,9 +63,6 @@ const ( 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" @@ -740,26 +734,6 @@ func (eh inviteDeleteEventHandler) Handle(s *Session, i interface{}) { } } -// messageAckEventHandler is an event handler for MessageAck events. -type messageAckEventHandler func(*Session, *MessageAck) - -// Type returns the event type for MessageAck events. -func (eh messageAckEventHandler) Type() string { - return messageAckEventType -} - -// New returns a new instance of MessageAck. -func (eh messageAckEventHandler) New() interface{} { - return &MessageAck{} -} - -// Handle is the handler for MessageAck events. -func (eh messageAckEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*MessageAck); ok { - eh(s, t) - } -} - // messageCreateEventHandler is an event handler for MessageCreate events. type messageCreateEventHandler func(*Session, *MessageCreate) @@ -975,46 +949,6 @@ func (eh readyEventHandler) Handle(s *Session, i interface{}) { } } -// relationshipAddEventHandler is an event handler for RelationshipAdd events. -type relationshipAddEventHandler func(*Session, *RelationshipAdd) - -// Type returns the event type for RelationshipAdd events. -func (eh relationshipAddEventHandler) Type() string { - return relationshipAddEventType -} - -// New returns a new instance of RelationshipAdd. -func (eh relationshipAddEventHandler) New() interface{} { - return &RelationshipAdd{} -} - -// Handle is the handler for RelationshipAdd events. -func (eh relationshipAddEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*RelationshipAdd); ok { - eh(s, t) - } -} - -// relationshipRemoveEventHandler is an event handler for RelationshipRemove events. -type relationshipRemoveEventHandler func(*Session, *RelationshipRemove) - -// Type returns the event type for RelationshipRemove events. -func (eh relationshipRemoveEventHandler) Type() string { - return relationshipRemoveEventType -} - -// New returns a new instance of RelationshipRemove. -func (eh relationshipRemoveEventHandler) New() interface{} { - return &RelationshipRemove{} -} - -// Handle is the handler for RelationshipRemove events. -func (eh relationshipRemoveEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*RelationshipRemove); ok { - eh(s, t) - } -} - // resumedEventHandler is an event handler for Resumed events. type resumedEventHandler func(*Session, *Resumed) @@ -1235,66 +1169,6 @@ func (eh typingStartEventHandler) Handle(s *Session, i interface{}) { } } -// userGuildSettingsUpdateEventHandler is an event handler for UserGuildSettingsUpdate events. -type userGuildSettingsUpdateEventHandler func(*Session, *UserGuildSettingsUpdate) - -// Type returns the event type for UserGuildSettingsUpdate events. -func (eh userGuildSettingsUpdateEventHandler) Type() string { - return userGuildSettingsUpdateEventType -} - -// New returns a new instance of UserGuildSettingsUpdate. -func (eh userGuildSettingsUpdateEventHandler) New() interface{} { - return &UserGuildSettingsUpdate{} -} - -// Handle is the handler for UserGuildSettingsUpdate events. -func (eh userGuildSettingsUpdateEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*UserGuildSettingsUpdate); ok { - eh(s, t) - } -} - -// userNoteUpdateEventHandler is an event handler for UserNoteUpdate events. -type userNoteUpdateEventHandler func(*Session, *UserNoteUpdate) - -// Type returns the event type for UserNoteUpdate events. -func (eh userNoteUpdateEventHandler) Type() string { - return userNoteUpdateEventType -} - -// New returns a new instance of UserNoteUpdate. -func (eh userNoteUpdateEventHandler) New() interface{} { - return &UserNoteUpdate{} -} - -// Handle is the handler for UserNoteUpdate events. -func (eh userNoteUpdateEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*UserNoteUpdate); ok { - eh(s, t) - } -} - -// userSettingsUpdateEventHandler is an event handler for UserSettingsUpdate events. -type userSettingsUpdateEventHandler func(*Session, *UserSettingsUpdate) - -// Type returns the event type for UserSettingsUpdate events. -func (eh userSettingsUpdateEventHandler) Type() string { - return userSettingsUpdateEventType -} - -// New returns a new instance of UserSettingsUpdate. -func (eh userSettingsUpdateEventHandler) New() interface{} { - return &UserSettingsUpdate{} -} - -// Handle is the handler for UserSettingsUpdate events. -func (eh userSettingsUpdateEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*UserSettingsUpdate); ok { - eh(s, t) - } -} - // userUpdateEventHandler is an event handler for UserUpdate events. type userUpdateEventHandler func(*Session, *UserUpdate) @@ -1447,8 +1321,6 @@ func handlerForInterface(handler interface{}) EventHandler { return inviteCreateEventHandler(v) case func(*Session, *InviteDelete): return inviteDeleteEventHandler(v) - case func(*Session, *MessageAck): - return messageAckEventHandler(v) case func(*Session, *MessageCreate): return messageCreateEventHandler(v) case func(*Session, *MessageDelete): @@ -1471,10 +1343,6 @@ func handlerForInterface(handler interface{}) EventHandler { return rateLimitEventHandler(v) case func(*Session, *Ready): return readyEventHandler(v) - case func(*Session, *RelationshipAdd): - return relationshipAddEventHandler(v) - case func(*Session, *RelationshipRemove): - return relationshipRemoveEventHandler(v) case func(*Session, *Resumed): return resumedEventHandler(v) case func(*Session, *StageInstanceEventCreate): @@ -1497,12 +1365,6 @@ func handlerForInterface(handler interface{}) EventHandler { return threadUpdateEventHandler(v) case func(*Session, *TypingStart): return typingStartEventHandler(v) - case func(*Session, *UserGuildSettingsUpdate): - return userGuildSettingsUpdateEventHandler(v) - case func(*Session, *UserNoteUpdate): - return userNoteUpdateEventHandler(v) - case func(*Session, *UserSettingsUpdate): - return userSettingsUpdateEventHandler(v) case func(*Session, *UserUpdate): return userUpdateEventHandler(v) case func(*Session, *VoiceServerUpdate): @@ -1548,7 +1410,6 @@ func init() { registerInterfaceProvider(interactionCreateEventHandler(nil)) registerInterfaceProvider(inviteCreateEventHandler(nil)) registerInterfaceProvider(inviteDeleteEventHandler(nil)) - registerInterfaceProvider(messageAckEventHandler(nil)) registerInterfaceProvider(messageCreateEventHandler(nil)) registerInterfaceProvider(messageDeleteEventHandler(nil)) registerInterfaceProvider(messageDeleteBulkEventHandler(nil)) @@ -1559,8 +1420,6 @@ func init() { registerInterfaceProvider(presenceUpdateEventHandler(nil)) registerInterfaceProvider(presencesReplaceEventHandler(nil)) registerInterfaceProvider(readyEventHandler(nil)) - registerInterfaceProvider(relationshipAddEventHandler(nil)) - registerInterfaceProvider(relationshipRemoveEventHandler(nil)) registerInterfaceProvider(resumedEventHandler(nil)) registerInterfaceProvider(stageInstanceEventCreateEventHandler(nil)) registerInterfaceProvider(stageInstanceEventDeleteEventHandler(nil)) @@ -1572,9 +1431,6 @@ func init() { registerInterfaceProvider(threadMembersUpdateEventHandler(nil)) registerInterfaceProvider(threadUpdateEventHandler(nil)) registerInterfaceProvider(typingStartEventHandler(nil)) - registerInterfaceProvider(userGuildSettingsUpdateEventHandler(nil)) - registerInterfaceProvider(userNoteUpdateEventHandler(nil)) - registerInterfaceProvider(userSettingsUpdateEventHandler(nil)) registerInterfaceProvider(userUpdateEventHandler(nil)) registerInterfaceProvider(voiceServerUpdateEventHandler(nil)) registerInterfaceProvider(voiceStateUpdateEventHandler(nil)) diff --git a/events.go b/events.go index 5cb7622d7..e5d83b9bd 100644 --- a/events.go +++ b/events.go @@ -36,19 +36,13 @@ type Event struct { // A Ready stores all data for the websocket READY event. type Ready struct { - Version int `json:"v"` - SessionID string `json:"session_id"` - User *User `json:"user"` - ReadState []*ReadState `json:"read_state"` - PrivateChannels []*Channel `json:"private_channels"` - Guilds []*Guild `json:"guilds"` + Version int `json:"v"` + SessionID string `json:"session_id"` + User *User `json:"user"` + Guilds []*Guild `json:"guilds"` + PrivateChannels []*Channel `json:"private_channels"` - // Undocumented fields - Settings *Settings `json:"user_settings"` - UserGuildSettings []*UserGuildSettings `json:"user_guild_settings"` - Relationships []*Relationship `json:"relationships"` - Presences []*Presence `json:"presences"` - Notes map[string]string `json:"notes"` + // TODO: Application and Shard } // ChannelCreate is the data for a ChannelCreate event. @@ -245,12 +239,6 @@ type GuildScheduledEventUserRemove struct { GuildID string `json:"guild_id"` } -// MessageAck is the data for a MessageAck event. -type MessageAck struct { - MessageID string `json:"message_id"` - ChannelID string `json:"channel_id"` -} - // MessageCreate is the data for a MessageCreate event. type MessageCreate struct { *Message @@ -314,16 +302,6 @@ type Resumed struct { Trace []string `json:"_trace"` } -// RelationshipAdd is the data for a RelationshipAdd event. -type RelationshipAdd struct { - *Relationship -} - -// RelationshipRemove is the data for a RelationshipRemove event. -type RelationshipRemove struct { - *Relationship -} - // TypingStart is the data for a TypingStart event. type TypingStart struct { UserID string `json:"user_id"` @@ -337,20 +315,6 @@ type UserUpdate struct { *User } -// UserSettingsUpdate is the data for a UserSettingsUpdate event. -type UserSettingsUpdate map[string]interface{} - -// UserGuildSettingsUpdate is the data for a UserGuildSettingsUpdate event. -type UserGuildSettingsUpdate struct { - *UserGuildSettings -} - -// UserNoteUpdate is the data for a UserNoteUpdate event. -type UserNoteUpdate struct { - ID string `json:"id"` - Note string `json:"note"` -} - // VoiceServerUpdate is the data for a VoiceServerUpdate event. type VoiceServerUpdate struct { Token string `json:"token"` diff --git a/structs.go b/structs.go index 416d5dd28..276e0a49b 100644 --- a/structs.go +++ b/structs.go @@ -200,23 +200,8 @@ type IntegrationAccount struct { // A VoiceRegion stores data for a specific voice region server. type VoiceRegion struct { - ID string `json:"id"` - Name string `json:"name"` - Hostname string `json:"sample_hostname"` - Port int `json:"sample_port"` -} - -// A VoiceICE stores data for voice ICE servers. -type VoiceICE struct { - TTL string `json:"ttl"` - Servers []*ICEServer `json:"servers"` -} - -// A ICEServer stores data for a specific voice ICE server. -type ICEServer struct { - URL string `json:"url"` - Username string `json:"username"` - Credential string `json:"credential"` + ID string `json:"id"` + Name string `json:"name"` } // InviteTargetType indicates the type of target of an invite @@ -1244,25 +1229,6 @@ func (m *Member) AvatarURL(size string) string { } -// A Settings stores data for a specific users Discord client settings. -type Settings struct { - RenderEmbeds bool `json:"render_embeds"` - InlineEmbedMedia bool `json:"inline_embed_media"` - InlineAttachmentMedia bool `json:"inline_attachment_media"` - EnableTTSCommand bool `json:"enable_tts_command"` - MessageDisplayCompact bool `json:"message_display_compact"` - ShowCurrentGame bool `json:"show_current_game"` - ConvertEmoticons bool `json:"convert_emoticons"` - Locale string `json:"locale"` - Theme string `json:"theme"` - GuildPositions []string `json:"guild_positions"` - RestrictedGuilds []string `json:"restricted_guilds"` - FriendSourceFlags *FriendSourceFlags `json:"friend_source_flags"` - Status Status `json:"status"` - DetectPlatformAccounts bool `json:"detect_platform_accounts"` - DeveloperMode bool `json:"developer_mode"` -} - // Status type definition type Status string @@ -1275,20 +1241,6 @@ const ( StatusOffline Status = "offline" ) -// FriendSourceFlags stores ... TODO :) -type FriendSourceFlags struct { - All bool `json:"all"` - MutualGuilds bool `json:"mutual_guilds"` - MutualFriends bool `json:"mutual_friends"` -} - -// A Relationship between the logged in user and Relationship.User -type Relationship struct { - User *User `json:"user"` - Type int `json:"type"` // 1 = friend, 2 = blocked, 3 = incoming friend req, 4 = sent friend req - ID string `json:"id"` -} - // A TooManyRequests struct holds information received from Discord // when receiving a HTTP 429 response. type TooManyRequests struct { @@ -1324,11 +1276,6 @@ type ReadState struct { ID string `json:"id"` } -// An Ack is used to ack messages -type Ack struct { - Token string `json:"token"` -} - // A GuildRole stores data for guild roles. type GuildRole struct { Role *Role `json:"role"` @@ -1698,32 +1645,6 @@ const ( AuditLogActionApplicationCommandPermissionUpdate AuditLogAction = 121 ) -// A UserGuildSettingsChannelOverride stores data for a channel override for a users guild settings. -type UserGuildSettingsChannelOverride struct { - Muted bool `json:"muted"` - MessageNotifications int `json:"message_notifications"` - ChannelID string `json:"channel_id"` -} - -// A UserGuildSettings stores data for a users guild settings. -type UserGuildSettings struct { - SupressEveryone bool `json:"suppress_everyone"` - Muted bool `json:"muted"` - MobilePush bool `json:"mobile_push"` - MessageNotifications int `json:"message_notifications"` - GuildID string `json:"guild_id"` - ChannelOverrides []*UserGuildSettingsChannelOverride `json:"channel_overrides"` -} - -// A UserGuildSettingsEdit stores data for editing UserGuildSettings -type UserGuildSettingsEdit struct { - SupressEveryone bool `json:"suppress_everyone"` - Muted bool `json:"muted"` - MobilePush bool `json:"mobile_push"` - MessageNotifications int `json:"message_notifications"` - ChannelOverrides map[string]*UserGuildSettingsChannelOverride `json:"channel_overrides"` -} - // GuildMemberParams stores data needed to update a member // https://discord.com/developers/docs/resources/guild#modify-guild-member type GuildMemberParams struct { From 576ecf0003b354152b21ceab5f4157ef687b9655 Mon Sep 17 00:00:00 2001 From: Fedor Lapshin Date: Wed, 17 Aug 2022 18:41:11 +0300 Subject: [PATCH 30/33] REST methods cleanup (#1217) * refactor(GuildRoleEdit)!: use struct params Refactor GuildRoleEdit to accept parameters using a Params struct. Therefore also allow partial edits. * feat(GuildRoleCreate): initial parameters Make GuildRoleCreate accept a struct with initial parameters for a role * refactor(ChannelEdit)!: replace ChannelEditComplex Move funcitonality of ChannelEditComplex to ChannelEdit * chore: deprecate ChannelEditComplex * feat(GuildEmbed)!: allow partial edits * Make fields omitempty * Make `Enabled` field a pointer * refactor(GuildEmbedEdit)!: use params struct Refactor GuildEmbedEdit to accept parameters through GuildEmbed struct * refactor(GuildMemberEdit)!: replace GuildMemberEditComplex Move functionality fo GuildMemberEditComplex to GuildMemberEdit * chore: deprecate GuildMemberEditComplex * refactor(GuildEmojiCreate)!: use struct params Refactor GuildEmojiCreate to take parameters using EmojiParams struct. * refactor(GuildEmojiEdit)!: use struct params Refactor GuildEmojiEdit to take parameters using EmojiParams struct. * refactor(GuildMemberAdd)!: use struct params Refactor GuildMemberAdd to take parameters using GuildMemberAddParams struct. * refactor(GuildTemplateEdit)!: use struct params Refactor GuildTemplateEdit to take parameters using GuildTemplateParams struct. * refactor(GuildTemplateCreate)!: use struct params Refactor GuildTemplateCreate to take parameters using GuildTemplateParams struct. * chore(rest)!: use pointers for Params structs Refactor methods with Params struct to use pointers * chore: grammar and style fixes in comments --- restapi.go | 167 +++++++++++++++++------------------------------------ structs.go | 52 ++++++++++++++++- 2 files changed, 103 insertions(+), 116 deletions(-) diff --git a/restapi.go b/restapi.go index cf660495e..e5d48a44f 100644 --- a/restapi.go +++ b/restapi.go @@ -772,22 +772,10 @@ func (s *Session) GuildMember(guildID, userID string) (st *Member, err error) { } // GuildMemberAdd force joins a user to the guild. -// accessToken : Valid access_token for the user. // guildID : The ID of a Guild. // userID : The ID of a User. -// nick : Value to set users nickname to -// roles : A list of role ID's to set on the member. -// mute : If the user is muted. -// deaf : If the user is deafened. -func (s *Session) GuildMemberAdd(accessToken, guildID, userID, nick string, roles []string, mute, deaf bool) (err error) { - - data := struct { - AccessToken string `json:"access_token"` - Nick string `json:"nick,omitempty"` - Roles []string `json:"roles,omitempty"` - Mute bool `json:"mute,omitempty"` - Deaf bool `json:"deaf,omitempty"` - }{accessToken, nick, roles, mute, deaf} +// data : Parameters of the user to add. +func (s *Session) GuildMemberAdd(guildID, userID string, data *GuildMemberAddParams) (err error) { _, err = s.RequestWithBucketID("PUT", EndpointGuildMember(guildID, userID), data, EndpointGuildMember(guildID, "")) if err != nil { @@ -820,25 +808,11 @@ func (s *Session) GuildMemberDeleteWithReason(guildID, userID, reason string) (e return } -// GuildMemberEdit edits the roles of a member. -// guildID : The ID of a Guild. -// userID : The ID of a User. -// roles : A list of role ID's to set on the member. -func (s *Session) GuildMemberEdit(guildID, userID string, roles []string) (err error) { - - data := struct { - Roles []string `json:"roles"` - }{roles} - - _, err = s.RequestWithBucketID("PATCH", EndpointGuildMember(guildID, userID), data, EndpointGuildMember(guildID, "")) - return -} - -// GuildMemberEditComplex edits the nickname and roles of a member. +// GuildMemberEdit edits and returns updated member. // guildID : The ID of a Guild. // userID : The ID of a User. -// data : A GuildMemberEditData struct with the new nickname and roles -func (s *Session) GuildMemberEditComplex(guildID, userID string, data GuildMemberParams) (st *Member, err error) { +// data : Updated GuildMember data. +func (s *Session) GuildMemberEdit(guildID, userID string, data *GuildMemberParams) (st *Member, err error) { var body []byte body, err = s.RequestWithBucketID("PATCH", EndpointGuildMember(guildID, userID), data, EndpointGuildMember(guildID, "")) if err != nil { @@ -849,6 +823,15 @@ func (s *Session) GuildMemberEditComplex(guildID, userID string, data GuildMembe return } +// GuildMemberEditComplex edits the nickname and roles of a member. +// NOTE: deprecated, use GuildMemberEdit instead. +// guildID : The ID of a Guild. +// userID : The ID of a User. +// data : A GuildMemberEditData struct with the new nickname and roles +func (s *Session) GuildMemberEditComplex(guildID, userID string, data *GuildMemberParams) (st *Member, err error) { + return s.GuildMemberEdit(guildID, userID, data) +} + // GuildMemberMove moves a guild member from one voice channel to another/none // guildID : The ID of a Guild. // userID : The ID of a User. @@ -1043,11 +1026,11 @@ func (s *Session) GuildRoles(guildID string) (st []*Role, err error) { return // TODO return pointer } -// GuildRoleCreate returns a new Guild Role. -// guildID: The ID of a Guild. -func (s *Session) GuildRoleCreate(guildID string) (st *Role, err error) { - - body, err := s.RequestWithBucketID("POST", EndpointGuildRoles(guildID), nil, EndpointGuildRoles(guildID)) +// GuildRoleCreate creates a new Guild Role and returns it. +// guildID : The ID of a Guild. +// data : New Role parameters. +func (s *Session) GuildRoleCreate(guildID string, data *RoleParams) (st *Role, err error) { + body, err := s.RequestWithBucketID("POST", EndpointGuildRoles(guildID), data, EndpointGuildRoles(guildID)) if err != nil { return } @@ -1057,30 +1040,17 @@ func (s *Session) GuildRoleCreate(guildID string) (st *Role, err error) { return } -// GuildRoleEdit updates an existing Guild Role with new values +// GuildRoleEdit updates an existing Guild Role and returns updated Role data. // guildID : The ID of a Guild. // roleID : The ID of a Role. -// name : The name of the Role. -// color : The color of the role (decimal, not hex). -// hoist : Whether to display the role's users separately. -// perm : The permissions for the role. -// mention : Whether this role is mentionable -func (s *Session) GuildRoleEdit(guildID, roleID, name string, color int, hoist bool, perm int64, mention bool) (st *Role, err error) { +// data : Updated Role data. +func (s *Session) GuildRoleEdit(guildID, roleID string, data *RoleParams) (st *Role, err error) { // Prevent sending a color int that is too big. - if color > 0xFFFFFF { - err = fmt.Errorf("color value cannot be larger than 0xFFFFFF") - return nil, err + if data.Color != nil && *data.Color > 0xFFFFFF { + return nil, fmt.Errorf("color value cannot be larger than 0xFFFFFF") } - data := struct { - Name string `json:"name"` // The role's name (overwrites existing) - Color int `json:"color"` // The color the role should have (as a decimal, not hex) - Hoist bool `json:"hoist"` // Whether to display the role's users separately - Permissions int64 `json:"permissions,string"` // The overall permissions number of the role (overwrites existing) - Mentionable bool `json:"mentionable"` // Whether this role is mentionable - }{name, color, hoist, perm, mention} - body, err := s.RequestWithBucketID("PATCH", EndpointGuildRole(guildID, roleID), data, EndpointGuildRole(guildID, "")) if err != nil { return @@ -1298,12 +1268,10 @@ func (s *Session) GuildEmbed(guildID string) (st *GuildEmbed, err error) { return } -// GuildEmbedEdit returns the embed for a Guild. +// GuildEmbedEdit edits the embed of a Guild. // guildID : The ID of a Guild. -func (s *Session) GuildEmbedEdit(guildID string, enabled bool, channelID string) (err error) { - - data := GuildEmbed{enabled, channelID} - +// data : New GuildEmbed data. +func (s *Session) GuildEmbedEdit(guildID string, data *GuildEmbed) (err error) { _, err = s.RequestWithBucketID("PATCH", EndpointGuildEmbed(guildID), data, EndpointGuildEmbed(guildID)) return } @@ -1371,19 +1339,10 @@ func (s *Session) GuildEmoji(guildID, emojiID string) (emoji *Emoji, err error) return } -// GuildEmojiCreate creates a new emoji +// GuildEmojiCreate creates a new Emoji. // guildID : The ID of a Guild. -// name : The Name of the Emoji. -// image : The base64 encoded emoji image, has to be smaller than 256KB. -// roles : The roles for which this emoji will be whitelisted, can be nil. -func (s *Session) GuildEmojiCreate(guildID, name, image string, roles []string) (emoji *Emoji, err error) { - - data := struct { - Name string `json:"name"` - Image string `json:"image"` - Roles []string `json:"roles,omitempty"` - }{name, image, roles} - +// data : New Emoji data. +func (s *Session) GuildEmojiCreate(guildID string, data *EmojiParams) (emoji *Emoji, err error) { body, err := s.RequestWithBucketID("POST", EndpointGuildEmojis(guildID), data, EndpointGuildEmojis(guildID)) if err != nil { return @@ -1393,18 +1352,11 @@ func (s *Session) GuildEmojiCreate(guildID, name, image string, roles []string) return } -// GuildEmojiEdit modifies an emoji +// GuildEmojiEdit modifies and returns updated Emoji. // guildID : The ID of a Guild. // emojiID : The ID of an Emoji. -// name : The Name of the Emoji. -// roles : The roles for which this emoji will be whitelisted, if nil or empty the roles will be reset. -func (s *Session) GuildEmojiEdit(guildID, emojiID, name string, roles []string) (emoji *Emoji, err error) { - - data := struct { - Name string `json:"name"` - Roles []string `json:"roles"` - }{name, roles} - +// data : Updated Emoji data. +func (s *Session) GuildEmojiEdit(guildID, emojiID string, data *EmojiParams) (emoji *Emoji, err error) { body, err := s.RequestWithBucketID("PATCH", EndpointGuildEmoji(guildID, emojiID), data, EndpointGuildEmojis(guildID)) if err != nil { return @@ -1470,16 +1422,9 @@ func (s *Session) GuildTemplates(guildID string) (st []*GuildTemplate, err error } // GuildTemplateCreate creates a template for the guild -// guildID: The ID of the guild -// name: The name of the template (1-100 characters) -// description: The description for the template (0-120 characters) -func (s *Session) GuildTemplateCreate(guildID, name, description string) (st *GuildTemplate) { - - data := struct { - Name string `json:"name"` - Description string `json:"description"` - }{name, description} - +// guildID : The ID of the guild +// data : Template metadata +func (s *Session) GuildTemplateCreate(guildID string, data *GuildTemplateParams) (st *GuildTemplate) { body, err := s.RequestWithBucketID("POST", EndpointGuildTemplates(guildID), data, EndpointGuildTemplates(guildID)) if err != nil { return @@ -1499,16 +1444,10 @@ func (s *Session) GuildTemplateSync(guildID, templateCode string) (err error) { } // GuildTemplateEdit modifies the template's metadata -// guildID: The ID of the guild -// templateCode: The code of the template -// name: The name of the template (1-100 characters) -// description: The description for the template (0-120 characters) -func (s *Session) GuildTemplateEdit(guildID, templateCode, name, description string) (st *GuildTemplate, err error) { - - data := struct { - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - }{name, description} +// guildID : The ID of the guild +// templateCode : The code of the template +// data : New template metadata +func (s *Session) GuildTemplateEdit(guildID, templateCode string, data *GuildTemplateParams) (st *GuildTemplate, err error) { body, err := s.RequestWithBucketID("PATCH", EndpointGuildTemplateSync(guildID, templateCode), data, EndpointGuildTemplateSync(guildID, "")) if err != nil { @@ -1544,19 +1483,10 @@ func (s *Session) Channel(channelID string) (st *Channel, err error) { return } -// ChannelEdit edits the given channel -// channelID : The ID of a Channel -// name : The new name to assign the channel. -func (s *Session) ChannelEdit(channelID, name string) (*Channel, error) { - return s.ChannelEditComplex(channelID, &ChannelEdit{ - Name: name, - }) -} - -// ChannelEditComplex edits an existing channel, replacing the parameters entirely with ChannelEdit struct -// channelID : The ID of a Channel -// data : The channel struct to send -func (s *Session) ChannelEditComplex(channelID string, data *ChannelEdit) (st *Channel, err error) { +// ChannelEdit edits the given channel and returns the updated Channel data. +// channelID : The ID of a Channel. +// data : New Channel data. +func (s *Session) ChannelEdit(channelID string, data *ChannelEdit) (st *Channel, err error) { body, err := s.RequestWithBucketID("PATCH", EndpointChannel(channelID), data, EndpointChannel(channelID)) if err != nil { return @@ -1564,6 +1494,15 @@ func (s *Session) ChannelEditComplex(channelID string, data *ChannelEdit) (st *C err = unmarshal(body, &st) return + +} + +// ChannelEditComplex edits an existing channel, replacing the parameters entirely with ChannelEdit struct +// NOTE: deprecated, use ChannelEdit instead +// channelID : The ID of a Channel +// data : The channel struct to send +func (s *Session) ChannelEditComplex(channelID string, data *ChannelEdit) (st *Channel, err error) { + return s.ChannelEdit(channelID, data) } // ChannelDelete deletes the given channel diff --git a/structs.go b/structs.go index 276e0a49b..26f507a9b 100644 --- a/structs.go +++ b/structs.go @@ -479,6 +479,17 @@ func (e *Emoji) APIName() string { return e.ID } +// EmojiParams represents parameters needed to create or update an Emoji. +type EmojiParams struct { + // Name of the emoji + Name string `json:"name,omitempty"` + // A base64 encoded emoji image, has to be smaller than 256KB. + // NOTE: can be only set on creation. + Image string `json:"image,omitempty"` + // Roles for which this emoji will be available. + Roles []string `json:"roles,omitempty"` +} + // StickerFormat is the file format of the Sticker. type StickerFormat int @@ -972,6 +983,14 @@ type GuildTemplate struct { IsDirty bool `json:"is_dirty"` } +// GuildTemplateParams stores the data needed to create or update a GuildTemplate. +type GuildTemplateParams struct { + // The name of the template (1-100 characters) + Name string `json:"name,omitempty"` + // The description of the template (0-120 characters) + Description string `json:"description,omitempty"` +} + // MessageNotifications is the notification level for a guild // https://discord.com/developers/docs/resources/guild#guild-object-default-message-notification-level type MessageNotifications int @@ -1100,6 +1119,20 @@ func (r *Role) Mention() string { return fmt.Sprintf("<@&%s>", r.ID) } +// RoleParams represents the parameters needed to create or update a Role +type RoleParams struct { + // The role's name + Name string `json:"name,omitempty"` + // The color the role should have (as a decimal, not hex) + Color *int `json:"color,omitempty"` + // Whether to display the role's users separately + Hoist *bool `json:"hoist,omitempty"` + // The overall permissions number of the role + Permissions *int64 `json:"permissions,omitempty,string"` + // Whether this role is mentionable + Mentionable *bool `json:"mentionable,omitempty"` +} + // Roles are a collection of Role type Roles []*Role @@ -1372,8 +1405,8 @@ type AutoModerationAction struct { // A GuildEmbed stores data for a guild embed. type GuildEmbed struct { - Enabled bool `json:"enabled"` - ChannelID string `json:"channel_id"` + Enabled *bool `json:"enabled,omitempty"` + ChannelID string `json:"channel_id,omitempty"` } // A GuildAuditLog stores data for a guild audit log. @@ -1701,6 +1734,21 @@ func (p GuildMemberParams) MarshalJSON() (res []byte, err error) { return json.Marshal(v) } +// GuildMemberAddParams stores data needed to add a user to a guild. +// NOTE: All fields are optional, except AccessToken. +type GuildMemberAddParams struct { + // Valid access_token for the user. + AccessToken string `json:"access_token"` + // Value to set users nickname to. + Nick string `json:"nick,omitempty"` + // A list of role ID's to set on the member. + Roles []string `json:"roles,omitempty"` + // Whether the user is muted. + Mute bool `json:"mute,omitempty"` + // Whether the user is deafened. + Deaf bool `json:"deaf,omitempty"` +} + // An APIErrorMessage is an api error message returned from discord type APIErrorMessage struct { Code int `json:"code"` From 622a7d890265424e6326294ab0294e3ef0e9f408 Mon Sep 17 00:00:00 2001 From: Fedor Lapshin Date: Wed, 17 Aug 2022 20:22:00 +0300 Subject: [PATCH 31/33] chore(GuildEdit)!: use a pointer for GuildParams (#1228) --- restapi.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/restapi.go b/restapi.go index e5d48a44f..79af46808 100644 --- a/restapi.go +++ b/restapi.go @@ -553,7 +553,7 @@ func (s *Session) GuildCreate(name string) (st *Guild, err error) { // GuildEdit edits a new Guild // guildID : The ID of a Guild // g : A GuildParams struct with the values Name, Region and VerificationLevel defined. -func (s *Session) GuildEdit(guildID string, g GuildParams) (st *Guild, err error) { +func (s *Session) GuildEdit(guildID string, g *GuildParams) (st *Guild, err error) { // Bounds checking for VerificationLevel, interval: [0, 4] if g.VerificationLevel != nil { From aa9af1488f6e4d39393bd4a5c85667f65f6bfad8 Mon Sep 17 00:00:00 2001 From: Fedor Lapshin Date: Wed, 17 Aug 2022 20:56:19 +0300 Subject: [PATCH 32/33] feat(*): bump version to 0.26.0 --- discord.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord.go b/discord.go index 336b3d225..d628f0e94 100644 --- a/discord.go +++ b/discord.go @@ -22,7 +22,7 @@ import ( ) // VERSION of DiscordGo, follows Semantic Versioning. (http://semver.org/) -const VERSION = "0.25.0" +const VERSION = "0.26.0" // New creates a new Discord session with provided token. // If the token is for a bot, it must be prefixed with "Bot " From fea3d775743828815cb5b9cd807fb8929446b7f1 Mon Sep 17 00:00:00 2001 From: Fedor Lapshin Date: Mon, 29 Aug 2022 18:10:38 +0300 Subject: [PATCH 33/33] feat(*): bump version to 0.26.1 --- discord.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord.go b/discord.go index d628f0e94..fc7b6d7a3 100644 --- a/discord.go +++ b/discord.go @@ -22,7 +22,7 @@ import ( ) // VERSION of DiscordGo, follows Semantic Versioning. (http://semver.org/) -const VERSION = "0.26.0" +const VERSION = "0.26.1" // New creates a new Discord session with provided token. // If the token is for a bot, it must be prefixed with "Bot "