summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--vendor/github.com/bwmarrin/discordgo/discord.go3
-rw-r--r--vendor/github.com/bwmarrin/discordgo/endpoints.go42
-rw-r--r--vendor/github.com/bwmarrin/discordgo/event.go14
-rw-r--r--vendor/github.com/bwmarrin/discordgo/message.go92
-rw-r--r--vendor/github.com/bwmarrin/discordgo/ratelimit.go47
-rw-r--r--vendor/github.com/bwmarrin/discordgo/restapi.go130
-rw-r--r--vendor/github.com/bwmarrin/discordgo/state.go72
-rw-r--r--vendor/github.com/bwmarrin/discordgo/structs.go87
-rw-r--r--vendor/github.com/bwmarrin/discordgo/user.go18
-rw-r--r--vendor/github.com/bwmarrin/discordgo/voice.go13
-rw-r--r--vendor/github.com/bwmarrin/discordgo/wsapi.go58
-rw-r--r--vendor/manifest2
12 files changed, 467 insertions, 111 deletions
diff --git a/vendor/github.com/bwmarrin/discordgo/discord.go b/vendor/github.com/bwmarrin/discordgo/discord.go
index 2f0b6fd4..40eabe18 100644
--- a/vendor/github.com/bwmarrin/discordgo/discord.go
+++ b/vendor/github.com/bwmarrin/discordgo/discord.go
@@ -21,7 +21,7 @@ import (
)
// VERSION of DiscordGo, follows Semantic Versioning. (http://semver.org/)
-const VERSION = "0.16.0"
+const VERSION = "0.17.0"
// ErrMFA will be risen by New when the user has 2FA.
var ErrMFA = errors.New("account has 2FA enabled")
@@ -59,6 +59,7 @@ func New(args ...interface{}) (s *Session, err error) {
MaxRestRetries: 3,
Client: &http.Client{Timeout: (20 * time.Second)},
sequence: new(int64),
+ LastHeartbeatAck: time.Now().UTC(),
}
// If no arguments are passed return the empty Session interface.
diff --git a/vendor/github.com/bwmarrin/discordgo/endpoints.go b/vendor/github.com/bwmarrin/discordgo/endpoints.go
index 96bcf28b..b10f9589 100644
--- a/vendor/github.com/bwmarrin/discordgo/endpoints.go
+++ b/vendor/github.com/bwmarrin/discordgo/endpoints.go
@@ -11,6 +11,9 @@
package discordgo
+// APIVersion is the Discord API version used for the REST and Websocket API.
+var APIVersion = "6"
+
// Known Discord API Endpoints.
var (
EndpointStatus = "https://status.discordapp.com/api/v2/"
@@ -18,13 +21,14 @@ var (
EndpointSmActive = EndpointSm + "active.json"
EndpointSmUpcoming = EndpointSm + "upcoming.json"
- EndpointDiscord = "https://discordapp.com/"
- EndpointAPI = EndpointDiscord + "api/"
- EndpointGuilds = EndpointAPI + "guilds/"
- EndpointChannels = EndpointAPI + "channels/"
- EndpointUsers = EndpointAPI + "users/"
- EndpointGateway = EndpointAPI + "gateway"
- EndpointWebhooks = EndpointAPI + "webhooks/"
+ EndpointDiscord = "https://discordapp.com/"
+ EndpointAPI = EndpointDiscord + "api/v" + APIVersion + "/"
+ EndpointGuilds = EndpointAPI + "guilds/"
+ EndpointChannels = EndpointAPI + "channels/"
+ EndpointUsers = EndpointAPI + "users/"
+ EndpointGateway = EndpointAPI + "gateway"
+ EndpointGatewayBot = EndpointGateway + "/bot"
+ EndpointWebhooks = EndpointAPI + "webhooks/"
EndpointCDN = "https://cdn.discordapp.com/"
EndpointCDNAttachments = EndpointCDN + "attachments/"
@@ -54,16 +58,17 @@ var (
EndpointReport = EndpointAPI + "report"
EndpointIntegrations = EndpointAPI + "integrations"
- EndpointUser = func(uID string) string { return EndpointUsers + uID }
- 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 }
- EndpointUserGuildSettings = func(uID, gID string) string { return EndpointUsers + uID + "/guilds/" + gID + "/settings" }
- EndpointUserChannels = func(uID string) string { return EndpointUsers + uID + "/channels" }
- EndpointUserDevices = func(uID string) string { return EndpointUsers + uID + "/devices" }
- EndpointUserConnections = func(uID string) string { return EndpointUsers + uID + "/connections" }
- EndpointUserNotes = func(uID string) string { return EndpointUsers + "@me/notes/" + uID }
+ EndpointUser = func(uID string) string { return EndpointUsers + uID }
+ EndpointUserAvatar = func(uID, aID string) string { return EndpointCDNAvatars + uID + "/" + aID + ".png" }
+ EndpointUserAvatarAnimated = func(uID, aID string) string { return EndpointCDNAvatars + uID + "/" + aID + ".gif" }
+ EndpointUserSettings = func(uID string) string { return EndpointUsers + uID + "/settings" }
+ EndpointUserGuilds = func(uID string) string { return EndpointUsers + uID + "/guilds" }
+ EndpointUserGuild = func(uID, gID string) string { return EndpointUsers + uID + "/guilds/" + gID }
+ EndpointUserGuildSettings = func(uID, gID string) string { return EndpointUsers + uID + "/guilds/" + gID + "/settings" }
+ EndpointUserChannels = func(uID string) string { return EndpointUsers + uID + "/channels" }
+ EndpointUserDevices = func(uID string) string { return EndpointUsers + uID + "/devices" }
+ EndpointUserConnections = func(uID string) string { return EndpointUsers + uID + "/connections" }
+ EndpointUserNotes = func(uID string) string { return EndpointUsers + "@me/notes/" + uID }
EndpointGuild = func(gID string) string { return EndpointGuilds + gID }
EndpointGuildInivtes = func(gID string) string { return EndpointGuilds + gID + "/invites" }
@@ -103,6 +108,9 @@ var (
EndpointWebhook = func(wID string) string { return EndpointWebhooks + wID }
EndpointWebhookToken = func(wID, token string) string { return EndpointWebhooks + wID + "/" + token }
+ EndpointMessageReactionsAll = func(cID, mID string) string {
+ return EndpointChannelMessage(cID, mID) + "/reactions"
+ }
EndpointMessageReactions = func(cID, mID, eID string) string {
return EndpointChannelMessage(cID, mID) + "/reactions/" + eID
}
diff --git a/vendor/github.com/bwmarrin/discordgo/event.go b/vendor/github.com/bwmarrin/discordgo/event.go
index 906c2aa8..3a03f46d 100644
--- a/vendor/github.com/bwmarrin/discordgo/event.go
+++ b/vendor/github.com/bwmarrin/discordgo/event.go
@@ -156,12 +156,20 @@ func (s *Session) removeEventHandlerInstance(t string, ehi *eventHandlerInstance
// Handles calling permanent and once handlers for an event type.
func (s *Session) handle(t string, i interface{}) {
for _, eh := range s.handlers[t] {
- go eh.eventHandler.Handle(s, i)
+ if s.SyncEvents {
+ eh.eventHandler.Handle(s, i)
+ } else {
+ go eh.eventHandler.Handle(s, i)
+ }
}
if len(s.onceHandlers[t]) > 0 {
for _, eh := range s.onceHandlers[t] {
- go eh.eventHandler.Handle(s, i)
+ if s.SyncEvents {
+ eh.eventHandler.Handle(s, i)
+ } else {
+ go eh.eventHandler.Handle(s, i)
+ }
}
s.onceHandlers[t] = nil
}
@@ -216,7 +224,7 @@ func (s *Session) onInterface(i interface{}) {
case *VoiceStateUpdate:
go s.onVoiceStateUpdate(t)
}
- err := s.State.onInterface(s, i)
+ err := s.State.OnInterface(s, i)
if err != nil {
s.log(LogDebug, "error dispatching internal event, %s", err)
}
diff --git a/vendor/github.com/bwmarrin/discordgo/message.go b/vendor/github.com/bwmarrin/discordgo/message.go
index 13c2da07..19345b95 100644
--- a/vendor/github.com/bwmarrin/discordgo/message.go
+++ b/vendor/github.com/bwmarrin/discordgo/message.go
@@ -10,9 +10,24 @@
package discordgo
import (
- "fmt"
"io"
"regexp"
+ "strings"
+)
+
+// MessageType is the type of Message
+type MessageType int
+
+// Block contains the valid known MessageType values
+const (
+ MessageTypeDefault MessageType = iota
+ MessageTypeRecipientAdd
+ MessageTypeRecipientRemove
+ MessageTypeCall
+ MessageTypeChannelNameChange
+ MessageTypeChannelIconChange
+ MessageTypeChannelPinnedMessage
+ MessageTypeGuildMemberJoin
)
// A Message stores all data related to a specific Discord message.
@@ -30,12 +45,14 @@ type Message struct {
Embeds []*MessageEmbed `json:"embeds"`
Mentions []*User `json:"mentions"`
Reactions []*MessageReactions `json:"reactions"`
+ Type MessageType `json:"type"`
}
// File stores info about files you e.g. send in messages.
type File struct {
- Name string
- Reader io.Reader
+ Name string
+ ContentType string
+ Reader io.Reader
}
// MessageSend stores all parameters you can send with ChannelMessageSendComplex.
@@ -43,7 +60,10 @@ type MessageSend struct {
Content string `json:"content,omitempty"`
Embed *MessageEmbed `json:"embed,omitempty"`
Tts bool `json:"tts"`
- File *File `json:"file"`
+ Files []*File `json:"-"`
+
+ // TODO: Remove this when compatibility is not required.
+ File *File `json:"-"`
}
// MessageEdit is used to chain parameters via ChannelMessageEditComplex, which
@@ -168,13 +188,65 @@ type MessageReactions struct {
// ContentWithMentionsReplaced will replace all @<id> mentions with the
// username of the mention.
-func (m *Message) ContentWithMentionsReplaced() string {
- if m.Mentions == nil {
- return m.Content
+func (m *Message) ContentWithMentionsReplaced() (content string) {
+ content = m.Content
+
+ for _, user := range m.Mentions {
+ content = strings.NewReplacer(
+ "<@"+user.ID+">", "@"+user.Username,
+ "<@!"+user.ID+">", "@"+user.Username,
+ ).Replace(content)
+ }
+ return
+}
+
+var patternChannels = regexp.MustCompile("<#[^>]*>")
+
+// ContentWithMoreMentionsReplaced will replace all @<id> mentions with the
+// username of the mention, but also role IDs and more.
+func (m *Message) ContentWithMoreMentionsReplaced(s *Session) (content string, err error) {
+ content = m.Content
+
+ if !s.StateEnabled {
+ content = m.ContentWithMentionsReplaced()
+ return
}
- content := m.Content
+
+ channel, err := s.State.Channel(m.ChannelID)
+ if err != nil {
+ content = m.ContentWithMentionsReplaced()
+ return
+ }
+
for _, user := range m.Mentions {
- content = regexp.MustCompile(fmt.Sprintf("<@!?(%s)>", user.ID)).ReplaceAllString(content, "@"+user.Username)
+ nick := user.Username
+
+ member, err := s.State.Member(channel.GuildID, user.ID)
+ if err == nil && member.Nick != "" {
+ nick = member.Nick
+ }
+
+ content = strings.NewReplacer(
+ "<@"+user.ID+">", "@"+user.Username,
+ "<@!"+user.ID+">", "@"+nick,
+ ).Replace(content)
}
- return content
+ for _, roleID := range m.MentionRoles {
+ role, err := s.State.Role(channel.GuildID, roleID)
+ if err != nil || !role.Mentionable {
+ continue
+ }
+
+ content = strings.Replace(content, "<&"+role.ID+">", "@"+role.Name, -1)
+ }
+
+ content = patternChannels.ReplaceAllStringFunc(content, func(mention string) string {
+ channel, err := s.State.Channel(mention[2 : len(mention)-1])
+ if err != nil || channel.Type == ChannelTypeGuildVoice {
+ return mention
+ }
+
+ return "#" + channel.Name
+ })
+ return
}
diff --git a/vendor/github.com/bwmarrin/discordgo/ratelimit.go b/vendor/github.com/bwmarrin/discordgo/ratelimit.go
index 876e98a9..223c0d04 100644
--- a/vendor/github.com/bwmarrin/discordgo/ratelimit.go
+++ b/vendor/github.com/bwmarrin/discordgo/ratelimit.go
@@ -3,17 +3,26 @@ package discordgo
import (
"net/http"
"strconv"
+ "strings"
"sync"
"sync/atomic"
"time"
)
+// customRateLimit holds information for defining a custom rate limit
+type customRateLimit struct {
+ suffix string
+ requests int
+ reset time.Duration
+}
+
// RateLimiter holds all ratelimit buckets
type RateLimiter struct {
sync.Mutex
- global *int64
- buckets map[string]*Bucket
- globalRateLimit time.Duration
+ global *int64
+ buckets map[string]*Bucket
+ globalRateLimit time.Duration
+ customRateLimits []*customRateLimit
}
// NewRatelimiter returns a new RateLimiter
@@ -22,6 +31,13 @@ func NewRatelimiter() *RateLimiter {
return &RateLimiter{
buckets: make(map[string]*Bucket),
global: new(int64),
+ customRateLimits: []*customRateLimit{
+ &customRateLimit{
+ suffix: "//reactions//",
+ requests: 1,
+ reset: 200 * time.Millisecond,
+ },
+ },
}
}
@@ -40,6 +56,14 @@ func (r *RateLimiter) getBucket(key string) *Bucket {
global: r.global,
}
+ // Check if there is a custom ratelimit set for this bucket ID.
+ for _, rl := range r.customRateLimits {
+ if strings.HasSuffix(b.Key, rl.suffix) {
+ b.customRateLimit = rl
+ break
+ }
+ }
+
r.buckets[key] = b
return b
}
@@ -76,13 +100,28 @@ type Bucket struct {
limit int
reset time.Time
global *int64
+
+ lastReset time.Time
+ customRateLimit *customRateLimit
}
// Release unlocks the bucket and reads the headers to update the buckets ratelimit info
// and locks up the whole thing in case if there's a global ratelimit.
func (b *Bucket) Release(headers http.Header) error {
-
defer b.Unlock()
+
+ // Check if the bucket uses a custom ratelimiter
+ if rl := b.customRateLimit; rl != nil {
+ if time.Now().Sub(b.lastReset) >= rl.reset {
+ b.remaining = rl.requests - 1
+ b.lastReset = time.Now()
+ }
+ if b.remaining < 1 {
+ b.reset = time.Now().Add(rl.reset)
+ }
+ return nil
+ }
+
if headers == nil {
return nil
}
diff --git a/vendor/github.com/bwmarrin/discordgo/restapi.go b/vendor/github.com/bwmarrin/discordgo/restapi.go
index cb482e68..836e4a41 100644
--- a/vendor/github.com/bwmarrin/discordgo/restapi.go
+++ b/vendor/github.com/bwmarrin/discordgo/restapi.go
@@ -23,6 +23,7 @@ import (
"log"
"mime/multipart"
"net/http"
+ "net/textproto"
"net/url"
"strconv"
"strings"
@@ -309,8 +310,8 @@ func (s *Session) UserUpdate(email, password, username, avatar, newPassword stri
// If left blank, avatar will be set to null/blank
data := struct {
- Email string `json:"email"`
- Password string `json:"password"`
+ Email string `json:"email,omitempty"`
+ Password string `json:"password,omitempty"`
Username string `json:"username,omitempty"`
Avatar string `json:"avatar,omitempty"`
NewPassword string `json:"new_password,omitempty"`
@@ -763,7 +764,21 @@ func (s *Session) GuildMember(guildID, userID string) (st *Member, err error) {
// userID : The ID of a User
func (s *Session) GuildMemberDelete(guildID, userID string) (err error) {
- _, err = s.RequestWithBucketID("DELETE", EndpointGuildMember(guildID, userID), nil, EndpointGuildMember(guildID, ""))
+ return s.GuildMemberDeleteWithReason(guildID, userID, "")
+}
+
+// GuildMemberDeleteWithReason removes the given user from the given guild.
+// guildID : The ID of a Guild.
+// userID : The ID of a User
+// reason : The reason for the kick
+func (s *Session) GuildMemberDeleteWithReason(guildID, userID, reason string) (err error) {
+
+ uri := EndpointGuildMember(guildID, userID)
+ if reason != "" {
+ uri += "?reason=" + url.QueryEscape(reason)
+ }
+
+ _, err = s.RequestWithBucketID("DELETE", uri, nil, EndpointGuildMember(guildID, ""))
return
}
@@ -1316,6 +1331,8 @@ func (s *Session) ChannelMessageSend(channelID string, content string) (*Message
})
}
+var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
+
// ChannelMessageSendComplex sends a message to the given channel.
// channelID : The ID of a Channel.
// data : The message struct to send.
@@ -1326,48 +1343,62 @@ func (s *Session) ChannelMessageSendComplex(channelID string, data *MessageSend)
endpoint := EndpointChannelMessages(channelID)
- var response []byte
+ // TODO: Remove this when compatibility is not required.
+ files := data.Files
if data.File != nil {
+ if files == nil {
+ files = []*File{data.File}
+ } else {
+ err = fmt.Errorf("cannot specify both File and Files")
+ return
+ }
+ }
+
+ var response []byte
+ if len(files) > 0 {
body := &bytes.Buffer{}
bodywriter := multipart.NewWriter(body)
- // What's a better way of doing this? Reflect? Generator? I'm open to suggestions
+ var payload []byte
+ payload, err = json.Marshal(data)
+ if err != nil {
+ return
+ }
+
+ var p io.Writer
- if data.Content != "" {
- if err = bodywriter.WriteField("content", data.Content); err != nil {
- return
- }
+ h := make(textproto.MIMEHeader)
+ h.Set("Content-Disposition", `form-data; name="payload_json"`)
+ h.Set("Content-Type", "application/json")
+
+ p, err = bodywriter.CreatePart(h)
+ if err != nil {
+ return
}
- if data.Embed != nil {
- var embed []byte
- embed, err = json.Marshal(data.Embed)
- if err != nil {
- return
+ if _, err = p.Write(payload); err != nil {
+ return
+ }
+
+ for i, file := range files {
+ h := make(textproto.MIMEHeader)
+ h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="file%d"; filename="%s"`, i, quoteEscaper.Replace(file.Name)))
+ contentType := file.ContentType
+ if contentType == "" {
+ contentType = "application/octet-stream"
}
- err = bodywriter.WriteField("embed", string(embed))
+ h.Set("Content-Type", contentType)
+
+ p, err = bodywriter.CreatePart(h)
if err != nil {
return
}
- }
- if data.Tts {
- if err = bodywriter.WriteField("tts", "true"); err != nil {
+ if _, err = io.Copy(p, file.Reader); 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
- }
-
err = bodywriter.Close()
if err != nil {
return
@@ -1685,6 +1716,28 @@ func (s *Session) Gateway() (gateway string, err error) {
return
}
+// GatewayBot returns the websocket Gateway address and the recommended number of shards
+func (s *Session) GatewayBot() (st *GatewayBotResponse, err error) {
+
+ response, err := s.RequestWithBucketID("GET", EndpointGatewayBot, nil, EndpointGatewayBot)
+ if err != nil {
+ return
+ }
+
+ err = unmarshal(response, &st)
+ if err != nil {
+ return
+ }
+
+ // Ensure the gateway always has a trailing slash.
+ // MacOS will fail to connect if we add query params without a trailing slash on the base domain.
+ if !strings.HasSuffix(st.URL, "/") {
+ st.URL += "/"
+ }
+
+ return
+}
+
// Functions specific to Webhooks
// WebhookCreate returns a new Webhook.
@@ -1810,14 +1863,9 @@ func (s *Session) WebhookEditWithToken(webhookID, token, name, avatar string) (s
// WebhookDelete deletes a webhook for a given ID
// webhookID: The ID of a webhook.
-func (s *Session) WebhookDelete(webhookID string) (st *Webhook, err error) {
+func (s *Session) WebhookDelete(webhookID string) (err error) {
- body, err := s.RequestWithBucketID("DELETE", EndpointWebhook(webhookID), nil, EndpointWebhooks)
- if err != nil {
- return
- }
-
- err = unmarshal(body, &st)
+ _, err = s.RequestWithBucketID("DELETE", EndpointWebhook(webhookID), nil, EndpointWebhooks)
return
}
@@ -1875,6 +1923,16 @@ func (s *Session) MessageReactionRemove(channelID, messageID, emojiID, userID st
return err
}
+// MessageReactionsRemoveAll deletes all reactions from a message
+// channelID : The channel ID
+// messageID : The message ID.
+func (s *Session) MessageReactionsRemoveAll(channelID, messageID string) error {
+
+ _, err := s.RequestWithBucketID("DELETE", EndpointMessageReactionsAll(channelID, messageID), nil, EndpointMessageReactionsAll(channelID, messageID))
+
+ return err
+}
+
// MessageReactions gets all the users reactions for a specific emoji.
// channelID : The channel ID.
// messageID : The message ID.
diff --git a/vendor/github.com/bwmarrin/discordgo/state.go b/vendor/github.com/bwmarrin/discordgo/state.go
index 7400ef62..35a8e757 100644
--- a/vendor/github.com/bwmarrin/discordgo/state.go
+++ b/vendor/github.com/bwmarrin/discordgo/state.go
@@ -42,6 +42,7 @@ type State struct {
guildMap map[string]*Guild
channelMap map[string]*Channel
+ memberMap map[string]map[string]*Member
}
// NewState creates an empty state.
@@ -59,9 +60,18 @@ func NewState() *State {
TrackPresences: true,
guildMap: make(map[string]*Guild),
channelMap: make(map[string]*Channel),
+ memberMap: make(map[string]map[string]*Member),
}
}
+func (s *State) createMemberMap(guild *Guild) {
+ members := make(map[string]*Member)
+ for _, m := range guild.Members {
+ members[m.User.ID] = m
+ }
+ s.memberMap[guild.ID] = members
+}
+
// GuildAdd adds a guild to the current world state, or
// updates it if it already exists.
func (s *State) GuildAdd(guild *Guild) error {
@@ -77,6 +87,14 @@ func (s *State) GuildAdd(guild *Guild) error {
s.channelMap[c.ID] = c
}
+ // If this guild contains a new member slice, we must regenerate the member map so the pointers stay valid
+ if guild.Members != nil {
+ s.createMemberMap(guild)
+ } else if _, ok := s.memberMap[guild.ID]; !ok {
+ // Even if we have no new member slice, we still initialize the member map for this guild if it doesn't exist
+ s.memberMap[guild.ID] = make(map[string]*Member)
+ }
+
if g, ok := s.guildMap[guild.ID]; ok {
// We are about to replace `g` in the state with `guild`, but first we need to
// make sure we preserve any fields that the `guild` doesn't contain from `g`.
@@ -271,14 +289,19 @@ func (s *State) MemberAdd(member *Member) error {
s.Lock()
defer s.Unlock()
- for i, m := range guild.Members {
- if m.User.ID == member.User.ID {
- guild.Members[i] = member
- return nil
- }
+ members, ok := s.memberMap[member.GuildID]
+ if !ok {
+ return ErrStateNotFound
+ }
+
+ m, ok := members[member.User.ID]
+ if !ok {
+ members[member.User.ID] = member
+ guild.Members = append(guild.Members, member)
+ } else {
+ *m = *member // Update the actual data, which will also update the member pointer in the slice
}
- guild.Members = append(guild.Members, member)
return nil
}
@@ -296,6 +319,17 @@ func (s *State) MemberRemove(member *Member) error {
s.Lock()
defer s.Unlock()
+ members, ok := s.memberMap[member.GuildID]
+ if !ok {
+ return ErrStateNotFound
+ }
+
+ _, ok = members[member.User.ID]
+ if !ok {
+ return ErrStateNotFound
+ }
+ delete(members, member.User.ID)
+
for i, m := range guild.Members {
if m.User.ID == member.User.ID {
guild.Members = append(guild.Members[:i], guild.Members[i+1:]...)
@@ -312,18 +346,17 @@ func (s *State) Member(guildID, userID string) (*Member, error) {
return nil, ErrNilState
}
- guild, err := s.Guild(guildID)
- if err != nil {
- return nil, err
- }
-
s.RLock()
defer s.RUnlock()
- for _, m := range guild.Members {
- if m.User.ID == userID {
- return m, nil
- }
+ members, ok := s.memberMap[guildID]
+ if !ok {
+ return nil, ErrStateNotFound
+ }
+
+ m, ok := members[userID]
+ if ok {
+ return m, nil
}
return nil, ErrStateNotFound
@@ -427,7 +460,7 @@ func (s *State) ChannelAdd(channel *Channel) error {
return nil
}
- if channel.IsPrivate {
+ if channel.Type == ChannelTypeDM || channel.Type == ChannelTypeGroupDM {
s.PrivateChannels = append(s.PrivateChannels, channel)
} else {
guild, ok := s.guildMap[channel.GuildID]
@@ -454,7 +487,7 @@ func (s *State) ChannelRemove(channel *Channel) error {
return err
}
- if channel.IsPrivate {
+ if channel.Type == ChannelTypeDM || channel.Type == ChannelTypeGroupDM {
s.Lock()
defer s.Unlock()
@@ -735,6 +768,7 @@ func (s *State) onReady(se *Session, r *Ready) (err error) {
for _, g := range s.Guilds {
s.guildMap[g.ID] = g
+ s.createMemberMap(g)
for _, c := range g.Channels {
s.channelMap[c.ID] = c
@@ -748,8 +782,8 @@ func (s *State) onReady(se *Session, r *Ready) (err error) {
return nil
}
-// onInterface handles all events related to states.
-func (s *State) onInterface(se *Session, i interface{}) (err error) {
+// OnInterface handles all events related to states.
+func (s *State) OnInterface(se *Session, i interface{}) (err error) {
if s == nil {
return ErrNilState
}
diff --git a/vendor/github.com/bwmarrin/discordgo/structs.go b/vendor/github.com/bwmarrin/discordgo/structs.go
index 32f435ce..c3e39566 100644
--- a/vendor/github.com/bwmarrin/discordgo/structs.go
+++ b/vendor/github.com/bwmarrin/discordgo/structs.go
@@ -50,6 +50,10 @@ type Session struct {
// active guilds and the members of the guilds.
StateEnabled bool
+ // Whether or not to call event handlers synchronously.
+ // e.g false = launch event handlers in their own goroutines.
+ SyncEvents bool
+
// Exposed but should not be modified by User.
// Whether the Data Websocket is ready
@@ -78,6 +82,9 @@ type Session struct {
// The http client used for REST requests
Client *http.Client
+ // Stores the last HeartbeatAck that was recieved (in UTC)
+ LastHeartbeatAck time.Time
+
// Event handlers
handlersMu sync.RWMutex
handlers map[string][]*eventHandlerInstance
@@ -141,18 +148,30 @@ type Invite struct {
Temporary bool `json:"temporary"`
}
+// ChannelType is the type of a Channel
+type ChannelType int
+
+// Block contains known ChannelType values
+const (
+ ChannelTypeGuildText ChannelType = iota
+ ChannelTypeDM
+ ChannelTypeGuildVoice
+ ChannelTypeGroupDM
+ ChannelTypeGuildCategory
+)
+
// A Channel holds all data related to an individual Discord channel.
type Channel struct {
ID string `json:"id"`
GuildID string `json:"guild_id"`
Name string `json:"name"`
Topic string `json:"topic"`
- Type string `json:"type"`
+ Type ChannelType `json:"type"`
LastMessageID string `json:"last_message_id"`
+ NSFW bool `json:"nsfw"`
Position int `json:"position"`
Bitrate int `json:"bitrate"`
- IsPrivate bool `json:"is_private"`
- Recipient *User `json:"recipient"`
+ Recipients []*User `json:"recipient"`
Messages []*Message `json:"-"`
PermissionOverwrites []*PermissionOverwrite `json:"permission_overwrites"`
}
@@ -292,13 +311,14 @@ type Presence struct {
Game *Game `json:"game"`
Nick string `json:"nick"`
Roles []string `json:"roles"`
+ Since *int `json:"since"`
}
// A Game struct holds the name of the "playing .." game for a user
type Game struct {
Name string `json:"name"`
Type int `json:"type"`
- URL string `json:"url"`
+ URL string `json:"url,omitempty"`
}
// UnmarshalJSON unmarshals json to Game struct
@@ -509,6 +529,12 @@ type MessageReaction struct {
ChannelID string `json:"channel_id"`
}
+// GatewayBotResponse stores the data for the gateway/bot response
+type GatewayBotResponse struct {
+ URL string `json:"url"`
+ Shards int `json:"shards"`
+}
+
// Constants for the different bit offsets of text channel permissions
const (
PermissionReadMessages = 1 << (iota + 10)
@@ -579,3 +605,56 @@ const (
PermissionManageServer |
PermissionAdministrator
)
+
+// Block contains Discord JSON Error Response codes
+const (
+ ErrCodeUnknownAccount = 10001
+ ErrCodeUnknownApplication = 10002
+ ErrCodeUnknownChannel = 10003
+ ErrCodeUnknownGuild = 10004
+ ErrCodeUnknownIntegration = 10005
+ ErrCodeUnknownInvite = 10006
+ ErrCodeUnknownMember = 10007
+ ErrCodeUnknownMessage = 10008
+ ErrCodeUnknownOverwrite = 10009
+ ErrCodeUnknownProvider = 10010
+ ErrCodeUnknownRole = 10011
+ ErrCodeUnknownToken = 10012
+ ErrCodeUnknownUser = 10013
+ ErrCodeUnknownEmoji = 10014
+
+ ErrCodeBotsCannotUseEndpoint = 20001
+ ErrCodeOnlyBotsCanUseEndpoint = 20002
+
+ ErrCodeMaximumGuildsReached = 30001
+ ErrCodeMaximumFriendsReached = 30002
+ ErrCodeMaximumPinsReached = 30003
+ ErrCodeMaximumGuildRolesReached = 30005
+ ErrCodeTooManyReactions = 30010
+
+ ErrCodeUnauthorized = 40001
+
+ ErrCodeMissingAccess = 50001
+ ErrCodeInvalidAccountType = 50002
+ ErrCodeCannotExecuteActionOnDMChannel = 50003
+ ErrCodeEmbedCisabled = 50004
+ ErrCodeCannotEditFromAnotherUser = 50005
+ ErrCodeCannotSendEmptyMessage = 50006
+ ErrCodeCannotSendMessagesToThisUser = 50007
+ ErrCodeCannotSendMessagesInVoiceChannel = 50008
+ ErrCodeChannelVerificationLevelTooHigh = 50009
+ ErrCodeOAuth2ApplicationDoesNotHaveBot = 50010
+ ErrCodeOAuth2ApplicationLimitReached = 50011
+ ErrCodeInvalidOAuthState = 50012
+ ErrCodeMissingPermissions = 50013
+ ErrCodeInvalidAuthenticationToken = 50014
+ ErrCodeNoteTooLong = 50015
+ ErrCodeTooFewOrTooManyMessagesToDelete = 50016
+ ErrCodeCanOnlyPinMessageToOriginatingChannel = 50019
+ ErrCodeCannotExecuteActionOnSystemMessage = 50021
+ ErrCodeMessageProvidedTooOldForBulkDelete = 50034
+ ErrCodeInvalidFormBody = 50035
+ ErrCodeInviteAcceptedToGuildApplicationsBotNotIn = 50036
+
+ ErrCodeReactionBlocked = 90001
+)
diff --git a/vendor/github.com/bwmarrin/discordgo/user.go b/vendor/github.com/bwmarrin/discordgo/user.go
index b3a7e4b2..76abdd1d 100644
--- a/vendor/github.com/bwmarrin/discordgo/user.go
+++ b/vendor/github.com/bwmarrin/discordgo/user.go
@@ -1,6 +1,9 @@
package discordgo
-import "fmt"
+import (
+ "fmt"
+ "strings"
+)
// A User stores all data for an individual Discord user.
type User struct {
@@ -24,3 +27,16 @@ func (u *User) String() string {
func (u *User) Mention() string {
return fmt.Sprintf("<@%s>", u.ID)
}
+
+// AvatarURL returns a URL to the user's avatar.
+// size: The size of the user's avatar as a power of two
+func (u *User) AvatarURL(size string) string {
+ var URL string
+ if strings.HasPrefix(u.Avatar, "a_") {
+ URL = EndpointUserAvatarAnimated(u.ID, u.Avatar)
+ } else {
+ URL = EndpointUserAvatar(u.ID, u.Avatar)
+ }
+
+ return URL + "?size=" + size
+}
diff --git a/vendor/github.com/bwmarrin/discordgo/voice.go b/vendor/github.com/bwmarrin/discordgo/voice.go
index da7b8c90..8f033aa0 100644
--- a/vendor/github.com/bwmarrin/discordgo/voice.go
+++ b/vendor/github.com/bwmarrin/discordgo/voice.go
@@ -796,7 +796,7 @@ func (v *VoiceConnection) opusReceiver(udpConn *net.UDPConn, close <-chan struct
}
// For now, skip anything except audio.
- if rlen < 12 || recvbuf[0] != 0x80 {
+ if rlen < 12 || (recvbuf[0] != 0x80 && recvbuf[0] != 0x90) {
continue
}
@@ -810,8 +810,17 @@ func (v *VoiceConnection) opusReceiver(udpConn *net.UDPConn, close <-chan struct
copy(nonce[:], recvbuf[0:12])
p.Opus, _ = secretbox.Open(nil, recvbuf[12:rlen], &nonce, &v.op4.SecretKey)
+ if len(p.Opus) > 8 && recvbuf[0] == 0x90 {
+ // Extension bit is set, first 8 bytes is the extended header
+ p.Opus = p.Opus[8:]
+ }
+
if c != nil {
- c <- &p
+ select {
+ case c <- &p:
+ case <-close:
+ return
+ }
}
}
}
diff --git a/vendor/github.com/bwmarrin/discordgo/wsapi.go b/vendor/github.com/bwmarrin/discordgo/wsapi.go
index 09128505..df87092e 100644
--- a/vendor/github.com/bwmarrin/discordgo/wsapi.go
+++ b/vendor/github.com/bwmarrin/discordgo/wsapi.go
@@ -15,7 +15,6 @@ import (
"compress/zlib"
"encoding/json"
"errors"
- "fmt"
"io"
"net/http"
"runtime"
@@ -87,7 +86,7 @@ func (s *Session) Open() (err error) {
}
// Add the version and encoding to the URL
- s.gateway = fmt.Sprintf("%s?v=5&encoding=json", s.gateway)
+ s.gateway = s.gateway + "?v=" + APIVersion + "&encoding=json"
}
header := http.Header{}
@@ -131,6 +130,7 @@ func (s *Session) Open() (err error) {
// lock.
s.listening = make(chan interface{})
go s.listen(s.wsConn, s.listening)
+ s.LastHeartbeatAck = time.Now().UTC()
s.Unlock()
@@ -199,10 +199,13 @@ type helloOp struct {
Trace []string `json:"_trace"`
}
+// FailedHeartbeatAcks is the Number of heartbeat intervals to wait until forcing a connection restart.
+const FailedHeartbeatAcks time.Duration = 5 * time.Millisecond
+
// heartbeat sends regular heartbeats to Discord so it knows the client
// is still connected. If you do not send these heartbeats Discord will
// disconnect the websocket connection after a few seconds.
-func (s *Session) heartbeat(wsConn *websocket.Conn, listening <-chan interface{}, i time.Duration) {
+func (s *Session) heartbeat(wsConn *websocket.Conn, listening <-chan interface{}, heartbeatIntervalMsec time.Duration) {
s.log(LogInformational, "called")
@@ -211,20 +214,26 @@ func (s *Session) heartbeat(wsConn *websocket.Conn, listening <-chan interface{}
}
var err error
- ticker := time.NewTicker(i * time.Millisecond)
+ ticker := time.NewTicker(heartbeatIntervalMsec * time.Millisecond)
defer ticker.Stop()
for {
+ s.RLock()
+ last := s.LastHeartbeatAck
+ s.RUnlock()
sequence := atomic.LoadInt64(s.sequence)
s.log(LogInformational, "sending gateway websocket heartbeat seq %d", sequence)
s.wsMutex.Lock()
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)
- s.Lock()
- s.DataReady = false
- s.Unlock()
+ if err != nil || time.Now().UTC().Sub(last) > (heartbeatIntervalMsec*FailedHeartbeatAcks) {
+ if err != nil {
+ s.log(LogError, "error sending heartbeat to gateway %s, %s", s.gateway, err)
+ } else {
+ s.log(LogError, "haven't gotten a heartbeat ACK in %v, triggering a reconnection", time.Now().UTC().Sub(last))
+ }
+ s.Close()
+ s.reconnect()
return
}
s.Lock()
@@ -241,8 +250,10 @@ func (s *Session) heartbeat(wsConn *websocket.Conn, listening <-chan interface{}
}
type updateStatusData struct {
- IdleSince *int `json:"idle_since"`
- Game *Game `json:"game"`
+ IdleSince *int `json:"since"`
+ Game *Game `json:"game"`
+ AFK bool `json:"afk"`
+ Status string `json:"status"`
}
type updateStatusOp struct {
@@ -265,7 +276,10 @@ func (s *Session) UpdateStreamingStatus(idle int, game string, url string) (err
return ErrWSNotFound
}
- var usd updateStatusData
+ usd := updateStatusData{
+ Status: "online",
+ }
+
if idle > 0 {
usd.IdleSince = &idle
}
@@ -398,7 +412,10 @@ func (s *Session) onEvent(messageType int, message []byte) {
// Reconnect
// Must immediately disconnect from gateway and reconnect to new gateway.
if e.Operation == 7 {
- // TODO
+ s.log(LogInformational, "Closing and reconnecting in response to Op7")
+ s.Close()
+ s.reconnect()
+ return
}
// Invalid Session
@@ -426,6 +443,14 @@ func (s *Session) onEvent(messageType int, message []byte) {
return
}
+ if e.Operation == 11 {
+ s.Lock()
+ s.LastHeartbeatAck = time.Now().UTC()
+ s.Unlock()
+ s.log(LogInformational, "got heartbeat ACK")
+ return
+ }
+
// Do not try to Dispatch a non-Dispatch Message
if e.Operation != 0 {
// But we probably should be doing something with them.
@@ -688,6 +713,13 @@ func (s *Session) reconnect() {
return
}
+ // Certain race conditions can call reconnect() twice. If this happens, we
+ // just break out of the reconnect loop
+ if err == ErrWSAlreadyOpen {
+ s.log(LogInformational, "Websocket already exists, no need to reconnect")
+ return
+ }
+
s.log(LogError, "error reconnecting to gateway, %s", err)
<-time.After(wait * time.Second)
diff --git a/vendor/manifest b/vendor/manifest
index d10ffa30..0861b044 100644
--- a/vendor/manifest
+++ b/vendor/manifest
@@ -61,7 +61,7 @@
"importpath": "github.com/bwmarrin/discordgo",
"repository": "https://github.com/bwmarrin/discordgo",
"vcs": "git",
- "revision": "d420e28024ad527390b43aa7f64e029083e11989",
+ "revision": "2fda7ce223a66a5b70b66987c22c3c94d022ee66",
"branch": "master",
"notests": true
},