소스 검색

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

LEGOlord208 7 년 전
부모
커밋
5cc3122299
7개의 변경된 파일174개의 추가작업 그리고 23개의 파일을 삭제
  1. 3 0
      endpoints.go
  2. 10 2
      event.go
  3. 43 4
      ratelimit.go
  4. 11 1
      restapi.go
  5. 49 15
      state.go
  6. 57 0
      structs.go
  7. 1 1
      wsapi.go

+ 3 - 0
endpoints.go

@@ -108,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
 	}

+ 10 - 2
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
 	}

+ 43 - 4
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
 	}

+ 11 - 1
restapi.go

@@ -767,7 +767,7 @@ func (s *Session) GuildMemberDelete(guildID, userID string) (err error) {
 	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.
 // userID    : The ID of a User
 // reason    : The reason for the kick
@@ -1932,6 +1932,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.

+ 49 - 15
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
@@ -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

+ 57 - 0
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
@@ -162,6 +166,7 @@ type Channel struct {
 	Topic                string                 `json:"topic"`
 	Type                 ChannelType            `json:"type"`
 	LastMessageID        string                 `json:"last_message_id"`
+	NSFW                 bool                   `json:"nsfw"`
 	Position             int                    `json:"position"`
 	Bitrate              int                    `json:"bitrate"`
 	Recipients           []*User                `json:"recipient"`
@@ -598,3 +603,55 @@ const (
 		PermissionManageServer |
 		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
+)

+ 1 - 1
wsapi.go

@@ -199,7 +199,7 @@ type helloOp struct {
 	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
 
 // heartbeat sends regular heartbeats to Discord so it knows the client