Browse Source

Merge branch 'staging/v1.0.0' into staging/v1.0.0

pull/152/head
Dooley_labs 5 years ago
committed by GitHub
parent
commit
a53dd604c5
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 16
      disco/api/client.py
  2. 15
      disco/gateway/client.py
  3. 6
      disco/gateway/events.py
  4. 20
      disco/state.py
  5. 7
      disco/types/guild.py
  6. 36
      disco/types/invite.py
  7. 23
      disco/util/emitter.py
  8. 15
      disco/util/functional.py

16
disco/api/client.py

@ -1,4 +1,3 @@
import six
import json import json
import warnings import warnings
@ -7,6 +6,7 @@ from gevent.local import local
from six.moves.urllib.parse import quote from six.moves.urllib.parse import quote
from disco.api.http import Routes, HTTPClient, to_bytes from disco.api.http import Routes, HTTPClient, to_bytes
from disco.util.functional import optional
from disco.util.logging import LoggingClass from disco.util.logging import LoggingClass
from disco.util.sanitize import S from disco.util.sanitize import S
from disco.types.user import User from disco.types.user import User
@ -22,16 +22,6 @@ from disco.types.voice import VoiceRegion
from disco.types.webhook import Webhook 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): def _reason_header(value):
return optional(**{'X-Audit-Log-Reason': quote(to_bytes(value)) if value else None}) return optional(**{'X-Audit-Log-Reason': quote(to_bytes(value)) if value else None})
@ -644,8 +634,8 @@ class APIClient(LoggingClass):
r = self.http(Routes.USERS_ME_CONNECTIONS_LIST) r = self.http(Routes.USERS_ME_CONNECTIONS_LIST)
return Connection.create_map(self.client, r.json()) return Connection.create_map(self.client, r.json())
def invites_get(self, invite): def invites_get(self, invite, with_counts=None):
r = self.http(Routes.INVITES_GET, dict(invite=invite)) r = self.http(Routes.INVITES_GET, dict(invite=invite), params=optional(with_counts=with_counts))
return Invite.create(self.client, r.json()) return Invite.create(self.client, r.json())
def invites_delete(self, invite, reason=None): def invites_delete(self, invite, reason=None):

15
disco/gateway/client.py

@ -273,7 +273,7 @@ class GatewayClient(LoggingClass):
gevent.spawn(self.connect_and_run) gevent.spawn(self.connect_and_run)
self.ws_event.wait() 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 Request a batch of Guild members from Discord. Generally this function
can be called when initially loading Guilds to fill the local member state. 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, { self.send(OPCode.REQUEST_GUILD_MEMBERS, {
# This is simply unfortunate naming on the part of Discord... # This is simply unfortunate naming on the part of Discord...
'guild_id': guild_id_or_ids, 'guild_id': guild_id_or_ids,
'limit': limit,
'presences': presences,
'query': query or '', '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, '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. The ID of the guild this member chunk is for.
members : list[:class:`disco.types.guild.GuildMember`] members : list[:class:`disco.types.guild.GuildMember`]
The chunk of members. 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) guild_id = Field(snowflake)
members = ListField(GuildMember) members = ListField(GuildMember)
not_found = ListField(snowflake)
presences = ListField(Presence)
@property @property
def guild(self): def guild(self):

20
disco/state.py

@ -229,6 +229,11 @@ class State(object):
elif event.channel.is_dm: elif event.channel.is_dm:
self.dms[event.channel.id] = event.channel self.dms[event.channel.id] = event.channel
self.channels[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): def on_channel_update(self, event):
if event.channel.id in self.channels: if event.channel.id in self.channels:
@ -285,6 +290,10 @@ class State(object):
if event.member.guild_id not in self.guilds: if event.member.guild_id not in self.guilds:
return return
# Avoid adding duplicate events to member_count.
if 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 self.guilds[event.member.guild_id].members[event.member.id] = event.member
def on_guild_member_update(self, event): def on_guild_member_update(self, event):
@ -303,6 +312,8 @@ class State(object):
if event.user.id not in self.guilds[event.guild_id].members: if event.user.id not in self.guilds[event.guild_id].members:
return return
self.guilds[event.guild_id].member_count -= 1
del self.guilds[event.guild_id].members[event.user.id] del self.guilds[event.guild_id].members[event.user.id]
def on_guild_members_chunk(self, event): def on_guild_members_chunk(self, event):
@ -319,6 +330,15 @@ class State(object):
else: else:
member.user = self.users[member.id] 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): def on_guild_role_create(self, event):
if event.guild_id not in self.guilds: if event.guild_id not in self.guilds:
return return

7
disco/types/guild.py

@ -514,8 +514,11 @@ class Guild(SlottedModel, Permissible):
return self.client.api.guilds_roles_modify(self.id, to_snowflake(role), **kwargs) return self.client.api.guilds_roles_modify(self.id, to_snowflake(role), **kwargs)
def request_guild_members(self, query=None, limit=0): def request_guild_members(self, query=None, limit=0, presences=False):
self.client.gw.request_guild_members(self.id, query, limit) 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): def sync(self):
warnings.warn( warnings.warn(

36
disco/types/invite.py

@ -1,9 +1,13 @@
from disco.types.base import SlottedModel, Field, datetime from disco.types.base import SlottedModel, Field, datetime, enum
from disco.types.user import User from disco.types.user import User
from disco.types.guild import Guild from disco.types.guild import Guild
from disco.types.channel import Channel from disco.types.channel import Channel
class InviteTargetUserType(object):
STREAM = 1
class Invite(SlottedModel): class Invite(SlottedModel):
""" """
An invite object. An invite object.
@ -12,30 +16,42 @@ class Invite(SlottedModel):
---------- ----------
code : str code : str
The invite code. The invite code.
inviter : :class:`disco.types.user.User`
The user who created this invite.
guild : :class:`disco.types.guild.Guild` guild : :class:`disco.types.guild.Guild`
The guild this invite is for. The guild this invite is for.
channel : :class:`disco.types.channel.Channel` channel : :class:`disco.types.channel.Channel`
The channel this invite is for. The channel this invite is for.
max_age : int target_user : :class:`disco.types.user.User`
The time after this invite's creation at which it expires. The user this invite targets.
max_uses : int target_user_type : int
The maximum number of uses. The type of user target for this invite.
approximate_presence_count : int
The approximate count of online members.
approximate_member_count : int
The approximate count of total members.
inviter : :class:`disco.types.user.User`
The user who created this invite.
uses : int uses : int
The current number of times the invite was used. The current number of times the invite was used.
max_uses : int
The maximum number of uses.
max_age : int
The time after this invite's creation at which it expires.
temporary : bool temporary : bool
Whether this invite only grants temporary membership. Whether this invite only grants temporary membership.
created_at : datetime created_at : datetime
When this invite was created. When this invite was created.
""" """
code = Field(str) code = Field(str)
inviter = Field(User)
guild = Field(Guild) guild = Field(Guild)
channel = Field(Channel) channel = Field(Channel)
max_age = Field(int) target_user = Field(User)
max_uses = Field(int) target_user_type = Field(enum(InviteTargetUserType))
approximate_presence_count = Field(int)
approximate_member_count = Field(int)
inviter = Field(User)
uses = Field(int) uses = Field(int)
max_uses = Field(int)
max_age = Field(int)
temporary = Field(bool) temporary = Field(bool)
created_at = Field(datetime) created_at = Field(datetime)

23
disco/util/emitter.py

@ -5,6 +5,9 @@ from gevent.event import AsyncResult
from gevent.queue import Queue, Full from gevent.queue import Queue, Full
from disco.util.logging import LoggingClass
class Priority(object): class Priority(object):
# BEFORE is the most dangerous priority level. Every event that flows through # BEFORE is the most dangerous priority level. Every event that flows through
# the given emitter instance will be dispatched _sequentially_ to all BEFORE # the given emitter instance will be dispatched _sequentially_ to all BEFORE
@ -106,7 +109,7 @@ class EmitterSubscription(object):
self.detach(emitter) self.detach(emitter)
class Emitter(object): class Emitter(LoggingClass):
def __init__(self): def __init__(self):
self.event_handlers = { self.event_handlers = {
k: defaultdict(list) for k in Priority.ALL 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, []): for listener in self.event_handlers[Priority.BEFORE].get(name, []):
try: try:
listener(*args, **kwargs) listener(*args, **kwargs)
except Exception: except Exception as e:
pass self.log.warning('BEFORE {} event handler `{}` raised {}: {}'.format(
name,
listener.callback.__name__,
e.__class__.__name__,
e,
))
# Next execute all AFTER handlers sequentially # Next execute all AFTER handlers sequentially
for listener in self.event_handlers[Priority.AFTER].get(name, []): for listener in self.event_handlers[Priority.AFTER].get(name, []):
try: try:
listener(*args, **kwargs) listener(*args, **kwargs)
except Exception: except Exception as e:
pass 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 # Next enqueue all sequential handlers. This just puts stuff into a queue
# without blocking, so we don't have to worry too much # 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() NO_MORE_SENTINEL = object()
@ -14,7 +14,7 @@ def take(seq, count):
count : int count : int
The number of elements to take. The number of elements to take.
""" """
for _ in range(count): for _ in six.moves.range(count):
i = next(seq, NO_MORE_SENTINEL) i = next(seq, NO_MORE_SENTINEL)
if i is NO_MORE_SENTINEL: if i is NO_MORE_SENTINEL:
return return
@ -32,7 +32,7 @@ def chunks(obj, size):
size : int size : int
Size of chunks to split list into. 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] yield obj[i:i + size]
@ -66,3 +66,12 @@ def simple_cached_property(method):
delattr(inst, key) delattr(inst, key)
return property(_getattr, _setattr, _delattr) 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