Browse Source

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

pull/162/head
Luke 6 years ago
parent
commit
6a6ec5f334
  1. 12
      disco/api/client.py
  2. 16
      disco/gateway/client.py
  3. 22
      disco/state.py
  4. 4
      disco/types/guild.py
  5. 23
      disco/util/emitter.py
  6. 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
@ -18,16 +18,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})

16
disco/gateway/client.py

@ -9,6 +9,7 @@ from websocket import ABNF
from disco.gateway.packets import OPCode, RECV, SEND
from disco.gateway.events import GatewayEvent
from disco.gateway.encoding import ENCODERS
from disco.util.functional import optional
from disco.util.websocket import Websocket
from disco.util.logging import LoggingClass
from disco.util.limiter import SimpleLimiter
@ -266,14 +267,21 @@ 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, user_id_or_ids=None, presences=None):
"""
Request a batch of Guild members from Discord. Generally this function
can be called when initially loading Guilds to fill the local member state.
When calling this function, if user_id_or_ids is passed then query will be ignored.
"""
self.send(OPCode.REQUEST_GUILD_MEMBERS, {
payload = {
# This is simply unfortunate naming on the part of Discord...
'guild_id': guild_id_or_ids,
'query': query or '',
'limit': limit,
})
}
payload.update(optional(
# This is simply Discord sticking to an unfortunate naming scheme...
user_ids=user_id_or_ids,
query=query or '' if user_id_or_ids is None else None,
presences=presences,
))
self.send(OPCode.REQUEST_GUILD_MEMBERS, payload)

22
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:
@ -285,6 +290,11 @@ class State(object):
if event.member.guild_id not in self.guilds:
return
if (self.guilds[event.member.guild_id].member_count is not UNSET and
# Avoid adding duplicate events to member_count.
event.member.id not in self.guilds[event.member.guild_id].members):
self.guilds[event.member.guild_id].member_count += 1
self.guilds[event.member.guild_id].members[event.member.id] = event.member
def on_guild_member_update(self, event):
@ -303,6 +313,9 @@ class State(object):
if event.user.id not in self.guilds[event.guild_id].members:
return
if self.guilds[event.guild_id].member_count is not UNSET:
self.guilds[event.guild_id].member_count -= 1
del self.guilds[event.guild_id].members[event.user.id]
def on_guild_members_chunk(self, event):
@ -319,6 +332,15 @@ class State(object):
else:
member.user = self.users[member.id]
if not event.presences:
return
for presence in event.presences:
if presence.user.id not in self.users:
self.users[presence.user.id] = presence.user
self.users[presence.user.id].presence = presence
def on_guild_role_create(self, event):
if event.guild_id not in self.guilds:
return

4
disco/types/guild.py

@ -450,8 +450,8 @@ 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, user_id_or_ids=None, presences=None):
self.client.gw.request_guild_members(self.id, query, limit, user_id_or_ids, 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__,
str(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__,
str(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