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 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})
@ -644,8 +634,8 @@ class APIClient(LoggingClass):
r = self.http(Routes.USERS_ME_CONNECTIONS_LIST)
return Connection.create_map(self.client, r.json())
def invites_get(self, invite):
r = self.http(Routes.INVITES_GET, dict(invite=invite))
def invites_get(self, invite, with_counts=None):
r = self.http(Routes.INVITES_GET, dict(invite=invite), params=optional(with_counts=with_counts))
return Invite.create(self.client, r.json())
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)
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):

20
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,10 @@ class State(object):
if event.member.guild_id not in self.guilds:
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
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:
return
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 +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

@ -514,8 +514,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(

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.guild import Guild
from disco.types.channel import Channel
class InviteTargetUserType(object):
STREAM = 1
class Invite(SlottedModel):
"""
An invite object.
@ -12,30 +16,42 @@ class Invite(SlottedModel):
----------
code : str
The invite code.
inviter : :class:`disco.types.user.User`
The user who created this invite.
guild : :class:`disco.types.guild.Guild`
The guild this invite is for.
channel : :class:`disco.types.channel.Channel`
The channel this invite is for.
max_age : int
The time after this invite's creation at which it expires.
max_uses : int
The maximum number of uses.
target_user : :class:`disco.types.user.User`
The user this invite targets.
target_user_type : int
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
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
Whether this invite only grants temporary membership.
created_at : datetime
When this invite was created.
"""
code = Field(str)
inviter = Field(User)
guild = Field(Guild)
channel = Field(Channel)
max_age = Field(int)
max_uses = Field(int)
target_user = Field(User)
target_user_type = Field(enum(InviteTargetUserType))
approximate_presence_count = Field(int)
approximate_member_count = Field(int)
inviter = Field(User)
uses = Field(int)
max_uses = Field(int)
max_age = Field(int)
temporary = Field(bool)
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 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