Drop support for Python 3.4 and make minimum version 3.5.2.
This commit is contained in:
parent
7eb918b19e
commit
f25091efe1
@ -87,14 +87,12 @@ Quick Example
|
||||
client = MyClient()
|
||||
client.run('token')
|
||||
|
||||
Note that in Python 3.4 you use ``@asyncio.coroutine`` instead of ``async def`` and ``yield from`` instead of ``await``.
|
||||
|
||||
You can find examples in the examples directory.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
* Python 3.4.2+
|
||||
* Python 3.5.2+
|
||||
* ``aiohttp`` library
|
||||
* ``websockets`` library
|
||||
* ``PyNaCl`` library (optional, for voice only)
|
||||
|
@ -188,8 +188,7 @@ class GuildChannel:
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@asyncio.coroutine
|
||||
def _move(self, position, parent_id=None, lock_permissions=False, *, reason):
|
||||
async def _move(self, position, parent_id=None, lock_permissions=False, *, reason):
|
||||
if position < 0:
|
||||
raise InvalidArgument('Channel position cannot be less than 0.')
|
||||
|
||||
@ -219,13 +218,12 @@ class GuildChannel:
|
||||
d.update(parent_id=parent_id, lock_permissions=lock_permissions)
|
||||
payload.append(d)
|
||||
|
||||
yield from http.bulk_channel_update(self.guild.id, payload, reason=reason)
|
||||
await http.bulk_channel_update(self.guild.id, payload, reason=reason)
|
||||
self.position = position
|
||||
if parent_id is not _undefined:
|
||||
self.category_id = int(parent_id) if parent_id else None
|
||||
|
||||
@asyncio.coroutine
|
||||
def _edit(self, options, reason):
|
||||
async def _edit(self, options, reason):
|
||||
try:
|
||||
parent = options.pop('category')
|
||||
except KeyError:
|
||||
@ -249,10 +247,10 @@ class GuildChannel:
|
||||
category = self.guild.get_channel(self.category_id)
|
||||
options['permission_overwrites'] = [c._asdict() for c in category._overwrites]
|
||||
else:
|
||||
yield from self._move(position, parent_id=parent_id, lock_permissions=lock_permissions, reason=reason)
|
||||
await self._move(position, parent_id=parent_id, lock_permissions=lock_permissions, reason=reason)
|
||||
|
||||
if options:
|
||||
data = yield from self._state.http.edit_channel(self.id, reason=reason, **options)
|
||||
data = await self._state.http.edit_channel(self.id, reason=reason, **options)
|
||||
self._update(self.guild, data)
|
||||
|
||||
def _fill_overwrites(self, data):
|
||||
@ -466,8 +464,7 @@ class GuildChannel:
|
||||
|
||||
return base
|
||||
|
||||
@asyncio.coroutine
|
||||
def delete(self, *, reason=None):
|
||||
async def delete(self, *, reason=None):
|
||||
"""|coro|
|
||||
|
||||
Deletes the channel.
|
||||
@ -489,10 +486,9 @@ class GuildChannel:
|
||||
HTTPException
|
||||
Deleting the channel failed.
|
||||
"""
|
||||
yield from self._state.http.delete_channel(self.id, reason=reason)
|
||||
await self._state.http.delete_channel(self.id, reason=reason)
|
||||
|
||||
@asyncio.coroutine
|
||||
def set_permissions(self, target, *, overwrite=_undefined, reason=None, **permissions):
|
||||
async def set_permissions(self, target, *, overwrite=_undefined, reason=None, **permissions):
|
||||
"""|coro|
|
||||
|
||||
Sets the channel specific permission overwrites for a target in the
|
||||
@ -579,15 +575,14 @@ class GuildChannel:
|
||||
# TODO: wait for event
|
||||
|
||||
if overwrite is None:
|
||||
yield from http.delete_channel_permissions(self.id, target.id, reason=reason)
|
||||
await http.delete_channel_permissions(self.id, target.id, reason=reason)
|
||||
elif isinstance(overwrite, PermissionOverwrite):
|
||||
(allow, deny) = overwrite.pair()
|
||||
yield from http.edit_channel_permissions(self.id, target.id, allow.value, deny.value, perm_type, reason=reason)
|
||||
await http.edit_channel_permissions(self.id, target.id, allow.value, deny.value, perm_type, reason=reason)
|
||||
else:
|
||||
raise InvalidArgument('Invalid overwrite type provided.')
|
||||
|
||||
@asyncio.coroutine
|
||||
def create_invite(self, *, reason=None, **fields):
|
||||
async def create_invite(self, *, reason=None, **fields):
|
||||
"""|coro|
|
||||
|
||||
Creates an instant invite.
|
||||
@ -624,11 +619,10 @@ class GuildChannel:
|
||||
The invite that was created.
|
||||
"""
|
||||
|
||||
data = yield from self._state.http.create_invite(self.id, reason=reason, **fields)
|
||||
data = await self._state.http.create_invite(self.id, reason=reason, **fields)
|
||||
return Invite.from_incomplete(data=data, state=self._state)
|
||||
|
||||
@asyncio.coroutine
|
||||
def invites(self):
|
||||
async def invites(self):
|
||||
"""|coro|
|
||||
|
||||
Returns a list of all active instant invites from this channel.
|
||||
@ -649,7 +643,7 @@ class GuildChannel:
|
||||
"""
|
||||
|
||||
state = self._state
|
||||
data = yield from state.http.invites_from_channel(self.id)
|
||||
data = await state.http.invites_from_channel(self.id)
|
||||
result = []
|
||||
|
||||
for invite in data:
|
||||
@ -676,13 +670,11 @@ class Messageable(metaclass=abc.ABCMeta):
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
@asyncio.coroutine
|
||||
@abc.abstractmethod
|
||||
def _get_channel(self):
|
||||
async def _get_channel(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@asyncio.coroutine
|
||||
def send(self, content=None, *, tts=False, embed=None, file=None, files=None, delete_after=None, nonce=None):
|
||||
async def send(self, content=None, *, tts=False, embed=None, file=None, files=None, delete_after=None, nonce=None):
|
||||
"""|coro|
|
||||
|
||||
Sends a message to the destination with the content given.
|
||||
@ -735,7 +727,7 @@ class Messageable(metaclass=abc.ABCMeta):
|
||||
The message that was sent.
|
||||
"""
|
||||
|
||||
channel = yield from self._get_channel()
|
||||
channel = await self._get_channel()
|
||||
state = self._state
|
||||
content = str(content) if content is not None else None
|
||||
if embed is not None:
|
||||
@ -749,7 +741,7 @@ class Messageable(metaclass=abc.ABCMeta):
|
||||
raise InvalidArgument('file parameter must be File')
|
||||
|
||||
try:
|
||||
data = yield from state.http.send_files(channel.id, files=[(file.open_file(), file.filename)],
|
||||
data = await state.http.send_files(channel.id, files=[(file.open_file(), file.filename)],
|
||||
content=content, tts=tts, embed=embed, nonce=nonce)
|
||||
finally:
|
||||
file.close()
|
||||
@ -760,28 +752,26 @@ class Messageable(metaclass=abc.ABCMeta):
|
||||
|
||||
try:
|
||||
param = [(f.open_file(), f.filename) for f in files]
|
||||
data = yield from state.http.send_files(channel.id, files=param, content=content, tts=tts,
|
||||
data = await state.http.send_files(channel.id, files=param, content=content, tts=tts,
|
||||
embed=embed, nonce=nonce)
|
||||
finally:
|
||||
for f in files:
|
||||
f.close()
|
||||
else:
|
||||
data = yield from state.http.send_message(channel.id, content, tts=tts, embed=embed, nonce=nonce)
|
||||
data = await state.http.send_message(channel.id, content, tts=tts, embed=embed, nonce=nonce)
|
||||
|
||||
ret = state.create_message(channel=channel, data=data)
|
||||
if delete_after is not None:
|
||||
@asyncio.coroutine
|
||||
def delete():
|
||||
yield from asyncio.sleep(delete_after, loop=state.loop)
|
||||
async def delete():
|
||||
await asyncio.sleep(delete_after, loop=state.loop)
|
||||
try:
|
||||
yield from ret.delete()
|
||||
await ret.delete()
|
||||
except:
|
||||
pass
|
||||
compat.create_task(delete(), loop=state.loop)
|
||||
asyncio.ensure_future(delete(), loop=state.loop)
|
||||
return ret
|
||||
|
||||
@asyncio.coroutine
|
||||
def trigger_typing(self):
|
||||
async def trigger_typing(self):
|
||||
"""|coro|
|
||||
|
||||
Triggers a *typing* indicator to the destination.
|
||||
@ -789,8 +779,8 @@ class Messageable(metaclass=abc.ABCMeta):
|
||||
*Typing* indicator will go away after 10 seconds, or after a message is sent.
|
||||
"""
|
||||
|
||||
channel = yield from self._get_channel()
|
||||
yield from self._state.http.send_typing(channel.id)
|
||||
channel = await self._get_channel()
|
||||
await self._state.http.send_typing(channel.id)
|
||||
|
||||
def typing(self):
|
||||
"""Returns a context manager that allows you to type for an indefinite period of time.
|
||||
@ -811,8 +801,7 @@ class Messageable(metaclass=abc.ABCMeta):
|
||||
"""
|
||||
return Typing(self)
|
||||
|
||||
@asyncio.coroutine
|
||||
def get_message(self, id):
|
||||
async def get_message(self, id):
|
||||
"""|coro|
|
||||
|
||||
Retrieves a single :class:`Message` from the destination.
|
||||
@ -839,12 +828,11 @@ class Messageable(metaclass=abc.ABCMeta):
|
||||
Retrieving the message failed.
|
||||
"""
|
||||
|
||||
channel = yield from self._get_channel()
|
||||
data = yield from self._state.http.get_message(channel.id, id)
|
||||
channel = await self._get_channel()
|
||||
data = await self._state.http.get_message(channel.id, id)
|
||||
return self._state.create_message(channel=channel, data=data)
|
||||
|
||||
@asyncio.coroutine
|
||||
def pins(self):
|
||||
async def pins(self):
|
||||
"""|coro|
|
||||
|
||||
Returns a :class:`list` of :class:`Message` that are currently pinned.
|
||||
@ -855,9 +843,9 @@ class Messageable(metaclass=abc.ABCMeta):
|
||||
Retrieving the pinned messages failed.
|
||||
"""
|
||||
|
||||
channel = yield from self._get_channel()
|
||||
channel = await self._get_channel()
|
||||
state = self._state
|
||||
data = yield from state.http.pins_from(channel.id)
|
||||
data = await state.http.pins_from(channel.id)
|
||||
return [state.create_message(channel=channel, data=m) for m in data]
|
||||
|
||||
def history(self, *, limit=100, before=None, after=None, around=None, reverse=None):
|
||||
@ -916,19 +904,6 @@ class Messageable(metaclass=abc.ABCMeta):
|
||||
|
||||
messages = await channel.history(limit=123).flatten()
|
||||
# messages is now a list of Message...
|
||||
|
||||
Python 3.4 Usage ::
|
||||
|
||||
count = 0
|
||||
iterator = channel.history(limit=200)
|
||||
while True:
|
||||
try:
|
||||
message = yield from iterator.next()
|
||||
except discord.NoMoreItems:
|
||||
break
|
||||
else:
|
||||
if message.author == client.user:
|
||||
counter += 1
|
||||
"""
|
||||
return HistoryIterator(self, limit=limit, before=before, after=after, around=around, reverse=reverse)
|
||||
|
||||
@ -951,8 +926,7 @@ class Connectable(metaclass=abc.ABCMeta):
|
||||
def _get_voice_state_pair(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@asyncio.coroutine
|
||||
def connect(self, *, timeout=60.0, reconnect=True):
|
||||
async def connect(self, *, timeout=60.0, reconnect=True):
|
||||
"""|coro|
|
||||
|
||||
Connects to voice and creates a :class:`VoiceClient` to establish
|
||||
@ -991,10 +965,10 @@ class Connectable(metaclass=abc.ABCMeta):
|
||||
state._add_voice_client(key_id, voice)
|
||||
|
||||
try:
|
||||
yield from voice.connect(reconnect=reconnect)
|
||||
await voice.connect(reconnect=reconnect)
|
||||
except asyncio.TimeoutError as e:
|
||||
try:
|
||||
yield from voice.disconnect(force=True)
|
||||
await voice.disconnect(force=True)
|
||||
except:
|
||||
# we don't care if disconnect failed because connection failed
|
||||
pass
|
||||
|
@ -37,10 +37,9 @@ import asyncio
|
||||
|
||||
__all__ = ('TextChannel', 'VoiceChannel', 'DMChannel', 'CategoryChannel', 'GroupChannel', '_channel_factory')
|
||||
|
||||
@asyncio.coroutine
|
||||
def _single_delete_strategy(messages):
|
||||
async def _single_delete_strategy(messages):
|
||||
for m in messages:
|
||||
yield from m.delete()
|
||||
await m.delete()
|
||||
|
||||
class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
|
||||
"""Represents a Discord guild text channel.
|
||||
@ -100,8 +99,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
|
||||
self.nsfw = data.get('nsfw', False)
|
||||
self._fill_overwrites(data)
|
||||
|
||||
@asyncio.coroutine
|
||||
def _get_channel(self):
|
||||
async def _get_channel(self):
|
||||
return self
|
||||
|
||||
def permissions_for(self, member):
|
||||
@ -124,8 +122,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
|
||||
n = self.name
|
||||
return self.nsfw or n == 'nsfw' or n[:5] == 'nsfw-'
|
||||
|
||||
@asyncio.coroutine
|
||||
def edit(self, *, reason=None, **options):
|
||||
async def edit(self, *, reason=None, **options):
|
||||
"""|coro|
|
||||
|
||||
Edits the channel.
|
||||
@ -161,10 +158,9 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
|
||||
HTTPException
|
||||
Editing the channel failed.
|
||||
"""
|
||||
yield from self._edit(options, reason=reason)
|
||||
await self._edit(options, reason=reason)
|
||||
|
||||
@asyncio.coroutine
|
||||
def delete_messages(self, messages):
|
||||
async def delete_messages(self, messages):
|
||||
"""|coro|
|
||||
|
||||
Deletes a list of messages. This is similar to :meth:`Message.delete`
|
||||
@ -205,17 +201,16 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
|
||||
|
||||
if len(messages) == 1:
|
||||
message_id = messages[0].id
|
||||
yield from self._state.http.delete_message(self.id, message_id)
|
||||
await self._state.http.delete_message(self.id, message_id)
|
||||
return
|
||||
|
||||
if len(messages) > 100:
|
||||
raise ClientException('Can only bulk delete messages up to 100 messages')
|
||||
|
||||
message_ids = [m.id for m in messages]
|
||||
yield from self._state.http.delete_messages(self.id, message_ids)
|
||||
await self._state.http.delete_messages(self.id, message_ids)
|
||||
|
||||
@asyncio.coroutine
|
||||
def purge(self, *, limit=100, check=None, before=None, after=None, around=None, reverse=False, bulk=True):
|
||||
async def purge(self, *, limit=100, check=None, before=None, after=None, around=None, reverse=False, bulk=True):
|
||||
"""|coro|
|
||||
|
||||
Purges a list of messages that meet the criteria given by the predicate
|
||||
@ -289,34 +284,34 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
|
||||
|
||||
while True:
|
||||
try:
|
||||
msg = yield from iterator.next()
|
||||
msg = await iterator.next()
|
||||
except NoMoreItems:
|
||||
# no more messages to poll
|
||||
if count >= 2:
|
||||
# more than 2 messages -> bulk delete
|
||||
to_delete = ret[-count:]
|
||||
yield from strategy(to_delete)
|
||||
await strategy(to_delete)
|
||||
elif count == 1:
|
||||
# delete a single message
|
||||
yield from ret[-1].delete()
|
||||
await ret[-1].delete()
|
||||
|
||||
return ret
|
||||
else:
|
||||
if count == 100:
|
||||
# we've reached a full 'queue'
|
||||
to_delete = ret[-100:]
|
||||
yield from strategy(to_delete)
|
||||
await strategy(to_delete)
|
||||
count = 0
|
||||
yield from asyncio.sleep(1)
|
||||
await asyncio.sleep(1)
|
||||
|
||||
if check(msg):
|
||||
if msg.id < minimum_time:
|
||||
# older than 14 days old
|
||||
if count == 1:
|
||||
yield from ret[-1].delete()
|
||||
await ret[-1].delete()
|
||||
elif count >= 2:
|
||||
to_delete = ret[-count:]
|
||||
yield from strategy(to_delete)
|
||||
await strategy(to_delete)
|
||||
|
||||
count = 0
|
||||
strategy = _single_delete_strategy
|
||||
@ -324,8 +319,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
|
||||
count += 1
|
||||
ret.append(msg)
|
||||
|
||||
@asyncio.coroutine
|
||||
def webhooks(self):
|
||||
async def webhooks(self):
|
||||
"""|coro|
|
||||
|
||||
Gets the list of webhooks from this channel.
|
||||
@ -343,11 +337,10 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
|
||||
The webhooks for this channel.
|
||||
"""
|
||||
|
||||
data = yield from self._state.http.channel_webhooks(self.id)
|
||||
data = await self._state.http.channel_webhooks(self.id)
|
||||
return [Webhook.from_state(d, state=self._state) for d in data]
|
||||
|
||||
@asyncio.coroutine
|
||||
def create_webhook(self, *, name=None, avatar=None):
|
||||
async def create_webhook(self, *, name=None, avatar=None):
|
||||
"""|coro|
|
||||
|
||||
Creates a webhook for this channel.
|
||||
@ -381,7 +374,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
|
||||
if name is not None:
|
||||
name = str(name)
|
||||
|
||||
data = yield from self._state.http.create_webhook(self.id, name=name, avatar=avatar)
|
||||
data = await self._state.http.create_webhook(self.id, name=name, avatar=avatar)
|
||||
return Webhook.from_state(data, state=self._state)
|
||||
|
||||
class VoiceChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable):
|
||||
@ -461,8 +454,7 @@ class VoiceChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable):
|
||||
ret.append(member)
|
||||
return ret
|
||||
|
||||
@asyncio.coroutine
|
||||
def edit(self, *, reason=None, **options):
|
||||
async def edit(self, *, reason=None, **options):
|
||||
"""|coro|
|
||||
|
||||
Edits the channel.
|
||||
@ -497,7 +489,7 @@ class VoiceChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable):
|
||||
Editing the channel failed.
|
||||
"""
|
||||
|
||||
yield from self._edit(options, reason=reason)
|
||||
await self._edit(options, reason=reason)
|
||||
|
||||
class CategoryChannel(discord.abc.GuildChannel, Hashable):
|
||||
"""Represents a Discord channel category.
|
||||
@ -558,8 +550,7 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable):
|
||||
n = self.name
|
||||
return self.nsfw or n == 'nsfw' or n[:5] == 'nsfw-'
|
||||
|
||||
@asyncio.coroutine
|
||||
def edit(self, *, reason=None, **options):
|
||||
async def edit(self, *, reason=None, **options):
|
||||
"""|coro|
|
||||
|
||||
Edits the channel.
|
||||
@ -593,11 +584,11 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable):
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
yield from self._move(position, reason=reason)
|
||||
await self._move(position, reason=reason)
|
||||
self.position = position
|
||||
|
||||
if options:
|
||||
data = yield from self._state.http.edit_channel(self.id, reason=reason, **options)
|
||||
data = await self._state.http.edit_channel(self.id, reason=reason, **options)
|
||||
self._update(self.guild, data)
|
||||
|
||||
@property
|
||||
@ -652,8 +643,7 @@ class DMChannel(discord.abc.Messageable, Hashable):
|
||||
self.me = me
|
||||
self.id = int(data['id'])
|
||||
|
||||
@asyncio.coroutine
|
||||
def _get_channel(self):
|
||||
async def _get_channel(self):
|
||||
return self
|
||||
|
||||
def __str__(self):
|
||||
@ -756,8 +746,7 @@ class GroupChannel(discord.abc.Messageable, Hashable):
|
||||
else:
|
||||
self.owner = utils.find(lambda u: u.id == owner_id, self.recipients)
|
||||
|
||||
@asyncio.coroutine
|
||||
def _get_channel(self):
|
||||
async def _get_channel(self):
|
||||
return self
|
||||
|
||||
def __str__(self):
|
||||
@ -820,8 +809,7 @@ class GroupChannel(discord.abc.Messageable, Hashable):
|
||||
|
||||
return base
|
||||
|
||||
@asyncio.coroutine
|
||||
def add_recipients(self, *recipients):
|
||||
async def add_recipients(self, *recipients):
|
||||
"""|coro|
|
||||
|
||||
Adds recipients to this group.
|
||||
@ -846,10 +834,9 @@ class GroupChannel(discord.abc.Messageable, Hashable):
|
||||
|
||||
req = self._state.http.add_group_recipient
|
||||
for recipient in recipients:
|
||||
yield from req(self.id, recipient.id)
|
||||
await req(self.id, recipient.id)
|
||||
|
||||
@asyncio.coroutine
|
||||
def remove_recipients(self, *recipients):
|
||||
async def remove_recipients(self, *recipients):
|
||||
"""|coro|
|
||||
|
||||
Removes recipients from this group.
|
||||
@ -869,10 +856,9 @@ class GroupChannel(discord.abc.Messageable, Hashable):
|
||||
|
||||
req = self._state.http.remove_group_recipient
|
||||
for recipient in recipients:
|
||||
yield from req(self.id, recipient.id)
|
||||
await req(self.id, recipient.id)
|
||||
|
||||
@asyncio.coroutine
|
||||
def edit(self, **fields):
|
||||
async def edit(self, **fields):
|
||||
"""|coro|
|
||||
|
||||
Edits the group.
|
||||
@ -900,11 +886,10 @@ class GroupChannel(discord.abc.Messageable, Hashable):
|
||||
if icon_bytes is not None:
|
||||
fields['icon'] = utils._bytes_to_base64_data(icon_bytes)
|
||||
|
||||
data = yield from self._state.http.edit_group(self.id, **fields)
|
||||
data = await self._state.http.edit_group(self.id, **fields)
|
||||
self._update_group(data)
|
||||
|
||||
@asyncio.coroutine
|
||||
def leave(self):
|
||||
async def leave(self):
|
||||
"""|coro|
|
||||
|
||||
Leave the group.
|
||||
@ -917,7 +902,7 @@ class GroupChannel(discord.abc.Messageable, Hashable):
|
||||
Leaving the group failed.
|
||||
"""
|
||||
|
||||
yield from self._state.http.leave_group(self.id)
|
||||
await self._state.http.leave_group(self.id)
|
||||
|
||||
def _channel_factory(channel_type):
|
||||
value = try_enum(ChannelType, channel_type)
|
||||
|
@ -48,7 +48,6 @@ import sys, re
|
||||
import signal
|
||||
from collections import namedtuple
|
||||
|
||||
PY35 = sys.version_info >= (3, 5)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
AppInfo = namedtuple('AppInfo', 'id name description icon owner')
|
||||
@ -139,12 +138,10 @@ class Client:
|
||||
|
||||
# internals
|
||||
|
||||
@asyncio.coroutine
|
||||
def _syncer(self, guilds):
|
||||
yield from self.ws.request_sync(guilds)
|
||||
async def _syncer(self, guilds):
|
||||
await self.ws.request_sync(guilds)
|
||||
|
||||
@asyncio.coroutine
|
||||
def _chunker(self, guild):
|
||||
async def _chunker(self, guild):
|
||||
try:
|
||||
guild_id = guild.id
|
||||
except AttributeError:
|
||||
@ -159,7 +156,7 @@ class Client:
|
||||
}
|
||||
}
|
||||
|
||||
yield from self.ws.send_as_json(payload)
|
||||
await self.ws.send_as_json(payload)
|
||||
|
||||
def handle_ready(self):
|
||||
self._ready.set()
|
||||
@ -218,15 +215,14 @@ class Client:
|
||||
""":obj:`bool`: Specifies if the client's internal cache is ready for use."""
|
||||
return self._ready.is_set()
|
||||
|
||||
@asyncio.coroutine
|
||||
def _run_event(self, coro, event_name, *args, **kwargs):
|
||||
async def _run_event(self, coro, event_name, *args, **kwargs):
|
||||
try:
|
||||
yield from coro(*args, **kwargs)
|
||||
await coro(*args, **kwargs)
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
except Exception:
|
||||
try:
|
||||
yield from self.on_error(event_name, *args, **kwargs)
|
||||
await self.on_error(event_name, *args, **kwargs)
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
@ -276,10 +272,9 @@ class Client:
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
compat.create_task(self._run_event(coro, method, *args, **kwargs), loop=self.loop)
|
||||
asyncio.ensure_future(self._run_event(coro, method, *args, **kwargs), loop=self.loop)
|
||||
|
||||
@asyncio.coroutine
|
||||
def on_error(self, event_method, *args, **kwargs):
|
||||
async def on_error(self, event_method, *args, **kwargs):
|
||||
"""|coro|
|
||||
|
||||
The default error handler provided by the client.
|
||||
@ -291,8 +286,7 @@ class Client:
|
||||
print('Ignoring exception in {}'.format(event_method), file=sys.stderr)
|
||||
traceback.print_exc()
|
||||
|
||||
@asyncio.coroutine
|
||||
def request_offline_members(self, *guilds):
|
||||
async def request_offline_members(self, *guilds):
|
||||
"""|coro|
|
||||
|
||||
Requests previously offline members from the guild to be filled up
|
||||
@ -318,12 +312,11 @@ class Client:
|
||||
if any(not g.large or g.unavailable for g in guilds):
|
||||
raise InvalidArgument('An unavailable or non-large guild was passed.')
|
||||
|
||||
yield from self._connection.request_offline_members(guilds)
|
||||
await self._connection.request_offline_members(guilds)
|
||||
|
||||
# login state management
|
||||
|
||||
@asyncio.coroutine
|
||||
def login(self, token, *, bot=True):
|
||||
async def login(self, token, *, bot=True):
|
||||
"""|coro|
|
||||
|
||||
Logs in the client with the specified credentials.
|
||||
@ -350,34 +343,31 @@ class Client:
|
||||
"""
|
||||
|
||||
log.info('logging in using static token')
|
||||
yield from self.http.static_login(token, bot=bot)
|
||||
await self.http.static_login(token, bot=bot)
|
||||
self._connection.is_bot = bot
|
||||
|
||||
@asyncio.coroutine
|
||||
def logout(self):
|
||||
async def logout(self):
|
||||
"""|coro|
|
||||
|
||||
Logs out of Discord and closes all connections.
|
||||
"""
|
||||
yield from self.close()
|
||||
await self.close()
|
||||
|
||||
@asyncio.coroutine
|
||||
def _connect(self):
|
||||
async def _connect(self):
|
||||
coro = DiscordWebSocket.from_client(self, shard_id=self.shard_id)
|
||||
self.ws = yield from asyncio.wait_for(coro, timeout=180.0, loop=self.loop)
|
||||
self.ws = await asyncio.wait_for(coro, timeout=180.0, loop=self.loop)
|
||||
while True:
|
||||
try:
|
||||
yield from self.ws.poll_event()
|
||||
await self.ws.poll_event()
|
||||
except ResumeWebSocket as e:
|
||||
log.info('Got a request to RESUME the websocket.')
|
||||
coro = DiscordWebSocket.from_client(self, shard_id=self.shard_id,
|
||||
session=self.ws.session_id,
|
||||
sequence=self.ws.sequence,
|
||||
resume=True)
|
||||
self.ws = yield from asyncio.wait_for(coro, timeout=180.0, loop=self.loop)
|
||||
self.ws = await asyncio.wait_for(coro, timeout=180.0, loop=self.loop)
|
||||
|
||||
@asyncio.coroutine
|
||||
def connect(self, *, reconnect=True):
|
||||
async def connect(self, *, reconnect=True):
|
||||
"""|coro|
|
||||
|
||||
Creates a websocket connection and lets the websocket listen
|
||||
@ -405,7 +395,7 @@ class Client:
|
||||
backoff = ExponentialBackoff()
|
||||
while not self.is_closed():
|
||||
try:
|
||||
yield from self._connect()
|
||||
await self._connect()
|
||||
except (OSError,
|
||||
HTTPException,
|
||||
GatewayNotFound,
|
||||
@ -416,7 +406,7 @@ class Client:
|
||||
websockets.WebSocketProtocolError) as e:
|
||||
|
||||
if not reconnect:
|
||||
yield from self.close()
|
||||
await self.close()
|
||||
if isinstance(e, ConnectionClosed) and e.code == 1000:
|
||||
# clean close, don't re-raise this
|
||||
return
|
||||
@ -431,15 +421,14 @@ class Client:
|
||||
# regardless and rely on is_closed instead
|
||||
if isinstance(e, ConnectionClosed):
|
||||
if e.code != 1000:
|
||||
yield from self.close()
|
||||
await self.close()
|
||||
raise
|
||||
|
||||
retry = backoff.delay()
|
||||
log.exception("Attempting a reconnect in %.2fs", retry)
|
||||
yield from asyncio.sleep(retry, loop=self.loop)
|
||||
await asyncio.sleep(retry, loop=self.loop)
|
||||
|
||||
@asyncio.coroutine
|
||||
def close(self):
|
||||
async def close(self):
|
||||
"""|coro|
|
||||
|
||||
Closes the connection to discord.
|
||||
@ -451,16 +440,16 @@ class Client:
|
||||
|
||||
for voice in self.voice_clients:
|
||||
try:
|
||||
yield from voice.disconnect()
|
||||
await voice.disconnect()
|
||||
except:
|
||||
# if an error happens during disconnects, disregard it.
|
||||
pass
|
||||
|
||||
if self.ws is not None and self.ws.open:
|
||||
yield from self.ws.close()
|
||||
await self.ws.close()
|
||||
|
||||
|
||||
yield from self.http.close()
|
||||
await self.http.close()
|
||||
self._ready.clear()
|
||||
|
||||
def clear(self):
|
||||
@ -475,8 +464,7 @@ class Client:
|
||||
self._connection.clear()
|
||||
self.http.recreate()
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self, *args, **kwargs):
|
||||
async def start(self, *args, **kwargs):
|
||||
"""|coro|
|
||||
|
||||
A shorthand coroutine for :meth:`login` + :meth:`connect`.
|
||||
@ -484,8 +472,8 @@ class Client:
|
||||
|
||||
bot = kwargs.pop('bot', True)
|
||||
reconnect = kwargs.pop('reconnect', True)
|
||||
yield from self.login(*args, bot=bot)
|
||||
yield from self.connect(reconnect=reconnect)
|
||||
await self.login(*args, bot=bot)
|
||||
await self.connect(reconnect=reconnect)
|
||||
|
||||
def _do_cleanup(self):
|
||||
log.info('Cleaning up event loop.')
|
||||
@ -493,7 +481,7 @@ class Client:
|
||||
if loop.is_closed():
|
||||
return # we're already cleaning up
|
||||
|
||||
task = compat.create_task(self.close(), loop=loop)
|
||||
task = asyncio.ensure_future(self.close(), loop=loop)
|
||||
|
||||
def _silence_gathered(fut):
|
||||
try:
|
||||
@ -558,7 +546,7 @@ class Client:
|
||||
loop.add_signal_handler(signal.SIGINT, self._do_cleanup)
|
||||
loop.add_signal_handler(signal.SIGTERM, self._do_cleanup)
|
||||
|
||||
task = compat.create_task(self.start(*args, **kwargs), loop=loop)
|
||||
task = asyncio.ensure_future(self.start(*args, **kwargs), loop=loop)
|
||||
|
||||
def stop_loop_on_finish(fut):
|
||||
loop.stop()
|
||||
@ -661,13 +649,12 @@ class Client:
|
||||
|
||||
# listeners/waiters
|
||||
|
||||
@asyncio.coroutine
|
||||
def wait_until_ready(self):
|
||||
async def wait_until_ready(self):
|
||||
"""|coro|
|
||||
|
||||
Waits until the client's internal cache is all ready.
|
||||
"""
|
||||
yield from self._ready.wait()
|
||||
await self._ready.wait()
|
||||
|
||||
def wait_for(self, event, *, check=None, timeout=None):
|
||||
"""|coro|
|
||||
@ -751,7 +738,7 @@ class Client:
|
||||
:ref:`event reference <discord-api-events>`.
|
||||
"""
|
||||
|
||||
future = compat.create_future(self.loop)
|
||||
future = self.loop.create_future()
|
||||
if check is None:
|
||||
def _check(*args):
|
||||
return True
|
||||
@ -782,8 +769,7 @@ class Client:
|
||||
Using the basic :meth:`event` decorator: ::
|
||||
|
||||
@client.event
|
||||
@asyncio.coroutine
|
||||
def on_ready():
|
||||
async def on_ready():
|
||||
print('Ready!')
|
||||
|
||||
Saving characters by using the :meth:`async_event` decorator: ::
|
||||
@ -808,8 +794,7 @@ class Client:
|
||||
|
||||
return self.event(coro)
|
||||
|
||||
@asyncio.coroutine
|
||||
def change_presence(self, *, activity=None, status=None, afk=False):
|
||||
async def change_presence(self, *, activity=None, status=None, afk=False):
|
||||
"""|coro|
|
||||
|
||||
Changes the client's presence.
|
||||
@ -851,7 +836,7 @@ class Client:
|
||||
status_enum = status
|
||||
status = str(status)
|
||||
|
||||
yield from self.ws.change_presence(activity=activity, status=status, afk=afk)
|
||||
await self.ws.change_presence(activity=activity, status=status, afk=afk)
|
||||
|
||||
for guild in self._connection.guilds:
|
||||
me = guild.me
|
||||
@ -863,8 +848,7 @@ class Client:
|
||||
|
||||
# Guild stuff
|
||||
|
||||
@asyncio.coroutine
|
||||
def create_guild(self, name, region=None, icon=None):
|
||||
async def create_guild(self, name, region=None, icon=None):
|
||||
"""|coro|
|
||||
|
||||
Creates a :class:`Guild`.
|
||||
@ -903,13 +887,12 @@ class Client:
|
||||
else:
|
||||
region = region.value
|
||||
|
||||
data = yield from self.http.create_guild(name, region, icon)
|
||||
data = await self.http.create_guild(name, region, icon)
|
||||
return Guild(data=data, state=self._connection)
|
||||
|
||||
# Invite management
|
||||
|
||||
@asyncio.coroutine
|
||||
def get_invite(self, url):
|
||||
async def get_invite(self, url):
|
||||
"""|coro|
|
||||
|
||||
Gets an :class:`Invite` from a discord.gg URL or ID.
|
||||
@ -939,11 +922,10 @@ class Client:
|
||||
"""
|
||||
|
||||
invite_id = self._resolve_invite(url)
|
||||
data = yield from self.http.get_invite(invite_id)
|
||||
data = await self.http.get_invite(invite_id)
|
||||
return Invite.from_incomplete(state=self._connection, data=data)
|
||||
|
||||
@asyncio.coroutine
|
||||
def delete_invite(self, invite):
|
||||
async def delete_invite(self, invite):
|
||||
"""|coro|
|
||||
|
||||
Revokes an :class:`Invite`, URL, or ID to an invite.
|
||||
@ -967,12 +949,11 @@ class Client:
|
||||
"""
|
||||
|
||||
invite_id = self._resolve_invite(invite)
|
||||
yield from self.http.delete_invite(invite_id)
|
||||
await self.http.delete_invite(invite_id)
|
||||
|
||||
# Miscellaneous stuff
|
||||
|
||||
@asyncio.coroutine
|
||||
def application_info(self):
|
||||
async def application_info(self):
|
||||
"""|coro|
|
||||
|
||||
Retrieve's the bot's application information.
|
||||
@ -987,13 +968,12 @@ class Client:
|
||||
HTTPException
|
||||
Retrieving the information failed somehow.
|
||||
"""
|
||||
data = yield from self.http.application_info()
|
||||
data = await self.http.application_info()
|
||||
return AppInfo(id=int(data['id']), name=data['name'],
|
||||
description=data['description'], icon=data['icon'],
|
||||
owner=User(state=self._connection, data=data['owner']))
|
||||
|
||||
@asyncio.coroutine
|
||||
def get_user_info(self, user_id):
|
||||
async def get_user_info(self, user_id):
|
||||
"""|coro|
|
||||
|
||||
Retrieves a :class:`User` based on their ID. This can only
|
||||
@ -1018,11 +998,10 @@ class Client:
|
||||
HTTPException
|
||||
Fetching the user failed.
|
||||
"""
|
||||
data = yield from self.http.get_user_info(user_id)
|
||||
data = await self.http.get_user_info(user_id)
|
||||
return User(state=self._connection, data=data)
|
||||
|
||||
@asyncio.coroutine
|
||||
def get_user_profile(self, user_id):
|
||||
async def get_user_profile(self, user_id):
|
||||
"""|coro|
|
||||
|
||||
Gets an arbitrary user's profile. This can only be used by non-bot accounts.
|
||||
@ -1046,7 +1025,7 @@ class Client:
|
||||
"""
|
||||
|
||||
state = self._connection
|
||||
data = yield from self.http.get_user_profile(user_id)
|
||||
data = await self.http.get_user_profile(user_id)
|
||||
|
||||
def transform(d):
|
||||
return state._get_guild(int(d['id']))
|
||||
@ -1060,8 +1039,7 @@ class Client:
|
||||
user=User(data=user, state=state),
|
||||
connected_accounts=data['connected_accounts'])
|
||||
|
||||
@asyncio.coroutine
|
||||
def get_webhook_info(self, webhook_id):
|
||||
async def get_webhook_info(self, webhook_id):
|
||||
"""|coro|
|
||||
|
||||
Retrieves a :class:`Webhook` with the specified ID.
|
||||
@ -1080,5 +1058,5 @@ class Client:
|
||||
:class:`Webhook`
|
||||
The webhook you requested.
|
||||
"""
|
||||
data = yield from self.http.get_webhook(webhook_id)
|
||||
data = await self.http.get_webhook(webhook_id)
|
||||
return Webhook.from_state(data, state=self._connection)
|
||||
|
@ -1,140 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-2017 Rapptz
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
import concurrent.futures
|
||||
import asyncio
|
||||
|
||||
try:
|
||||
create_task = asyncio.ensure_future
|
||||
except AttributeError:
|
||||
create_task = getattr(asyncio, 'async')
|
||||
|
||||
try:
|
||||
_create_future = asyncio.AbstractEventLoop.create_future
|
||||
except AttributeError:
|
||||
def create_future(loop):
|
||||
return asyncio.Future(loop=loop)
|
||||
else:
|
||||
def create_future(loop):
|
||||
return loop.create_future()
|
||||
|
||||
try:
|
||||
run_coroutine_threadsafe = asyncio.run_coroutine_threadsafe
|
||||
except AttributeError:
|
||||
# the following code is slightly modified from the
|
||||
# official asyncio repository that could be found here:
|
||||
# https://github.com/python/asyncio/blob/master/asyncio/futures.py
|
||||
# with a commit hash of 5c7efbcdfbe6a5c25b4cd5df22d9a15ab4062c8e
|
||||
# this portion is licensed under Apache license 2.0
|
||||
|
||||
def _set_concurrent_future_state(concurrent, source):
|
||||
"""Copy state from a future to a concurrent.futures.Future."""
|
||||
assert source.done()
|
||||
if source.cancelled():
|
||||
concurrent.cancel()
|
||||
if not concurrent.set_running_or_notify_cancel():
|
||||
return
|
||||
exception = source.exception()
|
||||
if exception is not None:
|
||||
concurrent.set_exception(exception)
|
||||
else:
|
||||
result = source.result()
|
||||
concurrent.set_result(result)
|
||||
|
||||
|
||||
def _copy_future_state(source, dest):
|
||||
"""Internal helper to copy state from another Future.
|
||||
The other Future may be a concurrent.futures.Future.
|
||||
"""
|
||||
assert source.done()
|
||||
if dest.cancelled():
|
||||
return
|
||||
assert not dest.done()
|
||||
if source.cancelled():
|
||||
dest.cancel()
|
||||
else:
|
||||
exception = source.exception()
|
||||
if exception is not None:
|
||||
dest.set_exception(exception)
|
||||
else:
|
||||
result = source.result()
|
||||
dest.set_result(result)
|
||||
|
||||
def _chain_future(source, destination):
|
||||
"""Chain two futures so that when one completes, so does the other.
|
||||
The result (or exception) of source will be copied to destination.
|
||||
If destination is cancelled, source gets cancelled too.
|
||||
Compatible with both asyncio.Future and concurrent.futures.Future.
|
||||
"""
|
||||
if not isinstance(source, (asyncio.Future, concurrent.futures.Future)):
|
||||
raise TypeError('A future is required for source argument')
|
||||
|
||||
if not isinstance(destination, (asyncio.Future, concurrent.futures.Future)):
|
||||
raise TypeError('A future is required for destination argument')
|
||||
|
||||
source_loop = source._loop if isinstance(source, asyncio.Future) else None
|
||||
dest_loop = destination._loop if isinstance(destination, asyncio.Future) else None
|
||||
|
||||
def _set_state(future, other):
|
||||
if isinstance(future, asyncio.Future):
|
||||
_copy_future_state(other, future)
|
||||
else:
|
||||
_set_concurrent_future_state(future, other)
|
||||
|
||||
def _call_check_cancel(destination):
|
||||
if destination.cancelled():
|
||||
if source_loop is None or source_loop is dest_loop:
|
||||
source.cancel()
|
||||
else:
|
||||
source_loop.call_soon_threadsafe(source.cancel)
|
||||
|
||||
def _call_set_state(source):
|
||||
if dest_loop is None or dest_loop is source_loop:
|
||||
_set_state(destination, source)
|
||||
else:
|
||||
dest_loop.call_soon_threadsafe(_set_state, destination, source)
|
||||
|
||||
destination.add_done_callback(_call_check_cancel)
|
||||
source.add_done_callback(_call_set_state)
|
||||
|
||||
def run_coroutine_threadsafe(coro, loop):
|
||||
"""Submit a coroutine object to a given event loop.
|
||||
|
||||
Return a concurrent.futures.Future to access the result.
|
||||
"""
|
||||
if not asyncio.iscoroutine(coro):
|
||||
raise TypeError('A coroutine object is required')
|
||||
|
||||
future = concurrent.futures.Future()
|
||||
|
||||
def callback():
|
||||
try:
|
||||
_chain_future(create_task(coro, loop=loop), future)
|
||||
except Exception as exc:
|
||||
if future.set_running_or_notify_cancel():
|
||||
future.set_exception(exc)
|
||||
raise
|
||||
loop.call_soon_threadsafe(callback)
|
||||
return future
|
@ -40,18 +40,17 @@ class Typing:
|
||||
self.loop = messageable._state.loop
|
||||
self.messageable = messageable
|
||||
|
||||
@asyncio.coroutine
|
||||
def do_typing(self):
|
||||
async def do_typing(self):
|
||||
try:
|
||||
channel = self._channel
|
||||
except AttributeError:
|
||||
channel = yield from self.messageable._get_channel()
|
||||
channel = await self.messageable._get_channel()
|
||||
|
||||
typing = channel._state.http.send_typing
|
||||
|
||||
while True:
|
||||
yield from typing(channel.id)
|
||||
yield from asyncio.sleep(5)
|
||||
await typing(channel.id)
|
||||
await asyncio.sleep(5)
|
||||
|
||||
def __enter__(self):
|
||||
self.task = create_task(self.do_typing(), loop=self.loop)
|
||||
@ -61,12 +60,10 @@ class Typing:
|
||||
def __exit__(self, exc_type, exc, tb):
|
||||
self.task.cancel()
|
||||
|
||||
@asyncio.coroutine
|
||||
def __aenter__(self):
|
||||
self._channel = channel = yield from self.messageable._get_channel()
|
||||
yield from channel._state.http.send_typing(channel.id)
|
||||
async def __aenter__(self):
|
||||
self._channel = channel = await self.messageable._get_channel()
|
||||
await channel._state.http.send_typing(channel.id)
|
||||
return self.__enter__()
|
||||
|
||||
@asyncio.coroutine
|
||||
def __aexit__(self, exc_type, exc, tb):
|
||||
async def __aexit__(self, exc_type, exc, tb):
|
||||
self.task.cancel()
|
||||
|
@ -203,8 +203,7 @@ class Emoji(Hashable):
|
||||
""":class:`Guild`: The guild this emoji belongs to."""
|
||||
return self._state._get_guild(self.guild_id)
|
||||
|
||||
@asyncio.coroutine
|
||||
def delete(self, *, reason=None):
|
||||
async def delete(self, *, reason=None):
|
||||
"""|coro|
|
||||
|
||||
Deletes the custom emoji.
|
||||
@ -227,10 +226,9 @@ class Emoji(Hashable):
|
||||
An error occurred deleting the emoji.
|
||||
"""
|
||||
|
||||
yield from self._state.http.delete_custom_emoji(self.guild.id, self.id, reason=reason)
|
||||
await self._state.http.delete_custom_emoji(self.guild.id, self.id, reason=reason)
|
||||
|
||||
@asyncio.coroutine
|
||||
def edit(self, *, name, reason=None):
|
||||
async def edit(self, *, name, reason=None):
|
||||
"""|coro|
|
||||
|
||||
Edits the custom emoji.
|
||||
@ -255,4 +253,4 @@ class Emoji(Hashable):
|
||||
An error occurred editing the emoji.
|
||||
"""
|
||||
|
||||
yield from self._state.http.edit_custom_emoji(self.guild.id, self.id, name=name, reason=reason)
|
||||
await self._state.http.edit_custom_emoji(self.guild.id, self.id, name=name, reason=reason)
|
||||
|
@ -40,9 +40,7 @@ class ClientException(DiscordException):
|
||||
|
||||
class NoMoreItems(DiscordException):
|
||||
"""Exception that is thrown when an async iteration operation has no more
|
||||
items. This is mainly exposed for Python 3.4 support where `StopAsyncIteration`
|
||||
is not provided.
|
||||
"""
|
||||
items."""
|
||||
pass
|
||||
|
||||
class GatewayNotFound(DiscordException):
|
||||
|
@ -91,8 +91,7 @@ _mention_pattern = re.compile('|'.join(_mentions_transforms.keys()))
|
||||
def _is_submodule(parent, child):
|
||||
return parent == child or child.startswith(parent + ".")
|
||||
|
||||
@asyncio.coroutine
|
||||
def _default_help_command(ctx, *commands : str):
|
||||
async def _default_help_command(ctx, *commands : str):
|
||||
"""Shows this message."""
|
||||
bot = ctx.bot
|
||||
destination = ctx.message.author if bot.pm_help else ctx.message.channel
|
||||
@ -102,7 +101,7 @@ def _default_help_command(ctx, *commands : str):
|
||||
|
||||
# help by itself just lists our own commands.
|
||||
if len(commands) == 0:
|
||||
pages = yield from bot.formatter.format_help_for(ctx, bot)
|
||||
pages = await bot.formatter.format_help_for(ctx, bot)
|
||||
elif len(commands) == 1:
|
||||
# try to see if it is a cog name
|
||||
name = _mention_pattern.sub(repl, commands[0])
|
||||
@ -112,15 +111,15 @@ def _default_help_command(ctx, *commands : str):
|
||||
else:
|
||||
command = bot.all_commands.get(name)
|
||||
if command is None:
|
||||
yield from destination.send(bot.command_not_found.format(name))
|
||||
await destination.send(bot.command_not_found.format(name))
|
||||
return
|
||||
|
||||
pages = yield from bot.formatter.format_help_for(ctx, command)
|
||||
pages = await bot.formatter.format_help_for(ctx, command)
|
||||
else:
|
||||
name = _mention_pattern.sub(repl, commands[0])
|
||||
command = bot.all_commands.get(name)
|
||||
if command is None:
|
||||
yield from destination.send(bot.command_not_found.format(name))
|
||||
await destination.send(bot.command_not_found.format(name))
|
||||
return
|
||||
|
||||
for key in commands[1:]:
|
||||
@ -128,13 +127,13 @@ def _default_help_command(ctx, *commands : str):
|
||||
key = _mention_pattern.sub(repl, key)
|
||||
command = command.all_commands.get(key)
|
||||
if command is None:
|
||||
yield from destination.send(bot.command_not_found.format(key))
|
||||
await destination.send(bot.command_not_found.format(key))
|
||||
return
|
||||
except AttributeError:
|
||||
yield from destination.send(bot.command_has_no_subcommands.format(command, key))
|
||||
await destination.send(bot.command_has_no_subcommands.format(command, key))
|
||||
return
|
||||
|
||||
pages = yield from bot.formatter.format_help_for(ctx, command)
|
||||
pages = await bot.formatter.format_help_for(ctx, command)
|
||||
|
||||
if bot.pm_help is None:
|
||||
characters = sum(map(lambda l: len(l), pages))
|
||||
@ -143,7 +142,7 @@ def _default_help_command(ctx, *commands : str):
|
||||
destination = ctx.message.author
|
||||
|
||||
for page in pages:
|
||||
yield from destination.send(page)
|
||||
await destination.send(page)
|
||||
|
||||
class BotBase(GroupMixin):
|
||||
def __init__(self, command_prefix, formatter=None, description=None, pm_help=False, **options):
|
||||
@ -189,10 +188,9 @@ class BotBase(GroupMixin):
|
||||
ev = 'on_' + event_name
|
||||
for event in self.extra_events.get(ev, []):
|
||||
coro = self._run_event(event, event_name, *args, **kwargs)
|
||||
discord.compat.create_task(coro, loop=self.loop)
|
||||
asyncio.ensure_future(coro, loop=self.loop)
|
||||
|
||||
@asyncio.coroutine
|
||||
def close(self):
|
||||
async def close(self):
|
||||
for extension in tuple(self.extensions):
|
||||
try:
|
||||
self.unload_extension(extension)
|
||||
@ -205,10 +203,9 @@ class BotBase(GroupMixin):
|
||||
except:
|
||||
pass
|
||||
|
||||
yield from super().close()
|
||||
await super().close()
|
||||
|
||||
@asyncio.coroutine
|
||||
def on_command_error(self, context, exception):
|
||||
async def on_command_error(self, context, exception):
|
||||
"""|coro|
|
||||
|
||||
The default command error handler provided by the bot.
|
||||
@ -335,17 +332,15 @@ class BotBase(GroupMixin):
|
||||
self.add_check(func, call_once=True)
|
||||
return func
|
||||
|
||||
@asyncio.coroutine
|
||||
def can_run(self, ctx, *, call_once=False):
|
||||
async def can_run(self, ctx, *, call_once=False):
|
||||
data = self._check_once if call_once else self._checks
|
||||
|
||||
if len(data) == 0:
|
||||
return True
|
||||
|
||||
return (yield from discord.utils.async_all(f(ctx) for f in data))
|
||||
return (await discord.utils.async_all(f(ctx) for f in data))
|
||||
|
||||
@asyncio.coroutine
|
||||
def is_owner(self, user):
|
||||
async def is_owner(self, user):
|
||||
"""Checks if a :class:`.User` or :class:`.Member` is the owner of
|
||||
this bot.
|
||||
|
||||
@ -359,7 +354,7 @@ class BotBase(GroupMixin):
|
||||
"""
|
||||
|
||||
if self.owner_id is None:
|
||||
app = yield from self.application_info()
|
||||
app = await self.application_info()
|
||||
self.owner_id = owner_id = app.owner.id
|
||||
return user.id == owner_id
|
||||
return user.id == self.owner_id
|
||||
@ -773,8 +768,7 @@ class BotBase(GroupMixin):
|
||||
|
||||
# command processing
|
||||
|
||||
@asyncio.coroutine
|
||||
def get_prefix(self, message):
|
||||
async def get_prefix(self, message):
|
||||
"""|coro|
|
||||
|
||||
Retrieves the prefix the bot is listening to
|
||||
@ -801,9 +795,7 @@ class BotBase(GroupMixin):
|
||||
"""
|
||||
prefix = ret = self.command_prefix
|
||||
if callable(prefix):
|
||||
ret = prefix(self, message)
|
||||
if asyncio.iscoroutine(ret):
|
||||
ret = yield from ret
|
||||
ret = await discord.utils.maybe_coroutine(prefix, self, ret)
|
||||
|
||||
if isinstance(ret, (list, tuple)):
|
||||
ret = [p for p in ret if p]
|
||||
@ -813,8 +805,7 @@ class BotBase(GroupMixin):
|
||||
|
||||
return ret
|
||||
|
||||
@asyncio.coroutine
|
||||
def get_context(self, message, *, cls=Context):
|
||||
async def get_context(self, message, *, cls=Context):
|
||||
"""|coro|
|
||||
|
||||
Returns the invocation context from the message.
|
||||
@ -850,7 +841,7 @@ class BotBase(GroupMixin):
|
||||
if self._skip_check(message.author.id, self.user.id):
|
||||
return ctx
|
||||
|
||||
prefix = yield from self.get_prefix(message)
|
||||
prefix = await self.get_prefix(message)
|
||||
invoked_prefix = prefix
|
||||
|
||||
if isinstance(prefix, str):
|
||||
@ -867,8 +858,7 @@ class BotBase(GroupMixin):
|
||||
ctx.command = self.all_commands.get(invoker)
|
||||
return ctx
|
||||
|
||||
@asyncio.coroutine
|
||||
def invoke(self, ctx):
|
||||
async def invoke(self, ctx):
|
||||
"""|coro|
|
||||
|
||||
Invokes the command given under the invocation context and
|
||||
@ -882,18 +872,17 @@ class BotBase(GroupMixin):
|
||||
if ctx.command is not None:
|
||||
self.dispatch('command', ctx)
|
||||
try:
|
||||
if (yield from self.can_run(ctx, call_once=True)):
|
||||
yield from ctx.command.invoke(ctx)
|
||||
if (await self.can_run(ctx, call_once=True)):
|
||||
await ctx.command.invoke(ctx)
|
||||
except CommandError as e:
|
||||
yield from ctx.command.dispatch_error(ctx, e)
|
||||
await ctx.command.dispatch_error(ctx, e)
|
||||
else:
|
||||
self.dispatch('command_completion', ctx)
|
||||
elif ctx.invoked_with:
|
||||
exc = CommandNotFound('Command "{}" is not found'.format(ctx.invoked_with))
|
||||
self.dispatch('command_error', ctx, exc)
|
||||
|
||||
@asyncio.coroutine
|
||||
def process_commands(self, message):
|
||||
async def process_commands(self, message):
|
||||
"""|coro|
|
||||
|
||||
This function processes the commands that have been registered
|
||||
@ -912,12 +901,11 @@ class BotBase(GroupMixin):
|
||||
message : discord.Message
|
||||
The message to process commands for.
|
||||
"""
|
||||
ctx = yield from self.get_context(message)
|
||||
yield from self.invoke(ctx)
|
||||
ctx = await self.get_context(message)
|
||||
await self.invoke(ctx)
|
||||
|
||||
@asyncio.coroutine
|
||||
def on_message(self, message):
|
||||
yield from self.process_commands(message)
|
||||
async def on_message(self, message):
|
||||
await self.process_commands(message)
|
||||
|
||||
class Bot(BotBase, discord.Client):
|
||||
"""Represents a discord bot.
|
||||
|
@ -86,8 +86,7 @@ class Context(discord.abc.Messageable):
|
||||
self.command_failed = attrs.pop('command_failed', False)
|
||||
self._state = self.message._state
|
||||
|
||||
@asyncio.coroutine
|
||||
def invoke(self, *args, **kwargs):
|
||||
async def invoke(self, *args, **kwargs):
|
||||
"""|coro|
|
||||
|
||||
Calls a command with the arguments given.
|
||||
@ -125,11 +124,10 @@ class Context(discord.abc.Messageable):
|
||||
arguments.append(self)
|
||||
arguments.extend(args[1:])
|
||||
|
||||
ret = yield from command.callback(*arguments, **kwargs)
|
||||
ret = await command.callback(*arguments, **kwargs)
|
||||
return ret
|
||||
|
||||
@asyncio.coroutine
|
||||
def reinvoke(self, *, call_hooks=False, restart=True):
|
||||
async def reinvoke(self, *, call_hooks=False, restart=True):
|
||||
"""|coro|
|
||||
|
||||
Calls the command again.
|
||||
@ -174,7 +172,7 @@ class Context(discord.abc.Messageable):
|
||||
to_call = cmd
|
||||
|
||||
try:
|
||||
yield from to_call.reinvoke(self, call_hooks=call_hooks)
|
||||
await to_call.reinvoke(self, call_hooks=call_hooks)
|
||||
finally:
|
||||
self.command = cmd
|
||||
view.index = index
|
||||
@ -188,8 +186,7 @@ class Context(discord.abc.Messageable):
|
||||
"""Checks if the invocation context is valid to be invoked with."""
|
||||
return self.prefix is not None and self.command is not None
|
||||
|
||||
@asyncio.coroutine
|
||||
def _get_channel(self):
|
||||
async def _get_channel(self):
|
||||
return self.channel
|
||||
|
||||
@property
|
||||
|
@ -57,8 +57,7 @@ class Converter:
|
||||
method to do its conversion logic. This method must be a coroutine.
|
||||
"""
|
||||
|
||||
@asyncio.coroutine
|
||||
def convert(self, ctx, argument):
|
||||
async def convert(self, ctx, argument):
|
||||
"""|coro|
|
||||
|
||||
The method to override to do conversion logic.
|
||||
@ -99,8 +98,7 @@ class MemberConverter(IDConverter):
|
||||
5. Lookup by nickname
|
||||
"""
|
||||
|
||||
@asyncio.coroutine
|
||||
def convert(self, ctx, argument):
|
||||
async def convert(self, ctx, argument):
|
||||
message = ctx.message
|
||||
bot = ctx.bot
|
||||
match = self._get_id_match(argument) or re.match(r'<@!?([0-9]+)>$', argument)
|
||||
@ -136,8 +134,7 @@ class UserConverter(IDConverter):
|
||||
3. Lookup by name#discrim
|
||||
4. Lookup by name
|
||||
"""
|
||||
@asyncio.coroutine
|
||||
def convert(self, ctx, argument):
|
||||
async def convert(self, ctx, argument):
|
||||
match = self._get_id_match(argument) or re.match(r'<@!?([0-9]+)>$', argument)
|
||||
result = None
|
||||
state = ctx._state
|
||||
@ -176,8 +173,7 @@ class TextChannelConverter(IDConverter):
|
||||
2. Lookup by mention.
|
||||
3. Lookup by name
|
||||
"""
|
||||
@asyncio.coroutine
|
||||
def convert(self, ctx, argument):
|
||||
async def convert(self, ctx, argument):
|
||||
bot = ctx.bot
|
||||
|
||||
match = self._get_id_match(argument) or re.match(r'<#([0-9]+)>$', argument)
|
||||
@ -216,8 +212,7 @@ class VoiceChannelConverter(IDConverter):
|
||||
2. Lookup by mention.
|
||||
3. Lookup by name
|
||||
"""
|
||||
@asyncio.coroutine
|
||||
def convert(self, ctx, argument):
|
||||
async def convert(self, ctx, argument):
|
||||
bot = ctx.bot
|
||||
match = self._get_id_match(argument) or re.match(r'<#([0-9]+)>$', argument)
|
||||
result = None
|
||||
@ -255,8 +250,7 @@ class CategoryChannelConverter(IDConverter):
|
||||
2. Lookup by mention.
|
||||
3. Lookup by name
|
||||
"""
|
||||
@asyncio.coroutine
|
||||
def convert(self, ctx, argument):
|
||||
async def convert(self, ctx, argument):
|
||||
bot = ctx.bot
|
||||
|
||||
match = self._get_id_match(argument) or re.match(r'<#([0-9]+)>$', argument)
|
||||
@ -295,8 +289,7 @@ class ColourConverter(Converter):
|
||||
|
||||
- The ``_`` in the name can be optionally replaced with spaces.
|
||||
"""
|
||||
@asyncio.coroutine
|
||||
def convert(self, ctx, argument):
|
||||
async def convert(self, ctx, argument):
|
||||
arg = argument.replace('0x', '').lower()
|
||||
|
||||
if arg[0] == '#':
|
||||
@ -323,8 +316,7 @@ class RoleConverter(IDConverter):
|
||||
2. Lookup by mention.
|
||||
3. Lookup by name
|
||||
"""
|
||||
@asyncio.coroutine
|
||||
def convert(self, ctx, argument):
|
||||
async def convert(self, ctx, argument):
|
||||
guild = ctx.message.guild
|
||||
if not guild:
|
||||
raise NoPrivateMessage()
|
||||
@ -338,8 +330,7 @@ class RoleConverter(IDConverter):
|
||||
|
||||
class GameConverter(Converter):
|
||||
"""Converts to :class:`Game`."""
|
||||
@asyncio.coroutine
|
||||
def convert(self, ctx, argument):
|
||||
async def convert(self, ctx, argument):
|
||||
return discord.Game(name=argument)
|
||||
|
||||
class InviteConverter(Converter):
|
||||
@ -347,10 +338,9 @@ class InviteConverter(Converter):
|
||||
|
||||
This is done via an HTTP request using :meth:`.Bot.get_invite`.
|
||||
"""
|
||||
@asyncio.coroutine
|
||||
def convert(self, ctx, argument):
|
||||
async def convert(self, ctx, argument):
|
||||
try:
|
||||
invite = yield from ctx.bot.get_invite(argument)
|
||||
invite = await ctx.bot.get_invite(argument)
|
||||
return invite
|
||||
except Exception as e:
|
||||
raise BadArgument('Invite is invalid or expired') from e
|
||||
@ -368,8 +358,7 @@ class EmojiConverter(IDConverter):
|
||||
2. Lookup by extracting ID from the emoji.
|
||||
3. Lookup by name
|
||||
"""
|
||||
@asyncio.coroutine
|
||||
def convert(self, ctx, argument):
|
||||
async def convert(self, ctx, argument):
|
||||
match = self._get_id_match(argument) or re.match(r'<a?:[a-zA-Z0-9\_]+:([0-9]+)>$', argument)
|
||||
result = None
|
||||
bot = ctx.bot
|
||||
@ -403,8 +392,7 @@ class PartialEmojiConverter(Converter):
|
||||
|
||||
This is done by extracting the animated flag, name and ID from the emoji.
|
||||
"""
|
||||
@asyncio.coroutine
|
||||
def convert(self, ctx, argument):
|
||||
async def convert(self, ctx, argument):
|
||||
match = re.match(r'<(a?):([a-zA-Z0-9\_]+):([0-9]+)>$', argument)
|
||||
|
||||
if match:
|
||||
@ -436,8 +424,7 @@ class clean_content(Converter):
|
||||
self.use_nicknames = use_nicknames
|
||||
self.escape_markdown = escape_markdown
|
||||
|
||||
@asyncio.coroutine
|
||||
def convert(self, ctx, argument):
|
||||
async def convert(self, ctx, argument):
|
||||
message = ctx.message
|
||||
transformations = {}
|
||||
|
||||
|
@ -41,10 +41,9 @@ __all__ = [ 'Command', 'Group', 'GroupMixin', 'command', 'group',
|
||||
|
||||
def wrap_callback(coro):
|
||||
@functools.wraps(coro)
|
||||
@asyncio.coroutine
|
||||
def wrapped(*args, **kwargs):
|
||||
async def wrapped(*args, **kwargs):
|
||||
try:
|
||||
ret = yield from coro(*args, **kwargs)
|
||||
ret = await coro(*args, **kwargs)
|
||||
except CommandError:
|
||||
raise
|
||||
except asyncio.CancelledError:
|
||||
@ -56,10 +55,9 @@ def wrap_callback(coro):
|
||||
|
||||
def hooked_wrapped_callback(command, ctx, coro):
|
||||
@functools.wraps(coro)
|
||||
@asyncio.coroutine
|
||||
def wrapped(*args, **kwargs):
|
||||
async def wrapped(*args, **kwargs):
|
||||
try:
|
||||
ret = yield from coro(*args, **kwargs)
|
||||
ret = await coro(*args, **kwargs)
|
||||
except CommandError:
|
||||
ctx.command_failed = True
|
||||
raise
|
||||
@ -70,7 +68,7 @@ def hooked_wrapped_callback(command, ctx, coro):
|
||||
ctx.command_failed = True
|
||||
raise CommandInvokeError(e) from e
|
||||
finally:
|
||||
yield from command.call_after_hooks(ctx)
|
||||
await command.call_after_hooks(ctx)
|
||||
return ret
|
||||
return wrapped
|
||||
|
||||
@ -182,8 +180,7 @@ class Command:
|
||||
self._before_invoke = None
|
||||
self._after_invoke = None
|
||||
|
||||
@asyncio.coroutine
|
||||
def dispatch_error(self, ctx, error):
|
||||
async def dispatch_error(self, ctx, error):
|
||||
ctx.command_failed = True
|
||||
cog = self.instance
|
||||
try:
|
||||
@ -193,9 +190,9 @@ class Command:
|
||||
else:
|
||||
injected = wrap_callback(coro)
|
||||
if cog is not None:
|
||||
yield from injected(cog, ctx, error)
|
||||
await injected(cog, ctx, error)
|
||||
else:
|
||||
yield from injected(ctx, error)
|
||||
await injected(ctx, error)
|
||||
|
||||
try:
|
||||
local = getattr(cog, '_{0.__class__.__name__}__error'.format(cog))
|
||||
@ -203,7 +200,7 @@ class Command:
|
||||
pass
|
||||
else:
|
||||
wrapped = wrap_callback(local)
|
||||
yield from wrapped(ctx, error)
|
||||
await wrapped(ctx, error)
|
||||
finally:
|
||||
ctx.bot.dispatch('command_error', ctx, error)
|
||||
|
||||
@ -212,8 +209,7 @@ class Command:
|
||||
self.instance = instance
|
||||
return self
|
||||
|
||||
@asyncio.coroutine
|
||||
def do_conversion(self, ctx, converter, argument):
|
||||
async def do_conversion(self, ctx, converter, argument):
|
||||
if converter is bool:
|
||||
return _convert_to_bool(argument)
|
||||
|
||||
@ -228,15 +224,15 @@ class Command:
|
||||
if inspect.isclass(converter):
|
||||
if issubclass(converter, converters.Converter):
|
||||
instance = converter()
|
||||
ret = yield from instance.convert(ctx, argument)
|
||||
ret = await instance.convert(ctx, argument)
|
||||
return ret
|
||||
else:
|
||||
method = getattr(converter, 'convert', None)
|
||||
if method is not None and inspect.ismethod(method):
|
||||
ret = yield from method(ctx, argument)
|
||||
ret = await method(ctx, argument)
|
||||
return ret
|
||||
elif isinstance(converter, converters.Converter):
|
||||
ret = yield from converter.convert(ctx, argument)
|
||||
ret = await converter.convert(ctx, argument)
|
||||
return ret
|
||||
|
||||
return converter(argument)
|
||||
@ -250,8 +246,7 @@ class Command:
|
||||
converter = str
|
||||
return converter
|
||||
|
||||
@asyncio.coroutine
|
||||
def transform(self, ctx, param):
|
||||
async def transform(self, ctx, param):
|
||||
required = param.default is param.empty
|
||||
converter = self._get_converter(param)
|
||||
consume_rest_is_special = param.kind == param.KEYWORD_ONLY and not self.rest_is_raw
|
||||
@ -271,7 +266,7 @@ class Command:
|
||||
argument = quoted_word(view)
|
||||
|
||||
try:
|
||||
return (yield from self.do_conversion(ctx, converter, argument))
|
||||
return (await self.do_conversion(ctx, converter, argument))
|
||||
except CommandError as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
@ -354,8 +349,7 @@ class Command:
|
||||
def __str__(self):
|
||||
return self.qualified_name
|
||||
|
||||
@asyncio.coroutine
|
||||
def _parse_arguments(self, ctx):
|
||||
async def _parse_arguments(self, ctx):
|
||||
ctx.args = [ctx] if self.instance is None else [self.instance, ctx]
|
||||
ctx.kwargs = {}
|
||||
args = ctx.args
|
||||
@ -382,21 +376,21 @@ class Command:
|
||||
|
||||
for name, param in iterator:
|
||||
if param.kind == param.POSITIONAL_OR_KEYWORD:
|
||||
transformed = yield from self.transform(ctx, param)
|
||||
transformed = await self.transform(ctx, param)
|
||||
args.append(transformed)
|
||||
elif param.kind == param.KEYWORD_ONLY:
|
||||
# kwarg only param denotes "consume rest" semantics
|
||||
if self.rest_is_raw:
|
||||
converter = self._get_converter(param)
|
||||
argument = view.read_rest()
|
||||
kwargs[name] = yield from self.do_conversion(ctx, converter, argument)
|
||||
kwargs[name] = await self.do_conversion(ctx, converter, argument)
|
||||
else:
|
||||
kwargs[name] = yield from self.transform(ctx, param)
|
||||
kwargs[name] = await self.transform(ctx, param)
|
||||
break
|
||||
elif param.kind == param.VAR_POSITIONAL:
|
||||
while not view.eof:
|
||||
try:
|
||||
transformed = yield from self.transform(ctx, param)
|
||||
transformed = await self.transform(ctx, param)
|
||||
args.append(transformed)
|
||||
except RuntimeError:
|
||||
break
|
||||
@ -405,24 +399,22 @@ class Command:
|
||||
if not view.eof:
|
||||
raise TooManyArguments('Too many arguments passed to ' + self.qualified_name)
|
||||
|
||||
@asyncio.coroutine
|
||||
def _verify_checks(self, ctx):
|
||||
async def _verify_checks(self, ctx):
|
||||
if not self.enabled:
|
||||
raise DisabledCommand('{0.name} command is disabled'.format(self))
|
||||
|
||||
if not (yield from self.can_run(ctx)):
|
||||
if not (await self.can_run(ctx)):
|
||||
raise CheckFailure('The check functions for command {0.qualified_name} failed.'.format(self))
|
||||
|
||||
@asyncio.coroutine
|
||||
def call_before_hooks(self, ctx):
|
||||
async def call_before_hooks(self, ctx):
|
||||
# now that we're done preparing we can call the pre-command hooks
|
||||
# first, call the command local hook:
|
||||
cog = self.instance
|
||||
if self._before_invoke is not None:
|
||||
if cog is None:
|
||||
yield from self._before_invoke(ctx)
|
||||
await self._before_invoke(ctx)
|
||||
else:
|
||||
yield from self._before_invoke(cog, ctx)
|
||||
await self._before_invoke(cog, ctx)
|
||||
|
||||
# call the cog local hook if applicable:
|
||||
try:
|
||||
@ -430,37 +422,35 @@ class Command:
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
yield from hook(ctx)
|
||||
await hook(ctx)
|
||||
|
||||
# call the bot global hook if necessary
|
||||
hook = ctx.bot._before_invoke
|
||||
if hook is not None:
|
||||
yield from hook(ctx)
|
||||
await hook(ctx)
|
||||
|
||||
@asyncio.coroutine
|
||||
def call_after_hooks(self, ctx):
|
||||
async def call_after_hooks(self, ctx):
|
||||
cog = self.instance
|
||||
if self._after_invoke is not None:
|
||||
if cog is None:
|
||||
yield from self._after_invoke(ctx)
|
||||
await self._after_invoke(ctx)
|
||||
else:
|
||||
yield from self._after_invoke(cog, ctx)
|
||||
await self._after_invoke(cog, ctx)
|
||||
|
||||
try:
|
||||
hook = getattr(cog, '_{0.__class__.__name__}__after_invoke'.format(cog))
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
yield from hook(ctx)
|
||||
await hook(ctx)
|
||||
|
||||
hook = ctx.bot._after_invoke
|
||||
if hook is not None:
|
||||
yield from hook(ctx)
|
||||
await hook(ctx)
|
||||
|
||||
@asyncio.coroutine
|
||||
def prepare(self, ctx):
|
||||
async def prepare(self, ctx):
|
||||
ctx.command = self
|
||||
yield from self._verify_checks(ctx)
|
||||
await self._verify_checks(ctx)
|
||||
|
||||
if self._buckets.valid:
|
||||
bucket = self._buckets.get_bucket(ctx.message)
|
||||
@ -468,8 +458,8 @@ class Command:
|
||||
if retry_after:
|
||||
raise CommandOnCooldown(bucket, retry_after)
|
||||
|
||||
yield from self._parse_arguments(ctx)
|
||||
yield from self.call_before_hooks(ctx)
|
||||
await self._parse_arguments(ctx)
|
||||
await self.call_before_hooks(ctx)
|
||||
|
||||
def is_on_cooldown(self, ctx):
|
||||
"""Checks whether the command is currently on cooldown.
|
||||
@ -502,34 +492,32 @@ class Command:
|
||||
bucket = self._buckets.get_bucket(ctx.message)
|
||||
bucket.reset()
|
||||
|
||||
@asyncio.coroutine
|
||||
def invoke(self, ctx):
|
||||
yield from self.prepare(ctx)
|
||||
async def invoke(self, ctx):
|
||||
await self.prepare(ctx)
|
||||
|
||||
# terminate the invoked_subcommand chain.
|
||||
# since we're in a regular command (and not a group) then
|
||||
# the invoked subcommand is None.
|
||||
ctx.invoked_subcommand = None
|
||||
injected = hooked_wrapped_callback(self, ctx, self.callback)
|
||||
yield from injected(*ctx.args, **ctx.kwargs)
|
||||
await injected(*ctx.args, **ctx.kwargs)
|
||||
|
||||
@asyncio.coroutine
|
||||
def reinvoke(self, ctx, *, call_hooks=False):
|
||||
async def reinvoke(self, ctx, *, call_hooks=False):
|
||||
ctx.command = self
|
||||
yield from self._parse_arguments(ctx)
|
||||
await self._parse_arguments(ctx)
|
||||
|
||||
if call_hooks:
|
||||
yield from self.call_before_hooks(ctx)
|
||||
await self.call_before_hooks(ctx)
|
||||
|
||||
ctx.invoked_subcommand = None
|
||||
try:
|
||||
yield from self.callback(*ctx.args, **ctx.kwargs)
|
||||
await self.callback(*ctx.args, **ctx.kwargs)
|
||||
except:
|
||||
ctx.command_failed = True
|
||||
raise
|
||||
finally:
|
||||
if call_hooks:
|
||||
yield from self.call_after_hooks(ctx)
|
||||
await self.call_after_hooks(ctx)
|
||||
|
||||
def error(self, coro):
|
||||
"""A decorator that registers a coroutine as a local error handler.
|
||||
@ -667,8 +655,7 @@ class Command:
|
||||
|
||||
return ' '.join(result)
|
||||
|
||||
@asyncio.coroutine
|
||||
def can_run(self, ctx):
|
||||
async def can_run(self, ctx):
|
||||
"""|coro|
|
||||
|
||||
Checks if the command can be executed by checking all the predicates
|
||||
@ -695,7 +682,7 @@ class Command:
|
||||
ctx.command = self
|
||||
|
||||
try:
|
||||
if not (yield from ctx.bot.can_run(ctx)):
|
||||
if not (await ctx.bot.can_run(ctx)):
|
||||
raise CheckFailure('The global check functions for command {0.qualified_name} failed.'.format(self))
|
||||
|
||||
cog = self.instance
|
||||
@ -705,7 +692,7 @@ class Command:
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
ret = yield from discord.utils.maybe_coroutine(local_check, ctx)
|
||||
ret = await discord.utils.maybe_coroutine(local_check, ctx)
|
||||
if not ret:
|
||||
return False
|
||||
|
||||
@ -714,7 +701,7 @@ class Command:
|
||||
# since we have no checks, then we just return True.
|
||||
return True
|
||||
|
||||
return (yield from discord.utils.async_all(predicate(ctx) for predicate in predicates))
|
||||
return (await discord.utils.async_all(predicate(ctx) for predicate in predicates))
|
||||
finally:
|
||||
ctx.command = original
|
||||
|
||||
@ -903,11 +890,10 @@ class Group(GroupMixin, Command):
|
||||
self.invoke_without_command = attrs.pop('invoke_without_command', False)
|
||||
super().__init__(**attrs)
|
||||
|
||||
@asyncio.coroutine
|
||||
def invoke(self, ctx):
|
||||
async def invoke(self, ctx):
|
||||
early_invoke = not self.invoke_without_command
|
||||
if early_invoke:
|
||||
yield from self.prepare(ctx)
|
||||
await self.prepare(ctx)
|
||||
|
||||
view = ctx.view
|
||||
previous = view.index
|
||||
@ -920,26 +906,25 @@ class Group(GroupMixin, Command):
|
||||
|
||||
if early_invoke:
|
||||
injected = hooked_wrapped_callback(self, ctx, self.callback)
|
||||
yield from injected(*ctx.args, **ctx.kwargs)
|
||||
await injected(*ctx.args, **ctx.kwargs)
|
||||
|
||||
if trigger and ctx.invoked_subcommand:
|
||||
ctx.invoked_with = trigger
|
||||
yield from ctx.invoked_subcommand.invoke(ctx)
|
||||
await ctx.invoked_subcommand.invoke(ctx)
|
||||
elif not early_invoke:
|
||||
# undo the trigger parsing
|
||||
view.index = previous
|
||||
view.previous = previous
|
||||
yield from super().invoke(ctx)
|
||||
await super().invoke(ctx)
|
||||
|
||||
@asyncio.coroutine
|
||||
def reinvoke(self, ctx, *, call_hooks=False):
|
||||
async def reinvoke(self, ctx, *, call_hooks=False):
|
||||
early_invoke = not self.invoke_without_command
|
||||
if early_invoke:
|
||||
ctx.command = self
|
||||
yield from self._parse_arguments(ctx)
|
||||
await self._parse_arguments(ctx)
|
||||
|
||||
if call_hooks:
|
||||
yield from self.call_before_hooks(ctx)
|
||||
await self.call_before_hooks(ctx)
|
||||
|
||||
view = ctx.view
|
||||
previous = view.index
|
||||
@ -952,22 +937,22 @@ class Group(GroupMixin, Command):
|
||||
|
||||
if early_invoke:
|
||||
try:
|
||||
yield from self.callback(*ctx.args, **ctx.kwargs)
|
||||
await self.callback(*ctx.args, **ctx.kwargs)
|
||||
except:
|
||||
ctx.command_failed = True
|
||||
raise
|
||||
finally:
|
||||
if call_hooks:
|
||||
yield from self.call_after_hooks(ctx)
|
||||
await self.call_after_hooks(ctx)
|
||||
|
||||
if trigger and ctx.invoked_subcommand:
|
||||
ctx.invoked_with = trigger
|
||||
yield from ctx.invoked_subcommand.reinvoke(ctx, call_hooks=call_hooks)
|
||||
await ctx.invoked_subcommand.reinvoke(ctx, call_hooks=call_hooks)
|
||||
elif not early_invoke:
|
||||
# undo the trigger parsing
|
||||
view.index = previous
|
||||
view.previous = previous
|
||||
yield from super().reinvoke(ctx, call_hooks=call_hooks)
|
||||
await super().reinvoke(ctx, call_hooks=call_hooks)
|
||||
|
||||
# Decorators
|
||||
|
||||
@ -1279,9 +1264,8 @@ def is_owner():
|
||||
from :exc:`.CheckFailure`.
|
||||
"""
|
||||
|
||||
@asyncio.coroutine
|
||||
def predicate(ctx):
|
||||
if not (yield from ctx.bot.is_owner(ctx.author)):
|
||||
async def predicate(ctx):
|
||||
if not (await ctx.bot.is_owner(ctx.author)):
|
||||
raise NotOwner('You do not own this bot.')
|
||||
return True
|
||||
|
||||
|
@ -199,8 +199,7 @@ class HelpFormatter:
|
||||
return "Type {0}{1} command for more info on a command.\n" \
|
||||
"You can also type {0}{1} category for more info on a category.".format(self.clean_prefix, command_name)
|
||||
|
||||
@asyncio.coroutine
|
||||
def filter_command_list(self):
|
||||
async def filter_command_list(self):
|
||||
"""Returns a filtered list of commands based on the two attributes
|
||||
provided, :attr:`show_check_failure` and :attr:`show_hidden`.
|
||||
Also filters based on if :meth:`~.HelpFormatter.is_cog` is valid.
|
||||
@ -224,14 +223,13 @@ class HelpFormatter:
|
||||
|
||||
return True
|
||||
|
||||
@asyncio.coroutine
|
||||
def predicate(tup):
|
||||
async def predicate(tup):
|
||||
if sane_no_suspension_point_predicate(tup) is False:
|
||||
return False
|
||||
|
||||
cmd = tup[1]
|
||||
try:
|
||||
return (yield from cmd.can_run(self.context))
|
||||
return (await cmd.can_run(self.context))
|
||||
except CommandError:
|
||||
return False
|
||||
|
||||
@ -242,7 +240,7 @@ class HelpFormatter:
|
||||
# Gotta run every check and verify it
|
||||
ret = []
|
||||
for elem in iterator:
|
||||
valid = yield from predicate(elem)
|
||||
valid = await predicate(elem)
|
||||
if valid:
|
||||
ret.append(elem)
|
||||
|
||||
@ -258,8 +256,7 @@ class HelpFormatter:
|
||||
shortened = self.shorten(entry)
|
||||
self._paginator.add_line(shortened)
|
||||
|
||||
@asyncio.coroutine
|
||||
def format_help_for(self, context, command_or_bot):
|
||||
async def format_help_for(self, context, command_or_bot):
|
||||
"""Formats the help page and handles the actual heavy lifting of how
|
||||
the help command looks like. To change the behaviour, override the
|
||||
:meth:`~.HelpFormatter.format` method.
|
||||
@ -278,10 +275,9 @@ class HelpFormatter:
|
||||
"""
|
||||
self.context = context
|
||||
self.command = command_or_bot
|
||||
return (yield from self.format())
|
||||
return (await self.format())
|
||||
|
||||
@asyncio.coroutine
|
||||
def format(self):
|
||||
async def format(self):
|
||||
"""Handles the actual behaviour involved with formatting.
|
||||
|
||||
To change the behaviour, this method should be overridden.
|
||||
@ -323,7 +319,7 @@ class HelpFormatter:
|
||||
# last place sorting position.
|
||||
return cog + ':' if cog is not None else '\u200bNo Category:'
|
||||
|
||||
filtered = yield from self.filter_command_list()
|
||||
filtered = await self.filter_command_list()
|
||||
if self.is_bot():
|
||||
data = sorted(filtered, key=category)
|
||||
for category, commands in itertools.groupby(data, key=category):
|
||||
|
@ -71,7 +71,7 @@ class KeepAliveHandler(threading.Thread):
|
||||
if self._last_ack + self.heartbeat_timeout < time.monotonic():
|
||||
log.warn("Shard ID %s has stopped responding to the gateway. Closing and restarting." % self.shard_id)
|
||||
coro = self.ws.close(4000)
|
||||
f = compat.run_coroutine_threadsafe(coro, loop=self.ws.loop)
|
||||
f = asyncio.run_coroutine_threadsafe(coro, loop=self.ws.loop)
|
||||
|
||||
try:
|
||||
f.result()
|
||||
@ -84,7 +84,7 @@ class KeepAliveHandler(threading.Thread):
|
||||
data = self.get_payload()
|
||||
log.debug(self.msg, data['d'])
|
||||
coro = self.ws.send_as_json(data)
|
||||
f = compat.run_coroutine_threadsafe(coro, loop=self.ws.loop)
|
||||
f = asyncio.run_coroutine_threadsafe(coro, loop=self.ws.loop)
|
||||
try:
|
||||
# block until sending is complete
|
||||
f.result()
|
||||
@ -190,14 +190,13 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
|
||||
self._buffer = bytearray()
|
||||
|
||||
@classmethod
|
||||
@asyncio.coroutine
|
||||
def from_client(cls, client, *, shard_id=None, session=None, sequence=None, resume=False):
|
||||
async def from_client(cls, client, *, shard_id=None, session=None, sequence=None, resume=False):
|
||||
"""Creates a main websocket for Discord from a :class:`Client`.
|
||||
|
||||
This is for internal use only.
|
||||
"""
|
||||
gateway = yield from client.http.get_gateway()
|
||||
ws = yield from websockets.connect(gateway, loop=client.loop, klass=cls)
|
||||
gateway = await client.http.get_gateway()
|
||||
ws = await websockets.connect(gateway, loop=client.loop, klass=cls)
|
||||
|
||||
# dynamically add attributes needed
|
||||
ws.token = client.http.token
|
||||
@ -215,19 +214,19 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
|
||||
log.info('Created websocket connected to %s', gateway)
|
||||
|
||||
# poll event for OP Hello
|
||||
yield from ws.poll_event()
|
||||
await ws.poll_event()
|
||||
|
||||
if not resume:
|
||||
yield from ws.identify()
|
||||
await ws.identify()
|
||||
return ws
|
||||
|
||||
yield from ws.resume()
|
||||
await ws.resume()
|
||||
try:
|
||||
yield from ws.ensure_open()
|
||||
await ws.ensure_open()
|
||||
except websockets.exceptions.ConnectionClosed:
|
||||
# ws got closed so let's just do a regular IDENTIFY connect.
|
||||
log.info('RESUME failed (the websocket decided to close) for Shard ID %s. Retrying.', shard_id)
|
||||
return (yield from cls.from_client(client, shard_id=shard_id))
|
||||
return (await cls.from_client(client, shard_id=shard_id))
|
||||
else:
|
||||
return ws
|
||||
|
||||
@ -251,13 +250,12 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
|
||||
A future to wait for.
|
||||
"""
|
||||
|
||||
future = compat.create_future(self.loop)
|
||||
future = self.loop.create_future()
|
||||
entry = EventListener(event=event, predicate=predicate, result=result, future=future)
|
||||
self._dispatch_listeners.append(entry)
|
||||
return future
|
||||
|
||||
@asyncio.coroutine
|
||||
def identify(self):
|
||||
async def identify(self):
|
||||
"""Sends the IDENTIFY packet."""
|
||||
payload = {
|
||||
'op': self.IDENTIFY,
|
||||
@ -291,11 +289,10 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
|
||||
'afk': False
|
||||
}
|
||||
|
||||
yield from self.send_as_json(payload)
|
||||
await self.send_as_json(payload)
|
||||
log.info('Shard ID %s has sent the IDENTIFY payload.', self.shard_id)
|
||||
|
||||
@asyncio.coroutine
|
||||
def resume(self):
|
||||
async def resume(self):
|
||||
"""Sends the RESUME packet."""
|
||||
payload = {
|
||||
'op': self.RESUME,
|
||||
@ -306,11 +303,10 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
|
||||
}
|
||||
}
|
||||
|
||||
yield from self.send_as_json(payload)
|
||||
await self.send_as_json(payload)
|
||||
log.info('Shard ID %s has sent the RESUME payload.', self.shard_id)
|
||||
|
||||
@asyncio.coroutine
|
||||
def received_message(self, msg):
|
||||
async def received_message(self, msg):
|
||||
self._dispatch('socket_raw_receive', msg)
|
||||
|
||||
if isinstance(msg, bytes):
|
||||
@ -342,7 +338,7 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
|
||||
# so we terminate our connection and raise an
|
||||
# internal exception signalling to reconnect.
|
||||
log.info('Received RECONNECT opcode.')
|
||||
yield from self.close()
|
||||
await self.close()
|
||||
raise ResumeWebSocket(self.shard_id)
|
||||
|
||||
if op == self.HEARTBEAT_ACK:
|
||||
@ -351,27 +347,27 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
|
||||
|
||||
if op == self.HEARTBEAT:
|
||||
beat = self._keep_alive.get_payload()
|
||||
yield from self.send_as_json(beat)
|
||||
await self.send_as_json(beat)
|
||||
return
|
||||
|
||||
if op == self.HELLO:
|
||||
interval = data['heartbeat_interval'] / 1000.0
|
||||
self._keep_alive = KeepAliveHandler(ws=self, interval=interval, shard_id=self.shard_id)
|
||||
# send a heartbeat immediately
|
||||
yield from self.send_as_json(self._keep_alive.get_payload())
|
||||
await self.send_as_json(self._keep_alive.get_payload())
|
||||
self._keep_alive.start()
|
||||
return
|
||||
|
||||
if op == self.INVALIDATE_SESSION:
|
||||
if data == True:
|
||||
yield from asyncio.sleep(5.0, loop=self.loop)
|
||||
yield from self.close()
|
||||
await asyncio.sleep(5.0, loop=self.loop)
|
||||
await self.close()
|
||||
raise ResumeWebSocket(self.shard_id)
|
||||
|
||||
self.sequence = None
|
||||
self.session_id = None
|
||||
log.info('Shard ID %s session has been invalidated.' % self.shard_id)
|
||||
yield from self.identify()
|
||||
await self.identify()
|
||||
return
|
||||
|
||||
if op != self.DISPATCH:
|
||||
@ -435,8 +431,7 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
|
||||
def _can_handle_close(self, code):
|
||||
return code not in (1000, 4004, 4010, 4011)
|
||||
|
||||
@asyncio.coroutine
|
||||
def poll_event(self):
|
||||
async def poll_event(self):
|
||||
"""Polls for a DISPATCH event and handles the general gateway loop.
|
||||
|
||||
Raises
|
||||
@ -445,8 +440,8 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
|
||||
The websocket connection was terminated for unhandled reasons.
|
||||
"""
|
||||
try:
|
||||
msg = yield from self.recv()
|
||||
yield from self.received_message(msg)
|
||||
msg = await self.recv()
|
||||
await self.received_message(msg)
|
||||
except websockets.exceptions.ConnectionClosed as e:
|
||||
if self._can_handle_close(e.code):
|
||||
log.info('Websocket closed with %s (%s), attempting a reconnect.', e.code, e.reason)
|
||||
@ -455,21 +450,18 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
|
||||
log.info('Websocket closed with %s (%s), cannot reconnect.', e.code, e.reason)
|
||||
raise ConnectionClosed(e, shard_id=self.shard_id) from e
|
||||
|
||||
@asyncio.coroutine
|
||||
def send(self, data):
|
||||
async def send(self, data):
|
||||
self._dispatch('socket_raw_send', data)
|
||||
yield from super().send(data)
|
||||
await super().send(data)
|
||||
|
||||
@asyncio.coroutine
|
||||
def send_as_json(self, data):
|
||||
async def send_as_json(self, data):
|
||||
try:
|
||||
yield from super().send(utils.to_json(data))
|
||||
await super().send(utils.to_json(data))
|
||||
except websockets.exceptions.ConnectionClosed as e:
|
||||
if not self._can_handle_close(e.code):
|
||||
raise ConnectionClosed(e, shard_id=self.shard_id) from e
|
||||
|
||||
@asyncio.coroutine
|
||||
def change_presence(self, *, activity=None, status=None, afk=False, since=0.0):
|
||||
async def change_presence(self, *, activity=None, status=None, afk=False, since=0.0):
|
||||
if activity is not None:
|
||||
if not isinstance(activity, _ActivityTag):
|
||||
raise InvalidArgument('activity must be one of Game, Streaming, or Activity.')
|
||||
@ -490,18 +482,16 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
|
||||
|
||||
sent = utils.to_json(payload)
|
||||
log.debug('Sending "%s" to change status', sent)
|
||||
yield from self.send(sent)
|
||||
await self.send(sent)
|
||||
|
||||
@asyncio.coroutine
|
||||
def request_sync(self, guild_ids):
|
||||
async def request_sync(self, guild_ids):
|
||||
payload = {
|
||||
'op': self.GUILD_SYNC,
|
||||
'd': list(guild_ids)
|
||||
}
|
||||
yield from self.send_as_json(payload)
|
||||
await self.send_as_json(payload)
|
||||
|
||||
@asyncio.coroutine
|
||||
def voice_state(self, guild_id, channel_id, self_mute=False, self_deaf=False):
|
||||
async def voice_state(self, guild_id, channel_id, self_mute=False, self_deaf=False):
|
||||
payload = {
|
||||
'op': self.VOICE_STATE,
|
||||
'd': {
|
||||
@ -513,14 +503,13 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
|
||||
}
|
||||
|
||||
log.debug('Updating our voice state to %s.', payload)
|
||||
yield from self.send_as_json(payload)
|
||||
await self.send_as_json(payload)
|
||||
|
||||
@asyncio.coroutine
|
||||
def close_connection(self, *args, **kwargs):
|
||||
async def close_connection(self, *args, **kwargs):
|
||||
if self._keep_alive:
|
||||
self._keep_alive.stop()
|
||||
|
||||
yield from super().close_connection(*args, **kwargs)
|
||||
await super().close_connection(*args, **kwargs)
|
||||
|
||||
class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
|
||||
"""Implements the websocket protocol for handling voice connections.
|
||||
@ -565,13 +554,11 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
|
||||
self.max_size = None
|
||||
self._keep_alive = None
|
||||
|
||||
@asyncio.coroutine
|
||||
def send_as_json(self, data):
|
||||
async def send_as_json(self, data):
|
||||
log.debug('Sending voice websocket frame: %s.', data)
|
||||
yield from self.send(utils.to_json(data))
|
||||
await self.send(utils.to_json(data))
|
||||
|
||||
@asyncio.coroutine
|
||||
def resume(self):
|
||||
async def resume(self):
|
||||
state = self._connection
|
||||
payload = {
|
||||
'op': self.RESUME,
|
||||
@ -581,10 +568,9 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
|
||||
'session_id': state.session_id
|
||||
}
|
||||
}
|
||||
yield from self.send_as_json(payload)
|
||||
await self.send_as_json(payload)
|
||||
|
||||
@asyncio.coroutine
|
||||
def identify(self):
|
||||
async def identify(self):
|
||||
state = self._connection
|
||||
payload = {
|
||||
'op': self.IDENTIFY,
|
||||
@ -595,27 +581,25 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
|
||||
'token': state.token
|
||||
}
|
||||
}
|
||||
yield from self.send_as_json(payload)
|
||||
await self.send_as_json(payload)
|
||||
|
||||
@classmethod
|
||||
@asyncio.coroutine
|
||||
def from_client(cls, client, *, resume=False):
|
||||
async def from_client(cls, client, *, resume=False):
|
||||
"""Creates a voice websocket for the :class:`VoiceClient`."""
|
||||
gateway = 'wss://' + client.endpoint + '/?v=3'
|
||||
ws = yield from websockets.connect(gateway, loop=client.loop, klass=cls)
|
||||
ws = await websockets.connect(gateway, loop=client.loop, klass=cls)
|
||||
ws.gateway = gateway
|
||||
ws._connection = client
|
||||
ws._max_heartbeat_timeout = 60.0
|
||||
|
||||
if resume:
|
||||
yield from ws.resume()
|
||||
await ws.resume()
|
||||
else:
|
||||
yield from ws.identify()
|
||||
await ws.identify()
|
||||
|
||||
return ws
|
||||
|
||||
@asyncio.coroutine
|
||||
def select_protocol(self, ip, port):
|
||||
async def select_protocol(self, ip, port):
|
||||
payload = {
|
||||
'op': self.SELECT_PROTOCOL,
|
||||
'd': {
|
||||
@ -628,10 +612,9 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
|
||||
}
|
||||
}
|
||||
|
||||
yield from self.send_as_json(payload)
|
||||
await self.send_as_json(payload)
|
||||
|
||||
@asyncio.coroutine
|
||||
def speak(self, is_speaking=True):
|
||||
async def speak(self, is_speaking=True):
|
||||
payload = {
|
||||
'op': self.SPEAKING,
|
||||
'd': {
|
||||
@ -640,10 +623,9 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
|
||||
}
|
||||
}
|
||||
|
||||
yield from self.send_as_json(payload)
|
||||
await self.send_as_json(payload)
|
||||
|
||||
@asyncio.coroutine
|
||||
def received_message(self, msg):
|
||||
async def received_message(self, msg):
|
||||
log.debug('Voice websocket frame received: %s', msg)
|
||||
op = msg['op']
|
||||
data = msg.get('d')
|
||||
@ -652,17 +634,16 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
|
||||
interval = data['heartbeat_interval'] / 1000.0
|
||||
self._keep_alive = VoiceKeepAliveHandler(ws=self, interval=interval)
|
||||
self._keep_alive.start()
|
||||
yield from self.initial_connection(data)
|
||||
await self.initial_connection(data)
|
||||
elif op == self.HEARTBEAT_ACK:
|
||||
self._keep_alive.ack()
|
||||
elif op == self.INVALIDATE_SESSION:
|
||||
log.info('Voice RESUME failed.')
|
||||
yield from self.identify()
|
||||
await self.identify()
|
||||
elif op == self.SESSION_DESCRIPTION:
|
||||
yield from self.load_secret_key(data)
|
||||
await self.load_secret_key(data)
|
||||
|
||||
@asyncio.coroutine
|
||||
def initial_connection(self, data):
|
||||
async def initial_connection(self, data):
|
||||
state = self._connection
|
||||
state.ssrc = data['ssrc']
|
||||
state.voice_port = data['port']
|
||||
@ -670,7 +651,7 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
|
||||
packet = bytearray(70)
|
||||
struct.pack_into('>I', packet, 0, state.ssrc)
|
||||
state.socket.sendto(packet, (state.endpoint_ip, state.voice_port))
|
||||
recv = yield from self.loop.sock_recv(state.socket, 70)
|
||||
recv = await self.loop.sock_recv(state.socket, 70)
|
||||
log.debug('received packet in initial_connection: %s', recv)
|
||||
|
||||
# the ip is ascii starting at the 4th byte and ending at the first null
|
||||
@ -683,28 +664,25 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
|
||||
state.port = struct.unpack_from('<H', recv, len(recv) - 2)[0]
|
||||
|
||||
log.debug('detected ip: %s port: %s', state.ip, state.port)
|
||||
yield from self.select_protocol(state.ip, state.port)
|
||||
await self.select_protocol(state.ip, state.port)
|
||||
log.info('selected the voice protocol for use')
|
||||
|
||||
@asyncio.coroutine
|
||||
def load_secret_key(self, data):
|
||||
async def load_secret_key(self, data):
|
||||
log.info('received secret key for voice connection')
|
||||
self._connection.secret_key = data.get('secret_key')
|
||||
yield from self.speak()
|
||||
await self.speak()
|
||||
|
||||
@asyncio.coroutine
|
||||
def poll_event(self):
|
||||
async def poll_event(self):
|
||||
try:
|
||||
msg = yield from asyncio.wait_for(self.recv(), timeout=30.0, loop=self.loop)
|
||||
yield from self.received_message(json.loads(msg))
|
||||
msg = await asyncio.wait_for(self.recv(), timeout=30.0, loop=self.loop)
|
||||
await self.received_message(json.loads(msg))
|
||||
except websockets.exceptions.ConnectionClosed as e:
|
||||
raise ConnectionClosed(e, shard_id=None) from e
|
||||
|
||||
@asyncio.coroutine
|
||||
def close_connection(self, *args, **kwargs):
|
||||
async def close_connection(self, *args, **kwargs):
|
||||
if self._keep_alive:
|
||||
self._keep_alive.stop()
|
||||
|
||||
yield from super().close_connection(*args, **kwargs)
|
||||
await super().close_connection(*args, **kwargs)
|
||||
|
||||
|
||||
|
@ -543,8 +543,7 @@ class Guild(Hashable):
|
||||
return self._state.http.create_channel(self.id, name, channel_type.value, parent_id=parent_id,
|
||||
permission_overwrites=perms, reason=reason)
|
||||
|
||||
@asyncio.coroutine
|
||||
def create_text_channel(self, name, *, overwrites=None, category=None, reason=None):
|
||||
async def create_text_channel(self, name, *, overwrites=None, category=None, reason=None):
|
||||
"""|coro|
|
||||
|
||||
Creates a :class:`TextChannel` for the guild.
|
||||
@ -606,28 +605,26 @@ class Guild(Hashable):
|
||||
:class:`TextChannel`
|
||||
The channel that was just created.
|
||||
"""
|
||||
data = yield from self._create_channel(name, overwrites, ChannelType.text, category, reason=reason)
|
||||
data = await self._create_channel(name, overwrites, ChannelType.text, category, reason=reason)
|
||||
channel = TextChannel(state=self._state, guild=self, data=data)
|
||||
|
||||
# temporarily add to the cache
|
||||
self._channels[channel.id] = channel
|
||||
return channel
|
||||
|
||||
@asyncio.coroutine
|
||||
def create_voice_channel(self, name, *, overwrites=None, category=None, reason=None):
|
||||
async def create_voice_channel(self, name, *, overwrites=None, category=None, reason=None):
|
||||
"""|coro|
|
||||
|
||||
Same as :meth:`create_text_channel` except makes a :class:`VoiceChannel` instead.
|
||||
"""
|
||||
data = yield from self._create_channel(name, overwrites, ChannelType.voice, category, reason=reason)
|
||||
data = await self._create_channel(name, overwrites, ChannelType.voice, category, reason=reason)
|
||||
channel = VoiceChannel(state=self._state, guild=self, data=data)
|
||||
|
||||
# temporarily add to the cache
|
||||
self._channels[channel.id] = channel
|
||||
return channel
|
||||
|
||||
@asyncio.coroutine
|
||||
def create_category(self, name, *, overwrites=None, reason=None):
|
||||
async def create_category(self, name, *, overwrites=None, reason=None):
|
||||
"""|coro|
|
||||
|
||||
Same as :meth:`create_text_channel` except makes a :class:`CategoryChannel` instead.
|
||||
@ -637,7 +634,7 @@ class Guild(Hashable):
|
||||
The ``category`` parameter is not supported in this function since categories
|
||||
cannot have categories.
|
||||
"""
|
||||
data = yield from self._create_channel(name, overwrites, ChannelType.category, reason=reason)
|
||||
data = await self._create_channel(name, overwrites, ChannelType.category, reason=reason)
|
||||
channel = CategoryChannel(state=self._state, guild=self, data=data)
|
||||
|
||||
# temporarily add to the cache
|
||||
@ -646,8 +643,7 @@ class Guild(Hashable):
|
||||
|
||||
create_category_channel = create_category
|
||||
|
||||
@asyncio.coroutine
|
||||
def leave(self):
|
||||
async def leave(self):
|
||||
"""|coro|
|
||||
|
||||
Leaves the guild.
|
||||
@ -662,10 +658,9 @@ class Guild(Hashable):
|
||||
HTTPException
|
||||
Leaving the guild failed.
|
||||
"""
|
||||
yield from self._state.http.leave_guild(self.id)
|
||||
await self._state.http.leave_guild(self.id)
|
||||
|
||||
@asyncio.coroutine
|
||||
def delete(self):
|
||||
async def delete(self):
|
||||
"""|coro|
|
||||
|
||||
Deletes the guild. You must be the guild owner to delete the
|
||||
@ -679,10 +674,9 @@ class Guild(Hashable):
|
||||
You do not have permissions to delete the guild.
|
||||
"""
|
||||
|
||||
yield from self._state.http.delete_guild(self.id)
|
||||
await self._state.http.delete_guild(self.id)
|
||||
|
||||
@asyncio.coroutine
|
||||
def edit(self, *, reason=None, **fields):
|
||||
async def edit(self, *, reason=None, **fields):
|
||||
"""|coro|
|
||||
|
||||
Edits the guild.
|
||||
@ -748,7 +742,7 @@ class Guild(Hashable):
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
yield from http.change_vanity_code(self.id, vanity_code, reason=reason)
|
||||
await http.change_vanity_code(self.id, vanity_code, reason=reason)
|
||||
|
||||
try:
|
||||
splash_bytes = fields['splash']
|
||||
@ -798,7 +792,7 @@ class Guild(Hashable):
|
||||
|
||||
fields['verification_level'] = level.value
|
||||
|
||||
yield from http.edit_guild(self.id, reason=reason, **fields)
|
||||
await http.edit_guild(self.id, reason=reason, **fields)
|
||||
|
||||
@asyncio.coroutine
|
||||
def get_ban(self, user):
|
||||
@ -836,8 +830,7 @@ class Guild(Hashable):
|
||||
reason=data['reason']
|
||||
)
|
||||
|
||||
@asyncio.coroutine
|
||||
def bans(self):
|
||||
async def bans(self):
|
||||
"""|coro|
|
||||
|
||||
Retrieves all the users that are banned from the guild.
|
||||
@ -863,13 +856,12 @@ class Guild(Hashable):
|
||||
A list of BanEntry objects.
|
||||
"""
|
||||
|
||||
data = yield from self._state.http.get_bans(self.id)
|
||||
data = await self._state.http.get_bans(self.id)
|
||||
return [BanEntry(user=User(state=self._state, data=e['user']),
|
||||
reason=e['reason'])
|
||||
for e in data]
|
||||
|
||||
@asyncio.coroutine
|
||||
def prune_members(self, *, days, reason=None):
|
||||
async def prune_members(self, *, days, reason=None):
|
||||
"""|coro|
|
||||
|
||||
Prunes the guild from its inactive members.
|
||||
@ -908,11 +900,10 @@ class Guild(Hashable):
|
||||
if not isinstance(days, int):
|
||||
raise InvalidArgument('Expected int for ``days``, received {0.__class__.__name__} instead.'.format(days))
|
||||
|
||||
data = yield from self._state.http.prune_members(self.id, days, reason=reason)
|
||||
data = await self._state.http.prune_members(self.id, days, reason=reason)
|
||||
return data['pruned']
|
||||
|
||||
@asyncio.coroutine
|
||||
def webhooks(self):
|
||||
async def webhooks(self):
|
||||
"""|coro|
|
||||
|
||||
Gets the list of webhooks from this guild.
|
||||
@ -930,11 +921,10 @@ class Guild(Hashable):
|
||||
The webhooks for this guild.
|
||||
"""
|
||||
|
||||
data = yield from self._state.http.guild_webhooks(self.id)
|
||||
data = await self._state.http.guild_webhooks(self.id)
|
||||
return [Webhook.from_state(d, state=self._state) for d in data]
|
||||
|
||||
@asyncio.coroutine
|
||||
def estimate_pruned_members(self, *, days):
|
||||
async def estimate_pruned_members(self, *, days):
|
||||
"""|coro|
|
||||
|
||||
Similar to :meth:`prune_members` except instead of actually
|
||||
@ -964,11 +954,10 @@ class Guild(Hashable):
|
||||
if not isinstance(days, int):
|
||||
raise InvalidArgument('Expected int for ``days``, received {0.__class__.__name__} instead.'.format(days))
|
||||
|
||||
data = yield from self._state.http.estimate_pruned_members(self.id, days)
|
||||
data = await self._state.http.estimate_pruned_members(self.id, days)
|
||||
return data['pruned']
|
||||
|
||||
@asyncio.coroutine
|
||||
def invites(self):
|
||||
async def invites(self):
|
||||
"""|coro|
|
||||
|
||||
Returns a list of all active instant invites from the guild.
|
||||
@ -989,7 +978,7 @@ class Guild(Hashable):
|
||||
The list of invites that are currently active.
|
||||
"""
|
||||
|
||||
data = yield from self._state.http.invites_from(self.id)
|
||||
data = await self._state.http.invites_from(self.id)
|
||||
result = []
|
||||
for invite in data:
|
||||
channel = self.get_channel(int(invite['channel']['id']))
|
||||
@ -999,8 +988,7 @@ class Guild(Hashable):
|
||||
|
||||
return result
|
||||
|
||||
@asyncio.coroutine
|
||||
def create_custom_emoji(self, *, name, image, reason=None):
|
||||
async def create_custom_emoji(self, *, name, image, reason=None):
|
||||
"""|coro|
|
||||
|
||||
Creates a custom :class:`Emoji` for the guild.
|
||||
@ -1036,11 +1024,10 @@ class Guild(Hashable):
|
||||
"""
|
||||
|
||||
img = utils._bytes_to_base64_data(image)
|
||||
data = yield from self._state.http.create_custom_emoji(self.id, name, img, reason=reason)
|
||||
data = await self._state.http.create_custom_emoji(self.id, name, img, reason=reason)
|
||||
return self._state.store_emoji(self, data)
|
||||
|
||||
@asyncio.coroutine
|
||||
def create_role(self, *, reason=None, **fields):
|
||||
async def create_role(self, *, reason=None, **fields):
|
||||
"""|coro|
|
||||
|
||||
Creates a :class:`Role` for the guild.
|
||||
@ -1102,14 +1089,13 @@ class Guild(Hashable):
|
||||
if key not in valid_keys:
|
||||
raise InvalidArgument('%r is not a valid field.' % key)
|
||||
|
||||
data = yield from self._state.http.create_role(self.id, reason=reason, **fields)
|
||||
data = await self._state.http.create_role(self.id, reason=reason, **fields)
|
||||
role = Role(guild=self, data=data, state=self._state)
|
||||
|
||||
# TODO: add to cache
|
||||
return role
|
||||
|
||||
@asyncio.coroutine
|
||||
def kick(self, user, *, reason=None):
|
||||
async def kick(self, user, *, reason=None):
|
||||
"""|coro|
|
||||
|
||||
Kicks a user from the guild.
|
||||
@ -1133,10 +1119,9 @@ class Guild(Hashable):
|
||||
HTTPException
|
||||
Kicking failed.
|
||||
"""
|
||||
yield from self._state.http.kick(user.id, self.id, reason=reason)
|
||||
await self._state.http.kick(user.id, self.id, reason=reason)
|
||||
|
||||
@asyncio.coroutine
|
||||
def ban(self, user, *, reason=None, delete_message_days=1):
|
||||
async def ban(self, user, *, reason=None, delete_message_days=1):
|
||||
"""|coro|
|
||||
|
||||
Bans a user from the guild.
|
||||
@ -1163,10 +1148,9 @@ class Guild(Hashable):
|
||||
HTTPException
|
||||
Banning failed.
|
||||
"""
|
||||
yield from self._state.http.ban(user.id, self.id, delete_message_days, reason=reason)
|
||||
await self._state.http.ban(user.id, self.id, delete_message_days, reason=reason)
|
||||
|
||||
@asyncio.coroutine
|
||||
def unban(self, user, *, reason=None):
|
||||
async def unban(self, user, *, reason=None):
|
||||
"""|coro|
|
||||
|
||||
Unbans a user from the guild.
|
||||
@ -1190,10 +1174,9 @@ class Guild(Hashable):
|
||||
HTTPException
|
||||
Unbanning failed.
|
||||
"""
|
||||
yield from self._state.http.unban(user.id, self.id, reason=reason)
|
||||
await self._state.http.unban(user.id, self.id, reason=reason)
|
||||
|
||||
@asyncio.coroutine
|
||||
def vanity_invite(self):
|
||||
async def vanity_invite(self):
|
||||
"""|coro|
|
||||
|
||||
Returns the guild's special vanity invite.
|
||||
@ -1218,11 +1201,11 @@ class Guild(Hashable):
|
||||
"""
|
||||
|
||||
# we start with { code: abc }
|
||||
payload = yield from self._state.http.get_vanity_code(self.id)
|
||||
payload = await self._state.http.get_vanity_code(self.id)
|
||||
|
||||
# get the vanity URL channel since default channels aren't
|
||||
# reliable or a thing anymore
|
||||
data = yield from self._state.http.get_invite(payload['code'])
|
||||
data = await self._state.http.get_invite(payload['code'])
|
||||
|
||||
payload['guild'] = self
|
||||
payload['channel'] = self.get_channel(int(data['channel']['id']))
|
||||
|
@ -38,9 +38,8 @@ log = logging.getLogger(__name__)
|
||||
from .errors import HTTPException, Forbidden, NotFound, LoginFailure, GatewayNotFound
|
||||
from . import __version__, utils
|
||||
|
||||
@asyncio.coroutine
|
||||
def json_or_text(response):
|
||||
text = yield from response.text(encoding='utf-8')
|
||||
async def json_or_text(response):
|
||||
text = await response.text(encoding='utf-8')
|
||||
if response.headers['content-type'] == 'application/json':
|
||||
return json.loads(text)
|
||||
return text
|
||||
@ -106,8 +105,7 @@ class HTTPClient:
|
||||
if self._session.closed:
|
||||
self._session = aiohttp.ClientSession(connector=self.connector, loop=self.loop)
|
||||
|
||||
@asyncio.coroutine
|
||||
def request(self, route, *, header_bypass_delay=None, **kwargs):
|
||||
async def request(self, route, *, header_bypass_delay=None, **kwargs):
|
||||
bucket = route.bucket
|
||||
method = route.method
|
||||
url = route.url
|
||||
@ -148,16 +146,16 @@ class HTTPClient:
|
||||
|
||||
if not self._global_over.is_set():
|
||||
# wait until the global lock is complete
|
||||
yield from self._global_over.wait()
|
||||
await self._global_over.wait()
|
||||
|
||||
yield from lock
|
||||
await lock
|
||||
with MaybeUnlock(lock) as maybe_lock:
|
||||
for tries in range(5):
|
||||
r = yield from self._session.request(method, url, **kwargs)
|
||||
log.debug('%s %s with %s has returned %s', method, url, kwargs.get('data'), r.status)
|
||||
try:
|
||||
async with self._session.request(method, url, **kwargs) as r:
|
||||
log.debug('%s %s with %s has returned %s', method, url, kwargs.get('data'), r.status)
|
||||
|
||||
# even errors have text involved in them so this is safe to call
|
||||
data = yield from json_or_text(r)
|
||||
data = await json_or_text(r)
|
||||
|
||||
# check if we have rate limit header information
|
||||
remaining = r.headers.get('X-Ratelimit-Remaining')
|
||||
@ -191,7 +189,7 @@ class HTTPClient:
|
||||
log.info('Global rate limit has been hit. Retrying in %.2f seconds.', retry_after)
|
||||
self._global_over.clear()
|
||||
|
||||
yield from asyncio.sleep(retry_after, loop=self.loop)
|
||||
await asyncio.sleep(retry_after, loop=self.loop)
|
||||
log.debug('Done sleeping for the rate limit. Retrying...')
|
||||
|
||||
# release the global lock now that the
|
||||
@ -204,7 +202,7 @@ class HTTPClient:
|
||||
|
||||
# we've received a 500 or 502, unconditional retry
|
||||
if r.status in {500, 502}:
|
||||
yield from asyncio.sleep(1 + tries * 2, loop=self.loop)
|
||||
await asyncio.sleep(1 + tries * 2, loop=self.loop)
|
||||
continue
|
||||
|
||||
# the usual error cases
|
||||
@ -214,32 +212,25 @@ class HTTPClient:
|
||||
raise NotFound(r, data)
|
||||
else:
|
||||
raise HTTPException(r, data)
|
||||
finally:
|
||||
# clean-up just in case
|
||||
yield from r.release()
|
||||
|
||||
# We've run out of retries, raise.
|
||||
raise HTTPException(r, data)
|
||||
|
||||
@asyncio.coroutine
|
||||
def get_attachment(self, url):
|
||||
resp = yield from self._session.get(url)
|
||||
try:
|
||||
async def get_attachment(self, url):
|
||||
async with self._session.get(url) as resp:
|
||||
if resp.status == 200:
|
||||
return (yield from resp.read())
|
||||
return (await resp.read())
|
||||
elif resp.status == 404:
|
||||
raise NotFound(resp, 'attachment not found')
|
||||
elif resp.status == 403:
|
||||
raise Forbidden(resp, 'cannot retrieve attachment')
|
||||
else:
|
||||
raise HTTPException(resp, 'failed to get attachment')
|
||||
finally:
|
||||
yield from resp.release()
|
||||
|
||||
# state management
|
||||
|
||||
@asyncio.coroutine
|
||||
def close(self):
|
||||
yield from self._session.close()
|
||||
async def close(self):
|
||||
await self._session.close()
|
||||
|
||||
def _token(self, token, *, bot=True):
|
||||
self.token = token
|
||||
@ -248,13 +239,12 @@ class HTTPClient:
|
||||
|
||||
# login management
|
||||
|
||||
@asyncio.coroutine
|
||||
def static_login(self, token, *, bot):
|
||||
async def static_login(self, token, *, bot):
|
||||
old_token, old_bot = self.token, self.bot_token
|
||||
self._token(token, bot=bot)
|
||||
|
||||
try:
|
||||
data = yield from self.request(Route('GET', '/users/@me'))
|
||||
data = await self.request(Route('GET', '/users/@me'))
|
||||
except HTTPException as e:
|
||||
self._token(old_token, bot=old_bot)
|
||||
if e.response.status == 401:
|
||||
@ -349,11 +339,10 @@ class HTTPClient:
|
||||
|
||||
return self.request(r, data=form)
|
||||
|
||||
@asyncio.coroutine
|
||||
def ack_message(self, channel_id, message_id):
|
||||
async def ack_message(self, channel_id, message_id):
|
||||
r = Route('POST', '/channels/{channel_id}/messages/{message_id}/ack', channel_id=channel_id,
|
||||
message_id=message_id)
|
||||
data = yield from self.request(r, json={'token': self._ack_token})
|
||||
data = await self.request(r, json={'token': self._ack_token})
|
||||
self._ack_token = data['token']
|
||||
|
||||
def ack_guild(self, guild_id):
|
||||
@ -751,10 +740,9 @@ class HTTPClient:
|
||||
def application_info(self):
|
||||
return self.request(Route('GET', '/oauth2/applications/@me'))
|
||||
|
||||
@asyncio.coroutine
|
||||
def get_gateway(self, *, encoding='json', v=6, zlib=True):
|
||||
async def get_gateway(self, *, encoding='json', v=6, zlib=True):
|
||||
try:
|
||||
data = yield from self.request(Route('GET', '/gateway'))
|
||||
data = await self.request(Route('GET', '/gateway'))
|
||||
except HTTPException as e:
|
||||
raise GatewayNotFound() from e
|
||||
if zlib:
|
||||
@ -763,10 +751,9 @@ class HTTPClient:
|
||||
value = '{0}?encoding={1}&v={2}'
|
||||
return value.format(data['url'], encoding, v)
|
||||
|
||||
@asyncio.coroutine
|
||||
def get_bot_gateway(self, *, encoding='json', v=6, zlib=True):
|
||||
async def get_bot_gateway(self, *, encoding='json', v=6, zlib=True):
|
||||
try:
|
||||
data = yield from self.request(Route('GET', '/gateway/bot'))
|
||||
data = await self.request(Route('GET', '/gateway/bot'))
|
||||
except HTTPException as e:
|
||||
raise GatewayNotFound() from e
|
||||
|
||||
|
@ -134,8 +134,7 @@ class Invite(Hashable):
|
||||
"""A property that retrieves the invite URL."""
|
||||
return 'http://discord.gg/' + self.code
|
||||
|
||||
@asyncio.coroutine
|
||||
def delete(self, *, reason=None):
|
||||
async def delete(self, *, reason=None):
|
||||
"""|coro|
|
||||
|
||||
Revokes the instant invite.
|
||||
@ -157,4 +156,4 @@ class Invite(Hashable):
|
||||
Revoking the invite failed.
|
||||
"""
|
||||
|
||||
yield from self._state.http.delete_invite(self.code, reason=reason)
|
||||
await self._state.http.delete_invite(self.code, reason=reason)
|
||||
|
@ -33,8 +33,6 @@ from .utils import time_snowflake, maybe_coroutine
|
||||
from .object import Object
|
||||
from .audit_logs import AuditLogEntry
|
||||
|
||||
PY35 = sys.version_info >= (3, 5)
|
||||
|
||||
class _AsyncIterator:
|
||||
__slots__ = ()
|
||||
|
||||
@ -52,15 +50,14 @@ class _AsyncIterator:
|
||||
|
||||
return self.find(predicate)
|
||||
|
||||
@asyncio.coroutine
|
||||
def find(self, predicate):
|
||||
async def find(self, predicate):
|
||||
while True:
|
||||
try:
|
||||
elem = yield from self.next()
|
||||
elem = await self.next()
|
||||
except NoMoreItems:
|
||||
return None
|
||||
|
||||
ret = yield from maybe_coroutine(predicate, elem)
|
||||
ret = await maybe_coroutine(predicate, elem)
|
||||
if ret:
|
||||
return elem
|
||||
|
||||
@ -70,30 +67,26 @@ class _AsyncIterator:
|
||||
def filter(self, predicate):
|
||||
return _FilteredAsyncIterator(self, predicate)
|
||||
|
||||
@asyncio.coroutine
|
||||
def flatten(self):
|
||||
async def flatten(self):
|
||||
ret = []
|
||||
while True:
|
||||
try:
|
||||
item = yield from self.next()
|
||||
item = await self.next()
|
||||
except NoMoreItems:
|
||||
return ret
|
||||
else:
|
||||
ret.append(item)
|
||||
|
||||
if PY35:
|
||||
@asyncio.coroutine
|
||||
def __aiter__(self):
|
||||
return self
|
||||
async def __aiter__(self):
|
||||
return self
|
||||
|
||||
@asyncio.coroutine
|
||||
def __anext__(self):
|
||||
try:
|
||||
msg = yield from self.next()
|
||||
except NoMoreItems:
|
||||
raise StopAsyncIteration()
|
||||
else:
|
||||
return msg
|
||||
async def __anext__(self):
|
||||
try:
|
||||
msg = await self.next()
|
||||
except NoMoreItems:
|
||||
raise StopAsyncIteration()
|
||||
else:
|
||||
return msg
|
||||
|
||||
def _identity(x):
|
||||
return x
|
||||
@ -103,11 +96,10 @@ class _MappedAsyncIterator(_AsyncIterator):
|
||||
self.iterator = iterator
|
||||
self.func = func
|
||||
|
||||
@asyncio.coroutine
|
||||
def next(self):
|
||||
async def next(self):
|
||||
# this raises NoMoreItems and will propagate appropriately
|
||||
item = yield from self.iterator.next()
|
||||
return (yield from maybe_coroutine(self.func, item))
|
||||
item = await self.iterator.next()
|
||||
return (await maybe_coroutine(self.func, item))
|
||||
|
||||
class _FilteredAsyncIterator(_AsyncIterator):
|
||||
def __init__(self, iterator, predicate):
|
||||
@ -118,14 +110,13 @@ class _FilteredAsyncIterator(_AsyncIterator):
|
||||
|
||||
self.predicate = predicate
|
||||
|
||||
@asyncio.coroutine
|
||||
def next(self):
|
||||
async def next(self):
|
||||
getter = self.iterator.next
|
||||
pred = self.predicate
|
||||
while True:
|
||||
# propagate NoMoreItems similar to _MappedAsyncIterator
|
||||
item = yield from getter()
|
||||
ret = yield from maybe_coroutine(pred, item)
|
||||
item = await getter()
|
||||
ret = await maybe_coroutine(pred, item)
|
||||
if ret:
|
||||
return item
|
||||
|
||||
@ -142,18 +133,16 @@ class ReactionIterator(_AsyncIterator):
|
||||
self.channel_id = message.channel.id
|
||||
self.users = asyncio.Queue(loop=state.loop)
|
||||
|
||||
@asyncio.coroutine
|
||||
def next(self):
|
||||
async def next(self):
|
||||
if self.users.empty():
|
||||
yield from self.fill_users()
|
||||
await self.fill_users()
|
||||
|
||||
try:
|
||||
return self.users.get_nowait()
|
||||
except asyncio.QueueEmpty:
|
||||
raise NoMoreItems()
|
||||
|
||||
@asyncio.coroutine
|
||||
def fill_users(self):
|
||||
async def fill_users(self):
|
||||
# this is a hack because >circular imports<
|
||||
from .user import User
|
||||
|
||||
@ -161,7 +150,7 @@ class ReactionIterator(_AsyncIterator):
|
||||
retrieve = self.limit if self.limit <= 100 else 100
|
||||
|
||||
after = self.after.id if self.after else None
|
||||
data = yield from self.getter(self.message.id, self.channel_id, self.emoji, retrieve, after=after)
|
||||
data = await self.getter(self.message.id, self.channel_id, self.emoji, retrieve, after=after)
|
||||
|
||||
if data:
|
||||
self.limit -= retrieve
|
||||
@ -169,15 +158,15 @@ class ReactionIterator(_AsyncIterator):
|
||||
|
||||
if self.guild is None:
|
||||
for element in reversed(data):
|
||||
yield from self.users.put(User(state=self.state, data=element))
|
||||
await self.users.put(User(state=self.state, data=element))
|
||||
else:
|
||||
for element in reversed(data):
|
||||
member_id = int(element['id'])
|
||||
member = self.guild.get_member(member_id)
|
||||
if member is not None:
|
||||
yield from self.users.put(member)
|
||||
await self.users.put(member)
|
||||
else:
|
||||
yield from self.users.put(User(state=self.state, data=element))
|
||||
await self.users.put(User(state=self.state, data=element))
|
||||
|
||||
class HistoryIterator(_AsyncIterator):
|
||||
"""Iterator for receiving a channel's message history.
|
||||
@ -270,10 +259,9 @@ class HistoryIterator(_AsyncIterator):
|
||||
else:
|
||||
self._retrieve_messages = self._retrieve_messages_before_strategy
|
||||
|
||||
@asyncio.coroutine
|
||||
def next(self):
|
||||
async def next(self):
|
||||
if self.messages.empty():
|
||||
yield from self.fill_messages()
|
||||
await self.fill_messages()
|
||||
|
||||
try:
|
||||
return self.messages.get_nowait()
|
||||
@ -292,15 +280,14 @@ class HistoryIterator(_AsyncIterator):
|
||||
self.retrieve = r
|
||||
return r > 0
|
||||
|
||||
@asyncio.coroutine
|
||||
def flatten(self):
|
||||
async def flatten(self):
|
||||
# this is similar to fill_messages except it uses a list instead
|
||||
# of a queue to place the messages in.
|
||||
result = []
|
||||
channel = yield from self.messageable._get_channel()
|
||||
channel = await self.messageable._get_channel()
|
||||
self.channel = channel
|
||||
while self._get_retrieve():
|
||||
data = yield from self._retrieve_messages(self.retrieve)
|
||||
data = await self._retrieve_messages(self.retrieve)
|
||||
if len(data) < 100:
|
||||
self.limit = 0 # terminate the infinite loop
|
||||
|
||||
@ -313,15 +300,14 @@ class HistoryIterator(_AsyncIterator):
|
||||
result.append(self.state.create_message(channel=channel, data=element))
|
||||
return result
|
||||
|
||||
@asyncio.coroutine
|
||||
def fill_messages(self):
|
||||
async def fill_messages(self):
|
||||
if not hasattr(self, 'channel'):
|
||||
# do the required set up
|
||||
channel = yield from self.messageable._get_channel()
|
||||
channel = await self.messageable._get_channel()
|
||||
self.channel = channel
|
||||
|
||||
if self._get_retrieve():
|
||||
data = yield from self._retrieve_messages(self.retrieve)
|
||||
data = await self._retrieve_messages(self.retrieve)
|
||||
if self.limit is None and len(data) < 100:
|
||||
self.limit = 0 # terminate the infinite loop
|
||||
|
||||
@ -332,41 +318,37 @@ class HistoryIterator(_AsyncIterator):
|
||||
|
||||
channel = self.channel
|
||||
for element in data:
|
||||
yield from self.messages.put(self.state.create_message(channel=channel, data=element))
|
||||
await self.messages.put(self.state.create_message(channel=channel, data=element))
|
||||
|
||||
@asyncio.coroutine
|
||||
def _retrieve_messages(self, retrieve):
|
||||
async def _retrieve_messages(self, retrieve):
|
||||
"""Retrieve messages and update next parameters."""
|
||||
pass
|
||||
|
||||
@asyncio.coroutine
|
||||
def _retrieve_messages_before_strategy(self, retrieve):
|
||||
async def _retrieve_messages_before_strategy(self, retrieve):
|
||||
"""Retrieve messages using before parameter."""
|
||||
before = self.before.id if self.before else None
|
||||
data = yield from self.logs_from(self.channel.id, retrieve, before=before)
|
||||
data = await self.logs_from(self.channel.id, retrieve, before=before)
|
||||
if len(data):
|
||||
if self.limit is not None:
|
||||
self.limit -= retrieve
|
||||
self.before = Object(id=int(data[-1]['id']))
|
||||
return data
|
||||
|
||||
@asyncio.coroutine
|
||||
def _retrieve_messages_after_strategy(self, retrieve):
|
||||
async def _retrieve_messages_after_strategy(self, retrieve):
|
||||
"""Retrieve messages using after parameter."""
|
||||
after = self.after.id if self.after else None
|
||||
data = yield from self.logs_from(self.channel.id, retrieve, after=after)
|
||||
data = await self.logs_from(self.channel.id, retrieve, after=after)
|
||||
if len(data):
|
||||
if self.limit is not None:
|
||||
self.limit -= retrieve
|
||||
self.after = Object(id=int(data[0]['id']))
|
||||
return data
|
||||
|
||||
@asyncio.coroutine
|
||||
def _retrieve_messages_around_strategy(self, retrieve):
|
||||
async def _retrieve_messages_around_strategy(self, retrieve):
|
||||
"""Retrieve messages using around parameter."""
|
||||
if self.around:
|
||||
around = self.around.id if self.around else None
|
||||
data = yield from self.logs_from(self.channel.id, retrieve, around=around)
|
||||
data = await self.logs_from(self.channel.id, retrieve, around=around)
|
||||
self.around = None
|
||||
return data
|
||||
return []
|
||||
@ -411,10 +393,9 @@ class AuditLogIterator(_AsyncIterator):
|
||||
else:
|
||||
self._strategy = self._before_strategy
|
||||
|
||||
@asyncio.coroutine
|
||||
def _before_strategy(self, retrieve):
|
||||
async def _before_strategy(self, retrieve):
|
||||
before = self.before.id if self.before else None
|
||||
data = yield from self.request(self.guild.id, limit=retrieve, user_id=self.user_id,
|
||||
data = await self.request(self.guild.id, limit=retrieve, user_id=self.user_id,
|
||||
action_type=self.action_type, before=before)
|
||||
|
||||
entries = data.get('audit_log_entries', [])
|
||||
@ -424,10 +405,9 @@ class AuditLogIterator(_AsyncIterator):
|
||||
self.before = Object(id=int(entries[-1]['id']))
|
||||
return data.get('users', []), entries
|
||||
|
||||
@asyncio.coroutine
|
||||
def _after_strategy(self, retrieve):
|
||||
async def _after_strategy(self, retrieve):
|
||||
after = self.after.id if self.after else None
|
||||
data = yield from self.request(self.guild.id, limit=retrieve, user_id=self.user_id,
|
||||
data = await self.request(self.guild.id, limit=retrieve, user_id=self.user_id,
|
||||
action_type=self.action_type, after=after)
|
||||
entries = data.get('audit_log_entries', [])
|
||||
if len(data) and entries:
|
||||
@ -436,10 +416,9 @@ class AuditLogIterator(_AsyncIterator):
|
||||
self.after = Object(id=int(entries[0]['id']))
|
||||
return data.get('users', []), entries
|
||||
|
||||
@asyncio.coroutine
|
||||
def next(self):
|
||||
async def next(self):
|
||||
if self.entries.empty():
|
||||
yield from self._fill()
|
||||
await self._fill()
|
||||
|
||||
try:
|
||||
return self.entries.get_nowait()
|
||||
@ -458,12 +437,11 @@ class AuditLogIterator(_AsyncIterator):
|
||||
self.retrieve = r
|
||||
return r > 0
|
||||
|
||||
@asyncio.coroutine
|
||||
def _fill(self):
|
||||
async def _fill(self):
|
||||
from .user import User
|
||||
|
||||
if self._get_retrieve():
|
||||
users, data = yield from self._strategy(self.retrieve)
|
||||
users, data = await self._strategy(self.retrieve)
|
||||
if self.limit is None and len(data) < 100:
|
||||
self.limit = 0 # terminate the infinite loop
|
||||
|
||||
@ -481,4 +459,4 @@ class AuditLogIterator(_AsyncIterator):
|
||||
if element['action_type'] is None:
|
||||
continue
|
||||
|
||||
yield from self.entries.put(AuditLogEntry(data=element, users=self._users, guild=self.guild))
|
||||
await self.entries.put(AuditLogEntry(data=element, users=self._users, guild=self.guild))
|
||||
|
@ -183,9 +183,8 @@ class Member(discord.abc.Messageable, _BaseUser):
|
||||
def __hash__(self):
|
||||
return hash(self._user.id)
|
||||
|
||||
@asyncio.coroutine
|
||||
def _get_channel(self):
|
||||
ch = yield from self.create_dm()
|
||||
async def _get_channel(self):
|
||||
ch = await self.create_dm()
|
||||
return ch
|
||||
|
||||
def _update_roles(self, data):
|
||||
@ -341,32 +340,28 @@ class Member(discord.abc.Messageable, _BaseUser):
|
||||
"""Optional[:class:`VoiceState`]: Returns the member's current voice state."""
|
||||
return self.guild._voice_state_for(self._user.id)
|
||||
|
||||
@asyncio.coroutine
|
||||
def ban(self, **kwargs):
|
||||
async def ban(self, **kwargs):
|
||||
"""|coro|
|
||||
|
||||
Bans this member. Equivalent to :meth:`Guild.ban`
|
||||
"""
|
||||
yield from self.guild.ban(self, **kwargs)
|
||||
await self.guild.ban(self, **kwargs)
|
||||
|
||||
@asyncio.coroutine
|
||||
def unban(self, *, reason=None):
|
||||
async def unban(self, *, reason=None):
|
||||
"""|coro|
|
||||
|
||||
Unbans this member. Equivalent to :meth:`Guild.unban`
|
||||
"""
|
||||
yield from self.guild.unban(self, reason=reason)
|
||||
await self.guild.unban(self, reason=reason)
|
||||
|
||||
@asyncio.coroutine
|
||||
def kick(self, *, reason=None):
|
||||
async def kick(self, *, reason=None):
|
||||
"""|coro|
|
||||
|
||||
Kicks this member. Equivalent to :meth:`Guild.kick`
|
||||
"""
|
||||
yield from self.guild.kick(self, reason=reason)
|
||||
await self.guild.kick(self, reason=reason)
|
||||
|
||||
@asyncio.coroutine
|
||||
def edit(self, *, reason=None, **fields):
|
||||
async def edit(self, *, reason=None, **fields):
|
||||
"""|coro|
|
||||
|
||||
Edits the member's data.
|
||||
@ -423,7 +418,7 @@ class Member(discord.abc.Messageable, _BaseUser):
|
||||
else:
|
||||
nick = nick if nick else ''
|
||||
if self._state.self_id == self.id:
|
||||
yield from http.change_my_nickname(guild_id, nick, reason=reason)
|
||||
await http.change_my_nickname(guild_id, nick, reason=reason)
|
||||
else:
|
||||
payload['nick'] = nick
|
||||
|
||||
@ -449,12 +444,11 @@ class Member(discord.abc.Messageable, _BaseUser):
|
||||
else:
|
||||
payload['roles'] = tuple(r.id for r in roles)
|
||||
|
||||
yield from http.edit_member(guild_id, self.id, reason=reason, **payload)
|
||||
await http.edit_member(guild_id, self.id, reason=reason, **payload)
|
||||
|
||||
# TODO: wait for WS event for modify-in-place behaviour
|
||||
|
||||
@asyncio.coroutine
|
||||
def move_to(self, channel, *, reason=None):
|
||||
async def move_to(self, channel, *, reason=None):
|
||||
"""|coro|
|
||||
|
||||
Moves a member to a new voice channel (they must be connected first).
|
||||
@ -471,10 +465,9 @@ class Member(discord.abc.Messageable, _BaseUser):
|
||||
reason: Optional[str]
|
||||
The reason for doing this action. Shows up on the audit log.
|
||||
"""
|
||||
yield from self.edit(voice_channel=channel, reason=reason)
|
||||
await self.edit(voice_channel=channel, reason=reason)
|
||||
|
||||
@asyncio.coroutine
|
||||
def add_roles(self, *roles, reason=None, atomic=True):
|
||||
async def add_roles(self, *roles, reason=None, atomic=True):
|
||||
"""|coro|
|
||||
|
||||
Gives the member a number of :class:`Role`\s.
|
||||
@ -504,16 +497,15 @@ class Member(discord.abc.Messageable, _BaseUser):
|
||||
|
||||
if not atomic:
|
||||
new_roles = utils._unique(Object(id=r.id) for s in (self.roles[1:], roles) for r in s)
|
||||
yield from self.edit(roles=new_roles, reason=reason)
|
||||
await self.edit(roles=new_roles, reason=reason)
|
||||
else:
|
||||
req = self._state.http.add_role
|
||||
guild_id = self.guild.id
|
||||
user_id = self.id
|
||||
for role in roles:
|
||||
yield from req(guild_id, user_id, role.id, reason=reason)
|
||||
await req(guild_id, user_id, role.id, reason=reason)
|
||||
|
||||
@asyncio.coroutine
|
||||
def remove_roles(self, *roles, reason=None, atomic=True):
|
||||
async def remove_roles(self, *roles, reason=None, atomic=True):
|
||||
"""|coro|
|
||||
|
||||
Removes :class:`Role`\s from this member.
|
||||
@ -549,10 +541,10 @@ class Member(discord.abc.Messageable, _BaseUser):
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
yield from self.edit(roles=new_roles, reason=reason)
|
||||
await self.edit(roles=new_roles, reason=reason)
|
||||
else:
|
||||
req = self._state.http.remove_role
|
||||
guild_id = self.guild.id
|
||||
user_id = self.id
|
||||
for role in roles:
|
||||
yield from req(guild_id, user_id, role.id, reason=reason)
|
||||
await req(guild_id, user_id, role.id, reason=reason)
|
||||
|
@ -71,8 +71,7 @@ class Attachment:
|
||||
self.proxy_url = data.get('proxy_url')
|
||||
self._http = state.http
|
||||
|
||||
@asyncio.coroutine
|
||||
def save(self, fp, *, seek_begin=True):
|
||||
async def save(self, fp, *, seek_begin=True):
|
||||
"""|coro|
|
||||
|
||||
Saves this attachment into a file-like object.
|
||||
@ -100,7 +99,7 @@ class Attachment:
|
||||
The number of bytes written.
|
||||
"""
|
||||
|
||||
data = yield from self._http.get_attachment(self.url)
|
||||
data = await self._http.get_attachment(self.url)
|
||||
if isinstance(fp, str):
|
||||
with open(fp, 'wb') as f:
|
||||
return f.write(data)
|
||||
@ -527,8 +526,7 @@ class Message:
|
||||
else:
|
||||
return '{0.author.name} started a call \N{EM DASH} Join the call.'.format(self)
|
||||
|
||||
@asyncio.coroutine
|
||||
def delete(self):
|
||||
async def delete(self):
|
||||
"""|coro|
|
||||
|
||||
Deletes the message.
|
||||
@ -544,10 +542,9 @@ class Message:
|
||||
HTTPException
|
||||
Deleting the message failed.
|
||||
"""
|
||||
yield from self._state.http.delete_message(self.channel.id, self.id)
|
||||
await self._state.http.delete_message(self.channel.id, self.id)
|
||||
|
||||
@asyncio.coroutine
|
||||
def edit(self, **fields):
|
||||
async def edit(self, **fields):
|
||||
"""|coro|
|
||||
|
||||
Edits the message.
|
||||
@ -589,7 +586,7 @@ class Message:
|
||||
if embed is not None:
|
||||
fields['embed'] = embed.to_dict()
|
||||
|
||||
data = yield from self._state.http.edit_message(self.id, self.channel.id, **fields)
|
||||
data = await self._state.http.edit_message(self.id, self.channel.id, **fields)
|
||||
self._update(channel=self.channel, data=data)
|
||||
|
||||
try:
|
||||
@ -598,18 +595,16 @@ class Message:
|
||||
pass
|
||||
else:
|
||||
if delete_after is not None:
|
||||
@asyncio.coroutine
|
||||
def delete():
|
||||
yield from asyncio.sleep(delete_after, loop=self._state.loop)
|
||||
async def delete():
|
||||
await asyncio.sleep(delete_after, loop=self._state.loop)
|
||||
try:
|
||||
yield from self._state.http.delete_message(self.channel.id, self.id)
|
||||
await self._state.http.delete_message(self.channel.id, self.id)
|
||||
except:
|
||||
pass
|
||||
|
||||
compat.create_task(delete(), loop=self._state.loop)
|
||||
asyncio.ensure_future(delete(), loop=self._state.loop)
|
||||
|
||||
@asyncio.coroutine
|
||||
def pin(self):
|
||||
async def pin(self):
|
||||
"""|coro|
|
||||
|
||||
Pins the message.
|
||||
@ -628,11 +623,10 @@ class Message:
|
||||
having more than 50 pinned messages.
|
||||
"""
|
||||
|
||||
yield from self._state.http.pin_message(self.channel.id, self.id)
|
||||
await self._state.http.pin_message(self.channel.id, self.id)
|
||||
self.pinned = True
|
||||
|
||||
@asyncio.coroutine
|
||||
def unpin(self):
|
||||
async def unpin(self):
|
||||
"""|coro|
|
||||
|
||||
Unpins the message.
|
||||
@ -650,11 +644,10 @@ class Message:
|
||||
Unpinning the message failed.
|
||||
"""
|
||||
|
||||
yield from self._state.http.unpin_message(self.channel.id, self.id)
|
||||
await self._state.http.unpin_message(self.channel.id, self.id)
|
||||
self.pinned = False
|
||||
|
||||
@asyncio.coroutine
|
||||
def add_reaction(self, emoji):
|
||||
async def add_reaction(self, emoji):
|
||||
"""|coro|
|
||||
|
||||
Add a reaction to the message.
|
||||
@ -694,10 +687,9 @@ class Message:
|
||||
else:
|
||||
raise InvalidArgument('emoji argument must be str, Emoji, or Reaction not {.__class__.__name__}.'.format(emoji))
|
||||
|
||||
yield from self._state.http.add_reaction(self.id, self.channel.id, emoji)
|
||||
await self._state.http.add_reaction(self.id, self.channel.id, emoji)
|
||||
|
||||
@asyncio.coroutine
|
||||
def remove_reaction(self, emoji, member):
|
||||
async def remove_reaction(self, emoji, member):
|
||||
"""|coro|
|
||||
|
||||
Remove a reaction by the member from the message.
|
||||
@ -742,12 +734,11 @@ class Message:
|
||||
raise InvalidArgument('emoji argument must be str, Emoji, or Reaction not {.__class__.__name__}.'.format(emoji))
|
||||
|
||||
if member.id == self._state.self_id:
|
||||
yield from self._state.http.remove_own_reaction(self.id, self.channel.id, emoji)
|
||||
await self._state.http.remove_own_reaction(self.id, self.channel.id, emoji)
|
||||
else:
|
||||
yield from self._state.http.remove_reaction(self.id, self.channel.id, emoji, member.id)
|
||||
await self._state.http.remove_reaction(self.id, self.channel.id, emoji, member.id)
|
||||
|
||||
@asyncio.coroutine
|
||||
def clear_reactions(self):
|
||||
async def clear_reactions(self):
|
||||
"""|coro|
|
||||
|
||||
Removes all the reactions from the message.
|
||||
@ -761,7 +752,7 @@ class Message:
|
||||
Forbidden
|
||||
You do not have the proper permissions to remove all the reactions.
|
||||
"""
|
||||
yield from self._state.http.clear_reactions(self.id, self.channel.id)
|
||||
await self._state.http.clear_reactions(self.id, self.channel.id)
|
||||
|
||||
def ack(self):
|
||||
"""|coro|
|
||||
|
@ -137,7 +137,7 @@ class Reaction:
|
||||
iterator = reaction.users()
|
||||
while True:
|
||||
try:
|
||||
user = yield from iterator.next()
|
||||
user = await iterator.next()
|
||||
except discord.NoMoreItems:
|
||||
break
|
||||
else:
|
||||
|
@ -52,8 +52,7 @@ class Relationship:
|
||||
def __repr__(self):
|
||||
return '<Relationship user={0.user!r} type={0.type!r}>'.format(self)
|
||||
|
||||
@asyncio.coroutine
|
||||
def delete(self):
|
||||
async def delete(self):
|
||||
"""|coro|
|
||||
|
||||
Deletes the relationship.
|
||||
@ -64,10 +63,9 @@ class Relationship:
|
||||
Deleting the relationship failed.
|
||||
"""
|
||||
|
||||
yield from self._state.http.remove_relationship(self.user.id)
|
||||
await self._state.http.remove_relationship(self.user.id)
|
||||
|
||||
@asyncio.coroutine
|
||||
def accept(self):
|
||||
async def accept(self):
|
||||
"""|coro|
|
||||
|
||||
Accepts the relationship request. e.g. accepting a
|
||||
@ -79,4 +77,4 @@ class Relationship:
|
||||
Accepting the relationship failed.
|
||||
"""
|
||||
|
||||
yield from self._state.http.add_relationship(self.user.id)
|
||||
await self._state.http.add_relationship(self.user.id)
|
||||
|
@ -171,8 +171,7 @@ class Role(Hashable):
|
||||
|
||||
return [member for member in all_members if self in member.roles]
|
||||
|
||||
@asyncio.coroutine
|
||||
def _move(self, position, reason):
|
||||
async def _move(self, position, reason):
|
||||
if position <= 0:
|
||||
raise InvalidArgument("Cannot move role to position 0 or below")
|
||||
|
||||
@ -196,10 +195,9 @@ class Role(Hashable):
|
||||
roles.append(self.id)
|
||||
|
||||
payload = [{"id": z[0], "position": z[1]} for z in zip(roles, change_range)]
|
||||
yield from http.move_role_position(self.guild.id, payload, reason=reason)
|
||||
await http.move_role_position(self.guild.id, payload, reason=reason)
|
||||
|
||||
@asyncio.coroutine
|
||||
def edit(self, *, reason=None, **fields):
|
||||
async def edit(self, *, reason=None, **fields):
|
||||
"""|coro|
|
||||
|
||||
Edits the role.
|
||||
@ -240,7 +238,7 @@ class Role(Hashable):
|
||||
|
||||
position = fields.get('position')
|
||||
if position is not None:
|
||||
yield from self._move(position, reason=reason)
|
||||
await self._move(position, reason=reason)
|
||||
self.position = position
|
||||
|
||||
try:
|
||||
@ -256,11 +254,10 @@ class Role(Hashable):
|
||||
'mentionable': fields.get('mentionable', self.mentionable)
|
||||
}
|
||||
|
||||
data = yield from self._state.http.edit_role(self.guild.id, self.id, reason=reason, **payload)
|
||||
data = await self._state.http.edit_role(self.guild.id, self.id, reason=reason, **payload)
|
||||
self._update(data)
|
||||
|
||||
@asyncio.coroutine
|
||||
def delete(self, *, reason=None):
|
||||
async def delete(self, *, reason=None):
|
||||
"""|coro|
|
||||
|
||||
Deletes the role.
|
||||
@ -281,4 +278,4 @@ class Role(Hashable):
|
||||
Deleting the role failed.
|
||||
"""
|
||||
|
||||
yield from self._state.http.delete_role(self.guild.id, self.id, reason=reason)
|
||||
await self._state.http.delete_role(self.guild.id, self.id, reason=reason)
|
||||
|
@ -43,7 +43,7 @@ class Shard:
|
||||
self.ws = ws
|
||||
self._client = client
|
||||
self.loop = self._client.loop
|
||||
self._current = compat.create_future(self.loop)
|
||||
self._current = self.loop.create_future()
|
||||
self._current.set_result(None) # we just need an already done future
|
||||
self._pending = asyncio.Event(loop=self.loop)
|
||||
self._pending_task = None
|
||||
@ -58,47 +58,36 @@ class Shard:
|
||||
def complete_pending_reads(self):
|
||||
self._pending.set()
|
||||
|
||||
def _pending_reads(self):
|
||||
async def _pending_reads(self):
|
||||
try:
|
||||
while self.is_pending():
|
||||
yield from self.poll()
|
||||
await self.poll()
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
def launch_pending_reads(self):
|
||||
self._pending_task = compat.create_task(self._pending_reads(), loop=self.loop)
|
||||
self._pending_task = asyncio.ensure_future(self._pending_reads(), loop=self.loop)
|
||||
|
||||
def wait(self):
|
||||
return self._pending_task
|
||||
|
||||
@asyncio.coroutine
|
||||
def poll(self):
|
||||
async def poll(self):
|
||||
try:
|
||||
yield from self.ws.poll_event()
|
||||
await self.ws.poll_event()
|
||||
except ResumeWebSocket as e:
|
||||
log.info('Got a request to RESUME the websocket at Shard ID %s.', self.id)
|
||||
coro = DiscordWebSocket.from_client(self._client, resume=True,
|
||||
shard_id=self.id,
|
||||
session=self.ws.session_id,
|
||||
sequence=self.ws.sequence)
|
||||
self.ws = yield from asyncio.wait_for(coro, timeout=180.0, loop=self.loop)
|
||||
self.ws = await asyncio.wait_for(coro, timeout=180.0, loop=self.loop)
|
||||
|
||||
def get_future(self):
|
||||
if self._current.done():
|
||||
self._current = compat.create_task(self.poll(), loop=self.loop)
|
||||
self._current = asyncio.ensure_future(self.poll(), loop=self.loop)
|
||||
|
||||
return self._current
|
||||
|
||||
@asyncio.coroutine
|
||||
def _ensure_coroutine_connect(gateway, loop):
|
||||
# In 3.5+ websockets.connect does not return a coroutine, but an awaitable.
|
||||
# The problem is that in 3.5.0 and in some cases 3.5.1, asyncio.ensure_future and
|
||||
# by proxy, asyncio.wait_for, do not accept awaitables, but rather futures or coroutines.
|
||||
# By wrapping it up into this function we ensure that it's in a coroutine and not an awaitable
|
||||
# even for 3.5.0 users.
|
||||
ws = yield from websockets.connect(gateway, loop=loop, klass=DiscordWebSocket)
|
||||
return ws
|
||||
|
||||
class AutoShardedClient(Client):
|
||||
"""A client similar to :class:`Client` except it handles the complications
|
||||
of sharding for the user into a more manageable and transparent single
|
||||
@ -149,8 +138,7 @@ class AutoShardedClient(Client):
|
||||
|
||||
self._connection._get_websocket = _get_websocket
|
||||
|
||||
@asyncio.coroutine
|
||||
def _chunker(self, guild, *, shard_id=None):
|
||||
async def _chunker(self, guild, *, shard_id=None):
|
||||
try:
|
||||
guild_id = guild.id
|
||||
shard_id = shard_id or guild.shard_id
|
||||
@ -167,7 +155,7 @@ class AutoShardedClient(Client):
|
||||
}
|
||||
|
||||
ws = self.shards[shard_id].ws
|
||||
yield from ws.send_as_json(payload)
|
||||
await ws.send_as_json(payload)
|
||||
|
||||
@property
|
||||
def latency(self):
|
||||
@ -189,8 +177,7 @@ class AutoShardedClient(Client):
|
||||
"""
|
||||
return [(shard_id, shard.ws.latency) for shard_id, shard in self.shards.items()]
|
||||
|
||||
@asyncio.coroutine
|
||||
def request_offline_members(self, *guilds):
|
||||
async def request_offline_members(self, *guilds):
|
||||
"""|coro|
|
||||
|
||||
Requests previously offline members from the guild to be filled up
|
||||
@ -219,16 +206,16 @@ class AutoShardedClient(Client):
|
||||
_guilds = sorted(guilds, key=lambda g: g.shard_id)
|
||||
for shard_id, sub_guilds in itertools.groupby(_guilds, key=lambda g: g.shard_id):
|
||||
sub_guilds = list(sub_guilds)
|
||||
yield from self._connection.request_offline_members(sub_guilds, shard_id=shard_id)
|
||||
await self._connection.request_offline_members(sub_guilds, shard_id=shard_id)
|
||||
|
||||
@asyncio.coroutine
|
||||
def launch_shard(self, gateway, shard_id):
|
||||
async def launch_shard(self, gateway, shard_id):
|
||||
try:
|
||||
ws = yield from asyncio.wait_for(_ensure_coroutine_connect(gateway, self.loop), loop=self.loop, timeout=180.0)
|
||||
coro = websockets.connect(gateway, loop=self.loop, klass=DiscordWebSocket)
|
||||
ws = await asyncio.wait_for(coro, loop=self.loop, timeout=180.0)
|
||||
except Exception as e:
|
||||
log.info('Failed to connect for shard_id: %s. Retrying...', shard_id)
|
||||
yield from asyncio.sleep(5.0, loop=self.loop)
|
||||
return (yield from self.launch_shard(gateway, shard_id))
|
||||
await asyncio.sleep(5.0, loop=self.loop)
|
||||
return (await self.launch_shard(gateway, shard_id))
|
||||
|
||||
ws.token = self.http.token
|
||||
ws._connection = self._connection
|
||||
@ -240,31 +227,30 @@ class AutoShardedClient(Client):
|
||||
|
||||
try:
|
||||
# OP HELLO
|
||||
yield from asyncio.wait_for(ws.poll_event(), loop=self.loop, timeout=180.0)
|
||||
yield from asyncio.wait_for(ws.identify(), loop=self.loop, timeout=180.0)
|
||||
await asyncio.wait_for(ws.poll_event(), loop=self.loop, timeout=180.0)
|
||||
await asyncio.wait_for(ws.identify(), loop=self.loop, timeout=180.0)
|
||||
except asyncio.TimeoutError:
|
||||
log.info('Timed out when connecting for shard_id: %s. Retrying...', shard_id)
|
||||
yield from asyncio.sleep(5.0, loop=self.loop)
|
||||
return (yield from self.launch_shard(gateway, shard_id))
|
||||
await asyncio.sleep(5.0, loop=self.loop)
|
||||
return (await self.launch_shard(gateway, shard_id))
|
||||
|
||||
# keep reading the shard while others connect
|
||||
self.shards[shard_id] = ret = Shard(ws, self)
|
||||
ret.launch_pending_reads()
|
||||
yield from asyncio.sleep(5.0, loop=self.loop)
|
||||
await asyncio.sleep(5.0, loop=self.loop)
|
||||
|
||||
@asyncio.coroutine
|
||||
def launch_shards(self):
|
||||
async def launch_shards(self):
|
||||
if self.shard_count is None:
|
||||
self.shard_count, gateway = yield from self.http.get_bot_gateway()
|
||||
self.shard_count, gateway = await self.http.get_bot_gateway()
|
||||
else:
|
||||
gateway = yield from self.http.get_gateway()
|
||||
gateway = await self.http.get_gateway()
|
||||
|
||||
self._connection.shard_count = self.shard_count
|
||||
|
||||
shard_ids = self.shard_ids if self.shard_ids else range(self.shard_count)
|
||||
|
||||
for shard_id in shard_ids:
|
||||
yield from self.launch_shard(gateway, shard_id)
|
||||
await self.launch_shard(gateway, shard_id)
|
||||
|
||||
shards_to_wait_for = []
|
||||
for shard in self.shards.values():
|
||||
@ -272,21 +258,19 @@ class AutoShardedClient(Client):
|
||||
shards_to_wait_for.append(shard.wait())
|
||||
|
||||
# wait for all pending tasks to finish
|
||||
yield from utils.sane_wait_for(shards_to_wait_for, timeout=300.0, loop=self.loop)
|
||||
await utils.sane_wait_for(shards_to_wait_for, timeout=300.0, loop=self.loop)
|
||||
|
||||
@asyncio.coroutine
|
||||
def _connect(self):
|
||||
yield from self.launch_shards()
|
||||
async def _connect(self):
|
||||
await self.launch_shards()
|
||||
|
||||
while True:
|
||||
pollers = [shard.get_future() for shard in self.shards.values()]
|
||||
done, pending = yield from asyncio.wait(pollers, loop=self.loop, return_when=asyncio.FIRST_COMPLETED)
|
||||
done, pending = await asyncio.wait(pollers, loop=self.loop, return_when=asyncio.FIRST_COMPLETED)
|
||||
for f in done:
|
||||
# we wanna re-raise to the main Client.connect handler if applicable
|
||||
f.result()
|
||||
|
||||
@asyncio.coroutine
|
||||
def close(self):
|
||||
async def close(self):
|
||||
"""|coro|
|
||||
|
||||
Closes the connection to discord.
|
||||
@ -298,16 +282,15 @@ class AutoShardedClient(Client):
|
||||
|
||||
for vc in self.voice_clients:
|
||||
try:
|
||||
yield from vc.disconnect()
|
||||
await vc.disconnect()
|
||||
except:
|
||||
pass
|
||||
|
||||
to_close = [shard.ws.close() for shard in self.shards.values()]
|
||||
yield from asyncio.wait(to_close, loop=self.loop)
|
||||
yield from self.http.close()
|
||||
await asyncio.wait(to_close, loop=self.loop)
|
||||
await self.http.close()
|
||||
|
||||
@asyncio.coroutine
|
||||
def change_presence(self, *, activity=None, status=None, afk=False, shard_id=None):
|
||||
async def change_presence(self, *, activity=None, status=None, afk=False, shard_id=None):
|
||||
"""|coro|
|
||||
|
||||
Changes the client's presence.
|
||||
@ -355,12 +338,12 @@ class AutoShardedClient(Client):
|
||||
|
||||
if shard_id is None:
|
||||
for shard in self.shards.values():
|
||||
yield from shard.ws.change_presence(activity=activity, status=status, afk=afk)
|
||||
await shard.ws.change_presence(activity=activity, status=status, afk=afk)
|
||||
|
||||
guilds = self._connection.guilds
|
||||
else:
|
||||
shard = self.shards[shard_id]
|
||||
yield from shard.ws.change_presence(activity=activity, status=status, afk=afk)
|
||||
await shard.ws.change_presence(activity=activity, status=status, afk=afk)
|
||||
guilds = [g for g in self._connection.guilds if g.shard_id == shard_id]
|
||||
|
||||
for guild in guilds:
|
||||
|
@ -255,8 +255,7 @@ class ConnectionState:
|
||||
|
||||
return channel, guild
|
||||
|
||||
@asyncio.coroutine
|
||||
def request_offline_members(self, guilds):
|
||||
async def request_offline_members(self, guilds):
|
||||
# get all the chunks
|
||||
chunks = []
|
||||
for guild in guilds:
|
||||
@ -265,17 +264,16 @@ class ConnectionState:
|
||||
# we only want to request ~75 guilds per chunk request.
|
||||
splits = [guilds[i:i + 75] for i in range(0, len(guilds), 75)]
|
||||
for split in splits:
|
||||
yield from self.chunker(split)
|
||||
await self.chunker(split)
|
||||
|
||||
# wait for the chunks
|
||||
if chunks:
|
||||
try:
|
||||
yield from utils.sane_wait_for(chunks, timeout=len(chunks) * 30.0, loop=self.loop)
|
||||
await utils.sane_wait_for(chunks, timeout=len(chunks) * 30.0, loop=self.loop)
|
||||
except asyncio.TimeoutError:
|
||||
log.info('Somehow timed out waiting for chunks.')
|
||||
|
||||
@asyncio.coroutine
|
||||
def _delay_ready(self):
|
||||
async def _delay_ready(self):
|
||||
try:
|
||||
launch = self._ready_state.launch
|
||||
|
||||
@ -285,11 +283,11 @@ class ConnectionState:
|
||||
# this snippet of code is basically waiting 2 seconds
|
||||
# until the last GUILD_CREATE was sent
|
||||
launch.set()
|
||||
yield from asyncio.sleep(2, loop=self.loop)
|
||||
await asyncio.sleep(2, loop=self.loop)
|
||||
|
||||
guilds = self._ready_state.guilds
|
||||
if self._fetch_offline:
|
||||
yield from self.request_offline_members(guilds)
|
||||
await self.request_offline_members(guilds)
|
||||
|
||||
# remove the state
|
||||
try:
|
||||
@ -300,7 +298,7 @@ class ConnectionState:
|
||||
# call GUILD_SYNC after we're done chunking
|
||||
if not self.is_bot:
|
||||
log.info('Requesting GUILD_SYNC for %s guilds', len(self.guilds))
|
||||
yield from self.syncer([s.id for s in self.guilds])
|
||||
await self.syncer([s.id for s in self.guilds])
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
else:
|
||||
@ -336,7 +334,7 @@ class ConnectionState:
|
||||
self._add_private_channel(factory(me=self.user, data=pm, state=self))
|
||||
|
||||
self.dispatch('connect')
|
||||
self._ready_task = compat.create_task(self._delay_ready(), loop=self.loop)
|
||||
self._ready_task = asyncio.ensure_future(self._delay_ready(), loop=self.loop)
|
||||
|
||||
def parse_resumed(self, data):
|
||||
self.dispatch('resumed')
|
||||
@ -617,13 +615,12 @@ class ConnectionState:
|
||||
|
||||
return self._add_guild_from_data(data)
|
||||
|
||||
@asyncio.coroutine
|
||||
def _chunk_and_dispatch(self, guild, unavailable):
|
||||
async def _chunk_and_dispatch(self, guild, unavailable):
|
||||
chunks = list(self.chunks_needed(guild))
|
||||
yield from self.chunker(guild)
|
||||
await self.chunker(guild)
|
||||
if chunks:
|
||||
try:
|
||||
yield from utils.sane_wait_for(chunks, timeout=len(chunks), loop=self.loop)
|
||||
await utils.sane_wait_for(chunks, timeout=len(chunks), loop=self.loop)
|
||||
except asyncio.TimeoutError:
|
||||
log.info('Somehow timed out waiting for chunks.')
|
||||
|
||||
@ -662,7 +659,7 @@ class ConnectionState:
|
||||
# since we're not waiting for 'useful' READY we'll just
|
||||
# do the chunk request here if wanted
|
||||
if self._fetch_offline:
|
||||
compat.create_task(self._chunk_and_dispatch(guild, unavailable), loop=self.loop)
|
||||
asyncio.ensure_future(self._chunk_and_dispatch(guild, unavailable), loop=self.loop)
|
||||
return
|
||||
|
||||
# Dispatch available if newly available
|
||||
@ -807,7 +804,7 @@ class ConnectionState:
|
||||
|
||||
vc = self._get_voice_client(key_id)
|
||||
if vc is not None:
|
||||
compat.create_task(vc._create_socket(key_id, data))
|
||||
asyncio.ensure_future(vc._create_socket(key_id, data))
|
||||
|
||||
def parse_typing_start(self, data):
|
||||
channel, guild = self._get_guild_channel(data)
|
||||
@ -886,7 +883,7 @@ class ConnectionState:
|
||||
return Message(state=self, channel=channel, data=data)
|
||||
|
||||
def receive_chunk(self, guild_id):
|
||||
future = compat.create_future(self.loop)
|
||||
future = self.loop.create_future()
|
||||
listener = Listener(ListenerType.chunk, future, lambda s: s.id == guild_id)
|
||||
self._listeners.append(listener)
|
||||
return future
|
||||
@ -896,8 +893,7 @@ class AutoShardedConnectionState(ConnectionState):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._ready_task = None
|
||||
|
||||
@asyncio.coroutine
|
||||
def request_offline_members(self, guilds, *, shard_id):
|
||||
async def request_offline_members(self, guilds, *, shard_id):
|
||||
# get all the chunks
|
||||
chunks = []
|
||||
for guild in guilds:
|
||||
@ -906,30 +902,29 @@ class AutoShardedConnectionState(ConnectionState):
|
||||
# we only want to request ~75 guilds per chunk request.
|
||||
splits = [guilds[i:i + 75] for i in range(0, len(guilds), 75)]
|
||||
for split in splits:
|
||||
yield from self.chunker(split, shard_id=shard_id)
|
||||
await self.chunker(split, shard_id=shard_id)
|
||||
|
||||
# wait for the chunks
|
||||
if chunks:
|
||||
try:
|
||||
yield from utils.sane_wait_for(chunks, timeout=len(chunks) * 30.0, loop=self.loop)
|
||||
await utils.sane_wait_for(chunks, timeout=len(chunks) * 30.0, loop=self.loop)
|
||||
except asyncio.TimeoutError:
|
||||
log.info('Somehow timed out waiting for chunks.')
|
||||
|
||||
@asyncio.coroutine
|
||||
def _delay_ready(self):
|
||||
async def _delay_ready(self):
|
||||
launch = self._ready_state.launch
|
||||
while not launch.is_set():
|
||||
# this snippet of code is basically waiting 2 seconds
|
||||
# until the last GUILD_CREATE was sent
|
||||
launch.set()
|
||||
yield from asyncio.sleep(2.0 * self.shard_count, loop=self.loop)
|
||||
await asyncio.sleep(2.0 * self.shard_count, loop=self.loop)
|
||||
|
||||
if self._fetch_offline:
|
||||
guilds = sorted(self._ready_state.guilds, key=lambda g: g.shard_id)
|
||||
|
||||
for shard_id, sub_guilds in itertools.groupby(guilds, key=lambda g: g.shard_id):
|
||||
sub_guilds = list(sub_guilds)
|
||||
yield from self.request_offline_members(sub_guilds, shard_id=shard_id)
|
||||
await self.request_offline_members(sub_guilds, shard_id=shard_id)
|
||||
self.dispatch('shard_ready', shard_id)
|
||||
|
||||
# remove the state
|
||||
@ -964,4 +959,4 @@ class AutoShardedConnectionState(ConnectionState):
|
||||
|
||||
self.dispatch('connect')
|
||||
if self._ready_task is None:
|
||||
self._ready_task = compat.create_task(self._delay_ready(), loop=self.loop)
|
||||
self._ready_task = asyncio.ensure_future(self._delay_ready(), loop=self.loop)
|
||||
|
@ -313,8 +313,7 @@ class ClientUser(BaseUser):
|
||||
"""Returns a :class:`list` of :class:`User`\s that the user has blocked."""
|
||||
return [r.user for r in self._relationships.values() if r.type is RelationshipType.blocked]
|
||||
|
||||
@asyncio.coroutine
|
||||
def edit(self, **fields):
|
||||
async def edit(self, **fields):
|
||||
"""|coro|
|
||||
|
||||
Edits the current profile of the client.
|
||||
@ -387,7 +386,7 @@ class ClientUser(BaseUser):
|
||||
|
||||
http = self._state.http
|
||||
|
||||
data = yield from http.edit_profile(**args)
|
||||
data = await http.edit_profile(**args)
|
||||
if not_bot_account:
|
||||
self.email = data['email']
|
||||
try:
|
||||
@ -398,8 +397,7 @@ class ClientUser(BaseUser):
|
||||
# manually update data by calling __init__ explicitly.
|
||||
self.__init__(state=self._state, data=data)
|
||||
|
||||
@asyncio.coroutine
|
||||
def create_group(self, *recipients):
|
||||
async def create_group(self, *recipients):
|
||||
"""|coro|
|
||||
|
||||
Creates a group direct message with the recipients
|
||||
@ -434,7 +432,7 @@ class ClientUser(BaseUser):
|
||||
raise ClientException('You must have two or more recipients to create a group.')
|
||||
|
||||
users = [str(u.id) for u in recipients]
|
||||
data = yield from self._state.http.create_group(self.id, users)
|
||||
data = await self._state.http.create_group(self.id, users)
|
||||
return GroupChannel(me=self, data=data, state=self._state)
|
||||
|
||||
class User(BaseUser, discord.abc.Messageable):
|
||||
@ -477,9 +475,8 @@ class User(BaseUser, discord.abc.Messageable):
|
||||
def __repr__(self):
|
||||
return '<User id={0.id} name={0.name!r} discriminator={0.discriminator!r} bot={0.bot}>'.format(self)
|
||||
|
||||
@asyncio.coroutine
|
||||
def _get_channel(self):
|
||||
ch = yield from self.create_dm()
|
||||
async def _get_channel(self):
|
||||
ch = await self.create_dm()
|
||||
return ch
|
||||
|
||||
@property
|
||||
@ -491,8 +488,7 @@ class User(BaseUser, discord.abc.Messageable):
|
||||
"""
|
||||
return self._state._get_private_channel_by_user(self.id)
|
||||
|
||||
@asyncio.coroutine
|
||||
def create_dm(self):
|
||||
async def create_dm(self):
|
||||
"""Creates a :class:`DMChannel` with this user.
|
||||
|
||||
This should be rarely called, as this is done transparently for most
|
||||
@ -503,7 +499,7 @@ class User(BaseUser, discord.abc.Messageable):
|
||||
return found
|
||||
|
||||
state = self._state
|
||||
data = yield from state.http.start_private_message(self.id)
|
||||
data = await state.http.start_private_message(self.id)
|
||||
return state.add_dm_channel(data)
|
||||
|
||||
@property
|
||||
@ -525,8 +521,7 @@ class User(BaseUser, discord.abc.Messageable):
|
||||
return False
|
||||
return r.type is RelationshipType.blocked
|
||||
|
||||
@asyncio.coroutine
|
||||
def block(self):
|
||||
async def block(self):
|
||||
"""|coro|
|
||||
|
||||
Blocks the user.
|
||||
@ -539,10 +534,9 @@ class User(BaseUser, discord.abc.Messageable):
|
||||
Blocking the user failed.
|
||||
"""
|
||||
|
||||
yield from self._state.http.add_relationship(self.id, type=RelationshipType.blocked.value)
|
||||
await self._state.http.add_relationship(self.id, type=RelationshipType.blocked.value)
|
||||
|
||||
@asyncio.coroutine
|
||||
def unblock(self):
|
||||
async def unblock(self):
|
||||
"""|coro|
|
||||
|
||||
Unblocks the user.
|
||||
@ -554,10 +548,9 @@ class User(BaseUser, discord.abc.Messageable):
|
||||
HTTPException
|
||||
Unblocking the user failed.
|
||||
"""
|
||||
yield from self._state.http.remove_relationship(self.id)
|
||||
await self._state.http.remove_relationship(self.id)
|
||||
|
||||
@asyncio.coroutine
|
||||
def remove_friend(self):
|
||||
async def remove_friend(self):
|
||||
"""|coro|
|
||||
|
||||
Removes the user as a friend.
|
||||
@ -569,10 +562,9 @@ class User(BaseUser, discord.abc.Messageable):
|
||||
HTTPException
|
||||
Removing the user as a friend failed.
|
||||
"""
|
||||
yield from self._state.http.remove_relationship(self.id)
|
||||
await self._state.http.remove_relationship(self.id)
|
||||
|
||||
@asyncio.coroutine
|
||||
def send_friend_request(self):
|
||||
async def send_friend_request(self):
|
||||
"""|coro|
|
||||
|
||||
Sends the user a friend request.
|
||||
@ -584,10 +576,9 @@ class User(BaseUser, discord.abc.Messageable):
|
||||
HTTPException
|
||||
Sending the friend request failed.
|
||||
"""
|
||||
yield from self._state.http.send_friend_request(username=self.name, discriminator=self.discriminator)
|
||||
await self._state.http.send_friend_request(username=self.name, discriminator=self.discriminator)
|
||||
|
||||
@asyncio.coroutine
|
||||
def profile(self):
|
||||
async def profile(self):
|
||||
"""|coro|
|
||||
|
||||
Gets the user's profile. This can only be used by non-bot accounts.
|
||||
@ -606,7 +597,7 @@ class User(BaseUser, discord.abc.Messageable):
|
||||
"""
|
||||
|
||||
state = self._state
|
||||
data = yield from state.http.get_user_profile(self.id)
|
||||
data = await state.http.get_user_profile(self.id)
|
||||
|
||||
def transform(d):
|
||||
return state._get_guild(int(d['id']))
|
||||
|
@ -29,6 +29,7 @@ from .errors import InvalidArgument
|
||||
import datetime
|
||||
from base64 import b64encode
|
||||
from email.utils import parsedate_to_datetime
|
||||
from inspect import isawaitable as _isawaitable
|
||||
import asyncio
|
||||
import json
|
||||
import warnings, functools
|
||||
@ -264,27 +265,23 @@ def _parse_ratelimit_header(request):
|
||||
reset = datetime.datetime.fromtimestamp(int(request.headers['X-Ratelimit-Reset']), datetime.timezone.utc)
|
||||
return (reset - now).total_seconds()
|
||||
|
||||
@asyncio.coroutine
|
||||
def maybe_coroutine(f, *args, **kwargs):
|
||||
async def maybe_coroutine(f, *args, **kwargs):
|
||||
value = f(*args, **kwargs)
|
||||
if asyncio.iscoroutine(value):
|
||||
return (yield from value)
|
||||
if _isawaitable(value):
|
||||
return (await value)
|
||||
else:
|
||||
return value
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_all(gen):
|
||||
check = asyncio.iscoroutine
|
||||
async def async_all(gen, *, check=_isawaitable):
|
||||
for elem in gen:
|
||||
if check(elem):
|
||||
elem = yield from elem
|
||||
elem = await elem
|
||||
if not elem:
|
||||
return False
|
||||
return True
|
||||
|
||||
@asyncio.coroutine
|
||||
def sane_wait_for(futures, *, timeout, loop):
|
||||
done, pending = yield from asyncio.wait(futures, timeout=timeout, loop=loop)
|
||||
async def sane_wait_for(futures, *, timeout, loop):
|
||||
done, pending = await asyncio.wait(futures, timeout=timeout, loop=loop)
|
||||
|
||||
if len(pending) != 0:
|
||||
raise asyncio.TimeoutError()
|
||||
|
@ -129,8 +129,7 @@ class VoiceClient:
|
||||
|
||||
# connection related
|
||||
|
||||
@asyncio.coroutine
|
||||
def start_handshake(self):
|
||||
async def start_handshake(self):
|
||||
log.info('Starting voice handshake...')
|
||||
|
||||
key_id, key_name = self.channel._get_voice_client_key()
|
||||
@ -140,21 +139,20 @@ class VoiceClient:
|
||||
self._connections += 1
|
||||
|
||||
# request joining
|
||||
yield from ws.voice_state(guild_id, channel_id)
|
||||
await ws.voice_state(guild_id, channel_id)
|
||||
|
||||
try:
|
||||
yield from asyncio.wait_for(self._handshake_complete.wait(), timeout=self.timeout, loop=self.loop)
|
||||
await asyncio.wait_for(self._handshake_complete.wait(), timeout=self.timeout, loop=self.loop)
|
||||
except asyncio.TimeoutError as e:
|
||||
yield from self.terminate_handshake(remove=True)
|
||||
await self.terminate_handshake(remove=True)
|
||||
raise e
|
||||
|
||||
log.info('Voice handshake complete. Endpoint found %s (IP: %s)', self.endpoint, self.endpoint_ip)
|
||||
|
||||
@asyncio.coroutine
|
||||
def terminate_handshake(self, *, remove=False):
|
||||
async def terminate_handshake(self, *, remove=False):
|
||||
guild_id, channel_id = self.channel._get_voice_state_pair()
|
||||
self._handshake_complete.clear()
|
||||
yield from self.main_ws.voice_state(guild_id, None, self_mute=True)
|
||||
await self.main_ws.voice_state(guild_id, None, self_mute=True)
|
||||
|
||||
log.info('The voice handshake is being terminated for Channel ID %s (Guild ID %s)', channel_id, guild_id)
|
||||
if remove:
|
||||
@ -162,8 +160,7 @@ class VoiceClient:
|
||||
key_id, _ = self.channel._get_voice_client_key()
|
||||
self._state._remove_voice_client(key_id)
|
||||
|
||||
@asyncio.coroutine
|
||||
def _create_socket(self, server_id, data):
|
||||
async def _create_socket(self, server_id, data):
|
||||
self._connected.clear()
|
||||
self.session_id = self.main_ws.session_id
|
||||
self.server_id = server_id
|
||||
@ -190,13 +187,12 @@ class VoiceClient:
|
||||
if self._handshake_complete.is_set():
|
||||
# terminate the websocket and handle the reconnect loop if necessary.
|
||||
self._handshake_complete.clear()
|
||||
yield from self.ws.close(4000)
|
||||
await self.ws.close(4000)
|
||||
return
|
||||
|
||||
self._handshake_complete.set()
|
||||
|
||||
@asyncio.coroutine
|
||||
def connect(self, *, reconnect=True, _tries=0, do_handshake=True):
|
||||
async def connect(self, *, reconnect=True, _tries=0, do_handshake=True):
|
||||
log.info('Connecting to voice...')
|
||||
try:
|
||||
del self.secret_key
|
||||
@ -204,56 +200,54 @@ class VoiceClient:
|
||||
pass
|
||||
|
||||
if do_handshake:
|
||||
yield from self.start_handshake()
|
||||
await self.start_handshake()
|
||||
|
||||
try:
|
||||
self.ws = yield from DiscordVoiceWebSocket.from_client(self)
|
||||
self.ws = await DiscordVoiceWebSocket.from_client(self)
|
||||
self._connected.clear()
|
||||
while not hasattr(self, 'secret_key'):
|
||||
yield from self.ws.poll_event()
|
||||
await self.ws.poll_event()
|
||||
self._connected.set()
|
||||
except (ConnectionClosed, asyncio.TimeoutError):
|
||||
if reconnect and _tries < 5:
|
||||
log.exception('Failed to connect to voice... Retrying...')
|
||||
yield from asyncio.sleep(1 + _tries * 2.0, loop=self.loop)
|
||||
yield from self.terminate_handshake()
|
||||
yield from self.connect(reconnect=reconnect, _tries=_tries + 1)
|
||||
await asyncio.sleep(1 + _tries * 2.0, loop=self.loop)
|
||||
await self.terminate_handshake()
|
||||
await self.connect(reconnect=reconnect, _tries=_tries + 1)
|
||||
else:
|
||||
raise
|
||||
|
||||
if self._runner is None:
|
||||
self._runner = self.loop.create_task(self.poll_voice_ws(reconnect))
|
||||
|
||||
@asyncio.coroutine
|
||||
def poll_voice_ws(self, reconnect):
|
||||
async def poll_voice_ws(self, reconnect):
|
||||
backoff = ExponentialBackoff()
|
||||
while True:
|
||||
try:
|
||||
yield from self.ws.poll_event()
|
||||
await self.ws.poll_event()
|
||||
except (ConnectionClosed, asyncio.TimeoutError) as e:
|
||||
if isinstance(e, ConnectionClosed):
|
||||
if e.code == 1000:
|
||||
yield from self.disconnect()
|
||||
await self.disconnect()
|
||||
break
|
||||
|
||||
if not reconnect:
|
||||
yield from self.disconnect()
|
||||
await self.disconnect()
|
||||
raise e
|
||||
|
||||
retry = backoff.delay()
|
||||
log.exception('Disconnected from voice... Reconnecting in %.2fs.', retry)
|
||||
self._connected.clear()
|
||||
yield from asyncio.sleep(retry, loop=self.loop)
|
||||
yield from self.terminate_handshake()
|
||||
await asyncio.sleep(retry, loop=self.loop)
|
||||
await self.terminate_handshake()
|
||||
try:
|
||||
yield from self.connect(reconnect=True)
|
||||
await self.connect(reconnect=True)
|
||||
except asyncio.TimeoutError:
|
||||
# at this point we've retried 5 times... let's continue the loop.
|
||||
log.warning('Could not connect to voice... Retrying...')
|
||||
continue
|
||||
|
||||
@asyncio.coroutine
|
||||
def disconnect(self, *, force=False):
|
||||
async def disconnect(self, *, force=False):
|
||||
"""|coro|
|
||||
|
||||
Disconnects this voice client from voice.
|
||||
@ -266,15 +260,14 @@ class VoiceClient:
|
||||
|
||||
try:
|
||||
if self.ws:
|
||||
yield from self.ws.close()
|
||||
await self.ws.close()
|
||||
|
||||
yield from self.terminate_handshake(remove=True)
|
||||
await self.terminate_handshake(remove=True)
|
||||
finally:
|
||||
if self.socket:
|
||||
self.socket.close()
|
||||
|
||||
@asyncio.coroutine
|
||||
def move_to(self, channel):
|
||||
async def move_to(self, channel):
|
||||
"""|coro|
|
||||
|
||||
Moves you to a different voice channel.
|
||||
@ -285,7 +278,7 @@ class VoiceClient:
|
||||
The channel to move to. Must be a voice channel.
|
||||
"""
|
||||
guild_id, _ = self.channel._get_voice_state_pair()
|
||||
yield from self.main_ws.voice_state(guild_id, channel.id)
|
||||
await self.main_ws.voice_state(guild_id, channel.id)
|
||||
|
||||
def is_connected(self):
|
||||
""":class:`bool`: Indicates if the voice client is connected to voice."""
|
||||
|
@ -135,8 +135,7 @@ class AsyncWebhookAdapter(WebhookAdapter):
|
||||
self.session = session
|
||||
self.loop = session.loop
|
||||
|
||||
@asyncio.coroutine
|
||||
def request(self, verb, url, payload=None, multipart=None):
|
||||
async def request(self, verb, url, payload=None, multipart=None):
|
||||
headers = {}
|
||||
data = None
|
||||
if payload:
|
||||
@ -152,9 +151,8 @@ class AsyncWebhookAdapter(WebhookAdapter):
|
||||
data.add_field(key, value)
|
||||
|
||||
for tries in range(5):
|
||||
r = yield from self.session.request(verb, url, headers=headers, data=data)
|
||||
try:
|
||||
data = yield from r.text(encoding='utf-8')
|
||||
async with self.session.request(verb, url, headers=headers, data=data) as r:
|
||||
data = await r.text(encoding='utf-8')
|
||||
if r.headers['Content-Type'] == 'application/json':
|
||||
data = json.loads(data)
|
||||
|
||||
@ -162,7 +160,7 @@ class AsyncWebhookAdapter(WebhookAdapter):
|
||||
remaining = r.headers.get('X-Ratelimit-Remaining')
|
||||
if remaining == '0' and r.status != 429:
|
||||
delta = utils._parse_ratelimit_header(r)
|
||||
yield from asyncio.sleep(delta, loop=self.loop)
|
||||
await asyncio.sleep(delta, loop=self.loop)
|
||||
|
||||
if 300 > r.status >= 200:
|
||||
return data
|
||||
@ -170,11 +168,11 @@ class AsyncWebhookAdapter(WebhookAdapter):
|
||||
# we are being rate limited
|
||||
if r.status == 429:
|
||||
retry_after = data['retry_after'] / 1000.0
|
||||
yield from asyncio.sleep(retry_after, loop=self.loop)
|
||||
await asyncio.sleep(retry_after, loop=self.loop)
|
||||
continue
|
||||
|
||||
if r.status in (500, 502):
|
||||
yield from asyncio.sleep(1 + tries * 2, loop=self.loop)
|
||||
await asyncio.sleep(1 + tries * 2, loop=self.loop)
|
||||
continue
|
||||
|
||||
if r.status == 403:
|
||||
@ -183,12 +181,9 @@ class AsyncWebhookAdapter(WebhookAdapter):
|
||||
raise NotFound(r, data)
|
||||
else:
|
||||
raise HTTPException(r, data)
|
||||
finally:
|
||||
yield from r.release()
|
||||
|
||||
@asyncio.coroutine
|
||||
def handle_execution_response(self, response, *, wait):
|
||||
data = yield from response
|
||||
async def handle_execution_response(self, response, *, wait):
|
||||
data = await response
|
||||
if not wait:
|
||||
return data
|
||||
|
||||
|
26
docs/api.rst
26
docs/api.rst
@ -93,17 +93,8 @@ to handle it, which defaults to print a traceback and ignoring the exception.
|
||||
.. warning::
|
||||
|
||||
All the events must be a |corourl|_. If they aren't, then you might get unexpected
|
||||
errors. In order to turn a function into a coroutine they must either be ``async def``
|
||||
functions or in 3.4 decorated with :func:`asyncio.coroutine`.
|
||||
|
||||
The following two functions are examples of coroutine functions: ::
|
||||
|
||||
async def on_ready():
|
||||
pass
|
||||
|
||||
@asyncio.coroutine
|
||||
def on_ready():
|
||||
pass
|
||||
errors. In order to turn a function into a coroutine they must be ``async def``
|
||||
functions.
|
||||
|
||||
.. function:: on_connect()
|
||||
|
||||
@ -1306,22 +1297,11 @@ Some API functions return an "async iterator". An async iterator is something th
|
||||
capable of being used in an `async for <https://docs.python.org/3/reference/compound_stmts.html#the-async-for-statement>`_
|
||||
statement.
|
||||
|
||||
These async iterators can be used as follows in 3.5 or higher: ::
|
||||
These async iterators can be used as follows: ::
|
||||
|
||||
async for elem in channel.history():
|
||||
# do stuff with elem here
|
||||
|
||||
If you are using 3.4 however, you will have to use the more verbose way: ::
|
||||
|
||||
iterator = channel.history() # or whatever returns an async iterator
|
||||
while True:
|
||||
try:
|
||||
item = yield from iterator.next()
|
||||
except discord.NoMoreItems:
|
||||
break
|
||||
|
||||
# do stuff with item here
|
||||
|
||||
Certain utilities make working with async iterators easier, detailed below.
|
||||
|
||||
.. class:: AsyncIterator
|
||||
|
26
docs/faq.rst
26
docs/faq.rst
@ -15,27 +15,6 @@ Coroutines
|
||||
|
||||
Questions regarding coroutines and asyncio belong here.
|
||||
|
||||
I get a SyntaxError around the word ``async``\! What should I do?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This :exc:`SyntaxError` happens because you're using a Python version lower than 3.5. Python 3.4 uses ``@asyncio.coroutine`` and
|
||||
``yield from`` instead of ``async def`` and ``await``.
|
||||
|
||||
Thus you must do the following instead: ::
|
||||
|
||||
async def foo():
|
||||
await bar()
|
||||
|
||||
# into
|
||||
|
||||
@asyncio.coroutine
|
||||
def foo():
|
||||
yield from bar()
|
||||
|
||||
Don't forget to ``import asyncio`` on the top of your files.
|
||||
|
||||
**It is heavily recommended that you update to Python 3.5 or higher as it simplifies asyncio massively.**
|
||||
|
||||
What is a coroutine?
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@ -195,11 +174,6 @@ technically in another thread, we must take caution in calling thread-safe opera
|
||||
us, :mod:`asyncio` comes with a :func:`asyncio.run_coroutine_threadsafe` function that allows us to call
|
||||
a coroutine from another thread.
|
||||
|
||||
.. warning::
|
||||
|
||||
This function is only part of 3.5.1+ and 3.4.4+. If you are not using these Python versions then use
|
||||
``discord.compat.run_coroutine_threadsafe``.
|
||||
|
||||
However, this function returns a :class:`concurrent.Future` and to actually call it we have to fetch its result. Putting all of
|
||||
this together we can do the following: ::
|
||||
|
||||
|
@ -11,9 +11,9 @@ in creating applications that utilise the Discord API.
|
||||
Prerequisites
|
||||
---------------
|
||||
|
||||
discord.py works with Python 3.4.2 or higher. Support for earlier versions of Python
|
||||
is not provided. Python 2.7 or lower is not supported. Python 3.3 is not supported
|
||||
due to one of the dependencies (``aiohttp``) not supporting Python 3.3.
|
||||
discord.py works with Python 3.5.2 or higher. Support for earlier versions of Python
|
||||
is not provided. Python 2.7 or lower is not supported. Python 3.4 or lower is not supported
|
||||
due to one of the dependencies (``aiohttp``) not supporting Python 3.4.
|
||||
|
||||
|
||||
.. _installing:
|
||||
|
@ -14,6 +14,13 @@ new library.
|
||||
Part of the redesign involves making things more easy to use and natural. Things are done on the
|
||||
:ref:`models <discord_api_models>` instead of requiring a :class:`Client` instance to do any work.
|
||||
|
||||
Python Version Change
|
||||
-----------------------
|
||||
|
||||
In order to make development easier and also to allow for our dependencies to upgrade to allow usage of 3.7 or higher,
|
||||
the library had to remove support for Python versions lower than 3.5.2, which essentially means that **support for Python 3.4
|
||||
is dropped**.
|
||||
|
||||
Major Model Changes
|
||||
---------------------
|
||||
|
||||
@ -441,14 +448,14 @@ Prior to v1.0, certain functions like ``Client.logs_from`` would return a differ
|
||||
In v1.0, this change has been reverted and will now return a singular type meeting an abstract concept called
|
||||
:class:`AsyncIterator`.
|
||||
|
||||
This allows you to iterate over it like normal in Python 3.5+: ::
|
||||
This allows you to iterate over it like normal: ::
|
||||
|
||||
async for message in channel.history():
|
||||
print(message)
|
||||
|
||||
Or turn it into a list for either Python 3.4 or 3.5+: ::
|
||||
Or turn it into a list: ::
|
||||
|
||||
messages = await channel.history().flatten() # use yield from for 3.4!
|
||||
messages = await channel.history().flatten()
|
||||
for message in messages:
|
||||
print(message)
|
||||
|
||||
|
@ -1,2 +1,2 @@
|
||||
aiohttp>=2.0.0,<2.3.0
|
||||
websockets>=3.0,<4.0
|
||||
aiohttp>=3.3.0,<3.4.0
|
||||
websockets>=5.0,<6.0
|
||||
|
1
setup.py
1
setup.py
@ -63,7 +63,6 @@ setup(name='discord.py',
|
||||
'Intended Audience :: Developers',
|
||||
'Natural Language :: English',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Topic :: Internet',
|
||||
|
Loading…
x
Reference in New Issue
Block a user