Merge remote-tracking branch 'upstream/develop' into 1

This commit is contained in:
LEGOlord208 2017-08-29 07:48:34 +02:00
commit 5cc3122299
No known key found for this signature in database
GPG key ID: 3A818BC1F5617A24
7 changed files with 174 additions and 23 deletions

View file

@ -108,6 +108,9 @@ var (
EndpointWebhook = func(wID string) string { return EndpointWebhooks + wID } EndpointWebhook = func(wID string) string { return EndpointWebhooks + wID }
EndpointWebhookToken = func(wID, token string) string { return EndpointWebhooks + wID + "/" + token } 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 { EndpointMessageReactions = func(cID, mID, eID string) string {
return EndpointChannelMessage(cID, mID) + "/reactions/" + eID return EndpointChannelMessage(cID, mID) + "/reactions/" + eID
} }

View file

@ -156,12 +156,20 @@ func (s *Session) removeEventHandlerInstance(t string, ehi *eventHandlerInstance
// Handles calling permanent and once handlers for an event type. // Handles calling permanent and once handlers for an event type.
func (s *Session) handle(t string, i interface{}) { func (s *Session) handle(t string, i interface{}) {
for _, eh := range s.handlers[t] { 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 { if len(s.onceHandlers[t]) > 0 {
for _, eh := range s.onceHandlers[t] { 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 s.onceHandlers[t] = nil
} }

View file

@ -3,17 +3,26 @@ package discordgo
import ( import (
"net/http" "net/http"
"strconv" "strconv"
"strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "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 // RateLimiter holds all ratelimit buckets
type RateLimiter struct { type RateLimiter struct {
sync.Mutex sync.Mutex
global *int64 global *int64
buckets map[string]*Bucket buckets map[string]*Bucket
globalRateLimit time.Duration globalRateLimit time.Duration
customRateLimits []*customRateLimit
} }
// NewRatelimiter returns a new RateLimiter // NewRatelimiter returns a new RateLimiter
@ -22,6 +31,13 @@ func NewRatelimiter() *RateLimiter {
return &RateLimiter{ return &RateLimiter{
buckets: make(map[string]*Bucket), buckets: make(map[string]*Bucket),
global: new(int64), 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, 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 r.buckets[key] = b
return b return b
} }
@ -76,13 +100,28 @@ type Bucket struct {
limit int limit int
reset time.Time reset time.Time
global *int64 global *int64
lastReset time.Time
customRateLimit *customRateLimit
} }
// Release unlocks the bucket and reads the headers to update the buckets ratelimit info // 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. // and locks up the whole thing in case if there's a global ratelimit.
func (b *Bucket) Release(headers http.Header) error { func (b *Bucket) Release(headers http.Header) error {
defer b.Unlock() 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 { if headers == nil {
return nil return nil
} }

View file

@ -767,7 +767,7 @@ func (s *Session) GuildMemberDelete(guildID, userID string) (err error) {
return s.GuildMemberDeleteWithReason(guildID, userID, "") return s.GuildMemberDeleteWithReason(guildID, userID, "")
} }
// GuildMemberDelete removes the given user from the given guild. // GuildMemberDeleteWithReason removes the given user from the given guild.
// guildID : The ID of a Guild. // guildID : The ID of a Guild.
// userID : The ID of a User // userID : The ID of a User
// reason : The reason for the kick // reason : The reason for the kick
@ -1932,6 +1932,16 @@ func (s *Session) MessageReactionRemove(channelID, messageID, emojiID, userID st
return err 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. // MessageReactions gets all the users reactions for a specific emoji.
// channelID : The channel ID. // channelID : The channel ID.
// messageID : The message ID. // messageID : The message ID.

View file

@ -42,6 +42,7 @@ type State struct {
guildMap map[string]*Guild guildMap map[string]*Guild
channelMap map[string]*Channel channelMap map[string]*Channel
memberMap map[string]map[string]*Member
} }
// NewState creates an empty state. // NewState creates an empty state.
@ -59,9 +60,18 @@ func NewState() *State {
TrackPresences: true, TrackPresences: true,
guildMap: make(map[string]*Guild), guildMap: make(map[string]*Guild),
channelMap: make(map[string]*Channel), 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 // GuildAdd adds a guild to the current world state, or
// updates it if it already exists. // updates it if it already exists.
func (s *State) GuildAdd(guild *Guild) error { func (s *State) GuildAdd(guild *Guild) error {
@ -77,6 +87,14 @@ func (s *State) GuildAdd(guild *Guild) error {
s.channelMap[c.ID] = c 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 { if g, ok := s.guildMap[guild.ID]; ok {
// We are about to replace `g` in the state with `guild`, but first we need to // 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`. // 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() s.Lock()
defer s.Unlock() defer s.Unlock()
for i, m := range guild.Members { members, ok := s.memberMap[member.GuildID]
if m.User.ID == member.User.ID { if !ok {
guild.Members[i] = member return ErrStateNotFound
return nil }
}
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 return nil
} }
@ -296,6 +319,17 @@ func (s *State) MemberRemove(member *Member) error {
s.Lock() s.Lock()
defer s.Unlock() 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 { for i, m := range guild.Members {
if m.User.ID == member.User.ID { if m.User.ID == member.User.ID {
guild.Members = append(guild.Members[:i], guild.Members[i+1:]...) 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 return nil, ErrNilState
} }
guild, err := s.Guild(guildID)
if err != nil {
return nil, err
}
s.RLock() s.RLock()
defer s.RUnlock() defer s.RUnlock()
for _, m := range guild.Members { members, ok := s.memberMap[guildID]
if m.User.ID == userID { if !ok {
return m, nil return nil, ErrStateNotFound
} }
m, ok := members[userID]
if ok {
return m, nil
} }
return nil, ErrStateNotFound return nil, ErrStateNotFound
@ -735,6 +768,7 @@ func (s *State) onReady(se *Session, r *Ready) (err error) {
for _, g := range s.Guilds { for _, g := range s.Guilds {
s.guildMap[g.ID] = g s.guildMap[g.ID] = g
s.createMemberMap(g)
for _, c := range g.Channels { for _, c := range g.Channels {
s.channelMap[c.ID] = c s.channelMap[c.ID] = c

View file

@ -50,6 +50,10 @@ type Session struct {
// active guilds and the members of the guilds. // active guilds and the members of the guilds.
StateEnabled bool 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. // Exposed but should not be modified by User.
// Whether the Data Websocket is ready // Whether the Data Websocket is ready
@ -162,6 +166,7 @@ type Channel struct {
Topic string `json:"topic"` Topic string `json:"topic"`
Type ChannelType `json:"type"` Type ChannelType `json:"type"`
LastMessageID string `json:"last_message_id"` LastMessageID string `json:"last_message_id"`
NSFW bool `json:"nsfw"`
Position int `json:"position"` Position int `json:"position"`
Bitrate int `json:"bitrate"` Bitrate int `json:"bitrate"`
Recipients []*User `json:"recipient"` Recipients []*User `json:"recipient"`
@ -598,3 +603,55 @@ const (
PermissionManageServer | PermissionManageServer |
PermissionAdministrator PermissionAdministrator
) )
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
)

View file

@ -199,7 +199,7 @@ type helloOp struct {
Trace []string `json:"_trace"` Trace []string `json:"_trace"`
} }
// Number of heartbeat intervals to wait until forcing a connection restart. // FailedHeartbeatAcks is the Number of heartbeat intervals to wait until forcing a connection restart.
const FailedHeartbeatAcks time.Duration = 5 * time.Millisecond const FailedHeartbeatAcks time.Duration = 5 * time.Millisecond
// heartbeat sends regular heartbeats to Discord so it knows the client // heartbeat sends regular heartbeats to Discord so it knows the client