Merge remote-tracking branch 'upstream/develop' into 1
This commit is contained in:
commit
5cc3122299
7 changed files with 174 additions and 23 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
12
event.go
12
event.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
47
ratelimit.go
47
ratelimit.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
12
restapi.go
12
restapi.go
|
@ -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.
|
||||||
|
|
64
state.go
64
state.go
|
@ -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
|
||||||
|
|
57
structs.go
57
structs.go
|
@ -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
|
||||||
|
)
|
||||||
|
|
2
wsapi.go
2
wsapi.go
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue