summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--vendor/github.com/bwmarrin/discordgo/discord.go34
-rw-r--r--vendor/github.com/bwmarrin/discordgo/endpoints.go16
-rw-r--r--vendor/github.com/bwmarrin/discordgo/event.go28
-rw-r--r--vendor/github.com/bwmarrin/discordgo/eventhandlers.go173
-rw-r--r--vendor/github.com/bwmarrin/discordgo/events.go37
-rw-r--r--vendor/github.com/bwmarrin/discordgo/examples/airhorn/main.go41
-rw-r--r--vendor/github.com/bwmarrin/discordgo/examples/appmaker/main.go55
-rw-r--r--vendor/github.com/bwmarrin/discordgo/examples/avatar/localfile/main.go73
-rw-r--r--vendor/github.com/bwmarrin/discordgo/examples/avatar/main.go89
-rw-r--r--vendor/github.com/bwmarrin/discordgo/examples/avatar/url/main.go86
-rw-r--r--vendor/github.com/bwmarrin/discordgo/examples/mytoken/main.go7
-rw-r--r--vendor/github.com/bwmarrin/discordgo/examples/new_basic/main.go53
-rw-r--r--vendor/github.com/bwmarrin/discordgo/examples/pingpong/main.go35
-rw-r--r--vendor/github.com/bwmarrin/discordgo/message.go48
-rw-r--r--vendor/github.com/bwmarrin/discordgo/oauth2.go19
-rw-r--r--vendor/github.com/bwmarrin/discordgo/ratelimit.go55
-rw-r--r--vendor/github.com/bwmarrin/discordgo/restapi.go370
-rw-r--r--vendor/github.com/bwmarrin/discordgo/state.go271
-rw-r--r--vendor/github.com/bwmarrin/discordgo/structs.go66
-rw-r--r--vendor/github.com/bwmarrin/discordgo/tools/cmd/eventhandlers/main.go7
-rw-r--r--vendor/github.com/bwmarrin/discordgo/user.go26
-rw-r--r--vendor/github.com/bwmarrin/discordgo/voice.go55
-rw-r--r--vendor/github.com/bwmarrin/discordgo/wsapi.go79
-rw-r--r--vendor/manifest2
24 files changed, 1069 insertions, 656 deletions
diff --git a/vendor/github.com/bwmarrin/discordgo/discord.go b/vendor/github.com/bwmarrin/discordgo/discord.go
index 4081c65a..2f0b6fd4 100644
--- a/vendor/github.com/bwmarrin/discordgo/discord.go
+++ b/vendor/github.com/bwmarrin/discordgo/discord.go
@@ -13,10 +13,18 @@
// Package discordgo provides Discord binding for Go
package discordgo
-import "fmt"
+import (
+ "errors"
+ "fmt"
+ "net/http"
+ "time"
+)
-// VERSION of Discordgo, follows Symantic Versioning. (http://semver.org/)
-const VERSION = "0.15.0"
+// VERSION of DiscordGo, follows Semantic Versioning. (http://semver.org/)
+const VERSION = "0.16.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
@@ -31,6 +39,12 @@ const VERSION = "0.15.0"
// 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) {
// Create an empty Session interface.
@@ -43,6 +57,8 @@ func New(args ...interface{}) (s *Session, err error) {
ShardID: 0,
ShardCount: 1,
MaxRestRetries: 3,
+ Client: &http.Client{Timeout: (20 * time.Second)},
+ sequence: new(int64),
}
// If no arguments are passed return the empty Session interface.
@@ -60,7 +76,7 @@ func New(args ...interface{}) (s *Session, err error) {
case []string:
if len(v) > 3 {
- err = fmt.Errorf("Too many string parameters provided.")
+ err = fmt.Errorf("too many string parameters provided")
return
}
@@ -91,7 +107,7 @@ func New(args ...interface{}) (s *Session, err error) {
} else if s.Token == "" {
s.Token = v
} else {
- err = fmt.Errorf("Too many string parameters provided.")
+ err = fmt.Errorf("too many string parameters provided")
return
}
@@ -99,7 +115,7 @@ func New(args ...interface{}) (s *Session, err error) {
// TODO: Parse configuration struct
default:
- err = fmt.Errorf("Unsupported parameter type provided.")
+ err = fmt.Errorf("unsupported parameter type provided")
return
}
}
@@ -113,7 +129,11 @@ func New(args ...interface{}) (s *Session, err error) {
} else {
err = s.Login(auth, pass)
if err != nil || s.Token == "" {
- err = fmt.Errorf("Unable to fetch discord authentication token. %v", err)
+ if s.MFA {
+ err = ErrMFA
+ } else {
+ err = fmt.Errorf("Unable to fetch discord authentication token. %v", err)
+ }
return
}
}
diff --git a/vendor/github.com/bwmarrin/discordgo/endpoints.go b/vendor/github.com/bwmarrin/discordgo/endpoints.go
index f63240ff..96bcf28b 100644
--- a/vendor/github.com/bwmarrin/discordgo/endpoints.go
+++ b/vendor/github.com/bwmarrin/discordgo/endpoints.go
@@ -26,6 +26,13 @@ var (
EndpointGateway = EndpointAPI + "gateway"
EndpointWebhooks = EndpointAPI + "webhooks/"
+ EndpointCDN = "https://cdn.discordapp.com/"
+ EndpointCDNAttachments = EndpointCDN + "attachments/"
+ EndpointCDNAvatars = EndpointCDN + "avatars/"
+ EndpointCDNIcons = EndpointCDN + "icons/"
+ EndpointCDNSplashes = EndpointCDN + "splashes/"
+ EndpointCDNChannelIcons = EndpointCDN + "channel-icons/"
+
EndpointAuth = EndpointAPI + "auth/"
EndpointLogin = EndpointAuth + "login"
EndpointLogout = EndpointAuth + "logout"
@@ -48,7 +55,7 @@ var (
EndpointIntegrations = EndpointAPI + "integrations"
EndpointUser = func(uID string) string { return EndpointUsers + uID }
- EndpointUserAvatar = func(uID, aID string) string { return EndpointUsers + uID + "/avatars/" + aID + ".jpg" }
+ EndpointUserAvatar = func(uID, aID string) string { return EndpointCDNAvatars + uID + "/" + aID + ".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 }
@@ -56,6 +63,7 @@ var (
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 }
EndpointGuild = func(gID string) string { return EndpointGuilds + gID }
EndpointGuildInivtes = func(gID string) string { return EndpointGuilds + gID + "/invites" }
@@ -73,8 +81,8 @@ var (
EndpointGuildInvites = func(gID string) string { return EndpointGuilds + gID + "/invites" }
EndpointGuildEmbed = func(gID string) string { return EndpointGuilds + gID + "/embed" }
EndpointGuildPrune = func(gID string) string { return EndpointGuilds + gID + "/prune" }
- EndpointGuildIcon = func(gID, hash string) string { return EndpointGuilds + gID + "/icons/" + hash + ".jpg" }
- EndpointGuildSplash = func(gID, hash string) string { return EndpointGuilds + gID + "/splashes/" + hash + ".jpg" }
+ EndpointGuildIcon = func(gID, hash string) string { return EndpointCDNIcons + gID + "/" + hash + ".png" }
+ EndpointGuildSplash = func(gID, hash string) string { return EndpointCDNSplashes + gID + "/" + hash + ".png" }
EndpointGuildWebhooks = func(gID string) string { return EndpointGuilds + gID + "/webhooks" }
EndpointChannel = func(cID string) string { return EndpointChannels + cID }
@@ -89,6 +97,8 @@ var (
EndpointChannelMessagesPins = func(cID string) string { return EndpointChannel(cID) + "/pins" }
EndpointChannelMessagePin = func(cID, mID string) string { return EndpointChannel(cID) + "/pins/" + mID }
+ EndpointGroupIcon = func(cID, hash string) string { return EndpointCDNChannelIcons + cID + "/" + hash + ".png" }
+
EndpointChannelWebhooks = func(cID string) string { return EndpointChannel(cID) + "/webhooks" }
EndpointWebhook = func(wID string) string { return EndpointWebhooks + wID }
EndpointWebhookToken = func(wID, token string) string { return EndpointWebhooks + wID + "/" + token }
diff --git a/vendor/github.com/bwmarrin/discordgo/event.go b/vendor/github.com/bwmarrin/discordgo/event.go
index 245f0c1f..906c2aa8 100644
--- a/vendor/github.com/bwmarrin/discordgo/event.go
+++ b/vendor/github.com/bwmarrin/discordgo/event.go
@@ -1,7 +1,5 @@
package discordgo
-import "fmt"
-
// EventHandler is an interface for Discord events.
type EventHandler interface {
// Type returns the type of event this handler belongs to.
@@ -45,12 +43,15 @@ var registeredInterfaceProviders = map[string]EventInterfaceProvider{}
// registerInterfaceProvider registers a provider so that DiscordGo can
// access it's New() method.
-func registerInterfaceProvider(eh EventInterfaceProvider) error {
+func registerInterfaceProvider(eh EventInterfaceProvider) {
if _, ok := registeredInterfaceProviders[eh.Type()]; ok {
- return fmt.Errorf("event %s already registered", eh.Type())
+ return
+ // XXX:
+ // if we should error here, we need to do something with it.
+ // fmt.Errorf("event %s already registered", eh.Type())
}
registeredInterfaceProviders[eh.Type()] = eh
- return nil
+ return
}
// eventHandlerInstance is a wrapper around an event handler, as functions
@@ -210,14 +211,15 @@ func (s *Session) onInterface(i interface{}) {
setGuildIds(t.Guild)
case *GuildUpdate:
setGuildIds(t.Guild)
- case *Resumed:
- s.onResumed(t)
case *VoiceServerUpdate:
go s.onVoiceServerUpdate(t)
case *VoiceStateUpdate:
go s.onVoiceStateUpdate(t)
}
- s.State.onInterface(s, i)
+ err := s.State.onInterface(s, i)
+ if err != nil {
+ s.log(LogDebug, "error dispatching internal event, %s", err)
+ }
}
// onReady handles the ready event.
@@ -225,14 +227,4 @@ func (s *Session) onReady(r *Ready) {
// Store the SessionID within the Session struct.
s.sessionID = r.SessionID
-
- // Start the heartbeat to keep the connection alive.
- go s.heartbeat(s.wsConn, s.listening, r.HeartbeatInterval)
-}
-
-// onResumed handles the resumed event.
-func (s *Session) onResumed(r *Resumed) {
-
- // Start the heartbeat to keep the connection alive.
- go s.heartbeat(s.wsConn, s.listening, r.HeartbeatInterval)
}
diff --git a/vendor/github.com/bwmarrin/discordgo/eventhandlers.go b/vendor/github.com/bwmarrin/discordgo/eventhandlers.go
index 6d78bacd..5cc157de 100644
--- a/vendor/github.com/bwmarrin/discordgo/eventhandlers.go
+++ b/vendor/github.com/bwmarrin/discordgo/eventhandlers.go
@@ -7,46 +7,49 @@ package discordgo
// Event type values are used to match the events returned by Discord.
// EventTypes surrounded by __ are synthetic and are internal to DiscordGo.
const (
- channelCreateEventType = "CHANNEL_CREATE"
- channelDeleteEventType = "CHANNEL_DELETE"
- channelPinsUpdateEventType = "CHANNEL_PINS_UPDATE"
- channelUpdateEventType = "CHANNEL_UPDATE"
- connectEventType = "__CONNECT__"
- disconnectEventType = "__DISCONNECT__"
- eventEventType = "__EVENT__"
- guildBanAddEventType = "GUILD_BAN_ADD"
- guildBanRemoveEventType = "GUILD_BAN_REMOVE"
- guildCreateEventType = "GUILD_CREATE"
- guildDeleteEventType = "GUILD_DELETE"
- guildEmojisUpdateEventType = "GUILD_EMOJIS_UPDATE"
- guildIntegrationsUpdateEventType = "GUILD_INTEGRATIONS_UPDATE"
- guildMemberAddEventType = "GUILD_MEMBER_ADD"
- guildMemberRemoveEventType = "GUILD_MEMBER_REMOVE"
- guildMemberUpdateEventType = "GUILD_MEMBER_UPDATE"
- guildMembersChunkEventType = "GUILD_MEMBERS_CHUNK"
- guildRoleCreateEventType = "GUILD_ROLE_CREATE"
- guildRoleDeleteEventType = "GUILD_ROLE_DELETE"
- guildRoleUpdateEventType = "GUILD_ROLE_UPDATE"
- guildUpdateEventType = "GUILD_UPDATE"
- messageAckEventType = "MESSAGE_ACK"
- messageCreateEventType = "MESSAGE_CREATE"
- messageDeleteEventType = "MESSAGE_DELETE"
- messageReactionAddEventType = "MESSAGE_REACTION_ADD"
- messageReactionRemoveEventType = "MESSAGE_REACTION_REMOVE"
- messageUpdateEventType = "MESSAGE_UPDATE"
- presenceUpdateEventType = "PRESENCE_UPDATE"
- presencesReplaceEventType = "PRESENCES_REPLACE"
- rateLimitEventType = "__RATE_LIMIT__"
- readyEventType = "READY"
- relationshipAddEventType = "RELATIONSHIP_ADD"
- relationshipRemoveEventType = "RELATIONSHIP_REMOVE"
- resumedEventType = "RESUMED"
- typingStartEventType = "TYPING_START"
- userGuildSettingsUpdateEventType = "USER_GUILD_SETTINGS_UPDATE"
- userSettingsUpdateEventType = "USER_SETTINGS_UPDATE"
- userUpdateEventType = "USER_UPDATE"
- voiceServerUpdateEventType = "VOICE_SERVER_UPDATE"
- voiceStateUpdateEventType = "VOICE_STATE_UPDATE"
+ channelCreateEventType = "CHANNEL_CREATE"
+ channelDeleteEventType = "CHANNEL_DELETE"
+ channelPinsUpdateEventType = "CHANNEL_PINS_UPDATE"
+ channelUpdateEventType = "CHANNEL_UPDATE"
+ connectEventType = "__CONNECT__"
+ disconnectEventType = "__DISCONNECT__"
+ eventEventType = "__EVENT__"
+ guildBanAddEventType = "GUILD_BAN_ADD"
+ guildBanRemoveEventType = "GUILD_BAN_REMOVE"
+ guildCreateEventType = "GUILD_CREATE"
+ guildDeleteEventType = "GUILD_DELETE"
+ guildEmojisUpdateEventType = "GUILD_EMOJIS_UPDATE"
+ guildIntegrationsUpdateEventType = "GUILD_INTEGRATIONS_UPDATE"
+ guildMemberAddEventType = "GUILD_MEMBER_ADD"
+ guildMemberRemoveEventType = "GUILD_MEMBER_REMOVE"
+ guildMemberUpdateEventType = "GUILD_MEMBER_UPDATE"
+ guildMembersChunkEventType = "GUILD_MEMBERS_CHUNK"
+ guildRoleCreateEventType = "GUILD_ROLE_CREATE"
+ guildRoleDeleteEventType = "GUILD_ROLE_DELETE"
+ guildRoleUpdateEventType = "GUILD_ROLE_UPDATE"
+ guildUpdateEventType = "GUILD_UPDATE"
+ messageAckEventType = "MESSAGE_ACK"
+ messageCreateEventType = "MESSAGE_CREATE"
+ messageDeleteEventType = "MESSAGE_DELETE"
+ messageDeleteBulkEventType = "MESSAGE_DELETE_BULK"
+ messageReactionAddEventType = "MESSAGE_REACTION_ADD"
+ messageReactionRemoveEventType = "MESSAGE_REACTION_REMOVE"
+ messageReactionRemoveAllEventType = "MESSAGE_REACTION_REMOVE_ALL"
+ messageUpdateEventType = "MESSAGE_UPDATE"
+ presenceUpdateEventType = "PRESENCE_UPDATE"
+ presencesReplaceEventType = "PRESENCES_REPLACE"
+ rateLimitEventType = "__RATE_LIMIT__"
+ readyEventType = "READY"
+ relationshipAddEventType = "RELATIONSHIP_ADD"
+ relationshipRemoveEventType = "RELATIONSHIP_REMOVE"
+ resumedEventType = "RESUMED"
+ typingStartEventType = "TYPING_START"
+ userGuildSettingsUpdateEventType = "USER_GUILD_SETTINGS_UPDATE"
+ userNoteUpdateEventType = "USER_NOTE_UPDATE"
+ userSettingsUpdateEventType = "USER_SETTINGS_UPDATE"
+ userUpdateEventType = "USER_UPDATE"
+ voiceServerUpdateEventType = "VOICE_SERVER_UPDATE"
+ voiceStateUpdateEventType = "VOICE_STATE_UPDATE"
)
// channelCreateEventHandler is an event handler for ChannelCreate events.
@@ -137,11 +140,6 @@ func (eh connectEventHandler) Type() string {
return connectEventType
}
-// New returns a new instance of Connect.
-func (eh connectEventHandler) New() interface{} {
- return &Connect{}
-}
-
// Handle is the handler for Connect events.
func (eh connectEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*Connect); ok {
@@ -157,11 +155,6 @@ func (eh disconnectEventHandler) Type() string {
return disconnectEventType
}
-// New returns a new instance of Disconnect.
-func (eh disconnectEventHandler) New() interface{} {
- return &Disconnect{}
-}
-
// Handle is the handler for Disconnect events.
func (eh disconnectEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*Disconnect); ok {
@@ -177,11 +170,6 @@ func (eh eventEventHandler) Type() string {
return eventEventType
}
-// New returns a new instance of Event.
-func (eh eventEventHandler) New() interface{} {
- return &Event{}
-}
-
// Handle is the handler for Event events.
func (eh eventEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*Event); ok {
@@ -529,6 +517,26 @@ func (eh messageDeleteEventHandler) Handle(s *Session, i interface{}) {
}
}
+// messageDeleteBulkEventHandler is an event handler for MessageDeleteBulk events.
+type messageDeleteBulkEventHandler func(*Session, *MessageDeleteBulk)
+
+// Type returns the event type for MessageDeleteBulk events.
+func (eh messageDeleteBulkEventHandler) Type() string {
+ return messageDeleteBulkEventType
+}
+
+// New returns a new instance of MessageDeleteBulk.
+func (eh messageDeleteBulkEventHandler) New() interface{} {
+ return &MessageDeleteBulk{}
+}
+
+// Handle is the handler for MessageDeleteBulk events.
+func (eh messageDeleteBulkEventHandler) Handle(s *Session, i interface{}) {
+ if t, ok := i.(*MessageDeleteBulk); ok {
+ eh(s, t)
+ }
+}
+
// messageReactionAddEventHandler is an event handler for MessageReactionAdd events.
type messageReactionAddEventHandler func(*Session, *MessageReactionAdd)
@@ -569,6 +577,26 @@ func (eh messageReactionRemoveEventHandler) Handle(s *Session, i interface{}) {
}
}
+// messageReactionRemoveAllEventHandler is an event handler for MessageReactionRemoveAll events.
+type messageReactionRemoveAllEventHandler func(*Session, *MessageReactionRemoveAll)
+
+// Type returns the event type for MessageReactionRemoveAll events.
+func (eh messageReactionRemoveAllEventHandler) Type() string {
+ return messageReactionRemoveAllEventType
+}
+
+// New returns a new instance of MessageReactionRemoveAll.
+func (eh messageReactionRemoveAllEventHandler) New() interface{} {
+ return &MessageReactionRemoveAll{}
+}
+
+// Handle is the handler for MessageReactionRemoveAll events.
+func (eh messageReactionRemoveAllEventHandler) Handle(s *Session, i interface{}) {
+ if t, ok := i.(*MessageReactionRemoveAll); ok {
+ eh(s, t)
+ }
+}
+
// messageUpdateEventHandler is an event handler for MessageUpdate events.
type messageUpdateEventHandler func(*Session, *MessageUpdate)
@@ -637,11 +665,6 @@ func (eh rateLimitEventHandler) Type() string {
return rateLimitEventType
}
-// New returns a new instance of RateLimit.
-func (eh rateLimitEventHandler) New() interface{} {
- return &RateLimit{}
-}
-
// Handle is the handler for RateLimit events.
func (eh rateLimitEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*RateLimit); ok {
@@ -769,6 +792,26 @@ func (eh userGuildSettingsUpdateEventHandler) Handle(s *Session, i interface{})
}
}
+// userNoteUpdateEventHandler is an event handler for UserNoteUpdate events.
+type userNoteUpdateEventHandler func(*Session, *UserNoteUpdate)
+
+// Type returns the event type for UserNoteUpdate events.
+func (eh userNoteUpdateEventHandler) Type() string {
+ return userNoteUpdateEventType
+}
+
+// New returns a new instance of UserNoteUpdate.
+func (eh userNoteUpdateEventHandler) New() interface{} {
+ return &UserNoteUpdate{}
+}
+
+// Handle is the handler for UserNoteUpdate events.
+func (eh userNoteUpdateEventHandler) Handle(s *Session, i interface{}) {
+ if t, ok := i.(*UserNoteUpdate); ok {
+ eh(s, t)
+ }
+}
+
// userSettingsUpdateEventHandler is an event handler for UserSettingsUpdate events.
type userSettingsUpdateEventHandler func(*Session, *UserSettingsUpdate)
@@ -901,10 +944,14 @@ func handlerForInterface(handler interface{}) EventHandler {
return messageCreateEventHandler(v)
case func(*Session, *MessageDelete):
return messageDeleteEventHandler(v)
+ case func(*Session, *MessageDeleteBulk):
+ return messageDeleteBulkEventHandler(v)
case func(*Session, *MessageReactionAdd):
return messageReactionAddEventHandler(v)
case func(*Session, *MessageReactionRemove):
return messageReactionRemoveEventHandler(v)
+ case func(*Session, *MessageReactionRemoveAll):
+ return messageReactionRemoveAllEventHandler(v)
case func(*Session, *MessageUpdate):
return messageUpdateEventHandler(v)
case func(*Session, *PresenceUpdate):
@@ -925,6 +972,8 @@ func handlerForInterface(handler interface{}) EventHandler {
return typingStartEventHandler(v)
case func(*Session, *UserGuildSettingsUpdate):
return userGuildSettingsUpdateEventHandler(v)
+ case func(*Session, *UserNoteUpdate):
+ return userNoteUpdateEventHandler(v)
case func(*Session, *UserSettingsUpdate):
return userSettingsUpdateEventHandler(v)
case func(*Session, *UserUpdate):
@@ -937,6 +986,7 @@ func handlerForInterface(handler interface{}) EventHandler {
return nil
}
+
func init() {
registerInterfaceProvider(channelCreateEventHandler(nil))
registerInterfaceProvider(channelDeleteEventHandler(nil))
@@ -959,8 +1009,10 @@ func init() {
registerInterfaceProvider(messageAckEventHandler(nil))
registerInterfaceProvider(messageCreateEventHandler(nil))
registerInterfaceProvider(messageDeleteEventHandler(nil))
+ registerInterfaceProvider(messageDeleteBulkEventHandler(nil))
registerInterfaceProvider(messageReactionAddEventHandler(nil))
registerInterfaceProvider(messageReactionRemoveEventHandler(nil))
+ registerInterfaceProvider(messageReactionRemoveAllEventHandler(nil))
registerInterfaceProvider(messageUpdateEventHandler(nil))
registerInterfaceProvider(presenceUpdateEventHandler(nil))
registerInterfaceProvider(presencesReplaceEventHandler(nil))
@@ -970,6 +1022,7 @@ func init() {
registerInterfaceProvider(resumedEventHandler(nil))
registerInterfaceProvider(typingStartEventHandler(nil))
registerInterfaceProvider(userGuildSettingsUpdateEventHandler(nil))
+ registerInterfaceProvider(userNoteUpdateEventHandler(nil))
registerInterfaceProvider(userSettingsUpdateEventHandler(nil))
registerInterfaceProvider(userUpdateEventHandler(nil))
registerInterfaceProvider(voiceServerUpdateEventHandler(nil))
diff --git a/vendor/github.com/bwmarrin/discordgo/events.go b/vendor/github.com/bwmarrin/discordgo/events.go
index 19c11bda..c78fbdd2 100644
--- a/vendor/github.com/bwmarrin/discordgo/events.go
+++ b/vendor/github.com/bwmarrin/discordgo/events.go
@@ -2,7 +2,6 @@ package discordgo
import (
"encoding/json"
- "time"
)
// This file contains all the possible structs that can be
@@ -28,7 +27,7 @@ type RateLimit struct {
// Event provides a basic initial struct for all websocket events.
type Event struct {
Operation int `json:"op"`
- Sequence int `json:"s"`
+ Sequence int64 `json:"s"`
Type string `json:"t"`
RawData json.RawMessage `json:"d"`
// Struct contains one of the other types in this file.
@@ -37,19 +36,19 @@ type Event struct {
// A Ready stores all data for the websocket READY event.
type Ready struct {
- Version int `json:"v"`
- SessionID string `json:"session_id"`
- HeartbeatInterval time.Duration `json:"heartbeat_interval"`
- User *User `json:"user"`
- ReadState []*ReadState `json:"read_state"`
- PrivateChannels []*Channel `json:"private_channels"`
- Guilds []*Guild `json:"guilds"`
+ Version int `json:"v"`
+ SessionID string `json:"session_id"`
+ User *User `json:"user"`
+ ReadState []*ReadState `json:"read_state"`
+ PrivateChannels []*Channel `json:"private_channels"`
+ Guilds []*Guild `json:"guilds"`
// Undocumented fields
Settings *Settings `json:"user_settings"`
UserGuildSettings []*UserGuildSettings `json:"user_guild_settings"`
Relationships []*Relationship `json:"relationships"`
Presences []*Presence `json:"presences"`
+ Notes map[string]string `json:"notes"`
}
// ChannelCreate is the data for a ChannelCreate event.
@@ -179,6 +178,11 @@ type MessageReactionRemove struct {
*MessageReaction
}
+// MessageReactionRemoveAll is the data for a MessageReactionRemoveAll event.
+type MessageReactionRemoveAll struct {
+ *MessageReaction
+}
+
// PresencesReplace is the data for a PresencesReplace event.
type PresencesReplace []*Presence
@@ -191,8 +195,7 @@ type PresenceUpdate struct {
// Resumed is the data for a Resumed event.
type Resumed struct {
- HeartbeatInterval time.Duration `json:"heartbeat_interval"`
- Trace []string `json:"_trace"`
+ Trace []string `json:"_trace"`
}
// RelationshipAdd is the data for a RelationshipAdd event.
@@ -225,6 +228,12 @@ type UserGuildSettingsUpdate struct {
*UserGuildSettings
}
+// UserNoteUpdate is the data for a UserNoteUpdate event.
+type UserNoteUpdate struct {
+ ID string `json:"id"`
+ Note string `json:"note"`
+}
+
// VoiceServerUpdate is the data for a VoiceServerUpdate event.
type VoiceServerUpdate struct {
Token string `json:"token"`
@@ -236,3 +245,9 @@ type VoiceServerUpdate struct {
type VoiceStateUpdate struct {
*VoiceState
}
+
+// MessageDeleteBulk is the data for a MessageDeleteBulk event
+type MessageDeleteBulk struct {
+ Messages []string `json:"ids"`
+ ChannelID string `json:"channel_id"`
+}
diff --git a/vendor/github.com/bwmarrin/discordgo/examples/airhorn/main.go b/vendor/github.com/bwmarrin/discordgo/examples/airhorn/main.go
index ff5e5214..21ceb76b 100644
--- a/vendor/github.com/bwmarrin/discordgo/examples/airhorn/main.go
+++ b/vendor/github.com/bwmarrin/discordgo/examples/airhorn/main.go
@@ -6,7 +6,9 @@ import (
"fmt"
"io"
"os"
+ "os/signal"
"strings"
+ "syscall"
"time"
"github.com/bwmarrin/discordgo"
@@ -21,6 +23,7 @@ var token string
var buffer = make([][]byte, 0)
func main() {
+
if token == "" {
fmt.Println("No token provided. Please run: airhorn -t <bot token>")
return
@@ -56,21 +59,37 @@ func main() {
fmt.Println("Error opening Discord session: ", err)
}
+ // Wait here until CTRL-C or other term signal is received.
fmt.Println("Airhorn is now running. Press CTRL-C to exit.")
- // Simple way to keep program running until CTRL-C is pressed.
- <-make(chan struct{})
- return
+ sc := make(chan os.Signal, 1)
+ signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
+ <-sc
+
+ // Cleanly close down the Discord session.
+ dg.Close()
}
+// This function will be called (due to AddHandler above) when the bot receives
+// the "ready" event from Discord.
func ready(s *discordgo.Session, event *discordgo.Ready) {
+
// Set the playing status.
- _ = s.UpdateStatus(0, "!airhorn")
+ s.UpdateStatus(0, "!airhorn")
}
// This function will be called (due to AddHandler above) every time a new
// message is created on any channel that the autenticated bot has access to.
func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
+
+ // Ignore all messages created by the bot itself
+ // This isn't required in this specific example but it's a good practice.
+ if m.Author.ID == s.State.User.ID {
+ return
+ }
+
+ // check if the message is "!airhorn"
if strings.HasPrefix(m.Content, "!airhorn") {
+
// Find the channel that the message came from.
c, err := s.State.Channel(m.ChannelID)
if err != nil {
@@ -85,7 +104,7 @@ func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
return
}
- // Look for the message sender in that guilds current voice states.
+ // Look for the message sender in that guild's current voice states.
for _, vs := range g.VoiceStates {
if vs.UserID == m.Author.ID {
err = playSound(s, g.ID, vs.ChannelID)
@@ -102,6 +121,7 @@ func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
// This function will be called (due to AddHandler above) every time a new
// guild is joined.
func guildCreate(s *discordgo.Session, event *discordgo.GuildCreate) {
+
if event.Guild.Unavailable {
return
}
@@ -116,8 +136,8 @@ func guildCreate(s *discordgo.Session, event *discordgo.GuildCreate) {
// loadSound attempts to load an encoded sound file from disk.
func loadSound() error {
- file, err := os.Open("airhorn.dca")
+ file, err := os.Open("airhorn.dca")
if err != nil {
fmt.Println("Error opening dca file :", err)
return err
@@ -131,7 +151,7 @@ func loadSound() error {
// If this is the end of the file, just return.
if err == io.EOF || err == io.ErrUnexpectedEOF {
- file.Close()
+ err := file.Close()
if err != nil {
return err
}
@@ -160,6 +180,7 @@ func loadSound() error {
// playSound plays the current buffer to the provided channel.
func playSound(s *discordgo.Session, guildID, channelID string) (err error) {
+
// Join the provided voice channel.
vc, err := s.ChannelVoiceJoin(guildID, channelID, false, true)
if err != nil {
@@ -170,7 +191,7 @@ func playSound(s *discordgo.Session, guildID, channelID string) (err error) {
time.Sleep(250 * time.Millisecond)
// Start speaking.
- _ = vc.Speaking(true)
+ vc.Speaking(true)
// Send the buffer data.
for _, buff := range buffer {
@@ -178,13 +199,13 @@ func playSound(s *discordgo.Session, guildID, channelID string) (err error) {
}
// Stop speaking
- _ = vc.Speaking(false)
+ vc.Speaking(false)
// Sleep for a specificed amount of time before ending.
time.Sleep(250 * time.Millisecond)
// Disconnect from the provided voice channel.
- _ = vc.Disconnect()
+ vc.Disconnect()
return nil
}
diff --git a/vendor/github.com/bwmarrin/discordgo/examples/appmaker/main.go b/vendor/github.com/bwmarrin/discordgo/examples/appmaker/main.go
index bd0e3b88..286fe169 100644
--- a/vendor/github.com/bwmarrin/discordgo/examples/appmaker/main.go
+++ b/vendor/github.com/bwmarrin/discordgo/examples/appmaker/main.go
@@ -1,38 +1,42 @@
package main
import (
+ "encoding/json"
"flag"
"fmt"
+ "os"
"github.com/bwmarrin/discordgo"
)
// Variables used for command line options
var (
- Email string
- Password string
Token string
- AppName string
+ Name string
DeleteID string
ListOnly bool
)
func init() {
- flag.StringVar(&Email, "e", "", "Account Email")
- flag.StringVar(&Password, "p", "", "Account Password")
- flag.StringVar(&Token, "t", "", "Account Token")
+ flag.StringVar(&Token, "t", "", "Owner Account Token")
+ flag.StringVar(&Name, "n", "", "Name to give App/Bot")
flag.StringVar(&DeleteID, "d", "", "Application ID to delete")
flag.BoolVar(&ListOnly, "l", false, "List Applications Only")
- flag.StringVar(&AppName, "a", "", "App/Bot Name")
flag.Parse()
+
+ if Token == "" {
+ flag.Usage()
+ os.Exit(1)
+ }
}
func main() {
var err error
+
// Create a new Discord session using the provided login information.
- dg, err := discordgo.New(Email, Password, Token)
+ dg, err := discordgo.New(Token)
if err != nil {
fmt.Println("error creating Discord session,", err)
return
@@ -41,18 +45,17 @@ func main() {
// If -l set, only display a list of existing applications
// for the given account.
if ListOnly {
- aps, err2 := dg.Applications()
- if err2 != nil {
+
+ aps, err := dg.Applications()
+ if err != nil {
fmt.Println("error fetching applications,", err)
return
}
- for k, v := range aps {
- fmt.Printf("%d : --------------------------------------\n", k)
- fmt.Printf("ID: %s\n", v.ID)
- fmt.Printf("Name: %s\n", v.Name)
- fmt.Printf("Secret: %s\n", v.Secret)
- fmt.Printf("Description: %s\n", v.Description)
+ for _, v := range aps {
+ fmt.Println("-----------------------------------------------------")
+ b, _ := json.MarshalIndent(v, "", " ")
+ fmt.Println(string(b))
}
return
}
@@ -66,9 +69,14 @@ func main() {
return
}
+ if Name == "" {
+ flag.Usage()
+ os.Exit(1)
+ }
+
// Create a new application.
ap := &discordgo.Application{}
- ap.Name = AppName
+ ap.Name = Name
ap, err = dg.ApplicationCreate(ap)
if err != nil {
fmt.Println("error creating new applicaiton,", err)
@@ -76,9 +84,8 @@ func main() {
}
fmt.Printf("Application created successfully:\n")
- fmt.Printf("ID: %s\n", ap.ID)
- fmt.Printf("Name: %s\n", ap.Name)
- fmt.Printf("Secret: %s\n\n", ap.Secret)
+ b, _ := json.MarshalIndent(ap, "", " ")
+ fmt.Println(string(b))
// Create the bot account under the application we just created
bot, err := dg.ApplicationBotCreate(ap.ID)
@@ -88,11 +95,9 @@ func main() {
}
fmt.Printf("Bot account created successfully.\n")
- fmt.Printf("ID: %s\n", bot.ID)
- fmt.Printf("Username: %s\n", bot.Username)
- fmt.Printf("Token: %s\n\n", bot.Token)
+ b, _ = json.MarshalIndent(bot, "", " ")
+ fmt.Println(string(b))
+
fmt.Println("Please save the above posted info in a secure place.")
fmt.Println("You will need that information to login with your bot account.")
-
- return
}
diff --git a/vendor/github.com/bwmarrin/discordgo/examples/avatar/localfile/main.go b/vendor/github.com/bwmarrin/discordgo/examples/avatar/localfile/main.go
deleted file mode 100644
index adfe0b1d..00000000
--- a/vendor/github.com/bwmarrin/discordgo/examples/avatar/localfile/main.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package main
-
-import (
- "encoding/base64"
- "flag"
- "fmt"
- "io/ioutil"
- "net/http"
-
- "github.com/bwmarrin/discordgo"
-)
-
-// Variables used for command line parameters
-var (
- Email string
- Password string
- Token string
- Avatar string
- BotID string
- BotUsername string
-)
-
-func init() {
-
- flag.StringVar(&Email, "e", "", "Account Email")
- flag.StringVar(&Password, "p", "", "Account Password")
- flag.StringVar(&Token, "t", "", "Account Token")
- flag.StringVar(&Avatar, "f", "./avatar.jpg", "Avatar File Name")
- flag.Parse()
-}
-
-func main() {
-
- // Create a new Discord session using the provided login information.
- // Use discordgo.New(Token) to just use a token for login.
- dg, err := discordgo.New(Email, Password, Token)
- if err != nil {
- fmt.Println("error creating Discord session,", err)
- return
- }
-
- bot, err := dg.User("@me")
- if err != nil {
- fmt.Println("error fetching the bot details,", err)
- return
- }
-
- BotID = bot.ID
- BotUsername = bot.Username
- changeAvatar(dg)
-
- fmt.Println("Bot is now running. Press CTRL-C to exit.")
- // Simple way to keep program running until CTRL-C is pressed.
- <-make(chan struct{})
- return
-}
-
-// Helper function to change the avatar
-func changeAvatar(s *discordgo.Session) {
- img, err := ioutil.ReadFile(Avatar)
- if err != nil {
- fmt.Println(err)
- }
-
- base64 := base64.StdEncoding.EncodeToString(img)
-
- avatar := fmt.Sprintf("data:%s;base64,%s", http.DetectContentType(img), base64)
-
- _, err = s.UserUpdate("", "", BotUsername, avatar, "")
- if err != nil {
- fmt.Println(err)
- }
-}
diff --git a/vendor/github.com/bwmarrin/discordgo/examples/avatar/main.go b/vendor/github.com/bwmarrin/discordgo/examples/avatar/main.go
new file mode 100644
index 00000000..e0a9c880
--- /dev/null
+++ b/vendor/github.com/bwmarrin/discordgo/examples/avatar/main.go
@@ -0,0 +1,89 @@
+package main
+
+import (
+ "encoding/base64"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "os"
+
+ "github.com/bwmarrin/discordgo"
+)
+
+// Variables used for command line parameters
+var (
+ Token string
+ AvatarFile string
+ AvatarURL string
+)
+
+func init() {
+
+ flag.StringVar(&Token, "t", "", "Bot Token")
+ flag.StringVar(&AvatarFile, "f", "", "Avatar File Name")
+ flag.StringVar(&AvatarURL, "u", "", "URL to the avatar image")
+ flag.Parse()
+
+ if Token == "" || (AvatarFile == "" && AvatarURL == "") {
+ flag.Usage()
+ os.Exit(1)
+ }
+}
+
+func main() {
+
+ // Create a new Discord session using the provided login information.
+ dg, err := discordgo.New("Bot " + Token)
+ if err != nil {
+ fmt.Println("error creating Discord session,", err)
+ return
+ }
+
+ // Declare these here so they can be used in the below two if blocks and
+ // still carry over to the end of this function.
+ var base64img string
+ var contentType string
+
+ // If we're using a URL link for the Avatar
+ if AvatarURL != "" {
+
+ resp, err := http.Get(AvatarURL)
+ if err != nil {
+ fmt.Println("Error retrieving the file, ", err)
+ return
+ }
+
+ defer func() {
+ _ = resp.Body.Close()
+ }()
+
+ img, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ fmt.Println("Error reading the response, ", err)
+ return
+ }
+
+ contentType = http.DetectContentType(img)
+ base64img = base64.StdEncoding.EncodeToString(img)
+ }
+
+ // If we're using a local file for the Avatar
+ if AvatarFile != "" {
+ img, err := ioutil.ReadFile(AvatarFile)
+ if err != nil {
+ fmt.Println(err)
+ }
+
+ contentType = http.DetectContentType(img)
+ base64img = base64.StdEncoding.EncodeToString(img)
+ }
+
+ // 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, "")
+ if err != nil {
+ fmt.Println(err)
+ }
+}
diff --git a/vendor/github.com/bwmarrin/discordgo/examples/avatar/url/main.go b/vendor/github.com/bwmarrin/discordgo/examples/avatar/url/main.go
deleted file mode 100644
index 26170df5..00000000
--- a/vendor/github.com/bwmarrin/discordgo/examples/avatar/url/main.go
+++ /dev/null
@@ -1,86 +0,0 @@
-package main
-
-import (
- "encoding/base64"
- "flag"
- "fmt"
- "io/ioutil"
- "net/http"
-
- "github.com/bwmarrin/discordgo"
-)
-
-// Variables used for command line parameters
-var (
- Email string
- Password string
- Token string
- URL string
- BotID string
- BotUsername string
-)
-
-func init() {
-
- flag.StringVar(&Email, "e", "", "Account Email")
- flag.StringVar(&Password, "p", "", "Account Password")
- flag.StringVar(&Token, "t", "", "Account Token")
- flag.StringVar(&URL, "l", "http://bwmarrin.github.io/discordgo/img/discordgo.png", "Link to the avatar image")
- flag.Parse()
-}
-
-func main() {
-
- // Create a new Discord session using the provided login information.
- // Use discordgo.New(Token) to just use a token for login.
- dg, err := discordgo.New(Email, Password, Token)
- if err != nil {
- fmt.Println("error creating Discord session,", err)
- return
- }
-
- bot, err := dg.User("@me")
- if err != nil {
- fmt.Println("error fetching the bot details,", err)
- return
- }
-
- BotID = bot.ID
- BotUsername = bot.Username
- changeAvatar(dg)
-
- fmt.Println("Bot is now running. Press CTRL-C to exit.")
- // Simple way to keep program running until CTRL-C is pressed.
- <-make(chan struct{})
- return
-}
-
-// Helper function to change the avatar
-func changeAvatar(s *discordgo.Session) {
-
- resp, err := http.Get(URL)
- if err != nil {
- fmt.Println("Error retrieving the file, ", err)
- return
- }
-
- defer func() {
- _ = resp.Body.Close()
- }()
-
- img, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- fmt.Println("Error reading the response, ", err)
- return
- }
-
- base64 := base64.StdEncoding.EncodeToString(img)
-
- avatar := fmt.Sprintf("data:%s;base64,%s", http.DetectContentType(img), base64)
-
- _, err = s.UserUpdate("", "", BotUsername, avatar, "")
- if err != nil {
- fmt.Println("Error setting the avatar, ", err)
- }
-
-}
diff --git a/vendor/github.com/bwmarrin/discordgo/examples/mytoken/main.go b/vendor/github.com/bwmarrin/discordgo/examples/mytoken/main.go
index 5914fc8a..9375eadc 100644
--- a/vendor/github.com/bwmarrin/discordgo/examples/mytoken/main.go
+++ b/vendor/github.com/bwmarrin/discordgo/examples/mytoken/main.go
@@ -3,6 +3,7 @@ package main
import (
"flag"
"fmt"
+ "os"
"github.com/bwmarrin/discordgo"
)
@@ -18,6 +19,11 @@ func init() {
flag.StringVar(&Email, "e", "", "Account Email")
flag.StringVar(&Password, "p", "", "Account Password")
flag.Parse()
+
+ if Email == "" || Password == "" {
+ flag.Usage()
+ os.Exit(1)
+ }
}
func main() {
@@ -29,5 +35,6 @@ func main() {
return
}
+ // Print out your token.
fmt.Printf("Your Authentication Token is:\n\n%s\n", dg.Token)
}
diff --git a/vendor/github.com/bwmarrin/discordgo/examples/new_basic/main.go b/vendor/github.com/bwmarrin/discordgo/examples/new_basic/main.go
deleted file mode 100644
index 0191bb06..00000000
--- a/vendor/github.com/bwmarrin/discordgo/examples/new_basic/main.go
+++ /dev/null
@@ -1,53 +0,0 @@
-package main
-
-import (
- "flag"
- "fmt"
- "time"
-
- "github.com/bwmarrin/discordgo"
-)
-
-// Variables used for command line parameters
-var (
- Token string
-)
-
-func init() {
-
- flag.StringVar(&Token, "t", "", "Bot Token")
- flag.Parse()
-}
-
-func main() {
-
- // Create a new Discord session using the provided bot token.
- dg, err := discordgo.New("Bot " + Token)
- if err != nil {
- fmt.Println("error creating Discord session,", err)
- return
- }
-
- // Register messageCreate as a callback for the messageCreate events.
- dg.AddHandler(messageCreate)
-
- // Open the websocket and begin listening.
- err = dg.Open()
- if err != nil {
- fmt.Println("error opening connection,", err)
- return
- }
-
- fmt.Println("Bot is now running. Press CTRL-C to exit.")
- // Simple way to keep program running until CTRL-C is pressed.
- <-make(chan struct{})
- return
-}
-
-// This function will be called (due to AddHandler above) every time a new
-// message is created on any channel that the autenticated bot has access to.
-func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
-
- // Print message to stdout.
- fmt.Printf("%20s %20s %20s > %s\n", m.ChannelID, time.Now().Format(time.Stamp), m.Author.Username, m.Content)
-}
diff --git a/vendor/github.com/bwmarrin/discordgo/examples/pingpong/main.go b/vendor/github.com/bwmarrin/discordgo/examples/pingpong/main.go
index 2edd957e..155e782f 100644
--- a/vendor/github.com/bwmarrin/discordgo/examples/pingpong/main.go
+++ b/vendor/github.com/bwmarrin/discordgo/examples/pingpong/main.go
@@ -3,6 +3,9 @@ package main
import (
"flag"
"fmt"
+ "os"
+ "os/signal"
+ "syscall"
"github.com/bwmarrin/discordgo"
)
@@ -10,7 +13,6 @@ import (
// Variables used for command line parameters
var (
Token string
- BotID string
)
func init() {
@@ -28,29 +30,24 @@ func main() {
return
}
- // Get the account information.
- u, err := dg.User("@me")
- if err != nil {
- fmt.Println("error obtaining account details,", err)
- }
-
- // Store the account ID for later use.
- BotID = u.ID
-
- // Register messageCreate as a callback for the messageCreate events.
+ // Register the messageCreate func as a callback for MessageCreate events.
dg.AddHandler(messageCreate)
- // Open the websocket and begin listening.
+ // Open a websocket connection to Discord and begin listening.
err = dg.Open()
if err != nil {
fmt.Println("error opening connection,", err)
return
}
+ // Wait here until CTRL-C or other term signal is received.
fmt.Println("Bot is now running. Press CTRL-C to exit.")
- // Simple way to keep program running until CTRL-C is pressed.
- <-make(chan struct{})
- return
+ sc := make(chan os.Signal, 1)
+ signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
+ <-sc
+
+ // Cleanly close down the Discord session.
+ dg.Close()
}
// This function will be called (due to AddHandler above) every time a new
@@ -58,17 +55,17 @@ func main() {
func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
// Ignore all messages created by the bot itself
- if m.Author.ID == BotID {
+ // This isn't required in this specific example but it's a good practice.
+ if m.Author.ID == s.State.User.ID {
return
}
-
// If the message is "ping" reply with "Pong!"
if m.Content == "ping" {
- _, _ = s.ChannelMessageSend(m.ChannelID, "Pong!")
+ s.ChannelMessageSend(m.ChannelID, "Pong!")
}
// If the message is "pong" reply with "Ping!"
if m.Content == "pong" {
- _, _ = s.ChannelMessageSend(m.ChannelID, "Ping!")
+ s.ChannelMessageSend(m.ChannelID, "Ping!")
}
}
diff --git a/vendor/github.com/bwmarrin/discordgo/message.go b/vendor/github.com/bwmarrin/discordgo/message.go
index d7abda60..13c2da07 100644
--- a/vendor/github.com/bwmarrin/discordgo/message.go
+++ b/vendor/github.com/bwmarrin/discordgo/message.go
@@ -11,6 +11,7 @@ package discordgo
import (
"fmt"
+ "io"
"regexp"
)
@@ -31,6 +32,53 @@ type Message struct {
Reactions []*MessageReactions `json:"reactions"`
}
+// File stores info about files you e.g. send in messages.
+type File struct {
+ Name string
+ Reader io.Reader
+}
+
+// MessageSend stores all parameters you can send with ChannelMessageSendComplex.
+type MessageSend struct {
+ Content string `json:"content,omitempty"`
+ Embed *MessageEmbed `json:"embed,omitempty"`
+ Tts bool `json:"tts"`
+ File *File `json:"file"`
+}
+
+// MessageEdit is used to chain parameters via ChannelMessageEditComplex, which
+// is also where you should get the instance from.
+type MessageEdit struct {
+ Content *string `json:"content,omitempty"`
+ Embed *MessageEmbed `json:"embed,omitempty"`
+
+ ID string
+ Channel string
+}
+
+// NewMessageEdit returns a MessageEdit struct, initialized
+// with the Channel and ID.
+func NewMessageEdit(channelID string, messageID string) *MessageEdit {
+ return &MessageEdit{
+ Channel: channelID,
+ ID: messageID,
+ }
+}
+
+// SetContent is the same as setting the variable Content,
+// except it doesn't take a pointer.
+func (m *MessageEdit) SetContent(str string) *MessageEdit {
+ m.Content = &str
+ return m
+}
+
+// SetEmbed is a convenience function for setting the embed,
+// so you can chain commands.
+func (m *MessageEdit) SetEmbed(embed *MessageEmbed) *MessageEdit {
+ m.Embed = embed
+ return m
+}
+
// A MessageAttachment stores data for message attachments.
type MessageAttachment struct {
ID string `json:"id"`
diff --git a/vendor/github.com/bwmarrin/discordgo/oauth2.go b/vendor/github.com/bwmarrin/discordgo/oauth2.go
index 14ba6bbe..108b32fe 100644
--- a/vendor/github.com/bwmarrin/discordgo/oauth2.go
+++ b/vendor/github.com/bwmarrin/discordgo/oauth2.go
@@ -15,13 +15,18 @@ package discordgo
// An Application struct stores values for a Discord OAuth2 Application
type Application struct {
- ID string `json:"id,omitempty"`
- Name string `json:"name"`
- Description string `json:"description,omitempty"`
- Icon string `json:"icon,omitempty"`
- Secret string `json:"secret,omitempty"`
- RedirectURIs *[]string `json:"redirect_uris,omitempty"`
- Owner *User `json:"owner"`
+ ID string `json:"id,omitempty"`
+ Name string `json:"name"`
+ Description string `json:"description,omitempty"`
+ Icon string `json:"icon,omitempty"`
+ Secret string `json:"secret,omitempty"`
+ RedirectURIs *[]string `json:"redirect_uris,omitempty"`
+ BotRequireCodeGrant bool `json:"bot_require_code_grant,omitempty"`
+ BotPublic bool `json:"bot_public,omitempty"`
+ RPCApplicationState int `json:"rpc_application_state,omitempty"`
+ Flags int `json:"flags,omitempty"`
+ Owner *User `json:"owner"`
+ Bot *User `json:"bot"`
}
// Application returns an Application structure of a specific Application
diff --git a/vendor/github.com/bwmarrin/discordgo/ratelimit.go b/vendor/github.com/bwmarrin/discordgo/ratelimit.go
index bc320f0e..876e98a9 100644
--- a/vendor/github.com/bwmarrin/discordgo/ratelimit.go
+++ b/vendor/github.com/bwmarrin/discordgo/ratelimit.go
@@ -4,13 +4,14 @@ import (
"net/http"
"strconv"
"sync"
+ "sync/atomic"
"time"
)
// RateLimiter holds all ratelimit buckets
type RateLimiter struct {
sync.Mutex
- global *Bucket
+ global *int64
buckets map[string]*Bucket
globalRateLimit time.Duration
}
@@ -20,7 +21,7 @@ func NewRatelimiter() *RateLimiter {
return &RateLimiter{
buckets: make(map[string]*Bucket),
- global: &Bucket{Key: "global"},
+ global: new(int64),
}
}
@@ -58,8 +59,10 @@ func (r *RateLimiter) LockBucket(bucketID string) *Bucket {
}
// Check for global ratelimits
- r.global.Lock()
- r.global.Unlock()
+ sleepTo := time.Unix(0, atomic.LoadInt64(r.global))
+ if now := time.Now(); now.Before(sleepTo) {
+ time.Sleep(sleepTo.Sub(now))
+ }
b.remaining--
return b
@@ -72,7 +75,7 @@ type Bucket struct {
remaining int
limit int
reset time.Time
- global *Bucket
+ global *int64
}
// Release unlocks the bucket and reads the headers to update the buckets ratelimit info
@@ -89,41 +92,25 @@ func (b *Bucket) Release(headers http.Header) error {
global := headers.Get("X-RateLimit-Global")
retryAfter := headers.Get("Retry-After")
- // If it's global just keep the main ratelimit mutex locked
- if global != "" {
- parsedAfter, err := strconv.Atoi(retryAfter)
- if err != nil {
- return err
- }
-
- // Lock it in a new goroutine so that this isn't a blocking call
- go func() {
- // Make sure if several requests were waiting we don't sleep for n * retry-after
- // where n is the amount of requests that were going on
- sleepTo := time.Now().Add(time.Duration(parsedAfter) * time.Millisecond)
-
- b.global.Lock()
-
- sleepDuration := sleepTo.Sub(time.Now())
- if sleepDuration > 0 {
- time.Sleep(sleepDuration)
- }
-
- b.global.Unlock()
- }()
-
- return nil
- }
-
- // Update reset time if either retry after or reset headers are present
- // Prefer retryafter because it's more accurate with time sync and whatnot
+ // Update global and per bucket reset time if the proper headers are available
+ // If global is set, then it will block all buckets until after Retry-After
+ // If Retry-After without global is provided it will use that for the new reset
+ // time since it's more accurate than X-RateLimit-Reset.
+ // If Retry-After after is not proided, it will update the reset time from X-RateLimit-Reset
if retryAfter != "" {
parsedAfter, err := strconv.ParseInt(retryAfter, 10, 64)
if err != nil {
return err
}
- b.reset = time.Now().Add(time.Duration(parsedAfter) * time.Millisecond)
+ resetAt := time.Now().Add(time.Duration(parsedAfter) * time.Millisecond)
+
+ // Lock either this single bucket or all buckets
+ if global != "" {
+ atomic.StoreInt64(b.global, resetAt.UnixNano())
+ } else {
+ b.reset = resetAt
+ }
} else if reset != "" {
// Calculate the reset time by using the date header returned from discord
discordTime, err := http.ParseTime(headers.Get("Date"))
diff --git a/vendor/github.com/bwmarrin/discordgo/restapi.go b/vendor/github.com/bwmarrin/discordgo/restapi.go
index 5389c860..cb482e68 100644
--- a/vendor/github.com/bwmarrin/discordgo/restapi.go
+++ b/vendor/github.com/bwmarrin/discordgo/restapi.go
@@ -29,8 +29,15 @@ import (
"time"
)
-// ErrJSONUnmarshal is returned for JSON Unmarshall errors.
-var ErrJSONUnmarshal = errors.New("json unmarshal")
+// All error constants
+var (
+ ErrJSONUnmarshal = errors.New("json unmarshal")
+ ErrStatusOffline = errors.New("You can't set your Status to offline")
+ ErrVerificationLevelBounds = errors.New("VerificationLevel out of bounds, should be between 0 and 3")
+ ErrPruneDaysBounds = errors.New("the number of days should be more than or equal to 1")
+ ErrGuildNoIcon = errors.New("guild does not have an icon set")
+ ErrGuildNoSplash = errors.New("guild does not have a splash set")
+)
// Request is the same as RequestWithBucketID but the bucket id is the same as the urlStr
func (s *Session) Request(method, urlStr string, data interface{}) (response []byte, err error) {
@@ -87,9 +94,7 @@ func (s *Session) request(method, urlStr, contentType string, b []byte, bucketID
}
}
- client := &http.Client{Timeout: (20 * time.Second)}
-
- resp, err := client.Do(req)
+ resp, err := s.Client.Do(req)
if err != nil {
bucket.Release(nil)
return
@@ -175,6 +180,12 @@ func unmarshal(data []byte, v interface{}) error {
// ------------------------------------------------------------------------------------------------
// 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 {
@@ -189,6 +200,7 @@ func (s *Session) Login(email, password string) (err error) {
temp := struct {
Token string `json:"token"`
+ MFA bool `json:"mfa"`
}{}
err = unmarshal(response, &temp)
@@ -197,6 +209,7 @@ func (s *Session) Login(email, password string) (err error) {
}
s.Token = temp.Token
+ s.MFA = temp.MFA
return
}
@@ -264,15 +277,21 @@ func (s *Session) User(userID string) (st *User, err error) {
return
}
-// UserAvatar returns an image.Image of a users Avatar.
+// UserAvatar is deprecated. Please use UserAvatarDecode
// userID : A user ID or "@me" which is a shortcut of current user ID
func (s *Session) UserAvatar(userID string) (img image.Image, err error) {
u, err := s.User(userID)
if err != nil {
return
}
+ img, err = s.UserAvatarDecode(u)
+ return
+}
- body, err := s.RequestWithBucketID("GET", EndpointUserAvatar(userID, u.Avatar), nil, EndpointUserAvatar("", ""))
+// UserAvatarDecode returns an image.Image of a user's Avatar
+// user : The user which avatar should be retrieved
+func (s *Session) UserAvatarDecode(u *User) (img image.Image, err error) {
+ body, err := s.RequestWithBucketID("GET", EndpointUserAvatar(u.ID, u.Avatar), nil, EndpointUserAvatar("", ""))
if err != nil {
return
}
@@ -292,7 +311,7 @@ func (s *Session) UserUpdate(email, password, username, avatar, newPassword stri
data := struct {
Email string `json:"email"`
Password string `json:"password"`
- Username string `json:"username"`
+ Username string `json:"username,omitempty"`
Avatar string `json:"avatar,omitempty"`
NewPassword string `json:"new_password,omitempty"`
}{email, password, username, avatar, newPassword}
@@ -322,7 +341,7 @@ func (s *Session) UserSettings() (st *Settings, err error) {
// 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 = errors.New("You can't set your Status to offline")
+ err = ErrStatusOffline
return
}
@@ -370,9 +389,30 @@ func (s *Session) UserChannelCreate(recipientID string) (st *Channel, err error)
}
// UserGuilds returns an array of UserGuild structures for all guilds.
-func (s *Session) UserGuilds() (st []*UserGuild, err error) {
+// limit : The number guilds that can be returned. (max 100)
+// beforeID : If provided all guilds returned will be before given ID.
+// afterID : If provided all guilds returned will be after given ID.
+func (s *Session) UserGuilds(limit int, beforeID, afterID string) (st []*UserGuild, err error) {
+
+ v := url.Values{}
+
+ if limit > 0 {
+ v.Set("limit", strconv.Itoa(limit))
+ }
+ if afterID != "" {
+ v.Set("after", afterID)
+ }
+ if beforeID != "" {
+ v.Set("before", beforeID)
+ }
+
+ uri := EndpointUserGuilds("@me")
+
+ if len(v) > 0 {
+ uri = fmt.Sprintf("%s?%s", uri, v.Encode())
+ }
- body, err := s.RequestWithBucketID("GET", EndpointUserGuilds("@me"), nil, EndpointUserGuilds(""))
+ body, err := s.RequestWithBucketID("GET", uri, nil, EndpointUserGuilds(""))
if err != nil {
return
}
@@ -402,6 +442,13 @@ func (s *Session) UserGuildSettingsEdit(guildID string, settings *UserGuildSetti
// NOTE: This function is now deprecated and will be removed in the future.
// Please see the same function inside state.go
func (s *Session) UserChannelPermissions(userID, channelID string) (apermissions int, err error) {
+ // Try to just get permissions from state.
+ apermissions, err = s.State.UserChannelPermissions(userID, channelID)
+ if err == nil {
+ return
+ }
+
+ // Otherwise try get as much data from state as possible, falling back to the network.
channel, err := s.State.Channel(channelID)
if err != nil || channel == nil {
channel, err = s.Channel(channelID)
@@ -431,6 +478,19 @@ func (s *Session) UserChannelPermissions(userID, channelID string) (apermissions
}
}
+ return memberPermissions(guild, channel, member), nil
+}
+
+// Calculates the permissions for a member.
+// https://support.discordapp.com/hc/en-us/articles/206141927-How-is-the-permission-hierarchy-structured-
+func memberPermissions(guild *Guild, channel *Channel, member *Member) (apermissions int) {
+ userID := member.User.ID
+
+ if userID == guild.OwnerID {
+ apermissions = PermissionAll
+ return
+ }
+
for _, role := range guild.Roles {
if role.ID == guild.ID {
apermissions |= role.Permissions
@@ -447,21 +507,36 @@ func (s *Session) UserChannelPermissions(userID, channelID string) (apermissions
}
}
- if apermissions&PermissionAdministrator > 0 {
+ if apermissions&PermissionAdministrator == PermissionAdministrator {
apermissions |= PermissionAll
}
+ // Apply @everyone overrides from the channel.
+ for _, overwrite := range channel.PermissionOverwrites {
+ if guild.ID == overwrite.ID {
+ apermissions &= ^overwrite.Deny
+ apermissions |= overwrite.Allow
+ break
+ }
+ }
+
+ denies := 0
+ allows := 0
+
// Member overwrites can override role overrides, so do two passes
for _, overwrite := range channel.PermissionOverwrites {
for _, roleID := range member.Roles {
if overwrite.Type == "role" && roleID == overwrite.ID {
- apermissions &= ^overwrite.Deny
- apermissions |= overwrite.Allow
+ denies |= overwrite.Deny
+ allows |= overwrite.Allow
break
}
}
}
+ apermissions &= ^denies
+ apermissions |= allows
+
for _, overwrite := range channel.PermissionOverwrites {
if overwrite.Type == "member" && overwrite.ID == userID {
apermissions &= ^overwrite.Deny
@@ -470,11 +545,11 @@ func (s *Session) UserChannelPermissions(userID, channelID string) (apermissions
}
}
- if apermissions&PermissionAdministrator > 0 {
+ if apermissions&PermissionAdministrator == PermissionAdministrator {
apermissions |= PermissionAllChannel
}
- return
+ return apermissions
}
// ------------------------------------------------------------------------------------------------
@@ -527,7 +602,7 @@ func (s *Session) GuildEdit(guildID string, g GuildParams) (st *Guild, err error
if g.VerificationLevel != nil {
val := *g.VerificationLevel
if val < 0 || val > 3 {
- err = errors.New("VerificationLevel out of bounds, should be between 0 and 3")
+ err = ErrVerificationLevelBounds
return
}
}
@@ -551,13 +626,7 @@ func (s *Session) GuildEdit(guildID string, g GuildParams) (st *Guild, err error
}
}
- data := struct {
- Name string `json:"name,omitempty"`
- Region string `json:"region,omitempty"`
- VerificationLevel *VerificationLevel `json:"verification_level,omitempty"`
- }{g.Name, g.Region, g.VerificationLevel}
-
- body, err := s.RequestWithBucketID("PATCH", EndpointGuild(guildID), data, EndpointGuild(guildID))
+ body, err := s.RequestWithBucketID("PATCH", EndpointGuild(guildID), g, EndpointGuild(guildID))
if err != nil {
return
}
@@ -607,11 +676,28 @@ func (s *Session) GuildBans(guildID string) (st []*GuildBan, err error) {
// userID : The ID of a User
// days : The number of days of previous comments to delete.
func (s *Session) GuildBanCreate(guildID, userID string, days int) (err error) {
+ return s.GuildBanCreateWithReason(guildID, userID, "", days)
+}
+
+// GuildBanCreateWithReason bans the given user from the given guild also providing a reaso.
+// guildID : The ID of a Guild.
+// userID : The ID of a User
+// reason : The reason for this ban
+// days : The number of days of previous comments to delete.
+func (s *Session) GuildBanCreateWithReason(guildID, userID, reason string, days int) (err error) {
uri := EndpointGuildBan(guildID, userID)
+ queryParams := url.Values{}
if days > 0 {
- uri = fmt.Sprintf("%s?delete-message-days=%d", uri, days)
+ queryParams.Set("delete-message-days", strconv.Itoa(days))
+ }
+ if reason != "" {
+ queryParams.Set("reason", reason)
+ }
+
+ if len(queryParams) > 0 {
+ uri += "?" + queryParams.Encode()
}
_, err = s.RequestWithBucketID("PUT", uri, nil, EndpointGuildBan(guildID, ""))
@@ -722,12 +808,17 @@ func (s *Session) GuildMemberMove(guildID, userID, channelID string) (err error)
// GuildMemberNickname updates the nickname of a guild member
// guildID : The ID of a guild
// userID : The ID of a user
+// userID : The ID of a user or "@me" which is a shortcut of the current user ID
func (s *Session) GuildMemberNickname(guildID, userID, nickname string) (err error) {
data := struct {
Nick string `json:"nick"`
}{nickname}
+ if userID == "@me" {
+ userID += "/nick"
+ }
+
_, err = s.RequestWithBucketID("PATCH", EndpointGuildMember(guildID, userID), data, EndpointGuildMember(guildID, ""))
return
}
@@ -738,7 +829,7 @@ func (s *Session) GuildMemberNickname(guildID, userID, nickname string) (err err
// roleID : The ID of a Role to be assigned to the user.
func (s *Session) GuildMemberRoleAdd(guildID, userID, roleID string) (err error) {
- _, err = s.RequestWithBucketID("PUT", EndpointGuildMemberRole(guildID, userID, roleID), nil, EndpointGuildMemberRole(guildID, userID, roleID))
+ _, err = s.RequestWithBucketID("PUT", EndpointGuildMemberRole(guildID, userID, roleID), nil, EndpointGuildMemberRole(guildID, "", ""))
return
}
@@ -749,7 +840,7 @@ func (s *Session) GuildMemberRoleAdd(guildID, userID, roleID string) (err error)
// roleID : The ID of a Role to be removed from the user.
func (s *Session) GuildMemberRoleRemove(guildID, userID, roleID string) (err error) {
- _, err = s.RequestWithBucketID("DELETE", EndpointGuildMemberRole(guildID, userID, roleID), nil, EndpointGuildMemberRole(guildID, userID, roleID))
+ _, err = s.RequestWithBucketID("DELETE", EndpointGuildMemberRole(guildID, userID, roleID), nil, EndpointGuildMemberRole(guildID, "", ""))
return
}
@@ -904,7 +995,7 @@ func (s *Session) GuildPruneCount(guildID string, days uint32) (count uint32, er
count = 0
if days <= 0 {
- err = errors.New("The number of days should be more than or equal to 1.")
+ err = ErrPruneDaysBounds
return
}
@@ -934,7 +1025,7 @@ func (s *Session) GuildPrune(guildID string, days uint32) (count uint32, err err
count = 0
if days <= 0 {
- err = errors.New("The number of days should be more than or equal to 1.")
+ err = ErrPruneDaysBounds
return
}
@@ -1036,7 +1127,7 @@ func (s *Session) GuildIcon(guildID string) (img image.Image, err error) {
}
if g.Icon == "" {
- err = errors.New("Guild does not have an icon set.")
+ err = ErrGuildNoIcon
return
}
@@ -1058,7 +1149,7 @@ func (s *Session) GuildSplash(guildID string) (img image.Image, err error) {
}
if g.Splash == "" {
- err = errors.New("Guild does not have a splash set.")
+ err = ErrGuildNoSplash
return
}
@@ -1156,7 +1247,8 @@ func (s *Session) ChannelTyping(channelID string) (err error) {
// limit : The number messages that can be returned. (max 100)
// beforeID : If provided all messages returned will be before given ID.
// afterID : If provided all messages returned will be after given ID.
-func (s *Session) ChannelMessages(channelID string, limit int, beforeID, afterID string) (st []*Message, err error) {
+// aroundID : If provided all messages returned will be around given ID.
+func (s *Session) ChannelMessages(channelID string, limit int, beforeID, afterID, aroundID string) (st []*Message, err error) {
uri := EndpointChannelMessages(channelID)
@@ -1170,6 +1262,9 @@ func (s *Session) ChannelMessages(channelID string, limit int, beforeID, afterID
if beforeID != "" {
v.Set("before", beforeID)
}
+ if aroundID != "" {
+ v.Set("around", aroundID)
+ }
if len(v) > 0 {
uri = fmt.Sprintf("%s?%s", uri, v.Encode())
}
@@ -1212,20 +1307,76 @@ func (s *Session) ChannelMessageAck(channelID, messageID, lastToken string) (st
return
}
-// channelMessageSend sends a message to the given channel.
+// ChannelMessageSend sends a message to the given channel.
// channelID : The ID of a Channel.
// content : The message to send.
-// tts : Whether to send the message with TTS.
-func (s *Session) channelMessageSend(channelID, content string, tts bool) (st *Message, err error) {
+func (s *Session) ChannelMessageSend(channelID string, content string) (*Message, error) {
+ return s.ChannelMessageSendComplex(channelID, &MessageSend{
+ Content: content,
+ })
+}
- // TODO: nonce string ?
- data := struct {
- Content string `json:"content"`
- TTS bool `json:"tts"`
- }{content, tts}
+// ChannelMessageSendComplex sends a message to the given channel.
+// channelID : The ID of a Channel.
+// data : The message struct to send.
+func (s *Session) ChannelMessageSendComplex(channelID string, data *MessageSend) (st *Message, err error) {
+ if data.Embed != nil && data.Embed.Type == "" {
+ data.Embed.Type = "rich"
+ }
+
+ endpoint := EndpointChannelMessages(channelID)
+
+ var response []byte
+ if data.File != nil {
+ body := &bytes.Buffer{}
+ bodywriter := multipart.NewWriter(body)
+
+ // What's a better way of doing this? Reflect? Generator? I'm open to suggestions
+
+ if data.Content != "" {
+ if err = bodywriter.WriteField("content", data.Content); err != nil {
+ return
+ }
+ }
+
+ if data.Embed != nil {
+ var embed []byte
+ embed, err = json.Marshal(data.Embed)
+ if err != nil {
+ return
+ }
+ err = bodywriter.WriteField("embed", string(embed))
+ if err != nil {
+ return
+ }
+ }
+
+ if data.Tts {
+ if err = bodywriter.WriteField("tts", "true"); err != nil {
+ return
+ }
+ }
+
+ var writer io.Writer
+ writer, err = bodywriter.CreateFormFile("file", data.File.Name)
+ if err != nil {
+ return
+ }
+
+ _, err = io.Copy(writer, data.File.Reader)
+ if err != nil {
+ return
+ }
- // Send the message to the given channel
- response, err := s.RequestWithBucketID("POST", EndpointChannelMessages(channelID), data, EndpointChannelMessages(channelID))
+ err = bodywriter.Close()
+ if err != nil {
+ return
+ }
+
+ response, err = s.request("POST", endpoint, bodywriter.FormDataContentType(), body.Bytes(), endpoint, 0)
+ } else {
+ response, err = s.RequestWithBucketID("POST", endpoint, data, endpoint)
+ }
if err != nil {
return
}
@@ -1234,55 +1385,42 @@ func (s *Session) channelMessageSend(channelID, content string, tts bool) (st *M
return
}
-// ChannelMessageSend sends a message to the given channel.
-// channelID : The ID of a Channel.
-// content : The message to send.
-func (s *Session) ChannelMessageSend(channelID string, content string) (st *Message, err error) {
-
- return s.channelMessageSend(channelID, content, false)
-}
-
// ChannelMessageSendTTS sends a message to the given channel with Text to Speech.
// channelID : The ID of a Channel.
// content : The message to send.
-func (s *Session) ChannelMessageSendTTS(channelID string, content string) (st *Message, err error) {
-
- return s.channelMessageSend(channelID, content, true)
+func (s *Session) ChannelMessageSendTTS(channelID string, content string) (*Message, error) {
+ return s.ChannelMessageSendComplex(channelID, &MessageSend{
+ Content: content,
+ Tts: true,
+ })
}
-// ChannelMessageSendEmbed sends a message to the given channel with embedded data (bot only).
+// ChannelMessageSendEmbed sends a message to the given channel with embedded data.
// channelID : The ID of a Channel.
// embed : The embed data to send.
-func (s *Session) ChannelMessageSendEmbed(channelID string, embed *MessageEmbed) (st *Message, err error) {
- if embed != nil && embed.Type == "" {
- embed.Type = "rich"
- }
-
- data := struct {
- Embed *MessageEmbed `json:"embed"`
- }{embed}
-
- // Send the message to the given channel
- response, err := s.RequestWithBucketID("POST", EndpointChannelMessages(channelID), data, EndpointChannelMessages(channelID))
- if err != nil {
- return
- }
-
- err = unmarshal(response, &st)
- return
+func (s *Session) ChannelMessageSendEmbed(channelID string, embed *MessageEmbed) (*Message, error) {
+ return s.ChannelMessageSendComplex(channelID, &MessageSend{
+ Embed: embed,
+ })
}
// ChannelMessageEdit edits an existing message, replacing it entirely with
// the given content.
-// channeld : The ID of a Channel
-// messageID : the ID of a Message
-func (s *Session) ChannelMessageEdit(channelID, messageID, content string) (st *Message, err error) {
+// channelID : The ID of a Channel
+// messageID : The ID of a Message
+// content : The contents of the message
+func (s *Session) ChannelMessageEdit(channelID, messageID, content string) (*Message, error) {
+ return s.ChannelMessageEditComplex(NewMessageEdit(channelID, messageID).SetContent(content))
+}
- data := struct {
- Content string `json:"content"`
- }{content}
+// ChannelMessageEditComplex edits an existing message, replacing it entirely with
+// the given MessageEdit struct
+func (s *Session) ChannelMessageEditComplex(m *MessageEdit) (st *Message, err error) {
+ if m.Embed != nil && m.Embed.Type == "" {
+ m.Embed.Type = "rich"
+ }
- response, err := s.RequestWithBucketID("PATCH", EndpointChannelMessage(channelID, messageID), data, EndpointChannelMessage(channelID, ""))
+ response, err := s.RequestWithBucketID("PATCH", EndpointChannelMessage(m.Channel, m.ID), m, EndpointChannelMessage(m.Channel, ""))
if err != nil {
return
}
@@ -1291,26 +1429,12 @@ func (s *Session) ChannelMessageEdit(channelID, messageID, content string) (st *
return
}
-// ChannelMessageEditEmbed edits an existing message with embedded data (bot only).
+// ChannelMessageEditEmbed edits an existing message with embedded data.
// channelID : The ID of a Channel
// messageID : The ID of a Message
// embed : The embed data to send
-func (s *Session) ChannelMessageEditEmbed(channelID, messageID string, embed *MessageEmbed) (st *Message, err error) {
- if embed != nil && embed.Type == "" {
- embed.Type = "rich"
- }
-
- data := struct {
- Embed *MessageEmbed `json:"embed"`
- }{embed}
-
- response, err := s.RequestWithBucketID("PATCH", EndpointChannelMessage(channelID, messageID), data, EndpointChannelMessage(channelID, ""))
- if err != nil {
- return
- }
-
- err = unmarshal(response, &st)
- return
+func (s *Session) ChannelMessageEditEmbed(channelID, messageID string, embed *MessageEmbed) (*Message, error) {
+ return s.ChannelMessageEditComplex(NewMessageEdit(channelID, messageID).SetEmbed(embed))
}
// ChannelMessageDelete deletes a message from the Channel.
@@ -1385,48 +1509,18 @@ func (s *Session) ChannelMessagesPinned(channelID string) (st []*Message, err er
// channelID : The ID of a Channel.
// name: The name of the file.
// io.Reader : A reader for the file contents.
-func (s *Session) ChannelFileSend(channelID, name string, r io.Reader) (st *Message, err error) {
- return s.ChannelFileSendWithMessage(channelID, "", name, r)
+func (s *Session) ChannelFileSend(channelID, name string, r io.Reader) (*Message, error) {
+ return s.ChannelMessageSendComplex(channelID, &MessageSend{File: &File{Name: name, Reader: r}})
}
// ChannelFileSendWithMessage sends a file to the given channel with an message.
+// DEPRECATED. Use ChannelMessageSendComplex instead.
// channelID : The ID of a Channel.
// content: Optional Message content.
// name: The name of the file.
// io.Reader : A reader for the file contents.
-func (s *Session) ChannelFileSendWithMessage(channelID, content string, name string, r io.Reader) (st *Message, err error) {
-
- body := &bytes.Buffer{}
- bodywriter := multipart.NewWriter(body)
-
- if len(content) != 0 {
- if err := bodywriter.WriteField("content", content); err != nil {
- return nil, err
- }
- }
-
- writer, err := bodywriter.CreateFormFile("file", name)
- if err != nil {
- return nil, err
- }
-
- _, err = io.Copy(writer, r)
- if err != nil {
- return
- }
-
- err = bodywriter.Close()
- if err != nil {
- return
- }
-
- response, err := s.request("POST", EndpointChannelMessages(channelID), bodywriter.FormDataContentType(), body.Bytes(), EndpointChannelMessages(channelID), 0)
- if err != nil {
- return
- }
-
- err = unmarshal(response, &st)
- return
+func (s *Session) ChannelFileSendWithMessage(channelID, content string, name string, r io.Reader) (*Message, error) {
+ return s.ChannelMessageSendComplex(channelID, &MessageSend{File: &File{Name: name, Reader: r}, Content: content})
}
// ChannelInvites returns an array of Invite structures for the given channel
@@ -1563,7 +1657,7 @@ func (s *Session) VoiceICE() (st *VoiceICE, err error) {
// Functions specific to Discord Websockets
// ------------------------------------------------------------------------------------------------
-// Gateway returns the a websocket Gateway address
+// Gateway returns the websocket Gateway address
func (s *Session) Gateway() (gateway string, err error) {
response, err := s.RequestWithBucketID("GET", EndpointGateway, nil, EndpointGateway)
@@ -1809,6 +1903,20 @@ func (s *Session) MessageReactions(channelID, messageID, emojiID string, limit i
}
// ------------------------------------------------------------------------------------------------
+// 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)
// ------------------------------------------------------------------------------------------------
diff --git a/vendor/github.com/bwmarrin/discordgo/state.go b/vendor/github.com/bwmarrin/discordgo/state.go
index 25dd3d16..7400ef62 100644
--- a/vendor/github.com/bwmarrin/discordgo/state.go
+++ b/vendor/github.com/bwmarrin/discordgo/state.go
@@ -14,11 +14,16 @@ package discordgo
import (
"errors"
+ "sort"
"sync"
)
// ErrNilState is returned when the state is nil.
-var ErrNilState = errors.New("State not instantiated, please use discordgo.New() or assign Session.State.")
+var ErrNilState = errors.New("state not instantiated, please use discordgo.New() or assign Session.State")
+
+// ErrStateNotFound is returned when the state cache
+// requested is not found
+var ErrStateNotFound = errors.New("state cache not found")
// A State contains the current known state.
// As discord sends this in a READY blob, it seems reasonable to simply
@@ -33,6 +38,7 @@ type State struct {
TrackMembers bool
TrackRoles bool
TrackVoice bool
+ TrackPresences bool
guildMap map[string]*Guild
channelMap map[string]*Channel
@@ -45,13 +51,14 @@ func NewState() *State {
PrivateChannels: []*Channel{},
Guilds: []*Guild{},
},
- TrackChannels: true,
- TrackEmojis: true,
- TrackMembers: true,
- TrackRoles: true,
- TrackVoice: true,
- guildMap: make(map[string]*Guild),
- channelMap: make(map[string]*Channel),
+ TrackChannels: true,
+ TrackEmojis: true,
+ TrackMembers: true,
+ TrackRoles: true,
+ TrackVoice: true,
+ TrackPresences: true,
+ guildMap: make(map[string]*Guild),
+ channelMap: make(map[string]*Channel),
}
}
@@ -143,7 +150,108 @@ func (s *State) Guild(guildID string) (*Guild, error) {
return g, nil
}
- return nil, errors.New("Guild not found.")
+ return nil, ErrStateNotFound
+}
+
+// PresenceAdd adds a presence to the current world state, or
+// updates it if it already exists.
+func (s *State) PresenceAdd(guildID string, presence *Presence) error {
+ if s == nil {
+ return ErrNilState
+ }
+
+ guild, err := s.Guild(guildID)
+ if err != nil {
+ return err
+ }
+
+ s.Lock()
+ defer s.Unlock()
+
+ for i, p := range guild.Presences {
+ if p.User.ID == presence.User.ID {
+ //guild.Presences[i] = presence
+
+ //Update status
+ guild.Presences[i].Game = presence.Game
+ guild.Presences[i].Roles = presence.Roles
+ if presence.Status != "" {
+ guild.Presences[i].Status = presence.Status
+ }
+ if presence.Nick != "" {
+ guild.Presences[i].Nick = presence.Nick
+ }
+
+ //Update the optionally sent user information
+ //ID Is a mandatory field so you should not need to check if it is empty
+ guild.Presences[i].User.ID = presence.User.ID
+
+ if presence.User.Avatar != "" {
+ guild.Presences[i].User.Avatar = presence.User.Avatar
+ }
+ if presence.User.Discriminator != "" {
+ guild.Presences[i].User.Discriminator = presence.User.Discriminator
+ }
+ if presence.User.Email != "" {
+ guild.Presences[i].User.Email = presence.User.Email
+ }
+ if presence.User.Token != "" {
+ guild.Presences[i].User.Token = presence.User.Token
+ }
+ if presence.User.Username != "" {
+ guild.Presences[i].User.Username = presence.User.Username
+ }
+
+ return nil
+ }
+ }
+
+ guild.Presences = append(guild.Presences, presence)
+ return nil
+}
+
+// PresenceRemove removes a presence from the current world state.
+func (s *State) PresenceRemove(guildID string, presence *Presence) error {
+ if s == nil {
+ return ErrNilState
+ }
+
+ guild, err := s.Guild(guildID)
+ if err != nil {
+ return err
+ }
+
+ s.Lock()
+ defer s.Unlock()
+
+ for i, p := range guild.Presences {
+ if p.User.ID == presence.User.ID {
+ guild.Presences = append(guild.Presences[:i], guild.Presences[i+1:]...)
+ return nil
+ }
+ }
+
+ return ErrStateNotFound
+}
+
+// Presence gets a presence by ID from a guild.
+func (s *State) Presence(guildID, userID string) (*Presence, error) {
+ if s == nil {
+ return nil, ErrNilState
+ }
+
+ guild, err := s.Guild(guildID)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, p := range guild.Presences {
+ if p.User.ID == userID {
+ return p, nil
+ }
+ }
+
+ return nil, ErrStateNotFound
}
// TODO: Consider moving Guild state update methods onto *Guild.
@@ -195,7 +303,7 @@ func (s *State) MemberRemove(member *Member) error {
}
}
- return errors.New("Member not found.")
+ return ErrStateNotFound
}
// Member gets a member by ID from a guild.
@@ -218,7 +326,7 @@ func (s *State) Member(guildID, userID string) (*Member, error) {
}
}
- return nil, errors.New("Member not found.")
+ return nil, ErrStateNotFound
}
// RoleAdd adds a role to the current world state, or
@@ -268,7 +376,7 @@ func (s *State) RoleRemove(guildID, roleID string) error {
}
}
- return errors.New("Role not found.")
+ return ErrStateNotFound
}
// Role gets a role by ID from a guild.
@@ -291,10 +399,10 @@ func (s *State) Role(guildID, roleID string) (*Role, error) {
}
}
- return nil, errors.New("Role not found.")
+ return nil, ErrStateNotFound
}
-// ChannelAdd adds a guild to the current world state, or
+// ChannelAdd adds a channel to the current world state, or
// updates it if it already exists.
// Channels may exist either as PrivateChannels or inside
// a guild.
@@ -324,7 +432,7 @@ func (s *State) ChannelAdd(channel *Channel) error {
} else {
guild, ok := s.guildMap[channel.GuildID]
if !ok {
- return errors.New("Guild for channel not found.")
+ return ErrStateNotFound
}
guild.Channels = append(guild.Channels, channel)
@@ -403,7 +511,7 @@ func (s *State) Channel(channelID string) (*Channel, error) {
return c, nil
}
- return nil, errors.New("Channel not found.")
+ return nil, ErrStateNotFound
}
// Emoji returns an emoji for a guild and emoji id.
@@ -426,7 +534,7 @@ func (s *State) Emoji(guildID, emojiID string) (*Emoji, error) {
}
}
- return nil, errors.New("Emoji not found.")
+ return nil, ErrStateNotFound
}
// EmojiAdd adds an emoji to the current world state.
@@ -523,7 +631,12 @@ func (s *State) MessageRemove(message *Message) error {
return ErrNilState
}
- c, err := s.Channel(message.ChannelID)
+ return s.messageRemoveByID(message.ChannelID, message.ID)
+}
+
+// messageRemoveByID removes a message by channelID and messageID from the world state.
+func (s *State) messageRemoveByID(channelID, messageID string) error {
+ c, err := s.Channel(channelID)
if err != nil {
return err
}
@@ -532,13 +645,13 @@ func (s *State) MessageRemove(message *Message) error {
defer s.Unlock()
for i, m := range c.Messages {
- if m.ID == message.ID {
+ if m.ID == messageID {
c.Messages = append(c.Messages[:i], c.Messages[i+1:]...)
return nil
}
}
- return errors.New("Message not found.")
+ return ErrStateNotFound
}
func (s *State) voiceStateUpdate(update *VoiceStateUpdate) error {
@@ -592,7 +705,7 @@ func (s *State) Message(channelID, messageID string) (*Message, error) {
}
}
- return nil, errors.New("Message not found.")
+ return nil, ErrStateNotFound
}
// OnReady takes a Ready event and updates all internal state.
@@ -608,10 +721,9 @@ func (s *State) onReady(se *Session, r *Ready) (err error) {
// if state is disabled, store the bare essentials.
if !se.StateEnabled {
ready := Ready{
- Version: r.Version,
- SessionID: r.SessionID,
- HeartbeatInterval: r.HeartbeatInterval,
- User: r.User,
+ Version: r.Version,
+ SessionID: r.SessionID,
+ User: r.User,
}
s.Ready = ready
@@ -710,10 +822,55 @@ func (s *State) onInterface(se *Session, i interface{}) (err error) {
if s.MaxMessageCount != 0 {
err = s.MessageRemove(t.Message)
}
+ case *MessageDeleteBulk:
+ if s.MaxMessageCount != 0 {
+ for _, mID := range t.Messages {
+ s.messageRemoveByID(t.ChannelID, mID)
+ }
+ }
case *VoiceStateUpdate:
if s.TrackVoice {
err = s.voiceStateUpdate(t)
}
+ case *PresenceUpdate:
+ if s.TrackPresences {
+ s.PresenceAdd(t.GuildID, &t.Presence)
+ }
+ if s.TrackMembers {
+ if t.Status == StatusOffline {
+ return
+ }
+
+ var m *Member
+ m, err = s.Member(t.GuildID, t.User.ID)
+
+ if err != nil {
+ // Member not found; this is a user coming online
+ m = &Member{
+ GuildID: t.GuildID,
+ Nick: t.Nick,
+ User: t.User,
+ Roles: t.Roles,
+ }
+
+ } else {
+
+ if t.Nick != "" {
+ m.Nick = t.Nick
+ }
+
+ if t.User.Username != "" {
+ m.User.Username = t.User.Username
+ }
+
+ // PresenceUpdates always contain a list of roles, so there's no need to check for an empty list here
+ m.Roles = t.Roles
+
+ }
+
+ err = s.MemberAdd(m)
+ }
+
}
return
@@ -747,48 +904,46 @@ func (s *State) UserChannelPermissions(userID, channelID string) (apermissions i
return
}
- for _, role := range guild.Roles {
- if role.ID == guild.ID {
- apermissions |= role.Permissions
- break
- }
- }
+ return memberPermissions(guild, channel, member), nil
+}
- for _, role := range guild.Roles {
- for _, roleID := range member.Roles {
- if role.ID == roleID {
- apermissions |= role.Permissions
- break
- }
- }
+// UserColor returns the color of a user in a channel.
+// While colors are defined at a Guild level, determining for a channel is more useful in message handlers.
+// 0 is returned in cases of error, which is the color of @everyone.
+// userID : The ID of the user to calculate the color for.
+// channelID : The ID of the channel to calculate the color for.
+func (s *State) UserColor(userID, channelID string) int {
+ if s == nil {
+ return 0
}
- if apermissions&PermissionAdministrator > 0 {
- apermissions |= PermissionAll
+ channel, err := s.Channel(channelID)
+ if err != nil {
+ return 0
}
- // Member overwrites can override role overrides, so do two passes
- for _, overwrite := range channel.PermissionOverwrites {
- for _, roleID := range member.Roles {
- if overwrite.Type == "role" && roleID == overwrite.ID {
- apermissions &= ^overwrite.Deny
- apermissions |= overwrite.Allow
- break
- }
- }
+ guild, err := s.Guild(channel.GuildID)
+ if err != nil {
+ return 0
}
- for _, overwrite := range channel.PermissionOverwrites {
- if overwrite.Type == "member" && overwrite.ID == userID {
- apermissions &= ^overwrite.Deny
- apermissions |= overwrite.Allow
- break
- }
+ member, err := s.Member(guild.ID, userID)
+ if err != nil {
+ return 0
}
- if apermissions&PermissionAdministrator > 0 {
- apermissions |= PermissionAllChannel
+ roles := Roles(guild.Roles)
+ sort.Sort(roles)
+
+ for _, role := range roles {
+ for _, roleID := range member.Roles {
+ if role.ID == roleID {
+ if role.Color != 0 {
+ return role.Color
+ }
+ }
+ }
}
- return
+ return 0
}
diff --git a/vendor/github.com/bwmarrin/discordgo/structs.go b/vendor/github.com/bwmarrin/discordgo/structs.go
index 548ee52c..32f435ce 100644
--- a/vendor/github.com/bwmarrin/discordgo/structs.go
+++ b/vendor/github.com/bwmarrin/discordgo/structs.go
@@ -13,6 +13,7 @@ package discordgo
import (
"encoding/json"
+ "net/http"
"strconv"
"sync"
"time"
@@ -28,6 +29,7 @@ type Session struct {
// Authentication token for this session
Token string
+ MFA bool
// Debug for printing JSON request/responses
Debug bool // Deprecated, will be removed.
@@ -73,6 +75,9 @@ type Session struct {
// StateEnabled is true.
State *State
+ // The http client used for REST requests
+ Client *http.Client
+
// Event handlers
handlersMu sync.RWMutex
handlers map[string][]*eventHandlerInstance
@@ -88,7 +93,7 @@ type Session struct {
ratelimiter *RateLimiter
// sequence tracks the current gateway api websocket sequence number
- sequence int
+ sequence *int64
// stores sessions current Discord Gateway
gateway string
@@ -100,12 +105,6 @@ type Session struct {
wsMutex sync.Mutex
}
-type rateLimitMutex struct {
- sync.Mutex
- url map[string]*sync.Mutex
- // bucket map[string]*sync.Mutex // TODO :)
-}
-
// A VoiceRegion stores data for a specific voice region server.
type VoiceRegion struct {
ID string `json:"id"`
@@ -235,9 +234,15 @@ type UserGuild struct {
// A GuildParams stores all the data needed to update discord guild settings
type GuildParams struct {
- Name string `json:"name"`
- Region string `json:"region"`
- VerificationLevel *VerificationLevel `json:"verification_level"`
+ Name string `json:"name,omitempty"`
+ Region string `json:"region,omitempty"`
+ VerificationLevel *VerificationLevel `json:"verification_level,omitempty"`
+ DefaultMessageNotifications int `json:"default_message_notifications,omitempty"` // TODO: Separate type?
+ AfkChannelID string `json:"afk_channel_id,omitempty"`
+ AfkTimeout int `json:"afk_timeout,omitempty"`
+ Icon string `json:"icon,omitempty"`
+ OwnerID string `json:"owner_id,omitempty"`
+ Splash string `json:"splash,omitempty"`
}
// A Role stores information about Discord guild member roles.
@@ -252,6 +257,21 @@ type Role struct {
Permissions int `json:"permissions"`
}
+// Roles are a collection of Role
+type Roles []*Role
+
+func (r Roles) Len() int {
+ return len(r)
+}
+
+func (r Roles) Less(i, j int) bool {
+ return r[i].Position > r[j].Position
+}
+
+func (r Roles) Swap(i, j int) {
+ r[i], r[j] = r[j], r[i]
+}
+
// A VoiceState stores the voice states of Guilds
type VoiceState struct {
UserID string `json:"user_id"`
@@ -284,7 +304,7 @@ type Game struct {
// UnmarshalJSON unmarshals json to Game struct
func (g *Game) UnmarshalJSON(bytes []byte) error {
temp := &struct {
- Name string `json:"name"`
+ Name json.Number `json:"name"`
Type json.RawMessage `json:"type"`
URL string `json:"url"`
}{}
@@ -292,8 +312,8 @@ func (g *Game) UnmarshalJSON(bytes []byte) error {
if err != nil {
return err
}
- g.Name = temp.Name
g.URL = temp.URL
+ g.Name = temp.Name.String()
if temp.Type != nil {
err = json.Unmarshal(temp.Type, &g.Type)
@@ -324,19 +344,6 @@ type Member struct {
Roles []string `json:"roles"`
}
-// A User stores all data for an individual Discord user.
-type User struct {
- ID string `json:"id"`
- Email string `json:"email"`
- Username string `json:"username"`
- Avatar string `json:"Avatar"`
- Discriminator string `json:"discriminator"`
- Token string `json:"token"`
- Verified bool `json:"verified"`
- MFAEnabled bool `json:"mfa_enabled"`
- Bot bool `json:"bot"`
-}
-
// A Settings stores data for a specific users Discord client settings.
type Settings struct {
RenderEmbeds bool `json:"render_embeds"`
@@ -542,6 +549,8 @@ const (
PermissionAdministrator
PermissionManageChannels
PermissionManageServer
+ PermissionAddReactions
+ PermissionViewAuditLogs
PermissionAllText = PermissionReadMessages |
PermissionSendMessages |
@@ -561,9 +570,12 @@ const (
PermissionAllVoice |
PermissionCreateInstantInvite |
PermissionManageRoles |
- PermissionManageChannels
+ PermissionManageChannels |
+ PermissionAddReactions |
+ PermissionViewAuditLogs
PermissionAll = PermissionAllChannel |
PermissionKickMembers |
PermissionBanMembers |
- PermissionManageServer
+ PermissionManageServer |
+ PermissionAdministrator
)
diff --git a/vendor/github.com/bwmarrin/discordgo/tools/cmd/eventhandlers/main.go b/vendor/github.com/bwmarrin/discordgo/tools/cmd/eventhandlers/main.go
index f3894085..839f009d 100644
--- a/vendor/github.com/bwmarrin/discordgo/tools/cmd/eventhandlers/main.go
+++ b/vendor/github.com/bwmarrin/discordgo/tools/cmd/eventhandlers/main.go
@@ -37,18 +37,18 @@ type {{privateName .}}EventHandler func(*Session, *{{.}})
func (eh {{privateName .}}EventHandler) Type() string {
return {{privateName .}}EventType
}
-
+{{if isDiscordEvent .}}
// New returns a new instance of {{.}}.
func (eh {{privateName .}}EventHandler) New() interface{} {
return &{{.}}{}
-}
-
+}{{end}}
// Handle is the handler for {{.}} events.
func (eh {{privateName .}}EventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*{{.}}); ok {
eh(s, t)
}
}
+
{{end}}
func handlerForInterface(handler interface{}) EventHandler {
switch v := handler.(type) {
@@ -60,6 +60,7 @@ func handlerForInterface(handler interface{}) EventHandler {
return nil
}
+
func init() { {{range .}}{{if isDiscordEvent .}}
registerInterfaceProvider({{privateName .}}EventHandler(nil)){{end}}{{end}}
}
diff --git a/vendor/github.com/bwmarrin/discordgo/user.go b/vendor/github.com/bwmarrin/discordgo/user.go
new file mode 100644
index 00000000..b3a7e4b2
--- /dev/null
+++ b/vendor/github.com/bwmarrin/discordgo/user.go
@@ -0,0 +1,26 @@
+package discordgo
+
+import "fmt"
+
+// A User stores all data for an individual Discord user.
+type User struct {
+ ID string `json:"id"`
+ Email string `json:"email"`
+ Username string `json:"username"`
+ Avatar string `json:"avatar"`
+ Discriminator string `json:"discriminator"`
+ Token string `json:"token"`
+ Verified bool `json:"verified"`
+ MFAEnabled bool `json:"mfa_enabled"`
+ Bot bool `json:"bot"`
+}
+
+// String returns a unique identifier of the form username#discriminator
+func (u *User) String() string {
+ return fmt.Sprintf("%s#%s", u.Username, u.Discriminator)
+}
+
+// Mention return a string which mentions the user
+func (u *User) Mention() string {
+ return fmt.Sprintf("<@%s>", u.ID)
+}
diff --git a/vendor/github.com/bwmarrin/discordgo/voice.go b/vendor/github.com/bwmarrin/discordgo/voice.go
index 43de329e..da7b8c90 100644
--- a/vendor/github.com/bwmarrin/discordgo/voice.go
+++ b/vendor/github.com/bwmarrin/discordgo/voice.go
@@ -15,7 +15,6 @@ import (
"fmt"
"log"
"net"
- "runtime"
"strings"
"sync"
"time"
@@ -93,18 +92,22 @@ func (v *VoiceConnection) Speaking(b bool) (err error) {
}
if v.wsConn == nil {
- return fmt.Errorf("No VoiceConnection websocket.")
+ return fmt.Errorf("no VoiceConnection websocket")
}
data := voiceSpeakingOp{5, voiceSpeakingData{b, 0}}
v.wsMutex.Lock()
err = v.wsConn.WriteJSON(data)
v.wsMutex.Unlock()
+
+ v.Lock()
+ defer v.Unlock()
if err != nil {
v.speaking = false
log.Println("Speaking() write json error:", err)
return
}
+
v.speaking = b
return
@@ -139,9 +142,9 @@ func (v *VoiceConnection) Disconnect() (err error) {
// Send a OP4 with a nil channel to disconnect
if v.sessionID != "" {
data := voiceChannelJoinOp{4, voiceChannelJoinData{&v.GuildID, nil, true, true}}
- v.wsMutex.Lock()
+ v.session.wsMutex.Lock()
err = v.session.wsConn.WriteJSON(data)
- v.wsMutex.Unlock()
+ v.session.wsMutex.Unlock()
v.sessionID = ""
}
@@ -149,7 +152,10 @@ func (v *VoiceConnection) Disconnect() (err error) {
v.Close()
v.log(LogInformational, "Deleting VoiceConnection %s", v.GuildID)
+
+ v.session.Lock()
delete(v.session.VoiceConnections, v.GuildID)
+ v.session.Unlock()
return
}
@@ -185,7 +191,9 @@ func (v *VoiceConnection) Close() {
// To cleanly close a connection, a client should send a close
// frame and wait for the server to close the connection.
+ v.wsMutex.Lock()
err := v.wsConn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
+ v.wsMutex.Unlock()
if err != nil {
v.log(LogError, "error closing websocket, %s", err)
}
@@ -246,12 +254,15 @@ func (v *VoiceConnection) waitUntilConnected() error {
i := 0
for {
- if v.Ready {
+ v.RLock()
+ ready := v.Ready
+ v.RUnlock()
+ if ready {
return nil
}
if i > 10 {
- return fmt.Errorf("Timeout waiting for voice.")
+ return fmt.Errorf("timeout waiting for voice")
}
time.Sleep(1 * time.Second)
@@ -282,7 +293,7 @@ func (v *VoiceConnection) open() (err error) {
break
}
if i > 20 { // only loop for up to 1 second total
- return fmt.Errorf("Did not receive voice Session ID in time.")
+ return fmt.Errorf("did not receive voice Session ID in time")
}
time.Sleep(50 * time.Millisecond)
i++
@@ -409,8 +420,6 @@ func (v *VoiceConnection) onEvent(message []byte) {
go v.opusReceiver(v.udpConn, v.close, v.OpusRecv)
}
- // Send the ready event
- v.connected <- true
return
case 3: // HEARTBEAT response
@@ -418,6 +427,9 @@ func (v *VoiceConnection) onEvent(message []byte) {
return
case 4: // udp encryption secret key
+ v.Lock()
+ defer v.Unlock()
+
v.op4 = voiceOP4{}
if err := json.Unmarshal(e.RawData, &v.op4); err != nil {
v.log(LogError, "OP4 unmarshall error, %s, %s", err, string(e.RawData))
@@ -466,6 +478,7 @@ func (v *VoiceConnection) wsHeartbeat(wsConn *websocket.Conn, close <-chan struc
var err error
ticker := time.NewTicker(i * time.Millisecond)
+ defer ticker.Stop()
for {
v.log(LogDebug, "sending heartbeat packet")
v.wsMutex.Lock()
@@ -616,6 +629,7 @@ func (v *VoiceConnection) udpKeepAlive(udpConn *net.UDPConn, close <-chan struct
packet := make([]byte, 8)
ticker := time.NewTicker(i)
+ defer ticker.Stop()
for {
binary.LittleEndian.PutUint64(packet, sequence)
@@ -644,12 +658,16 @@ func (v *VoiceConnection) opusSender(udpConn *net.UDPConn, close <-chan struct{}
return
}
- runtime.LockOSThread()
-
// VoiceConnection is now ready to receive audio packets
// TODO: this needs reviewed as I think there must be a better way.
+ v.Lock()
v.Ready = true
- defer func() { v.Ready = false }()
+ v.Unlock()
+ defer func() {
+ v.Lock()
+ v.Ready = false
+ v.Unlock()
+ }()
var sequence uint16
var timestamp uint32
@@ -665,6 +683,7 @@ func (v *VoiceConnection) opusSender(udpConn *net.UDPConn, close <-chan struct{}
// start a send loop that loops until buf chan is closed
ticker := time.NewTicker(time.Millisecond * time.Duration(size/(rate/1000)))
+ defer ticker.Stop()
for {
// Get data from chan. If chan is closed, return.
@@ -678,7 +697,10 @@ func (v *VoiceConnection) opusSender(udpConn *net.UDPConn, close <-chan struct{}
// else, continue loop
}
- if !v.speaking {
+ v.RLock()
+ speaking := v.speaking
+ v.RUnlock()
+ if !speaking {
err := v.Speaking(true)
if err != nil {
v.log(LogError, "error sending speaking packet, %s", err)
@@ -691,7 +713,9 @@ func (v *VoiceConnection) opusSender(udpConn *net.UDPConn, close <-chan struct{}
// encrypt the opus data
copy(nonce[:], udpHeader)
+ v.RLock()
sendbuf := secretbox.Seal(udpHeader, recvbuf, &nonce, &v.op4.SecretKey)
+ v.RUnlock()
// block here until we're exactly at the right time :)
// Then send rtp audio packet to Discord over UDP
@@ -742,7 +766,6 @@ func (v *VoiceConnection) opusReceiver(udpConn *net.UDPConn, close <-chan struct
return
}
- p := Packet{}
recvbuf := make([]byte, 1024)
var nonce [24]byte
@@ -778,6 +801,7 @@ func (v *VoiceConnection) opusReceiver(udpConn *net.UDPConn, close <-chan struct
}
// build a audio packet struct
+ p := Packet{}
p.Type = recvbuf[0:2]
p.Sequence = binary.BigEndian.Uint16(recvbuf[2:4])
p.Timestamp = binary.BigEndian.Uint32(recvbuf[4:8])
@@ -837,6 +861,8 @@ func (v *VoiceConnection) reconnect() {
return
}
+ v.log(LogInformational, "error reconnecting to channel %s, %s", v.ChannelID, err)
+
// if the reconnect above didn't work lets just send a disconnect
// packet to reset things.
// Send a OP4 with a nil channel to disconnect
@@ -848,6 +874,5 @@ func (v *VoiceConnection) reconnect() {
v.log(LogError, "error sending disconnect packet, %s", err)
}
- v.log(LogInformational, "error reconnecting to channel %s, %s", v.ChannelID, err)
}
}
diff --git a/vendor/github.com/bwmarrin/discordgo/wsapi.go b/vendor/github.com/bwmarrin/discordgo/wsapi.go
index 99a6236a..09128505 100644
--- a/vendor/github.com/bwmarrin/discordgo/wsapi.go
+++ b/vendor/github.com/bwmarrin/discordgo/wsapi.go
@@ -19,17 +19,30 @@ import (
"io"
"net/http"
"runtime"
+ "sync/atomic"
"time"
"github.com/gorilla/websocket"
)
+// ErrWSAlreadyOpen is thrown when you attempt to open
+// a websocket that already is open.
+var ErrWSAlreadyOpen = errors.New("web socket already opened")
+
+// ErrWSNotFound is thrown when you attempt to use a websocket
+// that doesn't exist
+var ErrWSNotFound = errors.New("no websocket connection exists")
+
+// ErrWSShardBounds is thrown when you try to use a shard ID that is
+// less than the total shard count
+var ErrWSShardBounds = errors.New("ShardID must be less than ShardCount")
+
type resumePacket struct {
Op int `json:"op"`
Data struct {
Token string `json:"token"`
SessionID string `json:"session_id"`
- Sequence int `json:"seq"`
+ Sequence int64 `json:"seq"`
} `json:"d"`
}
@@ -57,7 +70,7 @@ func (s *Session) Open() (err error) {
}
if s.wsConn != nil {
- err = errors.New("Web socket already opened.")
+ err = ErrWSAlreadyOpen
return
}
@@ -74,7 +87,7 @@ func (s *Session) Open() (err error) {
}
// Add the version and encoding to the URL
- s.gateway = fmt.Sprintf("%s?v=4&encoding=json", s.gateway)
+ s.gateway = fmt.Sprintf("%s?v=5&encoding=json", s.gateway)
}
header := http.Header{}
@@ -89,13 +102,14 @@ func (s *Session) Open() (err error) {
return
}
- if s.sessionID != "" && s.sequence > 0 {
+ sequence := atomic.LoadInt64(s.sequence)
+ if s.sessionID != "" && sequence > 0 {
p := resumePacket{}
p.Op = 6
p.Data.Token = s.Token
p.Data.SessionID = s.sessionID
- p.Data.Sequence = s.sequence
+ p.Data.Sequence = sequence
s.log(LogInformational, "sending resume packet to gateway")
err = s.wsConn.WriteJSON(p)
@@ -176,8 +190,13 @@ func (s *Session) listen(wsConn *websocket.Conn, listening <-chan interface{}) {
}
type heartbeatOp struct {
- Op int `json:"op"`
- Data int `json:"d"`
+ Op int `json:"op"`
+ Data int64 `json:"d"`
+}
+
+type helloOp struct {
+ HeartbeatInterval time.Duration `json:"heartbeat_interval"`
+ Trace []string `json:"_trace"`
}
// heartbeat sends regular heartbeats to Discord so it knows the client
@@ -193,12 +212,13 @@ func (s *Session) heartbeat(wsConn *websocket.Conn, listening <-chan interface{}
var err error
ticker := time.NewTicker(i * time.Millisecond)
+ defer ticker.Stop()
for {
-
- s.log(LogInformational, "sending gateway websocket heartbeat seq %d", s.sequence)
+ sequence := atomic.LoadInt64(s.sequence)
+ s.log(LogInformational, "sending gateway websocket heartbeat seq %d", sequence)
s.wsMutex.Lock()
- err = wsConn.WriteJSON(heartbeatOp{1, s.sequence})
+ err = wsConn.WriteJSON(heartbeatOp{1, sequence})
s.wsMutex.Unlock()
if err != nil {
s.log(LogError, "error sending heartbeat to gateway %s, %s", s.gateway, err)
@@ -242,7 +262,7 @@ func (s *Session) UpdateStreamingStatus(idle int, game string, url string) (err
s.RLock()
defer s.RUnlock()
if s.wsConn == nil {
- return errors.New("no websocket connection exists")
+ return ErrWSNotFound
}
var usd updateStatusData
@@ -299,7 +319,7 @@ func (s *Session) RequestGuildMembers(guildID, query string, limit int) (err err
s.RLock()
defer s.RUnlock()
if s.wsConn == nil {
- return errors.New("no websocket connection exists")
+ return ErrWSNotFound
}
data := requestGuildMembersData{
@@ -365,7 +385,7 @@ func (s *Session) onEvent(messageType int, message []byte) {
if e.Operation == 1 {
s.log(LogInformational, "sending heartbeat in response to Op1")
s.wsMutex.Lock()
- err = s.wsConn.WriteJSON(heartbeatOp{1, s.sequence})
+ err = s.wsConn.WriteJSON(heartbeatOp{1, atomic.LoadInt64(s.sequence)})
s.wsMutex.Unlock()
if err != nil {
s.log(LogError, "error sending heartbeat in response to Op1")
@@ -396,6 +416,16 @@ func (s *Session) onEvent(messageType int, message []byte) {
return
}
+ if e.Operation == 10 {
+ var h helloOp
+ if err = json.Unmarshal(e.RawData, &h); err != nil {
+ s.log(LogError, "error unmarshalling helloOp, %s", err)
+ } else {
+ go s.heartbeat(s.wsConn, s.listening, h.HeartbeatInterval)
+ }
+ return
+ }
+
// Do not try to Dispatch a non-Dispatch Message
if e.Operation != 0 {
// But we probably should be doing something with them.
@@ -405,7 +435,7 @@ func (s *Session) onEvent(messageType int, message []byte) {
}
// Store the message sequence
- s.sequence = e.Sequence
+ atomic.StoreInt64(s.sequence, e.Sequence)
// Map event to registered event handlers and pass it along to any registered handlers.
if eh, ok := registeredInterfaceProviders[e.Type]; ok {
@@ -458,18 +488,24 @@ func (s *Session) ChannelVoiceJoin(gID, cID string, mute, deaf bool) (voice *Voi
s.log(LogInformational, "called")
+ s.RLock()
voice, _ = s.VoiceConnections[gID]
+ s.RUnlock()
if voice == nil {
voice = &VoiceConnection{}
+ s.Lock()
s.VoiceConnections[gID] = voice
+ s.Unlock()
}
+ voice.Lock()
voice.GuildID = gID
voice.ChannelID = cID
voice.deaf = deaf
voice.mute = mute
voice.session = s
+ voice.Unlock()
// Send the request to Discord that we want to join the voice channel
data := voiceChannelJoinOp{4, voiceChannelJoinData{&gID, &cID, mute, deaf}}
@@ -500,7 +536,9 @@ func (s *Session) onVoiceStateUpdate(st *VoiceStateUpdate) {
}
// Check if we have a voice connection to update
+ s.RLock()
voice, exists := s.VoiceConnections[st.GuildID]
+ s.RUnlock()
if !exists {
return
}
@@ -511,8 +549,11 @@ func (s *Session) onVoiceStateUpdate(st *VoiceStateUpdate) {
}
// Store the SessionID for later use.
+ voice.Lock()
voice.UserID = st.UserID
voice.sessionID = st.SessionID
+ voice.ChannelID = st.ChannelID
+ voice.Unlock()
}
// onVoiceServerUpdate handles the Voice Server Update data websocket event.
@@ -524,7 +565,9 @@ func (s *Session) onVoiceServerUpdate(st *VoiceServerUpdate) {
s.log(LogInformational, "called")
+ s.RLock()
voice, exists := s.VoiceConnections[st.GuildID]
+ s.RUnlock()
// If no VoiceConnection exists, just skip this
if !exists {
@@ -536,9 +579,11 @@ func (s *Session) onVoiceServerUpdate(st *VoiceServerUpdate) {
voice.Close()
// Store values for later use
+ voice.Lock()
voice.token = st.Token
voice.endpoint = st.Endpoint
voice.GuildID = st.GuildID
+ voice.Unlock()
// Open a conenction to the voice server
err := voice.open()
@@ -588,7 +633,7 @@ func (s *Session) identify() error {
if s.ShardCount > 1 {
if s.ShardID >= s.ShardCount {
- return errors.New("ShardID must be less than ShardCount")
+ return ErrWSShardBounds
}
data.Shard = &[2]int{s.ShardID, s.ShardCount}
@@ -628,6 +673,8 @@ func (s *Session) reconnect() {
// However, there seems to be cases where something "weird"
// happens. So we're doing this for now just to improve
// stability in those edge cases.
+ s.RLock()
+ defer s.RUnlock()
for _, v := range s.VoiceConnections {
s.log(LogInformational, "reconnecting voice connection to guild %s", v.GuildID)
@@ -675,7 +722,9 @@ func (s *Session) Close() (err error) {
s.log(LogInformational, "sending close frame")
// To cleanly close a connection, a client should send a close
// frame and wait for the server to close the connection.
+ s.wsMutex.Lock()
err := s.wsConn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
+ s.wsMutex.Unlock()
if err != nil {
s.log(LogInformational, "error closing websocket, %s", err)
}
diff --git a/vendor/manifest b/vendor/manifest
index d311a3ab..e1f81c95 100644
--- a/vendor/manifest
+++ b/vendor/manifest
@@ -45,7 +45,7 @@
"importpath": "github.com/bwmarrin/discordgo",
"repository": "https://github.com/bwmarrin/discordgo",
"vcs": "git",
- "revision": "5835676872d7fa65ee37b32672079c4ede2c1855",
+ "revision": "d420e28024ad527390b43aa7f64e029083e11989",
"branch": "master",
"notests": true
},