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. 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. diff --git a/discord.go b/discord.go index 61a341578..53440be2a 100644 --- a/discord.go +++ b/discord.go @@ -18,10 +18,12 @@ import ( "runtime" "strings" "time" + + "github.com/gorilla/websocket" ) // VERSION of DiscordGo, follows Semantic Versioning. (http://semver.org/) -const VERSION = "0.25.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 " @@ -41,12 +43,13 @@ 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(), } - // 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/endpoints.go b/endpoints.go index 38970ee54..68547913e 100644 --- a/endpoints.go +++ b/endpoints.go @@ -48,8 +48,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" } @@ -66,10 +64,14 @@ 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" } 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 d0e382f8a..9f69a607c 100644 --- a/eventhandlers.go +++ b/eventhandlers.go @@ -7,69 +7,168 @@ 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" + 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" + 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" + 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" + 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" + 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) + } +} + +// 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) @@ -455,66 +554,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) @@ -695,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) @@ -930,62 +949,82 @@ func (eh readyEventHandler) Handle(s *Session, i interface{}) { } } -// relationshipAddEventHandler is an event handler for RelationshipAdd events. -type relationshipAddEventHandler func(*Session, *RelationshipAdd) +// resumedEventHandler is an event handler for Resumed events. +type resumedEventHandler func(*Session, *Resumed) -// Type returns the event type for RelationshipAdd events. -func (eh relationshipAddEventHandler) Type() string { - return relationshipAddEventType +// Type returns the event type for Resumed events. +func (eh resumedEventHandler) Type() string { + return resumedEventType } -// New returns a new instance of RelationshipAdd. -func (eh relationshipAddEventHandler) New() interface{} { - return &RelationshipAdd{} +// New returns a new instance of Resumed. +func (eh resumedEventHandler) New() interface{} { + return &Resumed{} } -// Handle is the handler for RelationshipAdd events. -func (eh relationshipAddEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*RelationshipAdd); ok { +// Handle is the handler for Resumed events. +func (eh resumedEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*Resumed); ok { eh(s, t) } } -// relationshipRemoveEventHandler is an event handler for RelationshipRemove events. -type relationshipRemoveEventHandler func(*Session, *RelationshipRemove) +// stageInstanceEventCreateEventHandler is an event handler for StageInstanceEventCreate events. +type stageInstanceEventCreateEventHandler func(*Session, *StageInstanceEventCreate) -// Type returns the event type for RelationshipRemove events. -func (eh relationshipRemoveEventHandler) Type() string { - return relationshipRemoveEventType +// Type returns the event type for StageInstanceEventCreate events. +func (eh stageInstanceEventCreateEventHandler) Type() string { + return stageInstanceEventCreateEventType } -// New returns a new instance of RelationshipRemove. -func (eh relationshipRemoveEventHandler) New() interface{} { - return &RelationshipRemove{} +// New returns a new instance of StageInstanceEventCreate. +func (eh stageInstanceEventCreateEventHandler) New() interface{} { + return &StageInstanceEventCreate{} } -// Handle is the handler for RelationshipRemove events. -func (eh relationshipRemoveEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*RelationshipRemove); ok { +// Handle is the handler for StageInstanceEventCreate events. +func (eh stageInstanceEventCreateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*StageInstanceEventCreate); ok { eh(s, t) } } -// resumedEventHandler is an event handler for Resumed events. -type resumedEventHandler func(*Session, *Resumed) +// stageInstanceEventDeleteEventHandler is an event handler for StageInstanceEventDelete events. +type stageInstanceEventDeleteEventHandler func(*Session, *StageInstanceEventDelete) -// Type returns the event type for Resumed events. -func (eh resumedEventHandler) Type() string { - return resumedEventType +// Type returns the event type for StageInstanceEventDelete events. +func (eh stageInstanceEventDeleteEventHandler) Type() string { + return stageInstanceEventDeleteEventType } -// New returns a new instance of Resumed. -func (eh resumedEventHandler) New() interface{} { - return &Resumed{} +// New returns a new instance of StageInstanceEventDelete. +func (eh stageInstanceEventDeleteEventHandler) New() interface{} { + return &StageInstanceEventDelete{} } -// Handle is the handler for Resumed events. -func (eh resumedEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*Resumed); ok { +// 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) } } @@ -1130,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) @@ -1274,6 +1253,16 @@ 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, *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): @@ -1332,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): @@ -1356,12 +1343,14 @@ 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): + 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): @@ -1376,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): @@ -1396,6 +1379,11 @@ 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)) @@ -1422,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)) @@ -1433,9 +1420,10 @@ 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)) + registerInterfaceProvider(stageInstanceEventUpdateEventHandler(nil)) registerInterfaceProvider(threadCreateEventHandler(nil)) registerInterfaceProvider(threadDeleteEventHandler(nil)) registerInterfaceProvider(threadListSyncEventHandler(nil)) @@ -1443,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 d3e73bc6f..43b0c9f37 100644 --- a/events.go +++ b/events.go @@ -36,14 +36,14 @@ 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 *ReadStateList `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"` + PrivateChannels []*Channel `json:"private_channels"` + Guilds []*Guild `json:"guilds"` // Undocumented fields + ReadState *ReadStateList `json:"read_state"` Settings *Settings `json:"user_settings"` UserGuildSettings *UserGuildSettingsList `json:"user_guild_settings"` Relationships []*Relationship `json:"relationships"` @@ -51,6 +51,8 @@ type Ready struct { Notes map[string]string `json:"notes"` Users []*User `json:"users"` + + // TODO: Application and Shard } // ChannelCreate is the data for a ChannelCreate event. @@ -247,13 +249,6 @@ type GuildScheduledEventUserRemove struct { GuildID string `json:"guild_id"` } -// MessageAck is the data for a MessageAck event. -type MessageAck struct { - Version int `json:"version"` - MessageID string `json:"message_id"` - ChannelID string `json:"channel_id"` -} - // MessageCreate is the data for a MessageCreate event. type MessageCreate struct { *Message @@ -317,16 +312,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"` @@ -340,20 +325,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"` @@ -404,3 +375,38 @@ type InviteDelete struct { GuildID string `json:"guild_id"` Code string `json:"code"` } + +// ApplicationCommandPermissionsUpdate is the data for an ApplicationCommandPermissionsUpdate event +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/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 b464a4795..a70cd4455 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 := "" @@ -389,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{ @@ -415,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!", }, }) @@ -430,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/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/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 { diff --git a/interactions.go b/interactions.go index 7164f65ee..61a4e9920 100644 --- a/interactions.go +++ b/interactions.go @@ -9,6 +9,7 @@ import ( "io" "io/ioutil" "net/http" + "strconv" "time" ) @@ -32,11 +33,15 @@ 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"` 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. @@ -113,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. @@ -129,6 +138,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 +168,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. @@ -190,6 +212,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. @@ -517,9 +542,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/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/message.go b/message.go index 864b29ac0..3bca442f5 100644 --- a/message.go +++ b/message.go @@ -323,6 +323,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. @@ -457,7 +460,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"` } diff --git a/restapi.go b/restapi.go index 518319807..17c93403b 100644 --- a/restapi.go +++ b/restapi.go @@ -504,6 +504,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. @@ -725,7 +737,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 { @@ -944,22 +956,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 { @@ -992,25 +992,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 { @@ -1021,6 +1007,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. @@ -1215,11 +1210,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 } @@ -1229,30 +1224,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 @@ -1479,12 +1461,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 } @@ -1552,19 +1532,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 @@ -1574,18 +1545,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 @@ -1651,16 +1615,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 @@ -1680,16 +1637,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 { @@ -1725,19 +1676,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 @@ -1745,6 +1687,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 @@ -1951,6 +1902,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 @@ -3105,7 +3078,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, "GET "+endpoint) if err != nil { return } @@ -3153,6 +3126,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) @@ -3164,6 +3139,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) @@ -3398,3 +3375,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/state.go b/state.go index a25d55f50..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 @@ -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 { diff --git a/structs.go b/structs.go index 62c491d98..0be6512c5 100644 --- a/structs.go +++ b/structs.go @@ -61,12 +61,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. @@ -77,7 +77,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 @@ -97,6 +97,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 @@ -206,23 +209,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 @@ -231,8 +219,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. @@ -255,6 +243,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 @@ -369,7 +359,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"` @@ -379,10 +369,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 @@ -502,6 +492,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 @@ -711,7 +712,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"` @@ -963,19 +964,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"` @@ -999,6 +1000,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 @@ -1042,13 +1051,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"` @@ -1101,6 +1139,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 @@ -1118,15 +1170,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. @@ -1213,9 +1269,10 @@ func (m *Member) Mention() string { } // AvatarURL returns the URL of the member's avatar -// size: The size of the user's avatar as a power of two -// if size is an empty string, no size parameter will -// be added to the URL. +// +// size: The size of the user's avatar as a power of two +// if size is an empty string, no size parameter will +// be added to the URL. func (m *Member) AvatarURL(size string) string { if m.Avatar == "" { return m.User.AvatarURL(size) @@ -1226,25 +1283,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 @@ -1257,20 +1295,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 { @@ -1349,10 +1373,92 @@ 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"` - 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. @@ -1620,47 +1726,79 @@ const ( AuditLogActionThreadCreate AuditLogAction = 110 AuditLogActionThreadUpdate AuditLogAction = 111 AuditLogActionThreadDelete AuditLogAction = 112 -) - -// 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"` -} -type UserGuildSettingsList struct { - Version int `json:"version"` - Partial bool `json:"partial"` - Entries []*UserGuildSettings `json:"entries"` -} - -// 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"` -} + AuditLogActionApplicationCommandPermissionUpdate AuditLogAction = 121 +) // 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) +} + +// 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 @@ -2115,23 +2253,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 @@ -2166,7 +2306,9 @@ const ( IntentDirectMessages | IntentDirectMessageReactions | IntentDirectMessageTyping | - IntentGuildScheduledEvents + IntentGuildScheduledEvents | + IntentAutoModerationConfiguration | + IntentAutoModerationExecution IntentsAll = IntentsAllWithoutPrivileged | IntentGuildMembers | diff --git a/voice.go b/voice.go index aedb87904..efd880900 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 } @@ -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) @@ -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() diff --git a/webhook.go b/webhook.go index f54a45ce1..9209b7095 100644 --- a/webhook.go +++ b/webhook.go @@ -35,15 +35,16 @@ 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. 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"` } diff --git a/wsapi.go b/wsapi.go index 4a9f9585f..ba320a546 100644 --- a/wsapi.go +++ b/wsapi.go @@ -95,9 +95,11 @@ func (s *Session) Open() error { for k, v := range DroidWSHeaders { header.Add(k, v) } + } else { + 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 @@ -735,10 +737,10 @@ type voiceChannelJoinOp struct { // ChannelVoiceJoin joins the session user to a voice channel. // -// gID : Guild ID of the channel to join. -// cID : Channel ID of the channel to join. -// mute : If true, you will be set to muted upon joining. -// deaf : If true, you will be set to deafened upon joining. +// gID : Guild ID of the channel to join. +// cID : Channel ID of the channel to join. +// mute : If true, you will be set to muted upon joining. +// deaf : If true, you will be set to deafened upon joining. func (s *Session) ChannelVoiceJoin(gID, cID string, mute, deaf bool) (voice *VoiceConnection, err error) { s.log(LogInformational, "called") @@ -782,10 +784,10 @@ func (s *Session) ChannelVoiceJoin(gID, cID string, mute, deaf bool) (voice *Voi // // This should only be used when the VoiceServerUpdate will be intercepted and used elsewhere. // -// gID : Guild ID of the channel to join. -// cID : Channel ID of the channel to join, leave empty to disconnect. -// mute : If true, you will be set to muted upon joining. -// deaf : If true, you will be set to deafened upon joining. +// gID : Guild ID of the channel to join. +// cID : Channel ID of the channel to join, leave empty to disconnect. +// mute : If true, you will be set to muted upon joining. +// deaf : If true, you will be set to deafened upon joining. func (s *Session) ChannelVoiceJoinManual(gID, cID string, mute, deaf bool) (err error) { s.log(LogInformational, "called")