diff --git a/gateway/commands.go b/gateway/commands.go index 5b30f3b..0f0a429 100644 --- a/gateway/commands.go +++ b/gateway/commands.go @@ -101,9 +101,12 @@ func (g *Gateway) UpdateStatus(data UpdateStatusData) error { // Undocumented type GuildSubscribeData struct { - GuildID discord.Snowflake `json:"guild_id"` Typing bool `json:"typing"` Activities bool `json:"activities"` + GuildID discord.Snowflake `json:"guild_id"` + + // Channels is not documented. It's used to fetch the right members sidebar. + Channels map[discord.Snowflake][][2]int `json:"channels"` } func (g *Gateway) GuildSubscribe(data GuildSubscribeData) error { diff --git a/gateway/events.go b/gateway/events.go index ef73f97..092f0eb 100644 --- a/gateway/events.go +++ b/gateway/events.go @@ -106,6 +106,51 @@ type ( Presences []discord.Presence `json:"presences,omitempty"` } + // GuildMemberListUpdate is an undocumented event. It's received when the + // client sends over GuildSubscriptions with the Channels field used. + // The State package does not handle this event. + GuildMemberListUpdate struct { + ID string `json:"id"` + GuildID discord.Snowflake `json:"guild_id"` + MemberCount uint64 `json:"member_count"` + OnlineCount uint64 `json:"online_count"` + + // Groups is all the visible role sections. + Groups []GuildMemberListGroup `json:"groups"` + + Ops []GuildMemberListOp `json:"ops"` + } + GuildMemberListGroup struct { + ID string `json:"id"` // either discord.Snowflake Role IDs or "online" + Count uint64 `json:"count"` + } + GuildMemberListOp struct { + // Mysterious string, so far spotted to be [SYNC, INSERT, UPDATE, DELETE]. + Op string `json:"op"` + + // NON-SYNC ONLY + // Only available for Ops that aren't "SYNC". + Index int `json:"index,omitempty"` + Item GuildMemberListOpItem `json:"item,omitempty"` + + // SYNC ONLY + // Range requested in GuildSubscribeData. + Range [2]int `json:"range,omitempty"` + // Items is basically a linear list of roles and members, similarly to + // how the client renders it. No, it's not nested. + Items []GuildMemberListOpItem `json:"items,omitempty"` + } + // GuildMemberListOpItem is an enum. Either of the fields are provided, but + // never both. Refer to (*GuildMemberListUpdate).Ops for more. + GuildMemberListOpItem struct { + Group *GuildMemberListGroup `json:"group,omitempty"` + Member *struct { + discord.Member + HoistedRole string `json:"hoisted_role"` + Presence discord.Presence `json:"presence"` + } `json:"member,omitempty"` + } + GuildRoleCreateEvent struct { GuildID discord.Snowflake `json:"guild_id"` Role discord.Role `json:"role"` diff --git a/gateway/events_map.go b/gateway/events_map.go index 3580096..ea0286d 100644 --- a/gateway/events_map.go +++ b/gateway/events_map.go @@ -34,6 +34,10 @@ var EventCreator = map[string]func() Event{ "GUILD_MEMBER_UPDATE": func() Event { return new(GuildMemberUpdateEvent) }, "GUILD_MEMBERS_CHUNK": func() Event { return new(GuildMembersChunkEvent) }, + "GUILD_MEMBER_LIST_UPDATE": func() Event { + return new(GuildMemberListUpdate) + }, + "GUILD_ROLE_CREATE": func() Event { return new(GuildRoleCreateEvent) }, "GUILD_ROLE_UPDATE": func() Event { return new(GuildRoleUpdateEvent) }, "GUILD_ROLE_DELETE": func() Event { return new(GuildRoleDeleteEvent) }, diff --git a/gateway/gateway.go b/gateway/gateway.go index f0a4f6f..f64dcc9 100644 --- a/gateway/gateway.go +++ b/gateway/gateway.go @@ -27,6 +27,7 @@ const ( Version = "6" Encoding = "json" + // Compress = "zlib-stream" ) var ( @@ -135,6 +136,7 @@ func NewGatewayWithDriver(token string, driver json.Driver) (*Gateway, error) { param := url.Values{} param.Set("v", Version) param.Set("encoding", Encoding) + // param.Set("compress", Compress) // Append the form to the URL URL += "?" + param.Encode() @@ -249,15 +251,15 @@ func (g *Gateway) start() error { // This is where we'll get our events ch := g.WS.Listen() + // Make a new WaitGroup for use in background loops: + g.waitGroup = new(sync.WaitGroup) + // Wait for an OP 10 Hello var hello HelloEvent if _, err := AssertEvent(g, <-ch, HelloOP, &hello); err != nil { return errors.Wrap(err, "Error at Hello") } - // Make a new WaitGroup for use in background loops: - g.waitGroup = new(sync.WaitGroup) - // Send Discord either the Identify packet (if it's a fresh connection), or // a Resume packet (if it's a dead connection). if g.SessionID == "" { @@ -375,8 +377,9 @@ func (g *Gateway) send(lock bool, code OPCode, v interface{}) error { return errors.Wrap(err, "Failed to encode payload") } - ctx, cancel := context.WithTimeout(context.Background(), g.WSTimeout) - defer cancel() + // ctx, cancel := context.WithTimeout(context.Background(), g.WSTimeout) + // defer cancel() + ctx := context.Background() if lock { g.available.RLock() diff --git a/gateway/op.go b/gateway/op.go index 8d08480..563bfc1 100644 --- a/gateway/op.go +++ b/gateway/op.go @@ -13,21 +13,20 @@ import ( type OPCode uint8 const ( - DispatchOP OPCode = iota // recv - HeartbeatOP // send/recv - IdentifyOP // send... - StatusUpdateOP // - VoiceStateUpdateOP // - VoiceServerPingOP // - ResumeOP // - ReconnectOP // recv - RequestGuildMembersOP // send - InvalidSessionOP // recv... - HelloOP - HeartbeatAckOP - _ - CallConnectOP - GuildSubscriptionsOP + DispatchOP OPCode = 0 // recv + HeartbeatOP OPCode = 1 // send/recv + IdentifyOP OPCode = 2 // send... + StatusUpdateOP OPCode = 3 // + VoiceStateUpdateOP OPCode = 4 // + VoiceServerPingOP OPCode = 5 // + ResumeOP OPCode = 6 // + ReconnectOP OPCode = 7 // recv + RequestGuildMembersOP OPCode = 8 // send + InvalidSessionOP OPCode = 9 // recv... + HelloOP OPCode = 10 + HeartbeatAckOP OPCode = 11 + CallConnectOP OPCode = 13 + GuildSubscriptionsOP OPCode = 14 ) type OP struct {