Browse Source

Add user_id_or_ids and presences to guild member chunk and improve cache behaviour. (#162)

* request_guild_members update, logging on emitter event exceptions and improved state caching.

* Update the GuildMembersChunk GatewayEvent

* Match presence update's presence handling in guild members chunk

* Remove unnecessary string conversion

* Remove member count tracking.

* Split function

* Remove optional report
staging/v1.0.0
Luke 5 years ago
committed by Andrei Zbikowski
parent
commit
8dbe0421f6
  1. 12
      disco/api/client.py
  2. 15
      disco/gateway/client.py
  3. 6
      disco/gateway/events.py
  4. 14
      disco/state.py
  5. 7
      disco/types/guild.py
  6. 23
      disco/util/emitter.py
  7. 15
      disco/util/functional.py

12
disco/api/client.py

@ -1,4 +1,3 @@
import six
import json
import warnings
@ -7,6 +6,7 @@ from gevent.local import local
from six.moves.urllib.parse import quote
from disco.api.http import Routes, HTTPClient, to_bytes
from disco.util.functional import optional
from disco.util.logging import LoggingClass
from disco.util.sanitize import S
from disco.types.user import User
@ -22,16 +22,6 @@ from disco.types.voice import VoiceRegion
from disco.types.webhook import Webhook
def optional(**kwargs):
"""
Takes a set of keyword arguments, creating a dictionary with only the non-
null values.
:returns: dict
"""
return {k: v for k, v in six.iteritems(kwargs) if v is not None}
def _reason_header(value):
return optional(**{'X-Audit-Log-Reason': quote(to_bytes(value)) if value else None})

15
disco/gateway/client.py

@ -273,7 +273,7 @@ class GatewayClient(LoggingClass):
gevent.spawn(self.connect_and_run)
self.ws_event.wait()
def request_guild_members(self, guild_id_or_ids, query=None, limit=0):
def request_guild_members(self, guild_id_or_ids, query=None, limit=0, presences=False):
"""
Request a batch of Guild members from Discord. Generally this function
can be called when initially loading Guilds to fill the local member state.
@ -281,6 +281,19 @@ class GatewayClient(LoggingClass):
self.send(OPCode.REQUEST_GUILD_MEMBERS, {
# This is simply unfortunate naming on the part of Discord...
'guild_id': guild_id_or_ids,
'limit': limit,
'presences': presences,
'query': query or '',
})
def request_guild_members_by_id(self, guild_id_or_ids, user_id_or_ids, limit=0, presences=False):
"""
Request a batch of Guild members from Discord by their snowflake(s).
"""
self.send(OPCode.REQUEST_GUILD_MEMBERS, {
'guild_id': guild_id_or_ids,
'limit': limit,
'presences': presences,
# This is simply even more unfortunate naming from Discord...
'user_ids': user_id_or_ids,
})

6
disco/gateway/events.py

@ -338,9 +338,15 @@ class GuildMembersChunk(GatewayEvent):
The ID of the guild this member chunk is for.
members : list[:class:`disco.types.guild.GuildMember`]
The chunk of members.
not_found : list[snowflake]
An array of invalid requested guild members.
presences : list[:class:`disco.types.user.Presence`]
An array of requested member presence states.
"""
guild_id = Field(snowflake)
members = ListField(GuildMember)
not_found = ListField(snowflake)
presences = ListField(Presence)
@property
def guild(self):

14
disco/state.py

@ -229,6 +229,11 @@ class State(object):
elif event.channel.is_dm:
self.dms[event.channel.id] = event.channel
self.channels[event.channel.id] = event.channel
for user in six.itervalues(event.channel.recipients):
if user.id not in self.users:
self.users[user.id] = user
else:
event.channel.recipients[user.id] = self.users[user.id]
def on_channel_update(self, event):
if event.channel.id in self.channels:
@ -325,6 +330,15 @@ class State(object):
else:
member.user = self.users[member.id]
if not event.presences:
return
for presence in event.presences:
# TODO: this matches the recursive, hackfix method found in on_presence_update
user = presence.user
user.presence = presence
self.users[user.id].inplace_update(user)
def on_guild_role_create(self, event):
if event.guild_id not in self.guilds:
return

7
disco/types/guild.py

@ -455,8 +455,11 @@ class Guild(SlottedModel, Permissible):
return self.client.api.guilds_roles_modify(self.id, to_snowflake(role), **kwargs)
def request_guild_members(self, query=None, limit=0):
self.client.gw.request_guild_members(self.id, query, limit)
def request_guild_members(self, query=None, limit=0, presences=False):
self.client.gw.request_guild_members(self.id, query, limit, presences)
def request_guild_members_by_id(self, user_id_or_ids, limit=0, presences=False):
self.client.gw.request_guild_members_by_id(self.id, user_id_or_ids, limit, presences)
def sync(self):
warnings.warn(

23
disco/util/emitter.py

@ -5,6 +5,9 @@ from gevent.event import AsyncResult
from gevent.queue import Queue, Full
from disco.util.logging import LoggingClass
class Priority(object):
# BEFORE is the most dangerous priority level. Every event that flows through
# the given emitter instance will be dispatched _sequentially_ to all BEFORE
@ -106,7 +109,7 @@ class EmitterSubscription(object):
self.detach(emitter)
class Emitter(object):
class Emitter(LoggingClass):
def __init__(self):
self.event_handlers = {
k: defaultdict(list) for k in Priority.ALL
@ -117,15 +120,25 @@ class Emitter(object):
for listener in self.event_handlers[Priority.BEFORE].get(name, []):
try:
listener(*args, **kwargs)
except Exception:
pass
except Exception as e:
self.log.warning('BEFORE {} event handler `{}` raised {}: {}'.format(
name,
listener.callback.__name__,
e.__class__.__name__,
e,
))
# Next execute all AFTER handlers sequentially
for listener in self.event_handlers[Priority.AFTER].get(name, []):
try:
listener(*args, **kwargs)
except Exception:
pass
except Exception as e:
self.log.warning('AFTER {} event handler `{}` raised {}: {}'.format(
name,
listener.callback.__name__,
e.__class__.__name__,
e,
))
# Next enqueue all sequential handlers. This just puts stuff into a queue
# without blocking, so we don't have to worry too much

15
disco/util/functional.py

@ -1,4 +1,4 @@
from six.moves import range
import six
NO_MORE_SENTINEL = object()
@ -14,7 +14,7 @@ def take(seq, count):
count : int
The number of elements to take.
"""
for _ in range(count):
for _ in six.moves.range(count):
i = next(seq, NO_MORE_SENTINEL)
if i is NO_MORE_SENTINEL:
return
@ -32,7 +32,7 @@ def chunks(obj, size):
size : int
Size of chunks to split list into.
"""
for i in range(0, len(obj), size):
for i in six.moves.range(0, len(obj), size):
yield obj[i:i + size]
@ -66,3 +66,12 @@ def simple_cached_property(method):
delattr(inst, key)
return property(_getattr, _setattr, _delattr)
def optional(**kwargs):
"""
Takes a set of keyword arguments, creating a dictionary with only the non-
null values.
:returns: dict
"""
return {k: v for k, v in six.iteritems(kwargs) if v is not None}

Loading…
Cancel
Save