From 09e3d894b734d812cc53927b87bf7e503e61d9f7 Mon Sep 17 00:00:00 2001 From: nitroflap Date: Thu, 9 Dec 2021 04:03:43 +0300 Subject: [PATCH 01/35] feat: modal interactions and text input component --- components.go | 42 +++++++++++ examples/modals/main.go | 159 ++++++++++++++++++++++++++++++++++++++++ interactions.go | 56 +++++++++++++- 3 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 examples/modals/main.go diff --git a/components.go b/components.go index 2ad19a07d..b7b4708e7 100644 --- a/components.go +++ b/components.go @@ -13,6 +13,7 @@ const ( ActionsRowComponent ComponentType = 1 ButtonComponent ComponentType = 2 SelectMenuComponent ComponentType = 3 + InputTextComponent ComponentType = 4 ) // MessageComponent is a base interface for all message components. @@ -42,6 +43,8 @@ func (umc *unmarshalableMessageComponent) UnmarshalJSON(src []byte) error { umc.MessageComponent = &Button{} case SelectMenuComponent: umc.MessageComponent = &SelectMenu{} + case InputTextComponent: + umc.MessageComponent = &InputText{} default: return fmt.Errorf("unknown component type: %d", v.Type) } @@ -195,3 +198,42 @@ func (m SelectMenu) MarshalJSON() ([]byte, error) { Type: m.Type(), }) } + +// InputText represents text input component. +type InputText struct { + CustomID string `json:"custom_id,omitempty"` + Label string `json:"label"` + Style TextStyleType `json:"style"` + Placeholder string `json:"placeholder,omitempty"` + Value string `json:"value,omitempty"` + Required bool `json:"required"` + MinLength int `json:"min_length"` + MaxLength int `json:"max_length,omitempty"` +} + +// Type is a method to get the type of a component. +func (InputText) Type() ComponentType { + return InputTextComponent +} + +// MarshalJSON is a method for marshaling InputText to a JSON object. +func (m InputText) MarshalJSON() ([]byte, error) { + type inputText InputText + + return json.Marshal(struct { + inputText + Type ComponentType `json:"type"` + }{ + inputText: inputText(m), + Type: m.Type(), + }) +} + +// TextStyleType is style of text in InputText component. +type TextStyleType uint + +// Text styles +const ( + TextStyleShort TextStyleType = 1 + TextStyleParagraph TextStyleType = 2 +) diff --git a/examples/modals/main.go b/examples/modals/main.go new file mode 100644 index 000000000..0a38c54de --- /dev/null +++ b/examples/modals/main.go @@ -0,0 +1,159 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + "os/signal" + "strings" + + "github.com/bwmarrin/discordgo" +) + +// Bot parameters +var ( + GuildID = flag.String("guild", "", "Test guild ID") + BotToken = flag.String("token", "", "Bot access token") + AppID = flag.String("app", "", "Application ID") + Cleanup = flag.Bool("cleanup", true, "Cleanup of commands") + ResultsChannel = flag.String("results", "", "Channel where send survey results to") +) + +var s *discordgo.Session + +func init() { + flag.Parse() +} + +func init() { + var err error + s, err = discordgo.New("Bot " + *BotToken) + if err != nil { + log.Fatalf("Invalid bot parameters: %v", err) + } +} + +var ( + commands = []discordgo.ApplicationCommand{ + { + Name: "modals-survey", + Description: "Take a survey about modals", + }, + } + commandsHandlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){ + "modals-survey": func(s *discordgo.Session, i *discordgo.InteractionCreate) { + err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseModal, + Data: &discordgo.InteractionResponseData{ + CustomID: "modals_survey_" + i.Interaction.Member.User.ID, + Title: "Modals survey", + Components: []discordgo.MessageComponent{ + discordgo.ActionsRow{ + Components: []discordgo.MessageComponent{ + discordgo.InputText{ + CustomID: "opinion", + Label: "What is your opinion on them?", + Style: discordgo.TextStyleShort, + Placeholder: "Don't be shy, share your opinion with us", + Required: true, + MaxLength: 300, + }, + }, + }, + discordgo.ActionsRow{ + Components: []discordgo.MessageComponent{ + discordgo.InputText{ + CustomID: "suggestions", + Label: "What would you suggest to improve them?", + Style: discordgo.TextStyleParagraph, + Required: false, + MaxLength: 2000, + }, + }, + }, + }, + }, + }) + if err != nil { + panic(err) + } + }, + } +) + +func main() { + s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) { + log.Println("Bot is up!") + }) + + s.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) { + switch i.Type { + case discordgo.InteractionApplicationCommand: + if h, ok := commandsHandlers[i.ApplicationCommandData().Name]; ok { + h(s, i) + } + case discordgo.InteractionModalSubmit: + err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "Thank you for taking your time to fill this survey", + Flags: 1 << 6, + }, + }) + if err != nil { + panic(err) + } + data := i.ModalSubmitData() + + if !strings.HasPrefix(data.CustomID, "modals_survey") { + return + } + + userid := strings.Split(data.CustomID, "_")[2] + _, err = s.ChannelMessageSend(*ResultsChannel, fmt.Sprintf( + "Feedback received. From <@%s>\n\n**Opinion**:\n%s\n\n**Suggestions**:\n%s", + userid, + data.Components[0].(*discordgo.ActionsRow).Components[0].(*discordgo.InputText).Value, + data.Components[1].(*discordgo.ActionsRow).Components[0].(*discordgo.InputText).Value, + )) + if err != nil { + panic(err) + } + } + }) + + cmdIDs := make(map[string]string, len(commands)) + + for _, cmd := range commands { + rcmd, err := s.ApplicationCommandCreate(*AppID, *GuildID, &cmd) + if err != nil { + log.Fatalf("Cannot create slash command %q: %v", cmd.Name, err) + } + + cmdIDs[rcmd.ID] = rcmd.Name + } + + err := s.Open() + if err != nil { + log.Fatalf("Cannot open the session: %v", err) + } + defer s.Close() + + stop := make(chan os.Signal, 1) + signal.Notify(stop, os.Interrupt) + <-stop + log.Println("Graceful shutdown") + + if !*Cleanup { + return + } + + for id, name := range cmdIDs { + err := s.ApplicationCommandDelete(*AppID, *GuildID, id) + if err != nil { + log.Fatalf("Cannot delete slash command %q: %v", name, err) + } + } + +} diff --git a/interactions.go b/interactions.go index 6f1891792..b56e46ada 100644 --- a/interactions.go +++ b/interactions.go @@ -113,6 +113,7 @@ const ( InteractionApplicationCommand InteractionType = 2 InteractionMessageComponent InteractionType = 3 InteractionApplicationCommandAutocomplete InteractionType = 4 + InteractionModalSubmit InteractionType = 5 ) func (t InteractionType) String() string { @@ -123,6 +124,8 @@ func (t InteractionType) String() string { return "ApplicationCommand" case InteractionMessageComponent: return "MessageComponent" + case InteractionModalSubmit: + return "ModalSubmit" } return fmt.Sprintf("InteractionType(%d)", t) } @@ -137,8 +140,8 @@ type Interaction struct { // The message on which interaction was used. // NOTE: this field is only filled when a button click triggered the interaction. Otherwise it will be nil. - Message *Message `json:"message"` + Message *Message `json:"message"` // 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. @@ -186,6 +189,13 @@ func (i *Interaction) UnmarshalJSON(raw []byte) error { return err } i.Data = v + case InteractionModalSubmit: + v := ModalSubmitInteractionData{} + err = json.Unmarshal(tmp.Data, &v) + if err != nil { + return err + } + i.Data = v } return nil } @@ -208,6 +218,15 @@ func (i Interaction) ApplicationCommandData() (data ApplicationCommandInteractio return i.Data.(ApplicationCommandInteractionData) } +// ModalSubmitData is helper function to assert the innter InteractionData to ModalSubmitInteractionData. +// Make sure to check that the Type of the interaction is InteractionModalSubmit before calling. +func (i Interaction) ModalSubmitData() (data ModalSubmitInteractionData) { + if i.Type != InteractionModalSubmit { + panic("ModalSubmitData called on interaction of type " + i.Type.String()) + } + return i.Data.(ModalSubmitInteractionData) +} + // InteractionData is a common interface for all types of interaction data. type InteractionData interface { Type() InteractionType @@ -256,6 +275,36 @@ func (MessageComponentInteractionData) Type() InteractionType { return InteractionMessageComponent } +// ModalSubmitInteractionData contains the data of modal submit interaction. +type ModalSubmitInteractionData struct { + CustomID string `json:"custom_id"` + Components []MessageComponent `json:"-"` +} + +// Type returns the type of interaction data. +func (ModalSubmitInteractionData) Type() InteractionType { + return InteractionModalSubmit +} + +// UnmarshalJSON is a helper function to correctly unmarshal Components. +func (d *ModalSubmitInteractionData) UnmarshalJSON(data []byte) error { + type modalSubmitInteractionData ModalSubmitInteractionData + var v struct { + modalSubmitInteractionData + RawComponents []unmarshalableMessageComponent `json:"components"` + } + err := json.Unmarshal(data, &v) + if err != nil { + return err + } + *d = ModalSubmitInteractionData(v.modalSubmitInteractionData) + d.Components = make([]MessageComponent, len(v.RawComponents)) + for i, v := range v.RawComponents { + d.Components[i] = v.MessageComponent + } + return err +} + // ApplicationCommandInteractionDataOption represents an option of a slash command. type ApplicationCommandInteractionDataOption struct { Name string `json:"name"` @@ -398,6 +447,8 @@ const ( InteractionResponseUpdateMessage InteractionResponseType = 7 // InteractionApplicationCommandAutocompleteResult shows autocompletion results. Autocomplete interaction only. InteractionApplicationCommandAutocompleteResult InteractionResponseType = 8 + // InteractionResponseModal is for responding to an interaction with a modal window. + InteractionResponseModal InteractionResponseType = 9 ) // InteractionResponse represents a response for an interaction event. @@ -418,6 +469,9 @@ type InteractionResponseData struct { // NOTE: autocomplete interaction only. Choices []*ApplicationCommandOptionChoice `json:"choices,omitempty"` + + CustomID string `json:"custom_id,omitempty"` + Title string `json:"title,omitempty"` } // VerifyInteraction implements message verification of the discord interactions api From 6ff665e56e6058b6004f0178c7f15201159b7d3c Mon Sep 17 00:00:00 2001 From: Fedor Lapshin Date: Tue, 8 Feb 2022 16:38:19 +0300 Subject: [PATCH 02/35] feat: drop email-password session authentication. This commit drops old email/password authentication for New function. It violates Discord ToS and due to recent changes in rules for community resources on Discord API Docs it needs to be dropped. --- discord.go | 115 ++++------------------------------------------------- 1 file changed, 8 insertions(+), 107 deletions(-) diff --git a/discord.go b/discord.go index b8765deaf..b05fc0233 100644 --- a/discord.go +++ b/discord.go @@ -24,32 +24,12 @@ import ( // VERSION of DiscordGo, follows Semantic Versioning. (http://semver.org/) const VERSION = "0.23.0" -// ErrMFA will be risen by New when the user has 2FA. -var ErrMFA = errors.New("account has 2FA enabled") - -// New creates a new Discord session and will automate some startup -// tasks if given enough information to do so. Currently you can pass zero -// arguments and it will return an empty Discord session. -// There are 3 ways to call New: -// With a single auth token - All requests will use the token blindly -// (just tossing it into the HTTP Authorization header); -// no verification of the token will be done and requests may fail. -// IF THE TOKEN IS FOR A BOT, IT MUST BE PREFIXED WITH `BOT ` -// eg: `"Bot "` -// IF IT IS AN OAUTH2 ACCESS TOKEN, IT MUST BE PREFIXED WITH `Bearer ` -// eg: `"Bearer "` -// With an email and password - Discord will sign in with the provided -// credentials. -// With an email, password and auth token - Discord will verify the auth -// token, if it is invalid it will sign in with the provided -// credentials. This is the Discord recommended way to sign in. -// -// NOTE: While email/pass authentication is supported by DiscordGo it is -// HIGHLY DISCOURAGED by Discord. Please only use email/pass to obtain a token -// and then use that authentication token for all future connections. -// Also, doing any form of automation with a user (non Bot) account may result -// in that account being permanently banned from Discord. -func New(args ...interface{}) (s *Session, err error) { +// New creates a new Discord session with provided token. +// If the token is for a bot, it must be prefixed with "Bot ". +// e.g. "Bot ..." +// Or if it is an OAuth2 token, it must be prefixed with "Bearer " +// e.g. "Bearer ..." +func New(token string) (s *Session, err error) { // Create an empty Session interface. s = &Session{ @@ -75,87 +55,8 @@ func New(args ...interface{}) (s *Session, err error) { s.Identify.Properties.OS = runtime.GOOS s.Identify.Properties.Browser = "DiscordGo v" + VERSION s.Identify.Intents = MakeIntent(IntentsAllWithoutPrivileged) - - // If no arguments are passed return the empty Session interface. - if args == nil { - return - } - - // Variables used below when parsing func arguments - var auth, pass string - - // Parse passed arguments - for _, arg := range args { - - switch v := arg.(type) { - - case []string: - if len(v) > 3 { - err = fmt.Errorf("too many string parameters provided") - return - } - - // First string is either token or username - if len(v) > 0 { - auth = v[0] - } - - // If second string exists, it must be a password. - if len(v) > 1 { - pass = v[1] - } - - // If third string exists, it must be an auth token. - if len(v) > 2 { - s.Identify.Token = v[2] - s.Token = v[2] // TODO: Remove, Deprecated - Kept for backwards compatibility. - } - - case string: - // First string must be either auth token or username. - // Second string must be a password. - // Only 2 input strings are supported. - - if auth == "" { - auth = v - } else if pass == "" { - pass = v - } else if s.Token == "" { - s.Identify.Token = v - s.Token = v // TODO: Remove, Deprecated - Kept for backwards compatibility. - } else { - err = fmt.Errorf("too many string parameters provided") - return - } - - // case Config: - // TODO: Parse configuration struct - - default: - err = fmt.Errorf("unsupported parameter type provided") - return - } - } - - // If only one string was provided, assume it is an auth token. - // Otherwise get auth token from Discord, if a token was specified - // Discord will verify it for free, or log the user in if it is - // invalid. - if pass == "" { - s.Identify.Token = auth - s.Token = auth // TODO: Remove, Deprecated - Kept for backwards compatibility. - } else { - err = s.Login(auth, pass) - // TODO: Remove last s.Token part, Deprecated - Kept for backwards compatibility. - if err != nil || s.Identify.Token == "" || s.Token == "" { - if s.MFA { - err = ErrMFA - } else { - err = fmt.Errorf("Unable to fetch discord authentication token. %v", err) - } - return - } - } + s.Identify.Token = token + s.Token = token return } From e253d2882eca5231346329e1c9cc85a50b049f59 Mon Sep 17 00:00:00 2001 From: nitroflap Date: Tue, 8 Feb 2022 17:10:52 +0300 Subject: [PATCH 03/35] fix(discord#New): fixed imports and removed MakeIntent --- discord.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/discord.go b/discord.go index b05fc0233..5e7aab2c5 100644 --- a/discord.go +++ b/discord.go @@ -14,8 +14,6 @@ package discordgo import ( - "errors" - "fmt" "net/http" "runtime" "time" @@ -54,7 +52,7 @@ func New(token string) (s *Session, err error) { s.Identify.GuildSubscriptions = true s.Identify.Properties.OS = runtime.GOOS s.Identify.Properties.Browser = "DiscordGo v" + VERSION - s.Identify.Intents = MakeIntent(IntentsAllWithoutPrivileged) + s.Identify.Intents = IntentsAllWithoutPrivileged s.Identify.Token = token s.Token = token From 5bf32ec1832baaf4c9d59ec0144bcc8a5a8d9fbb Mon Sep 17 00:00:00 2001 From: nitroflap Date: Tue, 8 Feb 2022 17:13:44 +0300 Subject: [PATCH 04/35] feat: dropped unnecessary tests for New function --- discord_test.go | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/discord_test.go b/discord_test.go index 30d2c75a8..973e3a654 100644 --- a/discord_test.go +++ b/discord_test.go @@ -40,30 +40,6 @@ func init() { ////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////// START OF TESTS -// TestNew tests the New() function without any arguments. This should return -// a valid Session{} struct and no errors. -func TestNew(t *testing.T) { - - _, err := New() - if err != nil { - t.Errorf("New() returned error: %+v", err) - } -} - -// TestInvalidToken tests the New() function with an invalid token -func TestInvalidToken(t *testing.T) { - d, err := New("asjkldhflkjasdh") - if err != nil { - t.Fatalf("New(InvalidToken) returned error: %+v", err) - } - - // New with just a token does not do any communication, so attempt an api call. - _, err = d.UserSettings() - if err == nil { - t.Errorf("New(InvalidToken), d.UserSettings returned nil error.") - } -} - // TestNewToken tests the New() function with a Token. func TestNewToken(t *testing.T) { From d5c82ad3746213ae7e0720b13244f242c20e5177 Mon Sep 17 00:00:00 2001 From: nitroflap Date: Tue, 8 Feb 2022 18:25:24 +0300 Subject: [PATCH 05/35] feat(*)!: dropped undocumented endpoints --- endpoints.go | 34 +------ restapi.go | 250 +----------------------------------------------- restapi_test.go | 58 ----------- 3 files changed, 10 insertions(+), 332 deletions(-) diff --git a/endpoints.go b/endpoints.go index fdcbc17dd..400076d8a 100644 --- a/endpoints.go +++ b/endpoints.go @@ -41,26 +41,10 @@ var ( EndpointCDNChannelIcons = EndpointCDN + "channel-icons/" EndpointCDNBanners = EndpointCDN + "banners/" - EndpointAuth = EndpointAPI + "auth/" - EndpointLogin = EndpointAuth + "login" - EndpointLogout = EndpointAuth + "logout" - EndpointVerify = EndpointAuth + "verify" - EndpointVerifyResend = EndpointAuth + "verify/resend" - EndpointForgotPassword = EndpointAuth + "forgot" - EndpointResetPassword = EndpointAuth + "reset" - EndpointRegister = EndpointAuth + "register" - EndpointVoice = EndpointAPI + "/voice/" EndpointVoiceRegions = EndpointVoice + "regions" - EndpointVoiceIce = EndpointVoice + "ice" - - EndpointTutorial = EndpointAPI + "tutorial/" - EndpointTutorialIndicators = EndpointTutorial + "indicators" - EndpointTrack = EndpointAPI + "track" - EndpointSso = EndpointAPI + "sso" - EndpointReport = EndpointAPI + "report" - EndpointIntegrations = EndpointAPI + "integrations" + // TODO: EndpointUserGuildMember EndpointUser = func(uID string) string { return EndpointUsers + uID } EndpointUserAvatar = func(uID, aID string) string { return EndpointCDNAvatars + uID + "/" + aID + ".png" } @@ -69,14 +53,10 @@ var ( uDiscriminatorInt, _ := strconv.Atoi(uDiscriminator) return EndpointCDN + "embed/avatars/" + strconv.Itoa(uDiscriminatorInt%5) + ".png" } - EndpointUserSettings = func(uID string) string { return EndpointUsers + uID + "/settings" } - EndpointUserGuilds = func(uID string) string { return EndpointUsers + uID + "/guilds" } - EndpointUserGuild = func(uID, gID string) string { return EndpointUsers + uID + "/guilds/" + gID } - EndpointUserGuildSettings = func(uID, gID string) string { return EndpointUsers + uID + "/guilds/" + gID + "/settings" } - EndpointUserChannels = func(uID string) string { return EndpointUsers + uID + "/channels" } - EndpointUserDevices = func(uID string) string { return EndpointUsers + uID + "/devices" } - EndpointUserConnections = func(uID string) string { return EndpointUsers + uID + "/connections" } - EndpointUserNotes = func(uID string) string { return EndpointUsers + "@me/notes/" + uID } + EndpointUserGuilds = func(uID string) string { return EndpointUsers + uID + "/guilds" } + EndpointUserGuild = func(uID, gID string) string { return EndpointUsers + uID + "/guilds/" + gID } + EndpointUserChannels = func(uID string) string { return EndpointUsers + uID + "/channels" } + EndpointUserConnections = func(uID string) string { return EndpointUsers + uID + "/connections" } EndpointGuild = func(gID string) string { return EndpointGuilds + gID } EndpointGuildPreview = func(gID string) string { return EndpointGuilds + gID + "/preview" } @@ -171,10 +151,6 @@ var ( return EndpointWebhookMessage(aID, iToken, mID) } - EndpointRelationships = func() string { return EndpointUsers + "@me" + "/relationships" } - EndpointRelationship = func(uID string) string { return EndpointRelationships() + "/" + uID } - EndpointRelationshipsMutual = func(uID string) string { return EndpointUsers + uID + "/relationships" } - EndpointGuildCreate = EndpointAPI + "guilds" EndpointInvite = func(iID string) string { return EndpointAPI + "invites/" + iID } diff --git a/restapi.go b/restapi.go index 61fcd5fa6..955c5eeb7 100644 --- a/restapi.go +++ b/restapi.go @@ -182,91 +182,6 @@ func unmarshal(data []byte, v interface{}) error { return nil } -// ------------------------------------------------------------------------------------------------ -// Functions specific to Discord Sessions -// ------------------------------------------------------------------------------------------------ - -// Login asks the Discord server for an authentication token. -// -// NOTE: While email/pass authentication is supported by DiscordGo it is -// HIGHLY DISCOURAGED by Discord. Please only use email/pass to obtain a token -// and then use that authentication token for all future connections. -// Also, doing any form of automation with a user (non Bot) account may result -// in that account being permanently banned from Discord. -func (s *Session) Login(email, password string) (err error) { - - data := struct { - Email string `json:"email"` - Password string `json:"password"` - }{email, password} - - response, err := s.RequestWithBucketID("POST", EndpointLogin, data, EndpointLogin) - if err != nil { - return - } - - temp := struct { - Token string `json:"token"` - MFA bool `json:"mfa"` - }{} - - err = unmarshal(response, &temp) - if err != nil { - return - } - - s.Token = temp.Token - s.MFA = temp.MFA - return -} - -// Register sends a Register request to Discord, and returns the authentication token -// Note that this account is temporary and should be verified for future use. -// Another option is to save the authentication token external, but this isn't recommended. -func (s *Session) Register(username string) (token string, err error) { - - data := struct { - Username string `json:"username"` - }{username} - - response, err := s.RequestWithBucketID("POST", EndpointRegister, data, EndpointRegister) - if err != nil { - return - } - - temp := struct { - Token string `json:"token"` - }{} - - err = unmarshal(response, &temp) - if err != nil { - return - } - - token = temp.Token - return -} - -// Logout sends a logout request to Discord. -// This does not seem to actually invalidate the token. So you can still -// make API calls even after a Logout. So, it seems almost pointless to -// even use. -func (s *Session) Logout() (err error) { - - // _, err = s.Request("POST", LOGOUT, `{"token": "` + s.Token + `"}`) - - if s.Token == "" { - return - } - - data := struct { - Token string `json:"token"` - }{s.Token} - - _, err = s.RequestWithBucketID("POST", EndpointLogout, data, EndpointLogout) - return -} - // ------------------------------------------------------------------------------------------------ // Functions specific to Discord Users // ------------------------------------------------------------------------------------------------ @@ -307,8 +222,8 @@ func (s *Session) UserAvatarDecode(u *User) (img image.Image, err error) { return } -// UserUpdate updates a users settings. -func (s *Session) UserUpdate(email, password, username, avatar, newPassword string) (st *User, err error) { +// UserUpdate updates current user settings. +func (s *Session) UserUpdate(username, avatar string) (st *User, err error) { // NOTE: Avatar must be either the hash/id of existing Avatar or // data:image/png;base64,BASE64_STRING_OF_NEW_AVATAR_PNG @@ -316,12 +231,9 @@ func (s *Session) UserUpdate(email, password, username, avatar, newPassword stri // If left blank, avatar will be set to null/blank data := struct { - Email string `json:"email,omitempty"` - Password string `json:"password,omitempty"` - Username string `json:"username,omitempty"` - Avatar string `json:"avatar,omitempty"` - NewPassword string `json:"new_password,omitempty"` - }{email, password, username, avatar, newPassword} + Username string `json:"username,omitempty"` + Avatar string `json:"avatar,omitempty"` + }{username, avatar} body, err := s.RequestWithBucketID("PATCH", EndpointUser("@me"), data, EndpointUsers) if err != nil { @@ -332,39 +244,6 @@ func (s *Session) UserUpdate(email, password, username, avatar, newPassword stri return } -// UserSettings returns the settings for a given user -func (s *Session) UserSettings() (st *Settings, err error) { - - body, err := s.RequestWithBucketID("GET", EndpointUserSettings("@me"), nil, EndpointUserSettings("")) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// UserUpdateStatus update the user status -// status : The new status (Actual valid status are 'online','idle','dnd','invisible') -func (s *Session) UserUpdateStatus(status Status) (st *Settings, err error) { - if status == StatusOffline { - err = ErrStatusOffline - return - } - - data := struct { - Status Status `json:"status"` - }{status} - - body, err := s.RequestWithBucketID("PATCH", EndpointUserSettings("@me"), data, EndpointUserSettings("")) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - // UserConnections returns the user's connections func (s *Session) UserConnections() (conn []*UserConnection, err error) { response, err := s.RequestWithBucketID("GET", EndpointUserConnections("@me"), nil, EndpointUserConnections("@me")) @@ -380,19 +259,6 @@ func (s *Session) UserConnections() (conn []*UserConnection, err error) { return } -// UserChannels returns an array of Channel structures for all private -// channels. -func (s *Session) UserChannels() (st []*Channel, err error) { - - body, err := s.RequestWithBucketID("GET", EndpointUserChannels("@me"), nil, EndpointUserChannels("")) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - // UserChannelCreate creates a new User (Private) Channel with another User // recipientID : A user ID for the user to which this channel is opened with. func (s *Session) UserChannelCreate(recipientID string) (st *Channel, err error) { @@ -443,20 +309,6 @@ func (s *Session) UserGuilds(limit int, beforeID, afterID string) (st []*UserGui return } -// UserGuildSettingsEdit Edits the users notification settings for a guild -// guildID : The ID of the guild to edit the settings on -// settings : The settings to update -func (s *Session) UserGuildSettingsEdit(guildID string, settings *UserGuildSettingsEdit) (st *UserGuildSettings, err error) { - - body, err := s.RequestWithBucketID("PATCH", EndpointUserGuildSettings("@me", guildID), settings, EndpointUserGuildSettings("", guildID)) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - // UserChannelPermissions returns the permission of a user in a channel. // userID : The ID of the user to calculate permissions for. // channelID : The ID of the channel to calculate permission for. @@ -1958,18 +1810,6 @@ func (s *Session) VoiceRegions() (st []*VoiceRegion, err error) { return } -// VoiceICE returns the voice server ICE information -func (s *Session) VoiceICE() (st *VoiceICE, err error) { - - body, err := s.RequestWithBucketID("GET", EndpointVoiceIce, nil, EndpointVoiceIce) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - // ------------------------------------------------------------------------------------------------ // Functions specific to Discord Websockets // ------------------------------------------------------------------------------------------------ @@ -2348,86 +2188,6 @@ func (s *Session) MessageReactions(channelID, messageID, emojiID string, limit i return } -// ------------------------------------------------------------------------------------------------ -// Functions specific to user notes -// ------------------------------------------------------------------------------------------------ - -// UserNoteSet sets the note for a specific user. -func (s *Session) UserNoteSet(userID string, message string) (err error) { - data := struct { - Note string `json:"note"` - }{message} - - _, err = s.RequestWithBucketID("PUT", EndpointUserNotes(userID), data, EndpointUserNotes("")) - return -} - -// ------------------------------------------------------------------------------------------------ -// Functions specific to Discord Relationships (Friends list) -// ------------------------------------------------------------------------------------------------ - -// RelationshipsGet returns an array of all the relationships of the user. -func (s *Session) RelationshipsGet() (r []*Relationship, err error) { - body, err := s.RequestWithBucketID("GET", EndpointRelationships(), nil, EndpointRelationships()) - if err != nil { - return - } - - err = unmarshal(body, &r) - return -} - -// relationshipCreate creates a new relationship. (I.e. send or accept a friend request, block a user.) -// relationshipType : 1 = friend, 2 = blocked, 3 = incoming friend req, 4 = sent friend req -func (s *Session) relationshipCreate(userID string, relationshipType int) (err error) { - data := struct { - Type int `json:"type"` - }{relationshipType} - - _, err = s.RequestWithBucketID("PUT", EndpointRelationship(userID), data, EndpointRelationships()) - return -} - -// RelationshipFriendRequestSend sends a friend request to a user. -// userID: ID of the user. -func (s *Session) RelationshipFriendRequestSend(userID string) (err error) { - err = s.relationshipCreate(userID, 4) - return -} - -// RelationshipFriendRequestAccept accepts a friend request from a user. -// userID: ID of the user. -func (s *Session) RelationshipFriendRequestAccept(userID string) (err error) { - err = s.relationshipCreate(userID, 1) - return -} - -// RelationshipUserBlock blocks a user. -// userID: ID of the user. -func (s *Session) RelationshipUserBlock(userID string) (err error) { - err = s.relationshipCreate(userID, 2) - return -} - -// RelationshipDelete removes the relationship with a user. -// userID: ID of the user. -func (s *Session) RelationshipDelete(userID string) (err error) { - _, err = s.RequestWithBucketID("DELETE", EndpointRelationship(userID), nil, EndpointRelationships()) - return -} - -// RelationshipsMutualGet returns an array of all the users both @me and the given user is friends with. -// userID: ID of the user. -func (s *Session) RelationshipsMutualGet(userID string) (mf []*User, err error) { - body, err := s.RequestWithBucketID("GET", EndpointRelationshipsMutual(userID), nil, EndpointRelationshipsMutual(userID)) - if err != nil { - return - } - - err = unmarshal(body, &mf) - return -} - // ------------------------------------------------------------------------------------------------ // Functions specific to application (slash) commands // ------------------------------------------------------------------------------------------------ diff --git a/restapi_test.go b/restapi_test.go index 6f52ed349..2e23a58df 100644 --- a/restapi_test.go +++ b/restapi_test.go @@ -99,17 +99,6 @@ func TestUserChannelCreate(t *testing.T) { // TODO make sure the channel was added } -func TestUserChannels(t *testing.T) { - if dg == nil { - t.Skip("Cannot TestUserChannels, dg not set.") - } - - _, err := dg.UserChannels() - if err != nil { - t.Errorf(err.Error()) - } -} - func TestUserGuilds(t *testing.T) { if dg == nil { t.Skip("Cannot TestUserGuilds, dg not set.") @@ -121,41 +110,6 @@ func TestUserGuilds(t *testing.T) { } } -func TestUserSettings(t *testing.T) { - if dg == nil { - t.Skip("Cannot TestUserSettings, dg not set.") - } - - _, err := dg.UserSettings() - if err != nil { - t.Errorf(err.Error()) - } -} - -func TestUserUpdateStatus(t *testing.T) { - if dg == nil { - t.Skip("Cannot TestUserSettings, dg not set.") - } - - _, err := dg.UserUpdateStatus(StatusDoNotDisturb) - if err != nil { - t.Errorf(err.Error()) - } -} - -// TestLogout tests the Logout() function. This should not return an error. -func TestLogout(t *testing.T) { - - if dg == nil { - t.Skip("Cannot TestLogout, dg not set.") - } - - err := dg.Logout() - if err != nil { - t.Errorf("Logout() returned error: %+v", err) - } -} - func TestGateway(t *testing.T) { if dg == nil { @@ -178,18 +132,6 @@ func TestGatewayBot(t *testing.T) { } } -func TestVoiceICE(t *testing.T) { - - if dg == nil { - t.Skip("Skipping, dg not set.") - } - - _, err := dg.VoiceICE() - if err != nil { - t.Errorf("VoiceICE() returned error: %+v", err) - } -} - func TestVoiceRegions(t *testing.T) { if dg == nil { From a8b351019fc29c0f1ca9d565ade81f039eb0bd4d Mon Sep 17 00:00:00 2001 From: nitroflap Date: Tue, 8 Feb 2022 20:43:43 +0300 Subject: [PATCH 06/35] feat(*): dropped more undocumented endpoints --- endpoints.go | 58 ++++++++++++++++++++++++---------------------------- restapi.go | 24 ---------------------- 2 files changed, 27 insertions(+), 55 deletions(-) diff --git a/endpoints.go b/endpoints.go index 400076d8a..5124394ad 100644 --- a/endpoints.go +++ b/endpoints.go @@ -58,42 +58,40 @@ var ( EndpointUserChannels = func(uID string) string { return EndpointUsers + uID + "/channels" } EndpointUserConnections = func(uID string) string { return EndpointUsers + uID + "/connections" } - EndpointGuild = func(gID string) string { return EndpointGuilds + gID } - EndpointGuildPreview = func(gID string) string { return EndpointGuilds + gID + "/preview" } - EndpointGuildChannels = func(gID string) string { return EndpointGuilds + gID + "/channels" } - EndpointGuildMembers = func(gID string) string { return EndpointGuilds + gID + "/members" } - EndpointGuildMember = func(gID, uID string) string { return EndpointGuilds + gID + "/members/" + uID } - EndpointGuildMemberRole = func(gID, uID, rID string) string { return EndpointGuilds + gID + "/members/" + uID + "/roles/" + rID } - EndpointGuildBans = func(gID string) string { return EndpointGuilds + gID + "/bans" } - EndpointGuildBan = func(gID, uID string) string { return EndpointGuilds + gID + "/bans/" + uID } - EndpointGuildIntegrations = func(gID string) string { return EndpointGuilds + gID + "/integrations" } - EndpointGuildIntegration = func(gID, iID string) string { return EndpointGuilds + gID + "/integrations/" + iID } - EndpointGuildIntegrationSync = func(gID, iID string) string { return EndpointGuilds + gID + "/integrations/" + iID + "/sync" } - EndpointGuildRoles = func(gID string) string { return EndpointGuilds + gID + "/roles" } - EndpointGuildRole = func(gID, rID string) string { return EndpointGuilds + gID + "/roles/" + rID } - EndpointGuildInvites = func(gID string) string { return EndpointGuilds + gID + "/invites" } - EndpointGuildWidget = func(gID string) string { return EndpointGuilds + gID + "/widget" } - EndpointGuildEmbed = EndpointGuildWidget - EndpointGuildPrune = func(gID string) string { return EndpointGuilds + gID + "/prune" } - EndpointGuildIcon = func(gID, hash string) string { return EndpointCDNIcons + gID + "/" + hash + ".png" } - EndpointGuildIconAnimated = func(gID, hash string) string { return EndpointCDNIcons + gID + "/" + hash + ".gif" } - EndpointGuildSplash = func(gID, hash string) string { return EndpointCDNSplashes + gID + "/" + hash + ".png" } - EndpointGuildWebhooks = func(gID string) string { return EndpointGuilds + gID + "/webhooks" } - EndpointGuildAuditLogs = func(gID string) string { return EndpointGuilds + gID + "/audit-logs" } - EndpointGuildEmojis = func(gID string) string { return EndpointGuilds + gID + "/emojis" } - EndpointGuildEmoji = func(gID, eID string) string { return EndpointGuilds + gID + "/emojis/" + eID } - EndpointGuildBanner = func(gID, hash string) string { return EndpointCDNBanners + gID + "/" + hash + ".png" } - EndpointGuildStickers = func(gID string) string { return EndpointGuilds + gID + "/stickers" } - EndpointGuildSticker = func(gID, sID string) string { return EndpointGuilds + gID + "/stickers/" + sID } + EndpointGuild = func(gID string) string { return EndpointGuilds + gID } + EndpointGuildPreview = func(gID string) string { return EndpointGuilds + gID + "/preview" } + EndpointGuildChannels = func(gID string) string { return EndpointGuilds + gID + "/channels" } + EndpointGuildMembers = func(gID string) string { return EndpointGuilds + gID + "/members" } + EndpointGuildMember = func(gID, uID string) string { return EndpointGuilds + gID + "/members/" + uID } + EndpointGuildMemberRole = func(gID, uID, rID string) string { return EndpointGuilds + gID + "/members/" + uID + "/roles/" + rID } + EndpointGuildBans = func(gID string) string { return EndpointGuilds + gID + "/bans" } + EndpointGuildBan = func(gID, uID string) string { return EndpointGuilds + gID + "/bans/" + uID } + EndpointGuildIntegrations = func(gID string) string { return EndpointGuilds + gID + "/integrations" } + EndpointGuildIntegration = func(gID, iID string) string { return EndpointGuilds + gID + "/integrations/" + iID } + EndpointGuildRoles = func(gID string) string { return EndpointGuilds + gID + "/roles" } + EndpointGuildRole = func(gID, rID string) string { return EndpointGuilds + gID + "/roles/" + rID } + EndpointGuildInvites = func(gID string) string { return EndpointGuilds + gID + "/invites" } + EndpointGuildWidget = func(gID string) string { return EndpointGuilds + gID + "/widget" } + EndpointGuildEmbed = EndpointGuildWidget + EndpointGuildPrune = func(gID string) string { return EndpointGuilds + gID + "/prune" } + EndpointGuildIcon = func(gID, hash string) string { return EndpointCDNIcons + gID + "/" + hash + ".png" } + EndpointGuildIconAnimated = func(gID, hash string) string { return EndpointCDNIcons + gID + "/" + hash + ".gif" } + EndpointGuildSplash = func(gID, hash string) string { return EndpointCDNSplashes + gID + "/" + hash + ".png" } + EndpointGuildWebhooks = func(gID string) string { return EndpointGuilds + gID + "/webhooks" } + EndpointGuildAuditLogs = func(gID string) string { return EndpointGuilds + gID + "/audit-logs" } + EndpointGuildEmojis = func(gID string) string { return EndpointGuilds + gID + "/emojis" } + EndpointGuildEmoji = func(gID, eID string) string { return EndpointGuilds + gID + "/emojis/" + eID } + EndpointGuildBanner = func(gID, hash string) string { return EndpointCDNBanners + gID + "/" + hash + ".png" } + EndpointGuildStickers = func(gID string) string { return EndpointGuilds + gID + "/stickers" } + EndpointGuildSticker = func(gID, sID string) string { return EndpointGuilds + gID + "/stickers/" + sID } EndpointChannel = func(cID string) string { return EndpointChannels + cID } EndpointChannelPermissions = func(cID string) string { return EndpointChannels + cID + "/permissions" } - EndpointChannelPermission = func(cID, tID string) string { return EndpointChannels + cID + "/permissions/" + tID } + EndpointChannelPermission = func(cID, tID string) string { return EndpointChannelPermissions(cID) + "/" + tID } EndpointChannelInvites = func(cID string) string { return EndpointChannels + cID + "/invites" } EndpointChannelTyping = func(cID string) string { return EndpointChannels + cID + "/typing" } EndpointChannelMessages = func(cID string) string { return EndpointChannels + cID + "/messages" } EndpointChannelMessage = func(cID, mID string) string { return EndpointChannels + cID + "/messages/" + mID } - EndpointChannelMessageAck = func(cID, mID string) string { return EndpointChannels + cID + "/messages/" + mID + "/ack" } EndpointChannelMessagesBulkDelete = func(cID string) string { return EndpointChannel(cID) + "/messages/bulk-delete" } EndpointChannelMessagesPins = func(cID string) string { return EndpointChannel(cID) + "/pins" } EndpointChannelMessagePin = func(cID, mID string) string { return EndpointChannel(cID) + "/pins/" + mID } @@ -155,8 +153,6 @@ var ( EndpointInvite = func(iID string) string { return EndpointAPI + "invites/" + iID } - EndpointIntegrationsJoin = func(iID string) string { return EndpointAPI + "integrations/" + iID + "/join" } - EndpointEmoji = func(eID string) string { return EndpointCDN + "emojis/" + eID + ".png" } EndpointEmojiAnimated = func(eID string) string { return EndpointCDN + "emojis/" + eID + ".gif" } diff --git a/restapi.go b/restapi.go index 955c5eeb7..74ba48282 100644 --- a/restapi.go +++ b/restapi.go @@ -1100,15 +1100,6 @@ func (s *Session) GuildIntegrationDelete(guildID, integrationID string) (err err return } -// GuildIntegrationSync syncs an integration. -// guildID : The ID of a Guild. -// integrationID : The ID of an integration. -func (s *Session) GuildIntegrationSync(guildID, integrationID string) (err error) { - - _, err = s.RequestWithBucketID("POST", EndpointGuildIntegrationSync(guildID, integrationID), nil, EndpointGuildIntegration(guildID, "")) - return -} - // GuildIcon returns an image.Image of a guild icon. // guildID : The ID of a Guild. func (s *Session) GuildIcon(guildID string) (img image.Image, err error) { @@ -1388,21 +1379,6 @@ func (s *Session) ChannelMessage(channelID, messageID string) (st *Message, err return } -// ChannelMessageAck acknowledges and marks the given message as read -// channeld : The ID of a Channel -// messageID : the ID of a Message -// lastToken : token returned by last ack -func (s *Session) ChannelMessageAck(channelID, messageID, lastToken string) (st *Ack, err error) { - - body, err := s.RequestWithBucketID("POST", EndpointChannelMessageAck(channelID, messageID), &Ack{Token: lastToken}, EndpointChannelMessageAck(channelID, "")) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - // ChannelMessageSend sends a message to the given channel. // channelID : The ID of a Channel. // content : The message to send. From a0095c14ed964b9fe8de42b67c98b6167d6eb4f2 Mon Sep 17 00:00:00 2001 From: nitroflap Date: Wed, 9 Feb 2022 03:39:28 +0300 Subject: [PATCH 07/35] chore(components): rename InputText to TextInput --- components.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/components.go b/components.go index b7b4708e7..6c68de845 100644 --- a/components.go +++ b/components.go @@ -13,7 +13,7 @@ const ( ActionsRowComponent ComponentType = 1 ButtonComponent ComponentType = 2 SelectMenuComponent ComponentType = 3 - InputTextComponent ComponentType = 4 + TextInputComponent ComponentType = 4 ) // MessageComponent is a base interface for all message components. @@ -43,8 +43,8 @@ func (umc *unmarshalableMessageComponent) UnmarshalJSON(src []byte) error { umc.MessageComponent = &Button{} case SelectMenuComponent: umc.MessageComponent = &SelectMenu{} - case InputTextComponent: - umc.MessageComponent = &InputText{} + case TextInputComponent: + umc.MessageComponent = &TextInput{} default: return fmt.Errorf("unknown component type: %d", v.Type) } @@ -199,8 +199,8 @@ func (m SelectMenu) MarshalJSON() ([]byte, error) { }) } -// InputText represents text input component. -type InputText struct { +// TextInput represents text input component. +type TextInput struct { CustomID string `json:"custom_id,omitempty"` Label string `json:"label"` Style TextStyleType `json:"style"` @@ -212,13 +212,13 @@ type InputText struct { } // Type is a method to get the type of a component. -func (InputText) Type() ComponentType { - return InputTextComponent +func (TextInput) Type() ComponentType { + return TextInputComponent } -// MarshalJSON is a method for marshaling InputText to a JSON object. -func (m InputText) MarshalJSON() ([]byte, error) { - type inputText InputText +// MarshalJSON is a method for marshaling TextInput to a JSON object. +func (m TextInput) MarshalJSON() ([]byte, error) { + type inputText TextInput return json.Marshal(struct { inputText @@ -229,7 +229,7 @@ func (m InputText) MarshalJSON() ([]byte, error) { }) } -// TextStyleType is style of text in InputText component. +// TextStyleType is style of text in TextInput component. type TextStyleType uint // Text styles From 1b48b9b22e0b58e173fce5d3abda3e4b9395c3c9 Mon Sep 17 00:00:00 2001 From: nitroflap Date: Wed, 9 Feb 2022 03:45:50 +0300 Subject: [PATCH 08/35] fix(examples/modals): renamed InputText to TextInput --- examples/modals/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/modals/main.go b/examples/modals/main.go index 0a38c54de..284814e42 100644 --- a/examples/modals/main.go +++ b/examples/modals/main.go @@ -51,7 +51,7 @@ var ( Components: []discordgo.MessageComponent{ discordgo.ActionsRow{ Components: []discordgo.MessageComponent{ - discordgo.InputText{ + discordgo.TextInput{ CustomID: "opinion", Label: "What is your opinion on them?", Style: discordgo.TextStyleShort, @@ -63,7 +63,7 @@ var ( }, discordgo.ActionsRow{ Components: []discordgo.MessageComponent{ - discordgo.InputText{ + discordgo.TextInput{ CustomID: "suggestions", Label: "What would you suggest to improve them?", Style: discordgo.TextStyleParagraph, From 19d46d852a7a8b69b930290b2286c99ddc71b93d Mon Sep 17 00:00:00 2001 From: nitroflap Date: Wed, 9 Feb 2022 03:46:09 +0300 Subject: [PATCH 09/35] feat(examples/modals): added MinLength --- examples/modals/main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/modals/main.go b/examples/modals/main.go index 284814e42..bdb3960aa 100644 --- a/examples/modals/main.go +++ b/examples/modals/main.go @@ -58,6 +58,7 @@ var ( Placeholder: "Don't be shy, share your opinion with us", Required: true, MaxLength: 300, + MinLength: 10, }, }, }, From dcf6fe0128ee0e6456357e758120e28ff0dbcbd6 Mon Sep 17 00:00:00 2001 From: nitroflap Date: Wed, 9 Feb 2022 03:47:44 +0300 Subject: [PATCH 10/35] fix(examples/modals): more renaming of InputText to TextInput --- examples/modals/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/modals/main.go b/examples/modals/main.go index bdb3960aa..90e9887d6 100644 --- a/examples/modals/main.go +++ b/examples/modals/main.go @@ -115,8 +115,8 @@ func main() { _, err = s.ChannelMessageSend(*ResultsChannel, fmt.Sprintf( "Feedback received. From <@%s>\n\n**Opinion**:\n%s\n\n**Suggestions**:\n%s", userid, - data.Components[0].(*discordgo.ActionsRow).Components[0].(*discordgo.InputText).Value, - data.Components[1].(*discordgo.ActionsRow).Components[0].(*discordgo.InputText).Value, + data.Components[0].(*discordgo.ActionsRow).Components[0].(*discordgo.TextInput).Value, + data.Components[1].(*discordgo.ActionsRow).Components[0].(*discordgo.TextInput).Value, )) if err != nil { panic(err) From 147703db8e8d13f5099d56af059aa0c8267f28ae Mon Sep 17 00:00:00 2001 From: Fedor Lapshin Date: Wed, 9 Feb 2022 03:58:16 +0300 Subject: [PATCH 11/35] feat(components): renamed TextStyleType to TextInputStyleType --- components.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components.go b/components.go index 6c68de845..b2943c58a 100644 --- a/components.go +++ b/components.go @@ -229,11 +229,11 @@ func (m TextInput) MarshalJSON() ([]byte, error) { }) } -// TextStyleType is style of text in TextInput component. -type TextStyleType uint +// TextInputStyleType is style of text in TextInput component. +type TextInputStyleType uint // Text styles const ( - TextStyleShort TextStyleType = 1 - TextStyleParagraph TextStyleType = 2 + TextInputShort TextStyleType = 1 + TextInputParagraph TextStyleType = 2 ) From f4ad6e85ea5003719556676351d899dc8932ed7d Mon Sep 17 00:00:00 2001 From: constantoine Date: Wed, 9 Feb 2022 11:23:02 +0100 Subject: [PATCH 12/35] Add MessageFlags values --- message.go | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/message.go b/message.go index 7bde46f72..6252a4f27 100644 --- a/message.go +++ b/message.go @@ -178,11 +178,24 @@ type MessageFlags int // Valid MessageFlags values const ( - MessageFlagsCrossPosted MessageFlags = 1 << 0 - MessageFlagsIsCrossPosted MessageFlags = 1 << 1 - MessageFlagsSupressEmbeds MessageFlags = 1 << 2 - MessageFlagsSourceMessageDeleted MessageFlags = 1 << 3 - MessageFlagsUrgent MessageFlags = 1 << 4 + // MessageFlagsCrossPosted This message has been published to subscribed channels (via Channel Following). + MessageFlagsCrossPosted MessageFlags = 1 << iota + // MessageFlagsIsCrossPosted this message originated from a message in another channel (via Channel Following). + MessageFlagsIsCrossPosted + // MessageFlagsSupressEmbeds do not include any embeds when serializing this message. + MessageFlagsSupressEmbeds + // MessageFlagsSourceMessageDeleted the source message for this crosspost has been deleted (via Channel Following). + MessageFlagsSourceMessageDeleted + // MessageFlagsUrgent this message came from the urgent message system. + MessageFlagsUrgent + // MessageFlagsHasThread this message has an associated thread, with the same id as the message. + MessageFlagsHasThread + // MessageFlagsEphemeral this message is only visible to the user who invoked the Interaction. + MessageFlagsEphemeral + // MessageFlagsLoading this message is an Interaction Response and the bot is "thinking". + MessageFlagsLoading + // MessageFlagsFailedToMentionSomeRolesInThread this message failed to mention some roles and add their members to the thread. + MessageFlagsFailedToMentionSomeRolesInThread ) // File stores info about files you e.g. send in messages. From 7248e0673e05b14b16e9747586a224dbf60e151a Mon Sep 17 00:00:00 2001 From: constantoine Date: Wed, 9 Feb 2022 11:59:10 +0100 Subject: [PATCH 13/35] Explicit values --- message.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/message.go b/message.go index 6252a4f27..ef7f83fd3 100644 --- a/message.go +++ b/message.go @@ -179,23 +179,23 @@ type MessageFlags int // Valid MessageFlags values const ( // MessageFlagsCrossPosted This message has been published to subscribed channels (via Channel Following). - MessageFlagsCrossPosted MessageFlags = 1 << iota + MessageFlagsCrossPosted MessageFlags = 1 << 0 // MessageFlagsIsCrossPosted this message originated from a message in another channel (via Channel Following). - MessageFlagsIsCrossPosted + MessageFlagsIsCrossPosted MessageFlags = 1 << 1 // MessageFlagsSupressEmbeds do not include any embeds when serializing this message. - MessageFlagsSupressEmbeds + MessageFlagsSupressEmbeds MessageFlags = 1 << 2 // MessageFlagsSourceMessageDeleted the source message for this crosspost has been deleted (via Channel Following). - MessageFlagsSourceMessageDeleted + MessageFlagsSourceMessageDeleted MessageFlags = 1 << 3 // MessageFlagsUrgent this message came from the urgent message system. - MessageFlagsUrgent + MessageFlagsUrgent MessageFlags = 1 << 4 // MessageFlagsHasThread this message has an associated thread, with the same id as the message. - MessageFlagsHasThread + MessageFlagsHasThread MessageFlags = 1 << 5 // MessageFlagsEphemeral this message is only visible to the user who invoked the Interaction. - MessageFlagsEphemeral + MessageFlagsEphemeral MessageFlags = 1 << 6 // MessageFlagsLoading this message is an Interaction Response and the bot is "thinking". - MessageFlagsLoading + MessageFlagsLoading MessageFlags = 1 << 7 // MessageFlagsFailedToMentionSomeRolesInThread this message failed to mention some roles and add their members to the thread. - MessageFlagsFailedToMentionSomeRolesInThread + MessageFlagsFailedToMentionSomeRolesInThread MessageFlags = 1 << 8 ) // File stores info about files you e.g. send in messages. From 21ba398898a50ec5c426225981c4ef9371c44293 Mon Sep 17 00:00:00 2001 From: Fedor Lapshin Date: Wed, 9 Feb 2022 14:07:36 +0300 Subject: [PATCH 14/35] feat(components): add omitempty and use TextInputStyle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: ToπSenpai <15636011+TopiSenpai@users.noreply.github.com> --- components.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components.go b/components.go index b2943c58a..1f4ba58fb 100644 --- a/components.go +++ b/components.go @@ -201,13 +201,13 @@ func (m SelectMenu) MarshalJSON() ([]byte, error) { // TextInput represents text input component. type TextInput struct { - CustomID string `json:"custom_id,omitempty"` + CustomID string `json:"custom_id"` Label string `json:"label"` - Style TextStyleType `json:"style"` + Style TextInputStyle `json:"style"` Placeholder string `json:"placeholder,omitempty"` Value string `json:"value,omitempty"` - Required bool `json:"required"` - MinLength int `json:"min_length"` + Required bool `json:"required,omitempty"` + MinLength int `json:"min_length,omitempty"` MaxLength int `json:"max_length,omitempty"` } From e6b33f37b70abcafeee8c3422c0c09eee7d6c870 Mon Sep 17 00:00:00 2001 From: nitroflap Date: Wed, 9 Feb 2022 14:12:31 +0300 Subject: [PATCH 15/35] fix(components): renamed TextInputStyleType to TextInputStyle and fixed example --- components.go | 22 +++++++++++----------- examples/modals/main.go | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/components.go b/components.go index 1f4ba58fb..4537bb26a 100644 --- a/components.go +++ b/components.go @@ -201,14 +201,14 @@ func (m SelectMenu) MarshalJSON() ([]byte, error) { // TextInput represents text input component. type TextInput struct { - CustomID string `json:"custom_id"` - Label string `json:"label"` + CustomID string `json:"custom_id"` + Label string `json:"label"` Style TextInputStyle `json:"style"` - Placeholder string `json:"placeholder,omitempty"` - Value string `json:"value,omitempty"` - Required bool `json:"required,omitempty"` - MinLength int `json:"min_length,omitempty"` - MaxLength int `json:"max_length,omitempty"` + Placeholder string `json:"placeholder,omitempty"` + Value string `json:"value,omitempty"` + Required bool `json:"required,omitempty"` + MinLength int `json:"min_length,omitempty"` + MaxLength int `json:"max_length,omitempty"` } // Type is a method to get the type of a component. @@ -229,11 +229,11 @@ func (m TextInput) MarshalJSON() ([]byte, error) { }) } -// TextInputStyleType is style of text in TextInput component. -type TextInputStyleType uint +// TextInputStyle is style of text in TextInput component. +type TextInputStyle uint // Text styles const ( - TextInputShort TextStyleType = 1 - TextInputParagraph TextStyleType = 2 + TextInputShort TextInputStyle = 1 + TextInputParagraph TextInputStyle = 2 ) diff --git a/examples/modals/main.go b/examples/modals/main.go index 90e9887d6..effd8f1a3 100644 --- a/examples/modals/main.go +++ b/examples/modals/main.go @@ -54,7 +54,7 @@ var ( discordgo.TextInput{ CustomID: "opinion", Label: "What is your opinion on them?", - Style: discordgo.TextStyleShort, + Style: discordgo.TextInputShort, Placeholder: "Don't be shy, share your opinion with us", Required: true, MaxLength: 300, @@ -67,7 +67,7 @@ var ( discordgo.TextInput{ CustomID: "suggestions", Label: "What would you suggest to improve them?", - Style: discordgo.TextStyleParagraph, + Style: discordgo.TextInputParagraph, Required: false, MaxLength: 2000, }, From 3f0e0f86500fd1c8cc8094b2b0006104b75628df Mon Sep 17 00:00:00 2001 From: nitroflap Date: Tue, 15 Feb 2022 23:42:22 +0300 Subject: [PATCH 16/35] fix: typo in comment to New function --- discord.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord.go b/discord.go index 5e7aab2c5..a453a4dea 100644 --- a/discord.go +++ b/discord.go @@ -23,7 +23,7 @@ import ( const VERSION = "0.23.0" // New creates a new Discord session with provided token. -// If the token is for a bot, it must be prefixed with "Bot ". +// If the token is for a bot, it must be prefixed with "Bot " // e.g. "Bot ..." // Or if it is an OAuth2 token, it must be prefixed with "Bearer " // e.g. "Bearer ..." From e44c83a1fc44227fa89d07c5e4ce8fe35e416ff0 Mon Sep 17 00:00:00 2001 From: nitroflap Date: Wed, 16 Feb 2022 02:13:29 +0300 Subject: [PATCH 17/35] feat(interactions): added note about modals to InteractionResponse --- interactions.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interactions.go b/interactions.go index b56e46ada..b71ebb130 100644 --- a/interactions.go +++ b/interactions.go @@ -470,6 +470,8 @@ type InteractionResponseData struct { // NOTE: autocomplete interaction only. Choices []*ApplicationCommandOptionChoice `json:"choices,omitempty"` + // NOTE: modal interaction only. + CustomID string `json:"custom_id,omitempty"` Title string `json:"title,omitempty"` } From 195a4c4418d10486b02df93b0488a8a449bc4892 Mon Sep 17 00:00:00 2001 From: nitroflap Date: Wed, 16 Feb 2022 02:15:16 +0300 Subject: [PATCH 18/35] fix(interactions): typo in comment to ModalSubmitData --- interactions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interactions.go b/interactions.go index b71ebb130..1658af3b4 100644 --- a/interactions.go +++ b/interactions.go @@ -218,7 +218,7 @@ func (i Interaction) ApplicationCommandData() (data ApplicationCommandInteractio return i.Data.(ApplicationCommandInteractionData) } -// ModalSubmitData is helper function to assert the innter InteractionData to ModalSubmitInteractionData. +// ModalSubmitData is helper function to assert the inner InteractionData to ModalSubmitInteractionData. // Make sure to check that the Type of the interaction is InteractionModalSubmit before calling. func (i Interaction) ModalSubmitData() (data ModalSubmitInteractionData) { if i.Type != InteractionModalSubmit { From a731868627444345ed449b7346e636164cc97825 Mon Sep 17 00:00:00 2001 From: nitroflap Date: Wed, 16 Feb 2022 02:16:44 +0300 Subject: [PATCH 19/35] fix(interactions): unnecessary newline between field and comment --- interactions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interactions.go b/interactions.go index 1658af3b4..b65d72b2f 100644 --- a/interactions.go +++ b/interactions.go @@ -140,8 +140,8 @@ type Interaction struct { // The message on which interaction was used. // NOTE: this field is only filled when a button click triggered the interaction. Otherwise it will be nil. - Message *Message `json:"message"` + // The member who invoked this interaction. // NOTE: this field is only filled when the slash command was invoked in a guild; // if it was invoked in a DM, the `User` field will be filled instead. From c26bf45ce0d8db5359e5e82700bedae2cf32e676 Mon Sep 17 00:00:00 2001 From: Pedro Pessoa Date: Wed, 16 Feb 2022 08:50:16 -0300 Subject: [PATCH 20/35] Update auditlog keys and actions (#1098) * update audit log keys and actions * remove unncessary tab * move comments above * feat: corrected comments for audit log change keys * fix: typos in audit log change keys comments and definitions Co-authored-by: nitroflap --- structs.go | 209 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 160 insertions(+), 49 deletions(-) diff --git a/structs.go b/structs.go index 9d7c1fccb..384a56c60 100644 --- a/structs.go +++ b/structs.go @@ -956,53 +956,146 @@ type AuditLogChangeKey string // Block of valid AuditLogChangeKey const ( - AuditLogChangeKeyName AuditLogChangeKey = "name" - AuditLogChangeKeyIconHash AuditLogChangeKey = "icon_hash" - AuditLogChangeKeySplashHash AuditLogChangeKey = "splash_hash" - AuditLogChangeKeyOwnerID AuditLogChangeKey = "owner_id" - AuditLogChangeKeyRegion AuditLogChangeKey = "region" - AuditLogChangeKeyAfkChannelID AuditLogChangeKey = "afk_channel_id" - AuditLogChangeKeyAfkTimeout AuditLogChangeKey = "afk_timeout" - AuditLogChangeKeyMfaLevel AuditLogChangeKey = "mfa_level" - AuditLogChangeKeyVerificationLevel AuditLogChangeKey = "verification_level" - AuditLogChangeKeyExplicitContentFilter AuditLogChangeKey = "explicit_content_filter" + // AuditLogChangeKeyAfkChannelID is sent when afk channel changed (snowflake) - guild + AuditLogChangeKeyAfkChannelID AuditLogChangeKey = "afk_channel_id" + // AuditLogChangeKeyAfkTimeout is sent when afk timeout duration changed (int) - guild + AuditLogChangeKeyAfkTimeout AuditLogChangeKey = "afk_timeout" + // AuditLogChangeKeyAllow is sent when a permission on a text or voice channel was allowed for a role (string) - role + AuditLogChangeKeyAllow AuditLogChangeKey = "allow" + // AudirChangeKeyApplicationID is sent when application id of the added or removed webhook or bot (snowflake) - channel + AuditLogChangeKeyApplicationID AuditLogChangeKey = "application_id" + // AuditLogChangeKeyArchived is sent when thread was archived/unarchived (bool) - thread + AuditLogChangeKeyArchived AuditLogChangeKey = "archived" + // AuditLogChangeKeyAsset is sent when asset is changed (string) - sticker + AuditLogChangeKeyAsset AuditLogChangeKey = "asset" + // AuditLogChangeKeyAutoArchiveDuration is sent when auto archive duration changed (int) - thread + AuditLogChangeKeyAutoArchiveDuration AuditLogChangeKey = "auto_archive_duration" + // AuditLogChangeKeyAvailable is sent when availability of sticker changed (bool) - sticker + AuditLogChangeKeyAvailable AuditLogChangeKey = "available" + // AuditLogChangeKeyAvatarHash is sent when user avatar changed (string) - user + AuditLogChangeKeyAvatarHash AuditLogChangeKey = "avatar_hash" + // AuditLogChangeKeyBannerHash is sent when guild banner changed (string) - guild + AuditLogChangeKeyBannerHash AuditLogChangeKey = "banner_hash" + // AuditLogChangeKeyBitrate is sent when voice channel bitrate changed (int) - channel + AuditLogChangeKeyBitrate AuditLogChangeKey = "bitrate" + // AuditLogChangeKeyChannelID is sent when channel for invite code or guild scheduled event changed (snowflake) - invite or guild scheduled event + AuditLogChangeKeyChannelID AuditLogChangeKey = "channel_id" + // AuditLogChangeKeyCode is sent when invite code changed (string) - invite + AuditLogChangeKeyCode AuditLogChangeKey = "code" + // AuditLogChangeKeyColor is sent when role color changed (int) - role + AuditLogChangeKeyColor AuditLogChangeKey = "color" + // AuditLogChangeKeyCommunicationDisabledUntil is sent when member timeout state changed (ISO8601 timestamp) - member + AuditLogChangeKeyCommunicationDisabledUntil AuditLogChangeKey = "communication_disabled_until" + // AuditLogChangeKeyDeaf is sent when user server deafened/undeafened (bool) - member + AuditLogChangeKeyDeaf AuditLogChangeKey = "deaf" + // AuditLogChangeKeyDefaultAutoArchiveDuration is sent when default auto archive duration for newly created threads changed (int) - channel + AuditLogChangeKeyDefaultAutoArchiveDuration AuditLogChangeKey = "default_auto_archive_duration" + // AuditLogChangeKeyDefaultMessageNotification is sent when default message notification level changed (int) - guild AuditLogChangeKeyDefaultMessageNotification AuditLogChangeKey = "default_message_notifications" - AuditLogChangeKeyVanityURLCode AuditLogChangeKey = "vanity_url_code" - AuditLogChangeKeyRoleAdd AuditLogChangeKey = "$add" - AuditLogChangeKeyRoleRemove AuditLogChangeKey = "$remove" - AuditLogChangeKeyPruneDeleteDays AuditLogChangeKey = "prune_delete_days" - AuditLogChangeKeyWidgetEnabled AuditLogChangeKey = "widget_enabled" - AuditLogChangeKeyWidgetChannelID AuditLogChangeKey = "widget_channel_id" - AuditLogChangeKeySystemChannelID AuditLogChangeKey = "system_channel_id" - AuditLogChangeKeyPosition AuditLogChangeKey = "position" - AuditLogChangeKeyTopic AuditLogChangeKey = "topic" - AuditLogChangeKeyBitrate AuditLogChangeKey = "bitrate" - AuditLogChangeKeyPermissionOverwrite AuditLogChangeKey = "permission_overwrites" - AuditLogChangeKeyNSFW AuditLogChangeKey = "nsfw" - AuditLogChangeKeyApplicationID AuditLogChangeKey = "application_id" - AuditLogChangeKeyRateLimitPerUser AuditLogChangeKey = "rate_limit_per_user" - AuditLogChangeKeyPermissions AuditLogChangeKey = "permissions" - AuditLogChangeKeyColor AuditLogChangeKey = "color" - AuditLogChangeKeyHoist AuditLogChangeKey = "hoist" - AuditLogChangeKeyMentionable AuditLogChangeKey = "mentionable" - AuditLogChangeKeyAllow AuditLogChangeKey = "allow" - AuditLogChangeKeyDeny AuditLogChangeKey = "deny" - AuditLogChangeKeyCode AuditLogChangeKey = "code" - AuditLogChangeKeyChannelID AuditLogChangeKey = "channel_id" - AuditLogChangeKeyInviterID AuditLogChangeKey = "inviter_id" - AuditLogChangeKeyMaxUses AuditLogChangeKey = "max_uses" - AuditLogChangeKeyUses AuditLogChangeKey = "uses" - AuditLogChangeKeyMaxAge AuditLogChangeKey = "max_age" - AuditLogChangeKeyTempoary AuditLogChangeKey = "temporary" - AuditLogChangeKeyDeaf AuditLogChangeKey = "deaf" - AuditLogChangeKeyMute AuditLogChangeKey = "mute" - AuditLogChangeKeyNick AuditLogChangeKey = "nick" - AuditLogChangeKeyAvatarHash AuditLogChangeKey = "avatar_hash" - AuditLogChangeKeyID AuditLogChangeKey = "id" - AuditLogChangeKeyType AuditLogChangeKey = "type" - AuditLogChangeKeyEnableEmoticons AuditLogChangeKey = "enable_emoticons" - AuditLogChangeKeyExpireBehavior AuditLogChangeKey = "expire_behavior" - AuditLogChangeKeyExpireGracePeriod AuditLogChangeKey = "expire_grace_period" + // AuditLogChangeKeyDeny is sent when a permission on a text or voice channel was denied for a role (string) - role + AuditLogChangeKeyDeny AuditLogChangeKey = "deny" + // AuditLogChangeKeyDescription is sent when description changed (string) - guild, sticker, or guild scheduled event + AuditLogChangeKeyDescription AuditLogChangeKey = "description" + // AuditLogChangeKeyDiscoverySplashHash is sent when discovery splash changed (string) - guild + AuditLogChangeKeyDiscoverySplashHash AuditLogChangeKey = "discovery_splash_hash" + // AuditLogChangeKeyEnableEmoticons is sent when integration emoticons enabled/disabled (bool) - integration + AuditLogChangeKeyEnableEmoticons AuditLogChangeKey = "enable_emoticons" + // AuditLogChangeKeyEntityType is sent when entity type of guild scheduled event was changed (int) - guild scheduled event + AuditLogChangeKeyEntityType AuditLogChangeKey = "entity_type" + // AuditLogChangeKeyExpireBehavior is sent when integration expiring subscriber behavior changed (int) - integration + AuditLogChangeKeyExpireBehavior AuditLogChangeKey = "expire_behavior" + // AuditLogChangeKeyExpireGracePeriod is sent when integration expire grace period changed (int) - integration + AuditLogChangeKeyExpireGracePeriod AuditLogChangeKey = "expire_grace_period" + // AuditLogChangeKeyExplicitContentFilter is sent when change in whose messages are scanned and deleted for explicit content in the server is made (int) - guild + AuditLogChangeKeyExplicitContentFilter AuditLogChangeKey = "explicit_content_filter" + // AuditLogChangeKeyFormatType is sent when format type of sticker changed (int - sticker format type) - sticker + AuditLogChangeKeyFormatType AuditLogChangeKey = "format_type" + // AuditLogChangeKeyGuildID is sent when guild sticker is in changed (snowflake) - sticker + AuditLogChangeKeyGuildID AuditLogChangeKey = "guild_id" + // AuditLogChangeKeyHoist is sent when role is now displayed/no longer displayed separate from online users (bool) - role + AuditLogChangeKeyHoist AuditLogChangeKey = "hoist" + // AuditLogChangeKeyIconHash is sent when icon changed (string) - guild or role + AuditLogChangeKeyIconHash AuditLogChangeKey = "icon_hash" + // AuditLogChangeKeyID is sent when the id of the changed entity - sometimes used in conjunction with other keys (snowflake) - any + AuditLogChangeKeyID AuditLogChangeKey = "id" + // AuditLogChangeKeyInvitable is sent when private thread is now invitable/uninvitable (bool) - thread + AuditLogChangeKeyInvitable AuditLogChangeKey = "invitable" + // AuditLogChangeKeyInviterID is sent when person who created invite code changed (snowflake) - invite + AuditLogChangeKeyInviterID AuditLogChangeKey = "inviter_id" + // AuditLogChangeKeyLocation is sent when channel id for guild scheduled event changed (string) - guild scheduled event + AuditLogChangeKeyLocation AuditLogChangeKey = "location" + // AuditLogChangeKeyLocked is sent when thread was locked/unlocked (bool) - thread + AuditLogChangeKeyLocked AuditLogChangeKey = "locked" + // AuditLogChangeKeyMaxAge is sent when invite code expiration time changed (int) - invite + AuditLogChangeKeyMaxAge AuditLogChangeKey = "max_age" + // AuditLogChangeKeyMaxUses is sent when max number of times invite code can be used changed (int) - invite + AuditLogChangeKeyMaxUses AuditLogChangeKey = "max_uses" + // AuditLogChangeKeyMentionable is sent when role is now mentionable/unmentionable (bool) - role + AuditLogChangeKeyMentionable AuditLogChangeKey = "mentionable" + // AuditLogChangeKeyMfaLevel is sent when two-factor auth requirement changed (int - mfa level) - guild + AuditLogChangeKeyMfaLevel AuditLogChangeKey = "mfa_level" + // AuditLogChangeKeyMute is sent when user server muted/unmuted (bool) - member + AuditLogChangeKeyMute AuditLogChangeKey = "mute" + // AuditLogChangeKeyName is sent when name changed (string) - any + AuditLogChangeKeyName AuditLogChangeKey = "name" + // AuditLogChangeKeyNick is sent when user nickname changed (string) - member + AuditLogChangeKeyNick AuditLogChangeKey = "nick" + // AuditLogChangeKeyNSFW is sent when channel nsfw restriction changed (bool) - channel + AuditLogChangeKeyNSFW AuditLogChangeKey = "nsfw" + // AuditLogChangeKeyOwnerID is sent when owner changed (snowflake) - guild + AuditLogChangeKeyOwnerID AuditLogChangeKey = "owner_id" + // AuditLogChangeKeyPermissionOverwrite is sent when permissions on a channel changed (array of channel overwrite objects) - channel + AuditLogChangeKeyPermissionOverwrite AuditLogChangeKey = "permission_overwrites" + // AuditLogChangeKeyPermissions is sent when permissions for a role changed (string) - role + AuditLogChangeKeyPermissions AuditLogChangeKey = "permissions" + // AuditLogChangeKeyPosition is sent when text or voice channel position changed (int) - channel + AuditLogChangeKeyPosition AuditLogChangeKey = "position" + // AuditLogChangeKeyPreferredLocale is sent when preferred locale changed (string) - guild + AuditLogChangeKeyPreferredLocale AuditLogChangeKey = "preferred_locale" + // AuditLogChangeKeyPrivacylevel is sent when privacy level of the stage instance changed (integer - privacy level) - stage instance or guild scheduled event + AuditLogChangeKeyPrivacylevel AuditLogChangeKey = "privacy_level" + // AuditLogChangeKeyPruneDeleteDays is sent when number of days after which inactive and role-unassigned members are kicked changed (int) - guild + AuditLogChangeKeyPruneDeleteDays AuditLogChangeKey = "prune_delete_days" + // AuditLogChangeKeyPulibUpdatesChannelID is sent when id of the public updates channel changed (snowflake) - guild + AuditLogChangeKeyPulibUpdatesChannelID AuditLogChangeKey = "public_updates_channel_id" + // AuditLogChangeKeyRateLimitPerUser is sent when amount of seconds a user has to wait before sending another message changed (int) - channel + AuditLogChangeKeyRateLimitPerUser AuditLogChangeKey = "rate_limit_per_user" + // AuditLogChangeKeyRegion is sent when region changed (string) - guild + AuditLogChangeKeyRegion AuditLogChangeKey = "region" + // AuditLogChangeKeyRulesChannelID is sent when id of the rules channel changed (snowflake) - guild + AuditLogChangeKeyRulesChannelID AuditLogChangeKey = "rules_channel_id" + // AuditLogChangeKeySplashHash is sent when invite splash page artwork changed (string) - guild + AuditLogChangeKeySplashHash AuditLogChangeKey = "splash_hash" + // AuditLogChangeKeyStatus is sent when status of guild scheduled event was changed (int - guild scheduled event status) - guild scheduled event + AuditLogChangeKeyStatus AuditLogChangeKey = "status" + // AuditLogChangeKeySystemChannelID is sent when id of the system channel changed (snowflake) - guild + AuditLogChangeKeySystemChannelID AuditLogChangeKey = "system_channel_id" + // AuditLogChangeKeyTags is sent when related emoji of sticker changed (string) - sticker + AuditLogChangeKeyTags AuditLogChangeKey = "tags" + // AuditLogChangeKeyTemporary is sent when invite code is now temporary or never expires (bool) - invite + AuditLogChangeKeyTemporary AuditLogChangeKey = "temporary" + // AuditLogChangeKeyTopic is sent when text channel topic or stage instance topic changed (string) - channel or stage instance + AuditLogChangeKeyTopic AuditLogChangeKey = "topic" + // AuditLogChangeKeyType is sent when type of entity created (int or string) - any + AuditLogChangeKeyType AuditLogChangeKey = "type" + // AuditLogChangeKeyUnicodeEmoji is sent when role unicode emoji changed (string) - role + AuditLogChangeKeyUnicodeEmoji AuditLogChangeKey = "unicode_emoji" + // AuditLogChangeKeyUserLimit is sent when new user limit in a voice channel set (int) - voice channel + AuditLogChangeKeyUserLimit AuditLogChangeKey = "user_limit" + // AuditLogChangeKeyUses is sent when number of times invite code used changed (int) - invite + AuditLogChangeKeyUses AuditLogChangeKey = "uses" + // AuditLogChangeKeyVanityURLCode is sent when guild invite vanity url changed (string) - guild + AuditLogChangeKeyVanityURLCode AuditLogChangeKey = "vanity_url_code" + // AuditLogChangeKeyVerificationLevel is sent when required verification level changed (int - verification level) - guild + AuditLogChangeKeyVerificationLevel AuditLogChangeKey = "verification_level" + // AuditLogChangeKeyWidgetChannelID is sent when channel id of the server widget changed (snowflake) - guild + AuditLogChangeKeyWidgetChannelID AuditLogChangeKey = "widget_channel_id" + // AuditLogChangeKeyWidgetEnabled is sent when server widget enabled/disabled (bool) - guild + AuditLogChangeKeyWidgetEnabled AuditLogChangeKey = "widget_enabled" + // AuditLogChangeKeyRoleAdd is sent when new role added (array of partial role objects) - guild + AuditLogChangeKeyRoleAdd AuditLogChangeKey = "$add" + // AuditLogChangeKeyRoleRemove is sent when role removed (array of partial role objects) - guild + AuditLogChangeKeyRoleRemove AuditLogChangeKey = "$remove" ) // AuditLogOptions optional data for the AuditLog @@ -1049,6 +1142,9 @@ const ( AuditLogActionMemberBanRemove AuditLogAction = 23 AuditLogActionMemberUpdate AuditLogAction = 24 AuditLogActionMemberRoleUpdate AuditLogAction = 25 + AuditLogActionMemberMove AuditLogAction = 26 + AuditLogActionMemberDisconnect AuditLogAction = 27 + AuditLogActionBotAdd AuditLogAction = 28 AuditLogActionRoleCreate AuditLogAction = 30 AuditLogActionRoleUpdate AuditLogAction = 31 @@ -1071,9 +1167,24 @@ const ( AuditLogActionMessagePin AuditLogAction = 74 AuditLogActionMessageUnpin AuditLogAction = 75 - AuditLogActionIntegrationCreate AuditLogAction = 80 - AuditLogActionIntegrationUpdate AuditLogAction = 81 - AuditLogActionIntegrationDelete AuditLogAction = 82 + AuditLogActionIntegrationCreate AuditLogAction = 80 + AuditLogActionIntegrationUpdate AuditLogAction = 81 + AuditLogActionIntegrationDelete AuditLogAction = 82 + AuditLogActionStageInstanceCreate AuditLogAction = 83 + AuditLogActionStageInstanceUpdate AuditLogAction = 84 + AuditLogActionStageInstanceDelete AuditLogAction = 85 + + AuditLogActionStickerCreate AuditLogAction = 90 + AuditLogActionStickerUpdate AuditLogAction = 91 + AuditLogActionStickerDelete AuditLogAction = 92 + + AuditLogGuildScheduledEventCreate AuditLogAction = 100 + AuditLogGuildScheduledEventUpdare AuditLogAction = 101 + AuditLogGuildScheduledEventDelete AuditLogAction = 102 + + AuditLogActionThreadCreate AuditLogAction = 110 + AuditLogActionThreadUpdate AuditLogAction = 111 + AuditLogActionThreadDelete AuditLogAction = 112 ) // A UserGuildSettingsChannelOverride stores data for a channel override for a users guild settings. From 5ee06354aa7a277da487b427a6790512e1bd0e7e Mon Sep 17 00:00:00 2001 From: nitroflap Date: Wed, 16 Feb 2022 14:57:14 +0300 Subject: [PATCH 21/35] fix: added AuditLogChangeKeyTempoary back for compatibility --- structs.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/structs.go b/structs.go index 384a56c60..60de63dd7 100644 --- a/structs.go +++ b/structs.go @@ -1074,6 +1074,8 @@ const ( AuditLogChangeKeyTags AuditLogChangeKey = "tags" // AuditLogChangeKeyTemporary is sent when invite code is now temporary or never expires (bool) - invite AuditLogChangeKeyTemporary AuditLogChangeKey = "temporary" + // TODO: remove when compatibility is not required + AuditLogChangeKeyTempoary = AuditLogChangeKeyTemporary // AuditLogChangeKeyTopic is sent when text channel topic or stage instance topic changed (string) - channel or stage instance AuditLogChangeKeyTopic AuditLogChangeKey = "topic" // AuditLogChangeKeyType is sent when type of entity created (int or string) - any From 115adf2866b55809fad82b1437ef5efe62dedc3b Mon Sep 17 00:00:00 2001 From: Pedro Pessoa Date: Wed, 16 Feb 2022 09:29:20 -0300 Subject: [PATCH 22/35] add missing json error codes (#1096) * add missing json error codes * Update structs.go Co-authored-by: Fedor Lapshin * Update structs.go Co-authored-by: Fedor Lapshin * requested changes * Update structs.go Co-authored-by: Fedor Lapshin * Update structs.go Co-authored-by: Fedor Lapshin * formatting * add ErrCodeEmbedDisabled back Co-authored-by: Fedor Lapshin --- structs.go | 203 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 154 insertions(+), 49 deletions(-) diff --git a/structs.go b/structs.go index 60de63dd7..2a2369fb5 100644 --- a/structs.go +++ b/structs.go @@ -1449,57 +1449,162 @@ const ( // Block contains Discord JSON Error Response codes const ( - ErrCodeUnknownAccount = 10001 - ErrCodeUnknownApplication = 10002 - ErrCodeUnknownChannel = 10003 - ErrCodeUnknownGuild = 10004 - ErrCodeUnknownIntegration = 10005 - ErrCodeUnknownInvite = 10006 - ErrCodeUnknownMember = 10007 - ErrCodeUnknownMessage = 10008 - ErrCodeUnknownOverwrite = 10009 - ErrCodeUnknownProvider = 10010 - ErrCodeUnknownRole = 10011 - ErrCodeUnknownToken = 10012 - ErrCodeUnknownUser = 10013 - ErrCodeUnknownEmoji = 10014 - ErrCodeUnknownWebhook = 10015 - ErrCodeUnknownBan = 10026 - - ErrCodeBotsCannotUseEndpoint = 20001 - ErrCodeOnlyBotsCanUseEndpoint = 20002 - - ErrCodeMaximumGuildsReached = 30001 - ErrCodeMaximumFriendsReached = 30002 - ErrCodeMaximumPinsReached = 30003 - ErrCodeMaximumGuildRolesReached = 30005 - ErrCodeTooManyReactions = 30010 - - ErrCodeUnauthorized = 40001 - - ErrCodeMissingAccess = 50001 - ErrCodeInvalidAccountType = 50002 - ErrCodeCannotExecuteActionOnDMChannel = 50003 - ErrCodeEmbedDisabled = 50004 - ErrCodeCannotEditFromAnotherUser = 50005 - ErrCodeCannotSendEmptyMessage = 50006 - ErrCodeCannotSendMessagesToThisUser = 50007 - ErrCodeCannotSendMessagesInVoiceChannel = 50008 - ErrCodeChannelVerificationLevelTooHigh = 50009 - ErrCodeOAuth2ApplicationDoesNotHaveBot = 50010 - ErrCodeOAuth2ApplicationLimitReached = 50011 - ErrCodeInvalidOAuthState = 50012 - ErrCodeMissingPermissions = 50013 - ErrCodeInvalidAuthenticationToken = 50014 - ErrCodeNoteTooLong = 50015 - ErrCodeTooFewOrTooManyMessagesToDelete = 50016 - ErrCodeCanOnlyPinMessageToOriginatingChannel = 50019 - ErrCodeCannotExecuteActionOnSystemMessage = 50021 - ErrCodeMessageProvidedTooOldForBulkDelete = 50034 - ErrCodeInvalidFormBody = 50035 - ErrCodeInviteAcceptedToGuildApplicationsBotNotIn = 50036 + ErrCodeGeneralError = 0 + + ErrCodeUnknownAccount = 10001 + ErrCodeUnknownApplication = 10002 + ErrCodeUnknownChannel = 10003 + ErrCodeUnknownGuild = 10004 + ErrCodeUnknownIntegration = 10005 + ErrCodeUnknownInvite = 10006 + ErrCodeUnknownMember = 10007 + ErrCodeUnknownMessage = 10008 + ErrCodeUnknownOverwrite = 10009 + ErrCodeUnknownProvider = 10010 + ErrCodeUnknownRole = 10011 + ErrCodeUnknownToken = 10012 + ErrCodeUnknownUser = 10013 + ErrCodeUnknownEmoji = 10014 + ErrCodeUnknownWebhook = 10015 + ErrCodeUnknownWebhookService = 10016 + ErrCodeUnknownSession = 10020 + ErrCodeUnknownBan = 10026 + ErrCodeUnknownSKU = 10027 + ErrCodeUnknownStoreListing = 10028 + ErrCodeUnknownEntitlement = 10029 + ErrCodeUnknownBuild = 10030 + ErrCodeUnknownLobby = 10031 + ErrCodeUnknownBranch = 10032 + ErrCodeUnknownStoreDirectoryLayout = 10033 + ErrCodeUnknownRedistributable = 10036 + ErrCodeUnknownGiftCode = 10038 + ErrCodeUnknownStream = 10049 + ErrCodeUnknownPremiumServerSubscribeCooldown = 10050 + ErrCodeUnknownGuildTemplate = 10057 + ErrCodeUnknownDiscoveryCategory = 10059 + ErrCodeUnknownSticker = 10060 + ErrCodeUnknownInteraction = 10062 + ErrCodeUnknownApplicationCommand = 10063 + ErrCodeUnknownApplicationCommandPermissions = 10066 + ErrCodeUnknownStageInstance = 10067 + ErrCodeUnknownGuildMemberVerificationForm = 10068 + ErrCodeUnknownGuildWelcomeScreen = 10069 + ErrCodeUnknownGuildScheduledEvent = 10070 + ErrCodeUnknownGuildScheduledEventUser = 10071 + + ErrCodeBotsCannotUseEndpoint = 20001 + ErrCodeOnlyBotsCanUseEndpoint = 20002 + ErrCodeExplicitContentCannotBeSentToTheDesiredRecipients = 20009 + ErrCodeYouAreNotAuthorizedToPerformThisActionOnThisApplication = 20012 + ErrCodeThisActionCannotBePerformedDueToSlowmodeRateLimit = 20016 + ErrCodeOnlyTheOwnerOfThisAccountCanPerformThisAction = 20018 + ErrCodeMessageCannotBeEditedDueToAnnouncementRateLimits = 20022 + ErrCodeChannelHasHitWriteRateLimit = 20028 + ErrCodeTheWriteActionYouArePerformingOnTheServerHasHitTheWriteRateLimit = 20029 + ErrCodeStageTopicContainsNotAllowedWordsForPublicStages = 20031 + ErrCodeGuildPremiumSubscriptionLevelTooLow = 20035 + + ErrCodeMaximumPinsReached = 30003 + ErrCodeMaximumNumberOfRecipientsReached = 30004 + ErrCodeMaximumGuildRolesReached = 30005 + ErrCodeMaximumNumberOfWebhooksReached = 30007 + ErrCodeMaximumNumberOfEmojisReached = 30008 + ErrCodeTooManyReactions = 30010 + ErrCodeMaximumNumberOfGuildChannelsReached = 30013 + ErrCodeMaximumNumberOfAttachmentsInAMessageReached = 30015 + ErrCodeMaximumNumberOfInvitesReached = 30016 + ErrCodeMaximumNumberOfAnimatedEmojisReached = 30018 + ErrCodeMaximumNumberOfServerMembersReached = 30019 + ErrCodeMaximumNumberOfGuildDiscoverySubcategoriesReached = 30030 + ErrCodeGuildAlreadyHasATemplate = 30031 + ErrCodeMaximumNumberOfThreadParticipantsReached = 30033 + ErrCodeMaximumNumberOfBansForNonGuildMembersHaveBeenExceeded = 30035 + ErrCodeMaximumNumberOfBansFetchesHasBeenReached = 30037 + ErrCodeMaximumNumberOfUncompletedGuildScheduledEventsReached = 30038 + ErrCodeMaximumNumberOfStickersReached = 30039 + ErrCodeMaximumNumberOfPruneRequestsHasBeenReached = 30040 + ErrCodeMaximumNumberOfGuildWidgetSettingsUpdatesHasBeenReached = 30042 + ErrCodeMaximumNumberOfEditsToMessagesOlderThanOneHourReached = 30046 + + ErrCodeUnauthorized = 40001 + ErrCodeActionRequiredVerifiedAccount = 40002 + ErrCodeOpeningDirectMessagesTooFast = 40003 + ErrCodeSendMessagesHasBeenTemporarilyDisabled = 40004 + ErrCodeRequestEntityTooLarge = 40005 + ErrCodeFeatureTemporarilyDisabledServerSide = 40006 + ErrCodeUserIsBannedFromThisGuild = 40007 + ErrCodeTargetIsNotConnectedToVoice = 40032 + ErrCodeMessageAlreadyCrossposted = 40033 + ErrCodeAnApplicationWithThatNameAlreadyExists = 40041 + ErrCodeInteractionHasAlreadyBeenAcknowledged = 40060 + + ErrCodeMissingAccess = 50001 + ErrCodeInvalidAccountType = 50002 + ErrCodeCannotExecuteActionOnDMChannel = 50003 + ErrCodeEmbedDisabled = 50004 + ErrCodeGuildWidgetDisabled = 50004 + ErrCodeCannotEditFromAnotherUser = 50005 + ErrCodeCannotSendEmptyMessage = 50006 + ErrCodeCannotSendMessagesToThisUser = 50007 + ErrCodeCannotSendMessagesInVoiceChannel = 50008 + ErrCodeChannelVerificationLevelTooHigh = 50009 + ErrCodeOAuth2ApplicationDoesNotHaveBot = 50010 + ErrCodeOAuth2ApplicationLimitReached = 50011 + ErrCodeInvalidOAuthState = 50012 + ErrCodeMissingPermissions = 50013 + ErrCodeInvalidAuthenticationToken = 50014 + ErrCodeTooFewOrTooManyMessagesToDelete = 50016 + ErrCodeCanOnlyPinMessageToOriginatingChannel = 50019 + ErrCodeInviteCodeWasEitherInvalidOrTaken = 50020 + ErrCodeCannotExecuteActionOnSystemMessage = 50021 + ErrCodeCannotExecuteActionOnThisChannelType = 50024 + ErrCodeInvalidOAuth2AccessTokenProvided = 50025 + ErrCodeMissingRequiredOAuth2Scope = 50026 + ErrCodeInvalidWebhookTokenProvided = 50027 + ErrCodeInvalidRole = 50028 + ErrCodeInvalidRecipients = 50033 + ErrCodeMessageProvidedTooOldForBulkDelete = 50034 + ErrCodeInvalidFormBody = 50035 + ErrCodeInviteAcceptedToGuildApplicationsBotNotIn = 50036 + ErrCodeInvalidAPIVersionProvided = 50041 + ErrCodeFileUploadedExceedsTheMaximumSize = 50045 + ErrCodeInvalidFileUploaded = 50046 + ErrCodeInvalidGuild = 50055 + ErrCodeInvalidMessageType = 50068 + ErrCodeCannotDeleteAChannelRequiredForCommunityGuilds = 50074 + ErrCodeInvalidStickerSent = 50081 + ErrCodePerformedOperationOnArchivedThread = 50083 + ErrCodeBeforeValueIsEarlierThanThreadCreationDate = 50085 + ErrCodeCommunityServerChannelsMustBeTextChannels = 50086 + ErrCodeThisServerIsNotAvailableInYourLocation = 50095 + ErrCodeThisServerNeedsMonetizationEnabledInOrderToPerformThisAction = 50097 + ErrCodeThisServerNeedsMoreBoostsToPerformThisAction = 50101 + ErrCodeTheRequestBodyContainsInvalidJSON = 50109 + + ErrCodeNoUsersWithDiscordTagExist = 80004 ErrCodeReactionBlocked = 90001 + + ErrCodeAPIResourceIsCurrentlyOverloaded = 130000 + + ErrCodeTheStageIsAlreadyOpen = 150006 + + ErrCodeCannotReplyWithoutPermissionToReadMessageHistory = 160002 + ErrCodeThreadAlreadyCreatedForThisMessage = 160004 + ErrCodeThreadIsLocked = 160005 + ErrCodeMaximumNumberOfActiveThreadsReached = 160006 + ErrCodeMaximumNumberOfActiveAnnouncementThreadsReached = 160007 + + ErrCodeInvalidJSONForUploadedLottieFile = 170001 + ErrCodeUploadedLottiesCannotContainRasterizedImages = 170002 + ErrCodeStickerMaximumFramerateExceeded = 170003 + ErrCodeStickerFrameCountExceedsMaximumOfOneThousandFrames = 170004 + ErrCodeLottieAnimationMaximumDimensionsExceeded = 170005 + ErrCodeStickerFrameRateOutOfRange = 170006 + ErrCodeStickerAnimationDurationExceedsMaximumOfFiveSeconds = 170007 + + ErrCodeCannotUpdateAFinishedEvent = 180000 + ErrCodeFailedToCreateStageNeededForStageEvent = 180002 ) // Intent is the type of a Gateway Intent From c871464295222a37fcfafea7a23a0db45d9c9dd7 Mon Sep 17 00:00:00 2001 From: Pedro Pessoa Date: Wed, 16 Feb 2022 09:30:32 -0300 Subject: [PATCH 23/35] update intents (#1097) --- structs.go | 68 +++++++++++++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/structs.go b/structs.go index 2a2369fb5..29a2eb07a 100644 --- a/structs.go +++ b/structs.go @@ -1613,38 +1613,44 @@ type Intent int // Constants for the different bit offsets of intents const ( - IntentsGuilds Intent = 1 << 0 - IntentsGuildMembers Intent = 1 << 1 - IntentsGuildBans Intent = 1 << 2 - IntentsGuildEmojis Intent = 1 << 3 - IntentsGuildIntegrations Intent = 1 << 4 - IntentsGuildWebhooks Intent = 1 << 5 - IntentsGuildInvites Intent = 1 << 6 - IntentsGuildVoiceStates Intent = 1 << 7 - IntentsGuildPresences Intent = 1 << 8 - IntentsGuildMessages Intent = 1 << 9 - IntentsGuildMessageReactions Intent = 1 << 10 - IntentsGuildMessageTyping Intent = 1 << 11 - IntentsDirectMessages Intent = 1 << 12 - IntentsDirectMessageReactions Intent = 1 << 13 - IntentsDirectMessageTyping Intent = 1 << 14 - - IntentsAllWithoutPrivileged = IntentsGuilds | - IntentsGuildBans | - IntentsGuildEmojis | - IntentsGuildIntegrations | - IntentsGuildWebhooks | - IntentsGuildInvites | - IntentsGuildVoiceStates | - IntentsGuildMessages | - IntentsGuildMessageReactions | - IntentsGuildMessageTyping | - IntentsDirectMessages | - IntentsDirectMessageReactions | - IntentsDirectMessageTyping + 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 + + IntentsAllWithoutPrivileged = IntentGuilds | + IntentGuildBans | + IntentGuildEmojis | + IntentGuildIntegrations | + IntentGuildWebhooks | + IntentGuildInvites | + IntentGuildVoiceStates | + IntentGuildMessages | + IntentGuildMessageReactions | + IntentGuildMessageTyping | + IntentDirectMessages | + IntentDirectMessageReactions | + IntentDirectMessageTyping | + IntentGuildScheduledEvents + IntentsAll = IntentsAllWithoutPrivileged | - IntentsGuildMembers | - IntentsGuildPresences + IntentGuildMembers | + IntentGuildPresences | + IntentMessageContent + IntentsNone Intent = 0 ) From 971184a54275f83aed80607c1f7e1f073821cbbb Mon Sep 17 00:00:00 2001 From: Fedor Lapshin Date: Wed, 16 Feb 2022 16:24:17 +0300 Subject: [PATCH 24/35] CI (#1076) * feat: ci barebones * fix(.github/workflows): checkout * feat(.golangci.yml): removed presets * feat(workflows/ci): moved matrix to test job * feat(golangci-lint): disabled all linters except golint * fix(workflows/ci): golangci-lint action cache fix * feat(workflows/ci): custom step with golangci-lint on normal commit * feat(.github/workflows): moved setup-go into individual jobs * feat(workflows/ci): only new lint issues for pull requests --- .github/workflows/ci.yml | 60 ++++++++++++++++++++++++++++++++++++++++ .golangci.yml | 19 +++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .golangci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..63e14622f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,60 @@ +on: + push: + pull_request: + +name: CI + +jobs: + format: + runs-on: ubuntu-latest + steps: + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: 1.17 + - name: Code + uses: actions/checkout@v2 + - name: Check diff between gofmt and code + run: diff <(gofmt -d .) <(echo -n) + + test: + runs-on: ubuntu-latest + strategy: + matrix: + go-version: [1.13, 1.14, 1.15, 1.16, 1.17] + steps: + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + - name: Code + uses: actions/checkout@v2 + - run: go test -v -race ./... + + lint: + runs-on: ubuntu-latest + steps: + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: 1.17 + - name: Code + uses: actions/checkout@v2 + - name: Go vet + run: go vet -x ./... + + - name: GolangCI-Lint + uses: golangci/golangci-lint-action@v2.5.2 + if: github.event.name == 'pull_request' + with: + only-new-issues: true + skip-pkg-cache: true + skip-build-cache: true + + - name: GolangCI-Lint + if: github.event.name != 'pull_request' # See https://github.com/golangci/golangci-lint-action/issues/362 + run: | + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.43.0 + + $(go env GOPATH)/bin/golangci-lint run + diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 000000000..dd9d2e5b4 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,19 @@ +linters: + disable-all: true + enable: + # - staticcheck + # - unused + - golint + +linters-settings: + staticcheck: + go: "1.13" + + checks: ["all"] + + unused: + go: "1.13" + +issues: + include: + - EXC0002 From e16b1f8e5ba735f93321d1266d9b4754bc2024a5 Mon Sep 17 00:00:00 2001 From: nitroflap Date: Wed, 16 Feb 2022 16:31:13 +0300 Subject: [PATCH 25/35] fix(structs): added back old intent definitions for compatibility --- structs.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/structs.go b/structs.go index 29a2eb07a..17789f89f 100644 --- a/structs.go +++ b/structs.go @@ -1631,6 +1631,26 @@ const ( IntentMessageContent Intent = 1 << 15 IntentGuildScheduledEvents Intent = 1 << 16 + // TODO: remove when compatibility is not needed + + IntentsGuilds Intent = 1 << 0 + IntentsGuildMembers Intent = 1 << 1 + IntentsGuildBans Intent = 1 << 2 + IntentsGuildEmojis Intent = 1 << 3 + IntentsGuildIntegrations Intent = 1 << 4 + IntentsGuildWebhooks Intent = 1 << 5 + IntentsGuildInvites Intent = 1 << 6 + IntentsGuildVoiceStates Intent = 1 << 7 + IntentsGuildPresences Intent = 1 << 8 + IntentsGuildMessages Intent = 1 << 9 + IntentsGuildMessageReactions Intent = 1 << 10 + IntentsGuildMessageTyping Intent = 1 << 11 + IntentsDirectMessages Intent = 1 << 12 + IntentsDirectMessageReactions Intent = 1 << 13 + IntentsDirectMessageTyping Intent = 1 << 14 + IntentsMessageContent Intent = 1 << 15 + IntentsGuildScheduledEvents Intent = 1 << 16 + IntentsAllWithoutPrivileged = IntentGuilds | IntentGuildBans | IntentGuildEmojis | From 7044ee62cb915887c017b1bca67134637e989cbb Mon Sep 17 00:00:00 2001 From: nitroflap Date: Wed, 16 Feb 2022 16:32:48 +0300 Subject: [PATCH 26/35] fix(examples/avatar): parameters for UserUpdate --- examples/avatar/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/avatar/main.go b/examples/avatar/main.go index e0a9c8802..58fa608fd 100644 --- a/examples/avatar/main.go +++ b/examples/avatar/main.go @@ -82,7 +82,7 @@ func main() { // Now lets format our base64 image into the proper format Discord wants // and then call UserUpdate to set it as our user's Avatar. avatar := fmt.Sprintf("data:%s;base64,%s", contentType, base64img) - _, err = dg.UserUpdate("", "", "", avatar, "") + _, err = dg.UserUpdate("", avatar) if err != nil { fmt.Println(err) } From 3d0ad546d1cd8d7cbd89727c630e525cf13ca8ee Mon Sep 17 00:00:00 2001 From: nitroflap Date: Wed, 16 Feb 2022 16:38:21 +0300 Subject: [PATCH 27/35] feat(discord_test#init): check for oauth2 token --- discord_test.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/discord_test.go b/discord_test.go index 973e3a654..f35318558 100644 --- a/discord_test.go +++ b/discord_test.go @@ -15,11 +15,11 @@ var ( dg *Session // Stores a global discordgo user session dgBot *Session // Stores a global discordgo bot session - envToken = os.Getenv("DGU_TOKEN") // Token to use when authenticating the user account - envBotToken = os.Getenv("DGB_TOKEN") // Token to use when authenticating the bot account - envGuild = os.Getenv("DG_GUILD") // Guild ID to use for tests - envChannel = os.Getenv("DG_CHANNEL") // Channel ID to use for tests - envAdmin = os.Getenv("DG_ADMIN") // User ID of admin user to use for tests + envOAuth2Token = os.Getenv("DG_OAUTH2_TOKEN") // Token to use when authenticating using OAuth2 token + envBotToken = os.Getenv("DGB_TOKEN") // Token to use when authenticating the bot account + envGuild = os.Getenv("DG_GUILD") // Guild ID to use for tests + envChannel = os.Getenv("DG_CHANNEL") // Channel ID to use for tests + envAdmin = os.Getenv("DG_ADMIN") // User ID of admin user to use for tests ) func init() { @@ -30,10 +30,10 @@ func init() { } } - if d, err := New(envToken); err == nil { - dg = d - } else { - fmt.Println("dg is nil, error", err) + if envOAuth2Token != "" { + if d, err := New(envOAuth2Token); err == nil { + dg = d + } } } @@ -43,11 +43,11 @@ func init() { // TestNewToken tests the New() function with a Token. func TestNewToken(t *testing.T) { - if envToken == "" { + if envOAuth2Token == "" { t.Skip("Skipping New(token), DGU_TOKEN not set") } - d, err := New(envToken) + d, err := New(envOAuth2Token) if err != nil { t.Fatalf("New(envToken) returned error: %+v", err) } @@ -62,11 +62,11 @@ func TestNewToken(t *testing.T) { } func TestOpenClose(t *testing.T) { - if envToken == "" { + if envOAuth2Token == "" { t.Skip("Skipping TestClose, DGU_TOKEN not set") } - d, err := New(envToken) + d, err := New(envOAuth2Token) if err != nil { t.Fatalf("TestClose, New(envToken) returned error: %+v", err) } From 12b412a989e2635b7038eea089b920ffe0dffeca Mon Sep 17 00:00:00 2001 From: nitroflap Date: Wed, 16 Feb 2022 16:39:50 +0300 Subject: [PATCH 28/35] feat(discord_test): rewrote init as TestMain --- discord_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/discord_test.go b/discord_test.go index f35318558..f2dd28c3d 100644 --- a/discord_test.go +++ b/discord_test.go @@ -22,7 +22,7 @@ var ( envAdmin = os.Getenv("DG_ADMIN") // User ID of admin user to use for tests ) -func init() { +func TestMain(m *testing.M) { fmt.Println("Init is being called.") if envBotToken != "" { if d, err := New(envBotToken); err == nil { @@ -35,6 +35,8 @@ func init() { dg = d } } + + os.Exit(m.Run()) } ////////////////////////////////////////////////////////////////////////////// From d8f15b1bc606bbaec7bc94bd3e10fa3e0e7eff9c Mon Sep 17 00:00:00 2001 From: nitroflap Date: Wed, 16 Feb 2022 16:41:37 +0300 Subject: [PATCH 29/35] feat(discord_test): check for DGU_TOKEN for compatibility --- discord_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/discord_test.go b/discord_test.go index f2dd28c3d..720397ba3 100644 --- a/discord_test.go +++ b/discord_test.go @@ -30,6 +30,10 @@ func TestMain(m *testing.M) { } } + if envOAuth2Token == "" { + envOAuth2Token = os.Getenv("DGU_TOKEN") + } + if envOAuth2Token != "" { if d, err := New(envOAuth2Token); err == nil { dg = d From fca422b28f35af1961313fa1f8675c1ff4f6d516 Mon Sep 17 00:00:00 2001 From: nitroflap Date: Wed, 16 Feb 2022 16:47:30 +0300 Subject: [PATCH 30/35] fix(examples): signal.Notify unbuffered channel --- examples/autocomplete/main.go | 4 ++-- examples/slash_commands/main.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/autocomplete/main.go b/examples/autocomplete/main.go index 3e509af4d..55dbc6a39 100644 --- a/examples/autocomplete/main.go +++ b/examples/autocomplete/main.go @@ -239,8 +239,8 @@ func main() { log.Fatalf("Cannot register commands: %v", err) } - stop := make(chan os.Signal) - signal.Notify(stop, os.Interrupt) //nolint: staticcheck + stop := make(chan os.Signal, 1) + signal.Notify(stop, os.Interrupt) <-stop log.Println("Gracefully shutting down") diff --git a/examples/slash_commands/main.go b/examples/slash_commands/main.go index 1cb3dead0..3a1cf584c 100644 --- a/examples/slash_commands/main.go +++ b/examples/slash_commands/main.go @@ -379,7 +379,7 @@ func main() { defer s.Close() - stop := make(chan os.Signal) + stop := make(chan os.Signal, 1) signal.Notify(stop, os.Interrupt) <-stop log.Println("Gracefully shutdowning") From 0494cdf33cbd69e4153ad0207a18138df08915ee Mon Sep 17 00:00:00 2001 From: nitroflap Date: Wed, 16 Feb 2022 16:50:33 +0300 Subject: [PATCH 31/35] feat(components): added comment to MessageComponentFromJSON --- components.go | 1 + 1 file changed, 1 insertion(+) diff --git a/components.go b/components.go index 4537bb26a..a6716c0ef 100644 --- a/components.go +++ b/components.go @@ -51,6 +51,7 @@ func (umc *unmarshalableMessageComponent) UnmarshalJSON(src []byte) error { return json.Unmarshal(src, umc.MessageComponent) } +// MessageComponentFromJSON is a helper function for unmarshaling message components func MessageComponentFromJSON(b []byte) (MessageComponent, error) { var u unmarshalableMessageComponent err := u.UnmarshalJSON(b) From 0a0955c5f9e38e9c97f338c8ad4849eee3c1e098 Mon Sep 17 00:00:00 2001 From: Fedor Lapshin Date: Wed, 16 Feb 2022 21:02:44 +0300 Subject: [PATCH 32/35] Application commands: attachment option (#1088) * feat(interactions): application command attachment option * fix(interactions): corrected application command attachment option type --- interactions.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/interactions.go b/interactions.go index 33cc8ade6..be11d53cc 100644 --- a/interactions.go +++ b/interactions.go @@ -55,6 +55,7 @@ const ( ApplicationCommandOptionChannel ApplicationCommandOptionType = 7 ApplicationCommandOptionRole ApplicationCommandOptionType = 8 ApplicationCommandOptionMentionable ApplicationCommandOptionType = 9 + ApplicationCommandOptionAttachment ApplicationCommandOptionType = 11 ) func (t ApplicationCommandOptionType) String() string { @@ -77,6 +78,8 @@ func (t ApplicationCommandOptionType) String() string { return "Role" case ApplicationCommandOptionMentionable: return "Mentionable" + case ApplicationCommandOptionAttachment: + return "Attachment" } return fmt.Sprintf("ApplicationCommandOptionType(%d)", t) } @@ -255,11 +258,12 @@ type ApplicationCommandInteractionData struct { // Partial Member objects are missing user, deaf and mute fields. // Partial Channel objects only have id, name, type and permissions fields. type ApplicationCommandInteractionDataResolved struct { - Users map[string]*User `json:"users"` - Members map[string]*Member `json:"members"` - Roles map[string]*Role `json:"roles"` - Channels map[string]*Channel `json:"channels"` - Messages map[string]*Message `json:"messages"` + Users map[string]*User `json:"users"` + Members map[string]*Member `json:"members"` + Roles map[string]*Role `json:"roles"` + Channels map[string]*Channel `json:"channels"` + Messages map[string]*Message `json:"messages"` + Attachments map[string]*MessageAttachment `json:"attachments"` } // Type returns the type of interaction data. From d5bacb5401a6652f48edfbf133fd55a159b95b18 Mon Sep 17 00:00:00 2001 From: Bjorn Zandvliet Date: Wed, 16 Feb 2022 20:17:26 +0100 Subject: [PATCH 33/35] Add Default Permission to #943 (#1071) * Create edit application command permissions endpoint * Add remaining command permissions endpoints * Clean up naming a bit * Add docs for application command permissions endpoints * Update comments * More comments * :^) * Put the verb in API method names at the end * Review feedback * Add default permissions * feat(restapi): rewording of comments to application command permissions endpoints * fix(rest): errors for application commands permissions endpoints * style(interactions): changed order of fields in ApplicationCommand Co-authored-by: NotUnlikeTheWaves Co-authored-by: nitroflap --- endpoints.go | 6 ++++++ interactions.go | 47 ++++++++++++++++++++++++++++++++++------- restapi.go | 56 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 8 deletions(-) diff --git a/endpoints.go b/endpoints.go index 5124394ad..b161c0cac 100644 --- a/endpoints.go +++ b/endpoints.go @@ -133,6 +133,12 @@ var ( EndpointApplicationGuildCommand = func(aID, gID, cID string) string { return EndpointApplicationGuildCommands(aID, gID) + "/" + cID } + EndpointApplicationCommandPermissions = func(aID, gID, cID string) string { + return EndpointApplicationGuildCommand(aID, gID, cID) + "/permissions" + } + EndpointApplicationCommandsGuildPermissions = func(aID, gID string) string { + return EndpointApplicationGuildCommands(aID, gID) + "/permissions" + } EndpointInteraction = func(aID, iToken string) string { return EndpointAPI + "interactions/" + aID + "/" + iToken } diff --git a/interactions.go b/interactions.go index be11d53cc..9ce3d4811 100644 --- a/interactions.go +++ b/interactions.go @@ -30,15 +30,17 @@ const ( // ApplicationCommand represents an application's slash command. type ApplicationCommand struct { - ID string `json:"id,omitempty"` - ApplicationID string `json:"application_id,omitempty"` - Type ApplicationCommandType `json:"type,omitempty"` - Name string `json:"name"` - // NOTE: Chat commands only. Otherwise it mustn't be set. - Description string `json:"description,omitempty"` - Version string `json:"version,omitempty"` + ID string `json:"id,omitempty"` + ApplicationID string `json:"application_id,omitempty"` + Version string `json:"version,omitempty"` + Type ApplicationCommandType `json:"type,omitempty"` + Name string `json:"name"` + DefaultPermission *bool `json:"default_permission,omitempty"` + // NOTE: Chat commands only. Otherwise it mustn't be set. - Options []*ApplicationCommandOption `json:"options"` + + Description string `json:"description,omitempty"` + Options []*ApplicationCommandOption `json:"options"` } // ApplicationCommandOptionType indicates the type of a slash command's option. @@ -107,6 +109,35 @@ type ApplicationCommandOptionChoice struct { Value interface{} `json:"value"` } +// ApplicationCommandPermissions represents a single user or role permission for a command. +type ApplicationCommandPermissions struct { + ID string `json:"id"` + Type ApplicationCommandPermissionType `json:"type"` + Permission bool `json:"permission"` +} + +// ApplicationCommandPermissionsList represents a list of ApplicationCommandPermissions, needed for serializing to JSON. +type ApplicationCommandPermissionsList struct { + Permissions []*ApplicationCommandPermissions `json:"permissions"` +} + +// GuildApplicationCommandPermissions represents all permissions for a single guild command. +type GuildApplicationCommandPermissions struct { + ID string `json:"id"` + ApplicationID string `json:"application_id"` + GuildID string `json:"guild_id"` + Permissions []*ApplicationCommandPermissions `json:"permissions"` +} + +// ApplicationCommandPermissionType indicates whether a permission is user or role based. +type ApplicationCommandPermissionType uint8 + +// Application command permission types. +const ( + ApplicationCommandPermissionTypeRole ApplicationCommandPermissionType = 1 + ApplicationCommandPermissionTypeUser ApplicationCommandPermissionType = 2 +) + // InteractionType indicates the type of an interaction event. type InteractionType uint8 diff --git a/restapi.go b/restapi.go index 74ba48282..94c8de78a 100644 --- a/restapi.go +++ b/restapi.go @@ -2282,6 +2282,62 @@ func (s *Session) ApplicationCommands(appID, guildID string) (cmd []*Application return } +// GuildApplicationCommandsPermissions returns permissions for all application commands in a guild. +// appID : The application ID +// guildID : Guild ID to retrieve application commands permissions for. +func (s *Session) GuildApplicationCommandsPermissions(appID, guildID string) (permissions []*GuildApplicationCommandPermissions, err error) { + endpoint := EndpointApplicationCommandsGuildPermissions(appID, guildID) + + var body []byte + body, err = s.RequestWithBucketID("GET", endpoint, nil, endpoint) + if err != nil { + return + } + + err = unmarshal(body, &permissions) + return +} + +// ApplicationCommandPermissions returns all permissions of an application command +// appID : The Application ID +// guildID : The guild ID containing the application command +// cmdID : The command ID to retrieve the permissions of +func (s *Session) ApplicationCommandPermissions(appID, guildID, cmdID string) (permissions *GuildApplicationCommandPermissions, err error) { + endpoint := EndpointApplicationCommandPermissions(appID, guildID, cmdID) + + var body []byte + body, err = s.RequestWithBucketID("GET", endpoint, nil, endpoint) + if err != nil { + return + } + + err = unmarshal(body, &permissions) + return +} + +// ApplicationCommandPermissionsEdit edits the permissions of an application command +// appID : The Application ID +// 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 +func (s *Session) ApplicationCommandPermissionsEdit(appID, guildID, cmdID string, permissions *ApplicationCommandPermissionsList) (err error) { + endpoint := EndpointApplicationCommandPermissions(appID, guildID, cmdID) + + _, err = s.RequestWithBucketID("PUT", endpoint, permissions, endpoint) + return +} + +// ApplicationCommandPermissionsBatchEdit edits the permissions of a batch of commands +// 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 +func (s *Session) ApplicationCommandPermissionsBatchEdit(appID, guildID string, permissions []*GuildApplicationCommandPermissions) (err error) { + endpoint := EndpointApplicationCommandsGuildPermissions(appID, guildID) + + _, err = s.RequestWithBucketID("PUT", endpoint, permissions, endpoint) + return +} + // InteractionRespond creates the response to an interaction. // appID : The application ID. // interaction : Interaction instance. From e39ed2f8ce85b1000dc471b4e26831ece88f9d43 Mon Sep 17 00:00:00 2001 From: Fedor Lapshin Date: Wed, 16 Feb 2022 22:38:08 +0300 Subject: [PATCH 34/35] feat(restapi): reworded comment of GuildApplicationCommandsPermissions --- restapi.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/restapi.go b/restapi.go index 94c8de78a..77a1769f5 100644 --- a/restapi.go +++ b/restapi.go @@ -2282,7 +2282,7 @@ func (s *Session) ApplicationCommands(appID, guildID string) (cmd []*Application return } -// GuildApplicationCommandsPermissions returns permissions for all application commands in a guild. +// GuildApplicationCommandsPermissions returns permissions for application commands in a guild. // appID : The application ID // guildID : Guild ID to retrieve application commands permissions for. func (s *Session) GuildApplicationCommandsPermissions(appID, guildID string) (permissions []*GuildApplicationCommandPermissions, err error) { From 6015eed9333ed196717ebd065a60f7f1e49499b1 Mon Sep 17 00:00:00 2001 From: aidan michael lineberry Date: Wed, 16 Feb 2022 14:23:27 -0600 Subject: [PATCH 35/35] Vectorization of DiscordGo mark (#1099) * Add vector imagination of discordgo.png * Delete discordgo.png * Update README.md to use vector artwork Added alternative text and used relative reference to artwork * README.md more meaningful alt text Co-authored-by: Fedor Lapshin Co-authored-by: Fedor Lapshin --- README.md | 2 +- docs/img/discordgo.png | Bin 34331 -> 0 bytes docs/img/discordgo.svg | 45 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) delete mode 100644 docs/img/discordgo.png create mode 100644 docs/img/discordgo.svg diff --git a/README.md b/README.md index b1928bd02..2bcd43b81 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![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) - +DiscordGo logo DiscordGo is a [Go](https://golang.org/) package that provides low level bindings to the [Discord](https://discord.com/) chat client API. DiscordGo diff --git a/docs/img/discordgo.png b/docs/img/discordgo.png deleted file mode 100644 index eb15a3dc0a02cfdce0d3ec413b17841c76c059f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34331 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 M%f2cQdHebQ0FDv&rT_o{ diff --git a/docs/img/discordgo.svg b/docs/img/discordgo.svg new file mode 100644 index 000000000..059ae7ab7 --- /dev/null +++ b/docs/img/discordgo.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +