From 3fd53413deb184c9636bd70bcd3dbe34c291f95d Mon Sep 17 00:00:00 2001 From: jonas747 Date: Fri, 29 Jul 2016 23:13:55 +0200 Subject: [PATCH 1/6] Added userguild struct, and UserGuilds() now returns a userguild --- restapi.go | 2 +- structs.go | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/restapi.go b/restapi.go index bf5065b..b2faac1 100644 --- a/restapi.go +++ b/restapi.go @@ -351,7 +351,7 @@ func (s *Session) UserChannelCreate(recipientID string) (st *Channel, err error) } // UserGuilds returns an array of Guild structures for all guilds. -func (s *Session) UserGuilds() (st []*Guild, err error) { +func (s *Session) UserGuilds() (st []*UserGuild, err error) { body, err := s.Request("GET", EndpointUserGuilds("@me"), nil) if err != nil { diff --git a/structs.go b/structs.go index 19a291f..c9d54db 100644 --- a/structs.go +++ b/structs.go @@ -220,6 +220,15 @@ type Guild struct { Unavailable *bool `json:"unavailable"` } +// A UserGuild holds a brief version of a Guild +type UserGuild struct { + ID string `json:"id"` + Name string `json:"name"` + Icon string `json:"icon"` + Owner bool `json:"owner"` + Permissions int `json:"permissions"` +} + // A GuildParams stores all the data needed to update discord guild settings type GuildParams struct { Name string `json:"name"` From 9c7c9d3fd2650aa0ff95fb11dc1816e2b551c69a Mon Sep 17 00:00:00 2001 From: jonas747 Date: Sun, 25 Sep 2016 21:29:59 +0200 Subject: [PATCH 2/6] Added RequestGuildMembers to request guild members from the gateway --- events.go | 1 + structs.go | 6 ++++++ wsapi.go | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/events.go b/events.go index b1a9b09..49f12ad 100644 --- a/events.go +++ b/events.go @@ -30,6 +30,7 @@ var eventToInterface = map[string]interface{}{ "GUILD_ROLE_DELETE": GuildRoleDelete{}, "GUILD_INTEGRATIONS_UPDATE": GuildIntegrationsUpdate{}, "GUILD_EMOJIS_UPDATE": GuildEmojisUpdate{}, + "GUILD_MEMBERS_CHUNK": GuildMembersChunk{}, "MESSAGE_ACK": MessageAck{}, "MESSAGE_CREATE": MessageCreate{}, "MESSAGE_UPDATE": MessageUpdate{}, diff --git a/structs.go b/structs.go index 7fcf44b..dcf6fa0 100644 --- a/structs.go +++ b/structs.go @@ -412,6 +412,12 @@ type GuildEmojisUpdate struct { Emojis []*Emoji `json:"emojis"` } +// A GuildMembersChunk stores data for the Guild Members Chunk websocket event. +type GuildMembersChunk struct { + GuildID string `json:"guild_id"` + Members []*Member `json:"members"` +} + // A GuildIntegration stores data for a guild integration. type GuildIntegration struct { ID string `json:"id"` diff --git a/wsapi.go b/wsapi.go index a19c384..3bf2c93 100644 --- a/wsapi.go +++ b/wsapi.go @@ -269,6 +269,44 @@ func (s *Session) UpdateStatus(idle int, game string) (err error) { return s.UpdateStreamingStatus(idle, game, "") } +type requestGuildMembersData struct { + GuildID string `json:"guild_id"` + Query string `json:"query"` + Limit int `json:"limit"` +} + +type requestGuildMembersOp struct { + Op int `json:"op"` + Data requestGuildMembersData `json:"d"` +} + +// RequestGuildMembers requests guild members from the gateway +// The gateway responds with GuildMembersChunk events +// guildID : the ID of the guild to request members of +// query : string hat username sarts with, leave empty to return all members +// limit : max number of items to return, or 0 o reques all members matched +func (s *Session) RequestGuildMembers(guildID, query string, limit int) (err error) { + s.log(LogInformational, "called") + + s.RLock() + defer s.RUnlock() + if s.wsConn == nil { + return errors.New("no websocket connection exists") + } + + data := requestGuildMembersData{ + GuildID: guildID, + Query: query, + Limit: limit, + } + + s.wsMutex.Lock() + err = s.wsConn.WriteJSON(requestGuildMembersOp{8, data}) + s.wsMutex.Unlock() + + return +} + // onEvent is the "event handler" for all messages received on the // Discord Gateway API websocket connection. // From 1ecb7458e94c47d177888df0a2769fc2bce1e8f1 Mon Sep 17 00:00:00 2001 From: jonas747 Date: Wed, 28 Sep 2016 06:31:39 +0200 Subject: [PATCH 3/6] Fix typos --- wsapi.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wsapi.go b/wsapi.go index 3bf2c93..835c293 100644 --- a/wsapi.go +++ b/wsapi.go @@ -282,9 +282,9 @@ type requestGuildMembersOp struct { // RequestGuildMembers requests guild members from the gateway // The gateway responds with GuildMembersChunk events -// guildID : the ID of the guild to request members of -// query : string hat username sarts with, leave empty to return all members -// limit : max number of items to return, or 0 o reques all members matched +// guildID : The ID of the guild to request members of +// query : String that username sarts with, leave empty to return all members +// limit : Max number of items to return, or 0 to request all members matched func (s *Session) RequestGuildMembers(guildID, query string, limit int) (err error) { s.log(LogInformational, "called") From c9d0ac84637661dbf2b21bec69a5f611d77f93fe Mon Sep 17 00:00:00 2001 From: jonas747 Date: Wed, 28 Sep 2016 06:43:37 +0200 Subject: [PATCH 4/6] Update comment to UserGuild --- restapi.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/restapi.go b/restapi.go index b2faac1..3dbc55b 100644 --- a/restapi.go +++ b/restapi.go @@ -350,7 +350,7 @@ func (s *Session) UserChannelCreate(recipientID string) (st *Channel, err error) return } -// UserGuilds returns an array of Guild structures for all guilds. +// UserGuilds returns an array of UserGuild structures for all guilds. func (s *Session) UserGuilds() (st []*UserGuild, err error) { body, err := s.Request("GET", EndpointUserGuilds("@me"), nil) From 1dcdf130fdbedeef947605cfe8be5c8f640e9558 Mon Sep 17 00:00:00 2001 From: jonas747 Date: Wed, 28 Sep 2016 07:00:11 +0200 Subject: [PATCH 5/6] Fix Another typo --- wsapi.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wsapi.go b/wsapi.go index 835c293..6e69d95 100644 --- a/wsapi.go +++ b/wsapi.go @@ -283,7 +283,7 @@ type requestGuildMembersOp struct { // RequestGuildMembers requests guild members from the gateway // The gateway responds with GuildMembersChunk events // guildID : The ID of the guild to request members of -// query : String that username sarts with, leave empty to return all members +// query : String that username starts with, leave empty to return all members // limit : Max number of items to return, or 0 to request all members matched func (s *Session) RequestGuildMembers(guildID, query string, limit int) (err error) { s.log(LogInformational, "called") From 2e2e02fc1199061e83dbce28ef2e650f5ebf75e4 Mon Sep 17 00:00:00 2001 From: Chris Rhodes Date: Tue, 27 Sep 2016 22:09:44 -0700 Subject: [PATCH 6/6] Support a very light state in all cases to support b1nzy's upcoming PR (#260) Support a very light state in all cases to support b1nzy's upcoming PR --- discord.go | 1 + state.go | 74 +++++++++++++++++++++++++++++++++--------------------- wsapi.go | 11 ++++++++ 3 files changed, 57 insertions(+), 29 deletions(-) diff --git a/discord.go b/discord.go index 776f143..77bde70 100644 --- a/discord.go +++ b/discord.go @@ -237,6 +237,7 @@ func (s *Session) initialize() { s.AddHandler(s.onResumed) s.AddHandler(s.onVoiceServerUpdate) s.AddHandler(s.onVoiceStateUpdate) + s.AddHandler(s.State.onReady) s.AddHandler(s.State.onInterface) } diff --git a/state.go b/state.go index d2d90f3..aa7997e 100644 --- a/state.go +++ b/state.go @@ -55,33 +55,6 @@ func NewState() *State { } } -// OnReady takes a Ready event and updates all internal state. -func (s *State) OnReady(r *Ready) error { - if s == nil { - return ErrNilState - } - - s.Lock() - defer s.Unlock() - - s.Ready = *r - - for _, g := range s.Guilds { - s.guildMap[g.ID] = g - - for _, c := range g.Channels { - c.GuildID = g.ID - s.channelMap[c.ID] = c - } - } - - for _, c := range s.PrivateChannels { - s.channelMap[c.ID] = c - } - - return nil -} - // GuildAdd adds a guild to the current world state, or // updates it if it already exists. func (s *State) GuildAdd(guild *Guild) error { @@ -619,6 +592,48 @@ func (s *State) Message(channelID, messageID string) (*Message, error) { return nil, errors.New("Message not found.") } +// OnReady takes a Ready event and updates all internal state. +func (s *State) onReady(se *Session, r *Ready) (err error) { + if s == nil { + return ErrNilState + } + + s.Lock() + defer s.Unlock() + + // We must track at least the current user for Voice, even + // 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, + } + + s.Ready = ready + + return nil + } + + s.Ready = *r + + for _, g := range s.Guilds { + s.guildMap[g.ID] = g + + for _, c := range g.Channels { + c.GuildID = g.ID + s.channelMap[c.ID] = c + } + } + + for _, c := range s.PrivateChannels { + s.channelMap[c.ID] = c + } + + return nil +} + // onInterface handles all events related to states. func (s *State) onInterface(se *Session, i interface{}) (err error) { if s == nil { @@ -629,8 +644,6 @@ func (s *State) onInterface(se *Session, i interface{}) (err error) { } switch t := i.(type) { - case *Ready: - err = s.OnReady(t) case *GuildCreate: err = s.GuildAdd(t.Guild) case *GuildUpdate: @@ -702,6 +715,9 @@ func (s *State) onInterface(se *Session, i interface{}) (err error) { // userID : The ID of the user to calculate permissions for. // channelID : The ID of the channel to calculate permission for. func (s *State) UserChannelPermissions(userID, channelID string) (apermissions int, err error) { + if s == nil { + return 0, ErrNilState + } channel, err := s.Channel(channelID) if err != nil { diff --git a/wsapi.go b/wsapi.go index 6e69d95..362509f 100644 --- a/wsapi.go +++ b/wsapi.go @@ -47,6 +47,17 @@ func (s *Session) Open() (err error) { } }() + // A basic state is a hard requirement for Voice. + if s.State == nil { + state := NewState() + state.TrackChannels = false + state.TrackEmojis = false + state.TrackMembers = false + state.TrackRoles = false + state.TrackVoice = false + s.State = state + } + if s.wsConn != nil { err = errors.New("Web socket already opened.") return