From 30394d03f2348d707f7e795ee3a40a5676ef7b34 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Fri, 23 Dec 2016 23:00:09 -0500 Subject: [PATCH] Remove no longer used functions in Client. --- discord/client.py | 2267 +++++---------------------------------------- 1 file changed, 209 insertions(+), 2058 deletions(-) diff --git a/discord/client.py b/discord/client.py index b760ee832..74bb941cf 100644 --- a/discord/client.py +++ b/discord/client.py @@ -26,20 +26,14 @@ DEALINGS IN THE SOFTWARE. from . import __version__ as library_version from .user import User -from .member import Member -from .channel import * -from .guild import Guild -from .message import Message from .invite import Invite from .object import Object -from .role import Role from .errors import * from .state import ConnectionState from .permissions import Permissions, PermissionOverwrite from . import utils, compat -from .enums import ChannelType, GuildRegion, VerificationLevel, Status +from .enums import ChannelType, Status from .voice_client import VoiceClient -from .iterators import LogsFromIterator from .gateway import * from .emoji import Emoji from .http import HTTPClient @@ -259,33 +253,6 @@ class Client: return m.group(1) return invite - @asyncio.coroutine - def _resolve_destination(self, destination): - if isinstance(destination, TextChannel): - return destination.id, destination.guild.id - elif isinstance(destination, DMChannel): - return destination.id, None - elif isinstance(destination, Guild): - return destination.id, destination.id - elif isinstance(destination, User): - found = self.connection._get_private_channel_by_user(destination.id) - if found is None: - # Couldn't find the user, so start a PM with them first. - channel = yield from self.start_private_message(destination) - return channel.id, None - else: - return found.id, None - elif isinstance(destination, Object): - found = self.get_channel(destination.id) - if found is not None: - return (yield from self._resolve_destination(found)) - - # couldn't find it in cache so YOLO - return destination.id, destination.id - else: - fmt = 'Destination must be TextChannel, DMChannel, User, or Object. Received {0.__class__.__name__}' - raise InvalidArgument(fmt.format(destination)) - def __getattr__(self, name): if name in ('user', 'guilds', 'private_channels', 'messages', 'voice_clients'): return getattr(self.connection, name) @@ -881,1818 +848,292 @@ class Client: return self.event(coro) - # Message sending/management - @asyncio.coroutine - def start_private_message(self, user): + def request_offline_members(self, guild): """|coro| - Starts a private message with the user. This allows you to - :meth:`send_message` to the user. + Requests previously offline members from the guild to be filled up + into the :attr:`Guild.members` cache. This function is usually not + called. - Note - ----- - This method should rarely be called as :meth:`send_message` - does it automatically for you. + When the client logs on and connects to the websocket, Discord does + not provide the library with offline members if the number of members + in the guild is larger than 250. You can check if a guild is large + if :attr:`Guild.large` is ``True``. Parameters ----------- - user : :class:`User` - The user to start the private message with. - - Raises - ------ - HTTPException - The request failed. - InvalidArgument - The user argument was not of :class:`User`. + guild : :class:`Guild` or iterable + The guild to request offline members for. If this parameter is a + iterable then it is interpreted as an iterator of guilds to + request offline members for. """ - if not isinstance(user, User): - raise InvalidArgument('user argument must be a User') + if hasattr(guild, 'id'): + guild_id = guild.id + else: + guild_id = [s.id for s in guild] + + payload = { + 'op': 8, + 'd': { + 'guild_id': guild_id, + 'query': '', + 'limit': 0 + } + } - data = yield from self.http.start_private_message(user.id) - channel = PrivateChannel(me=self.user, data=data, state=self.connection.ctx) - self.connection._add_private_channel(channel) - return channel + yield from self.ws.send_as_json(payload) @asyncio.coroutine - def add_reaction(self, message, emoji): + def edit_profile(self, password=None, **fields): """|coro| - Add a reaction to the given message. + Edits the current profile of the client. + + If a bot account is used then the password field is optional, + otherwise it is required. + + The :attr:`Client.user` object is not modified directly afterwards until the + corresponding WebSocket event is received. + + Note + ----- + To upload an avatar, a *bytes-like object* must be passed in that + represents the image being uploaded. If this is done through a file + then the file must be opened via ``open('some_filename', 'rb')`` and + the *bytes-like object* is given through the use of ``fp.read()``. - The message must be a :class:`Message` that exists. emoji may be a unicode emoji, - or a custom server :class:`Emoji`. + The only image formats supported for uploading is JPEG and PNG. Parameters - ------------ - message : :class:`Message` - The message to react to. - emoji : :class:`Emoji` or str - The emoji to react with. + ----------- + password : str + The current password for the client's account. Not used + for bot accounts. + new_password : str + The new password you wish to change to. + email : str + The new email you wish to change to. + username :str + The new username you wish to change to. + avatar : bytes + A *bytes-like object* representing the image to upload. + Could be ``None`` to denote no avatar. Raises - -------- + ------ HTTPException - Adding the reaction failed. - Forbidden - You do not have the proper permissions to react to the message. - NotFound - The message or emoji you specified was not found. + Editing your profile failed. InvalidArgument - The message or emoji parameter is invalid. + Wrong image format passed for ``avatar``. + ClientException + Password is required for non-bot accounts. """ - if not isinstance(message, Message): - raise InvalidArgument('message argument must be a Message') - if not isinstance(emoji, (str, Emoji)): - raise InvalidArgument('emoji argument must be a string or Emoji') - if isinstance(emoji, Emoji): - emoji = '{}:{}'.format(emoji.name, emoji.id) + try: + avatar_bytes = fields['avatar'] + except KeyError: + avatar = self.user.avatar + else: + if avatar_bytes is not None: + avatar = utils._bytes_to_base64_data(avatar_bytes) + else: + avatar = None + + not_bot_account = not self.user.bot + if not_bot_account and password is None: + raise ClientException('Password is required for non-bot accounts.') + + args = { + 'password': password, + 'username': fields.get('username', self.user.name), + 'avatar': avatar + } + + if not_bot_account: + args['email'] = fields.get('email', self.email) + + if 'new_password' in fields: + args['new_password'] = fields['new_password'] + + data = yield from self.http.edit_profile(**args) + if not_bot_account: + self.email = data['email'] + if 'token' in data: + self.http._token(data['token'], bot=False) - yield from self.http.add_reaction(message.id, message.channel.id, emoji) + if self.cache_auth: + self._update_cache(self.email, password) @asyncio.coroutine - def remove_reaction(self, message, emoji, member): + def change_presence(self, *, game=None, status=None, afk=False): """|coro| - Remove a reaction by the member from the given message. - - If member != server.me, you need Manage Messages to remove the reaction. + Changes the client's presence. - The message must be a :class:`Message` that exists. emoji may be a unicode emoji, - or a custom server :class:`Emoji`. + The game parameter is a Game object (not a string) that represents + a game being played currently. Parameters - ------------ - message : :class:`Message` - The message. - emoji : :class:`Emoji` or str - The emoji to remove. - member : :class:`Member` - The member for which to delete the reaction. + ---------- + game: Optional[:class:`Game`] + The game being played. None if no game is being played. + status: Optional[:class:`Status`] + Indicates what status to change to. If None, then + :attr:`Status.online` is used. + afk: bool + Indicates if you are going AFK. This allows the discord + client to know how to handle push notifications better + for you in case you are actually idle and not lying. Raises - -------- - HTTPException - Removing the reaction failed. - Forbidden - You do not have the proper permissions to remove the reaction. - NotFound - The message or emoji you specified was not found. + ------ InvalidArgument - The message or emoji parameter is invalid. + If the ``game`` parameter is not :class:`Game` or None. """ - if not isinstance(message, Message): - raise InvalidArgument('message argument must be a Message') - if not isinstance(emoji, (str, Emoji)): - raise InvalidArgument('emoji must be a string or Emoji') - - if isinstance(emoji, Emoji): - emoji = '{}:{}'.format(emoji.name, emoji.id) - if member == self.user: - member_id = '@me' + if status is None: + status = 'online' + elif status is Status.offline: + status = 'invisible' else: - member_id = member.id + status = str(status) + + yield from self.ws.change_presence(game=game, status=status, afk=afk) + + # Invite management - yield from self.http.remove_reaction(message.id, message.channel.id, emoji, member_id) + def _fill_invite_data(self, data): + guild = self.connection._get_guild(data['guild']['id']) + if guild is not None: + ch_id = data['channel']['id'] + channel = guild.get_channel(ch_id) + else: + guild = Object(id=data['guild']['id']) + guild.name = data['guild']['name'] + channel = Object(id=data['channel']['id']) + channel.name = data['channel']['name'] + data['guild'] = guild + data['channel'] = channel @asyncio.coroutine - def get_reaction_users(self, reaction, limit=100, after=None): + def create_invite(self, destination, **options): """|coro| - Get the users that added a reaction to a message. + Creates an invite for the destination which could be either a + :class:`Guild` or :class:`Channel`. Parameters ------------ - reaction : :class:`Reaction` - The reaction to retrieve users for. - limit : int - The maximum number of results to return. - after : :class:`Member` or :class:`Object` - For pagination, reactions are sorted by member. + destination + The :class:`Guild` or :class:`Channel` to create the invite to. + max_age : int + How long the invite should last. If it's 0 then the invite + doesn't expire. Defaults to 0. + max_uses : int + How many uses the invite could be used for. If it's 0 then there + are unlimited uses. Defaults to 0. + temporary : bool + Denotes that the invite grants temporary membership + (i.e. they get kicked after they disconnect). Defaults to False. + xkcd : bool + Indicates if the invite URL is human readable. Defaults to False. Raises - -------- + ------- HTTPException - Getting the users for the reaction failed. - NotFound - The message or emoji you specified was not found. - InvalidArgument - The reaction parameter is invalid. - """ - if not isinstance(reaction, Reaction): - raise InvalidArgument('reaction must be a Reaction') - - emoji = reaction.emoji - - if isinstance(emoji, Emoji): - emoji = '{}:{}'.format(emoji.name, emoji.id) - - if after: - after = after.id + Invite creation failed. - data = yield from self.http.get_reaction_users( - reaction.message.id, reaction.message.channel.id, - emoji, limit, after=after) + Returns + -------- + :class:`Invite` + The invite that was created. + """ - return [User(**user) for user in data] + data = yield from self.http.create_invite(destination.id, **options) + self._fill_invite_data(data) + return Invite(**data) @asyncio.coroutine - def clear_reactions(self, message): + def get_invite(self, url): """|coro| - Removes all the reactions from a given message. + Gets a :class:`Invite` from a discord.gg URL or ID. - You need Manage Messages permission to use this. + Note + ------ + If the invite is for a guild you have not joined, the guild and channel + attributes of the returned invite will be :class:`Object` with the names + patched in. Parameters ----------- - message: :class:`Message` - The message to remove all reactions from. + url : str + The discord invite ID or URL (must be a discord.gg URL). Raises - -------- + ------- + NotFound + The invite has expired or is invalid. HTTPException - Removing the reactions failed. - Forbidden - You do not have the proper permissions to remove all the reactions. + Getting the invite failed. + + Returns + -------- + :class:`Invite` + The invite from the URL/ID. """ - yield from self.http.clear_reactions(message.id, message.channel.id) + + invite_id = self._resolve_invite(url) + data = yield from self.http.get_invite(invite_id) + self._fill_invite_data(data) + return Invite(**data) @asyncio.coroutine - def send_message(self, destination, content=None, *, tts=False, embed=None): + def invites_from(self, guild): """|coro| - Sends a message to the destination given with the content given. - - The destination could be a :class:`Channel`, :class:`PrivateChannel` or :class:`Guild`. - For convenience it could also be a :class:`User`. If it's a :class:`User` or :class:`PrivateChannel` - then it sends the message via private message, otherwise it sends the message to the channel. - If the destination is a :class:`Guild` then it's equivalent to calling - :attr:`Guild.default_channel` and sending it there. - - If it is a :class:`Object` instance then it is assumed to be the - destination ID. The destination ID is a *channel* so passing in a user - ID will not be a valid destination. - - .. versionchanged:: 0.9.0 - ``str`` being allowed was removed and replaced with :class:`Object`. - - The content must be a type that can convert to a string through ``str(content)``. - If the content is set to ``None`` (the default), then the ``embed`` parameter must - be provided. + Returns a list of all active instant invites from a :class:`Guild`. - If the ``embed`` parameter is provided, it must be of type :class:`Embed` and - it must be a rich embed type. + You must have proper permissions to get this information. Parameters - ------------ - destination - The location to send the message. - content - The content of the message to send. If this is missing, - then the ``embed`` parameter must be present. - tts : bool - Indicates if the message should be sent using text-to-speech. - embed: :class:`Embed` - The rich embed for the content. + ---------- + guild : :class:`Guild` + The guild to get invites from. Raises - -------- - HTTPException - Sending the message failed. + ------- Forbidden - You do not have the proper permissions to send the message. - NotFound - The destination was not found and hence is invalid. - InvalidArgument - The destination parameter is invalid. - - Examples - ---------- + You do not have proper permissions to get the information. + HTTPException + An error occurred while fetching the information. - Sending a regular message: + Returns + ------- + list of :class:`Invite` + The list of invites that are currently active. + """ - .. code-block:: python + data = yield from self.http.invites_from(guild.id) + result = [] + for invite in data: + channel = guild.get_channel(invite['channel']['id']) + invite['channel'] = channel + invite['guild'] = guild + result.append(Invite(**invite)) - await client.send_message(message.channel, 'Hello') + return result - Sending a TTS message: + @asyncio.coroutine + def accept_invite(self, invite): + """|coro| - .. code-block:: python + Accepts an :class:`Invite`, URL or ID to an invite. - await client.send_message(message.channel, 'Goodbye.', tts=True) - - Sending an embed message: - - .. code-block:: python - - em = discord.Embed(title='My Embed Title', description='My Embed Content.', colour=0xDEADBF) - em.set_author(name='Someone', icon_url=client.user.default_avatar_url) - await client.send_message(message.channel, embed=em) - - Returns - --------- - :class:`Message` - The message that was sent. - """ - - channel_id, guild_id = yield from self._resolve_destination(destination) - - content = str(content) if content else None - - if embed is not None: - embed = embed.to_dict() - - data = yield from self.http.send_message(channel_id, content, guild_id=guild_id, tts=tts, embed=embed) - channel = self.get_channel(data.get('channel_id')) - message = Message(channel=channel, state=self.connection.ctx, data=data) - return message - - @asyncio.coroutine - def send_typing(self, destination): - """|coro| - - Send a *typing* status to the destination. - - *Typing* status will go away after 10 seconds, or after a message is sent. - - The destination parameter follows the same rules as :meth:`send_message`. - - Parameters - ---------- - destination - The location to send the typing update. - """ - - channel_id, guild_id = yield from self._resolve_destination(destination) - yield from self.http.send_typing(channel_id) - - @asyncio.coroutine - def send_file(self, destination, fp, *, filename=None, content=None, tts=False): - """|coro| - - Sends a message to the destination given with the file given. - - The destination parameter follows the same rules as :meth:`send_message`. - - The ``fp`` parameter should be either a string denoting the location for a - file or a *file-like object*. The *file-like object* passed is **not closed** - at the end of execution. You are responsible for closing it yourself. - - .. note:: - - If the file-like object passed is opened via ``open`` then the modes - 'rb' should be used. - - The ``filename`` parameter is the filename of the file. - If this is not given then it defaults to ``fp.name`` or if ``fp`` is a string - then the ``filename`` will default to the string given. You can overwrite - this value by passing this in. - - Parameters - ------------ - destination - The location to send the message. - fp - The *file-like object* or file path to send. - filename : str - The filename of the file. Defaults to ``fp.name`` if it's available. - content - The content of the message to send along with the file. This is - forced into a string by a ``str(content)`` call. - tts : bool - If the content of the message should be sent with TTS enabled. - - Raises - ------- - HTTPException - Sending the file failed. - - Returns - -------- - :class:`Message` - The message sent. - """ - - channel_id, guild_id = yield from self._resolve_destination(destination) - - try: - with open(fp, 'rb') as f: - buffer = io.BytesIO(f.read()) - if filename is None: - _, filename = path_split(fp) - except TypeError: - buffer = fp - - data = yield from self.http.send_file(channel_id, buffer, guild_id=guild_id, - filename=filename, content=content, tts=tts) - channel = self.get_channel(data.get('channel_id')) - message = Message(channel=channel, state=self.connection.ctx, data=data) - return message - - @asyncio.coroutine - def delete_message(self, message): - """|coro| - - Deletes a :class:`Message`. - - Your own messages could be deleted without any proper permissions. However to - delete other people's messages, you need the proper permissions to do so. - - Parameters - ----------- - message : :class:`Message` - The message to delete. - - Raises - ------ - Forbidden - You do not have proper permissions to delete the message. - HTTPException - Deleting the message failed. - """ - channel = message.channel - guild_id = channel.guild.id if not getattr(channel, 'is_private', True) else None - yield from self.http.delete_message(channel.id, message.id, guild_id) - - @asyncio.coroutine - def delete_messages(self, messages): - """|coro| - - Deletes a list of messages. This is similar to :func:`delete_message` - except it bulk deletes multiple messages. - - The channel to check where the message is deleted from is handled via - the first element of the iterable's ``.channel.id`` attributes. If the - channel is not consistent throughout the entire sequence, then an - :exc:`HTTPException` will be raised. - - Usable only by bot accounts. - - Parameters - ----------- - messages : iterable of :class:`Message` - An iterable of messages denoting which ones to bulk delete. - - Raises - ------ - ClientException - The number of messages to delete is less than 2 or more than 100. - Forbidden - You do not have proper permissions to delete the messages or - you're not using a bot account. - HTTPException - Deleting the messages failed. - """ - - messages = list(messages) - if len(messages) > 100 or len(messages) < 2: - raise ClientException('Can only delete messages in the range of [2, 100]') - - channel = messages[0].channel - message_ids = [m.id for m in messages] - guild_id = channel.guild.id if not getattr(channel, 'is_private', True) else None - yield from self.http.delete_messages(channel.id, message_ids, guild_id) - - @asyncio.coroutine - def purge_from(self, channel, *, limit=100, check=None, before=None, after=None, around=None): - """|coro| - - Purges a list of messages that meet the criteria given by the predicate - ``check``. If a ``check`` is not provided then all messages are deleted - without discrimination. - - You must have Manage Messages permission to delete messages even if they - are your own. The Read Message History permission is also needed to - retrieve message history. - - Usable only by bot accounts. - - Parameters - ----------- - channel : :class:`Channel` - The channel to purge from. - limit : int - The number of messages to search through. This is not the number - of messages that will be deleted, though it can be. - check : predicate - The function used to check if a message should be deleted. - It must take a :class:`Message` as its sole parameter. - before : :class:`Message` or `datetime` - The message or date before which all deleted messages must be. - If a date is provided it must be a timezone-naive datetime representing UTC time. - after : :class:`Message` or `datetime` - The message or date after which all deleted messages must be. - If a date is provided it must be a timezone-naive datetime representing UTC time. - around : :class:`Message` or `datetime` - The message or date around which all deleted messages must be. - If a date is provided it must be a timezone-naive datetime representing UTC time. - - Raises - ------- - Forbidden - You do not have proper permissions to do the actions required or - you're not using a bot account. - HTTPException - Purging the messages failed. - - Examples - --------- - - Deleting bot's messages :: - - def is_me(m): - return m.author == client.user - - deleted = await client.purge_from(channel, limit=100, check=is_me) - await client.send_message(channel, 'Deleted {} message(s)'.format(len(deleted))) - - Returns - -------- - list - The list of messages that were deleted. - """ - - if check is None: - check = lambda m: True - - if isinstance(before, datetime.datetime): - before = Object(utils.time_snowflake(before, high=False)) - if isinstance(after, datetime.datetime): - after = Object(utils.time_snowflake(after, high=True)) - if isinstance(around, datetime.datetime): - around = Object(utils.time_snowflake(around, high=True)) - - iterator = LogsFromIterator(self, channel, limit, before=before, after=after, around=around) - ret = [] - count = 0 - - while True: - try: - msg = yield from iterator.iterate() - except asyncio.QueueEmpty: - # no more messages to poll - if count >= 2: - # more than 2 messages -> bulk delete - to_delete = ret[-count:] - yield from self.delete_messages(to_delete) - elif count == 1: - # delete a single message - yield from self.delete_message(ret[-1]) - - return ret - else: - if count == 100: - # we've reached a full 'queue' - to_delete = ret[-100:] - yield from self.delete_messages(to_delete) - count = 0 - yield from asyncio.sleep(1, loop=self.loop) - - if check(msg): - count += 1 - ret.append(msg) - - @asyncio.coroutine - def edit_message(self, message, new_content=None, *, embed=None): - """|coro| - - Edits a :class:`Message` with the new message content. - - The new_content must be able to be transformed into a string via ``str(new_content)``. - - If the ``new_content`` is not provided, then ``embed`` must be provided, which must - be of type :class:`Embed`. - - The :class:`Message` object is not directly modified afterwards until the - corresponding WebSocket event is received. - - Parameters - ----------- - message : :class:`Message` - The message to edit. - new_content - The new content to replace the message with. - embed: :class:`Embed` - The new embed to replace the original embed with. - - Raises - ------- - HTTPException - Editing the message failed. - - Returns - -------- - :class:`Message` - The new edited message. - """ - - channel = message.channel - content = str(new_content) if new_content else None - embed = embed.to_dict() if embed else None - guild_id = channel.guild.id if not getattr(channel, 'is_private', True) else None - data = yield from self.http.edit_message(message.id, channel.id, content, guild_id=guild_id, embed=embed) - return Message(channel=channel, state=self.connection.ctx, data=data) - - @asyncio.coroutine - def get_message(self, channel, id): - """|coro| - - Retrieves a single :class:`Message` from a :class:`Channel`. - - This can only be used by bot accounts. - - Parameters - ------------ - channel: :class:`Channel` or :class:`PrivateChannel` - The text channel to retrieve the message from. - id: str - The message ID to look for. - - Returns - -------- - :class:`Message` - The message asked for. - - Raises - -------- - NotFound - The specified channel or message was not found. - Forbidden - You do not have the permissions required to get a message. - HTTPException - Retrieving the message failed. - """ - - data = yield from self.http.get_message(channel.id, id) - return Message(channel=channel, state=self.connection.ctx, data=data) - - @asyncio.coroutine - def pin_message(self, message): - """|coro| - - Pins a message. You must have Manage Messages permissions - to do this in a non-private channel context. - - Parameters - ----------- - message: :class:`Message` - The message to pin. - - Raises - ------- - Forbidden - You do not have permissions to pin the message. - NotFound - The message or channel was not found. - HTTPException - Pinning the message failed, probably due to the channel - having more than 50 pinned messages. - """ - yield from self.http.pin_message(message.channel.id, message.id) - - @asyncio.coroutine - def unpin_message(self, message): - """|coro| - - Unpins a message. You must have Manage Messages permissions - to do this in a non-private channel context. - - Parameters - ----------- - message: :class:`Message` - The message to unpin. - - Raises - ------- - Forbidden - You do not have permissions to unpin the message. - NotFound - The message or channel was not found. - HTTPException - Unpinning the message failed. - """ - yield from self.http.unpin_message(message.channel.id, message.id) - - @asyncio.coroutine - def pins_from(self, channel): - """|coro| - - Returns a list of :class:`Message` that are currently pinned for - the specified :class:`Channel` or :class:`PrivateChannel`. - - Parameters - ----------- - channel: :class:`Channel` or :class:`PrivateChannel` - The channel to look through pins for. - - Raises - ------- - NotFound - The channel was not found. - HTTPException - Retrieving the pinned messages failed. - """ - - data = yield from self.http.pins_from(channel.id) - return [Message(channel=channel, state=self.connection.ctx, data=m) for m in data] - - def _logs_from(self, channel, limit=100, before=None, after=None, around=None): - """|coro| - - This coroutine returns a generator that obtains logs from a specified channel. - - Parameters - ----------- - channel : :class:`Channel` or :class:`PrivateChannel` - The channel to obtain the logs from. - limit : int - The number of messages to retrieve. - before : :class:`Message` or `datetime` - The message or date before which all returned messages must be. - If a date is provided it must be a timezone-naive datetime representing UTC time. - after : :class:`Message` or `datetime` - The message or date after which all returned messages must be. - If a date is provided it must be a timezone-naive datetime representing UTC time. - around : :class:`Message` or `datetime` - The message or date around which all returned messages must be. - If a date is provided it must be a timezone-naive datetime representing UTC time. - - Raises - ------ - Forbidden - You do not have permissions to get channel logs. - NotFound - The channel you are requesting for doesn't exist. - HTTPException - The request to get logs failed. - - Yields - ------- - :class:`Message` - The message with the message data parsed. - - Examples - --------- - - Basic logging: :: - - logs = yield from client.logs_from(channel) - for message in logs: - if message.content.startswith('!hello'): - if message.author == client.user: - yield from client.edit_message(message, 'goodbye') - - Python 3.5 Usage :: - - counter = 0 - async for message in client.logs_from(channel, limit=500): - if message.author == client.user: - counter += 1 - """ - before = getattr(before, 'id', None) - after = getattr(after, 'id', None) - around = getattr(around, 'id', None) - - return self.http.logs_from(channel.id, limit, before=before, after=after, around=around) - - if PY35: - def logs_from(self, channel, limit=100, *, before=None, after=None, around=None, reverse=False): - if isinstance(before, datetime.datetime): - before = Object(utils.time_snowflake(before, high=False)) - if isinstance(after, datetime.datetime): - after = Object(utils.time_snowflake(after, high=True)) - if isinstance(around, datetime.datetime): - around = Object(utils.time_snowflake(around)) - - return LogsFromIterator(self, channel, limit, before=before, after=after, around=around, reverse=reverse) - else: - @asyncio.coroutine - def logs_from(self, channel, limit=100, *, before=None, after=None): - if isinstance(before, datetime.datetime): - before = Object(utils.time_snowflake(before, high=False)) - if isinstance(after, datetime.datetime): - after = Object(utils.time_snowflake(after, high=True)) - - def generator(data): - for message in data: - yield Message(channel=channel, state=self.connection.ctx, data=message) - - result = [] - while limit > 0: - retrieve = limit if limit <= 100 else 100 - data = yield from self._logs_from(channel, retrieve, before, after) - if len(data): - limit -= retrieve - result.extend(data) - before = Object(id=data[-1]['id']) - else: - break - - return generator(result) - - logs_from.__doc__ = _logs_from.__doc__ - - # Member management - - @asyncio.coroutine - def request_offline_members(self, guild): - """|coro| - - Requests previously offline members from the guild to be filled up - into the :attr:`Guild.members` cache. This function is usually not - called. - - When the client logs on and connects to the websocket, Discord does - not provide the library with offline members if the number of members - in the guild is larger than 250. You can check if a guild is large - if :attr:`Guild.large` is ``True``. - - Parameters - ----------- - guild : :class:`Guild` or iterable - The guild to request offline members for. If this parameter is a - iterable then it is interpreted as an iterator of guilds to - request offline members for. - """ - - if hasattr(guild, 'id'): - guild_id = guild.id - else: - guild_id = [s.id for s in guild] - - payload = { - 'op': 8, - 'd': { - 'guild_id': guild_id, - 'query': '', - 'limit': 0 - } - } - - yield from self.ws.send_as_json(payload) - - @asyncio.coroutine - def kick(self, member): - """|coro| - - Kicks a :class:`Member` from the guild they belong to. - - Warning - -------- - This function kicks the :class:`Member` based on the guild it - belongs to, which is accessed via :attr:`Member.guild`. So you - must have the proper permissions in that guild. - - Parameters - ----------- - member : :class:`Member` - The member to kick from their guild. - - Raises - ------- - Forbidden - You do not have the proper permissions to kick. - HTTPException - Kicking failed. - """ - yield from self.http.kick(member.id, member.guild.id) - - @asyncio.coroutine - def ban(self, member, delete_message_days=1): - """|coro| - - Bans a :class:`Member` from the guild they belong to. - - Warning - -------- - This function bans the :class:`Member` based on the guild it - belongs to, which is accessed via :attr:`Member.guild`. So you - must have the proper permissions in that guild. - - Parameters - ----------- - member : :class:`Member` - The member to ban from their guild. - delete_message_days : int - The number of days worth of messages to delete from the user - in the guild. The minimum is 0 and the maximum is 7. - - Raises - ------- - Forbidden - You do not have the proper permissions to ban. - HTTPException - Banning failed. - """ - yield from self.http.ban(member.id, member.guild.id, delete_message_days) - - @asyncio.coroutine - def unban(self, guild, user): - """|coro| - - Unbans a :class:`User` from the guild they are banned from. - - Parameters - ----------- - guild : :class:`Guild` - The guild to unban the user from. - user : :class:`User` - The user to unban. - - Raises - ------- - Forbidden - You do not have the proper permissions to unban. - HTTPException - Unbanning failed. - """ - yield from self.http.unban(user.id, guild.id) - - @asyncio.coroutine - def guild_voice_state(self, member, *, mute=None, deafen=None): - """|coro| - - Guild mutes or deafens a specific :class:`Member`. - - Warning - -------- - This function mutes or un-deafens the :class:`Member` based on the - guild it belongs to, which is accessed via :attr:`Member.guild`. - So you must have the proper permissions in that guild. - - Parameters - ----------- - member : :class:`Member` - The member to unban from their guild. - mute: Optional[bool] - Indicates if the member should be guild muted or un-muted. - deafen: Optional[bool] - Indicates if the member should be guild deafened or un-deafened. - - Raises - ------- - Forbidden - You do not have the proper permissions to deafen or mute. - HTTPException - The operation failed. - """ - yield from self.http.guild_voice_state(member.id, member.guild.id, mute=mute, deafen=deafen) - - @asyncio.coroutine - def edit_profile(self, password=None, **fields): - """|coro| - - Edits the current profile of the client. - - If a bot account is used then the password field is optional, - otherwise it is required. - - The :attr:`Client.user` object is not modified directly afterwards until the - corresponding WebSocket event is received. - - Note - ----- - To upload an avatar, a *bytes-like object* must be passed in that - represents the image being uploaded. If this is done through a file - then the file must be opened via ``open('some_filename', 'rb')`` and - the *bytes-like object* is given through the use of ``fp.read()``. - - The only image formats supported for uploading is JPEG and PNG. - - Parameters - ----------- - password : str - The current password for the client's account. Not used - for bot accounts. - new_password : str - The new password you wish to change to. - email : str - The new email you wish to change to. - username :str - The new username you wish to change to. - avatar : bytes - A *bytes-like object* representing the image to upload. - Could be ``None`` to denote no avatar. - - Raises - ------ - HTTPException - Editing your profile failed. - InvalidArgument - Wrong image format passed for ``avatar``. - ClientException - Password is required for non-bot accounts. - """ - - try: - avatar_bytes = fields['avatar'] - except KeyError: - avatar = self.user.avatar - else: - if avatar_bytes is not None: - avatar = utils._bytes_to_base64_data(avatar_bytes) - else: - avatar = None - - not_bot_account = not self.user.bot - if not_bot_account and password is None: - raise ClientException('Password is required for non-bot accounts.') - - args = { - 'password': password, - 'username': fields.get('username', self.user.name), - 'avatar': avatar - } - - if not_bot_account: - args['email'] = fields.get('email', self.email) - - if 'new_password' in fields: - args['new_password'] = fields['new_password'] - - data = yield from self.http.edit_profile(**args) - if not_bot_account: - self.email = data['email'] - if 'token' in data: - self.http._token(data['token'], bot=False) - - if self.cache_auth: - self._update_cache(self.email, password) - - @asyncio.coroutine - @utils.deprecated('change_presence') - def change_status(self, game=None, idle=False): - """|coro| - - Changes the client's status. - - The game parameter is a Game object (not a string) that represents - a game being played currently. - - The idle parameter is a boolean parameter that indicates whether the - client should go idle or not. - - .. deprecated:: v0.13.0 - Use :meth:`change_presence` instead. - - Parameters - ---------- - game : Optional[:class:`Game`] - The game being played. None if no game is being played. - idle : bool - Indicates if the client should go idle. - - Raises - ------ - InvalidArgument - If the ``game`` parameter is not :class:`Game` or None. - """ - yield from self.ws.change_presence(game=game, idle=idle) - - @asyncio.coroutine - def change_presence(self, *, game=None, status=None, afk=False): - """|coro| - - Changes the client's presence. - - The game parameter is a Game object (not a string) that represents - a game being played currently. - - Parameters - ---------- - game: Optional[:class:`Game`] - The game being played. None if no game is being played. - status: Optional[:class:`Status`] - Indicates what status to change to. If None, then - :attr:`Status.online` is used. - afk: bool - Indicates if you are going AFK. This allows the discord - client to know how to handle push notifications better - for you in case you are actually idle and not lying. - - Raises - ------ - InvalidArgument - If the ``game`` parameter is not :class:`Game` or None. - """ - - if status is None: - status = 'online' - elif status is Status.offline: - status = 'invisible' - else: - status = str(status) - - yield from self.ws.change_presence(game=game, status=status, afk=afk) - - @asyncio.coroutine - def change_nickname(self, member, nickname): - """|coro| - - Changes a member's nickname. - - You must have the proper permissions to change someone's - (or your own) nickname. - - Parameters - ---------- - member : :class:`Member` - The member to change the nickname for. - nickname : Optional[str] - The nickname to change it to. ``None`` to remove - the nickname. - - Raises - ------ - Forbidden - You do not have permissions to change the nickname. - HTTPException - Changing the nickname failed. - """ - - nickname = nickname if nickname else '' - - if member == self.user: - yield from self.http.change_my_nickname(member.guild.id, nickname) - else: - yield from self.http.change_nickname(member.guild.id, member.id, nickname) - - # Channel management - - @asyncio.coroutine - def edit_channel(self, channel, **options): - """|coro| - - Edits a :class:`Channel`. - - You must have the proper permissions to edit the channel. - - To move the channel's position use :meth:`move_channel` instead. - - The :class:`Channel` object is not directly modified afterwards until the - corresponding WebSocket event is received. - - Parameters - ---------- - channel : :class:`Channel` - The channel to update. - name : str - The new channel name. - topic : str - The new channel's topic. - bitrate : int - The new channel's bitrate. Voice only. - user_limit : int - The new channel's user limit. Voice only. - - Raises - ------ - Forbidden - You do not have permissions to edit the channel. - HTTPException - Editing the channel failed. - """ - - keys = ('name', 'topic', 'position') - for key in keys: - if key not in options: - options[key] = getattr(channel, key) - - yield from self.http.edit_channel(channel.id, **options) - - @asyncio.coroutine - def move_channel(self, channel, position): - """|coro| - - Moves the specified :class:`Channel` to the given position in the GUI. - Note that voice channels and text channels have different position values. - - The :class:`Channel` object is not directly modified afterwards until the - corresponding WebSocket event is received. - - .. warning:: - - :class:`Object` instances do not work with this function. - - Parameters - ----------- - channel : :class:`Channel` - The channel to change positions of. - position : int - The position to insert the channel to. - - Raises - ------- - InvalidArgument - If position is less than 0 or greater than the number of channels. - Forbidden - You do not have permissions to change channel order. - HTTPException - If moving the channel failed, or you are of too low rank to move the channel. - """ - - if position < 0: - raise InvalidArgument('Channel position cannot be less than 0.') - - url = '{0}/{1.guild.id}/channels'.format(self.http.GUILDS, channel) - channels = [c for c in channel.guild.channels if c.type is channel.type] - - if position >= len(channels): - raise InvalidArgument('Channel position cannot be greater than {}'.format(len(channels) - 1)) - - channels.sort(key=lambda c: c.position) - - try: - # remove ourselves from the channel list - channels.remove(channel) - except ValueError: - # not there somehow lol - return - else: - # add ourselves at our designated position - channels.insert(position, channel) - - payload = [{'id': c.id, 'position': index } for index, c in enumerate(channels)] - yield from self.http.patch(url, json=payload, bucket='move_channel') - - @asyncio.coroutine - def create_channel(self, guild, name, *overwrites, type=None): - """|coro| - - Creates a :class:`Channel` in the specified :class:`Guild`. - - Note that you need the proper permissions to create the channel. - - The ``overwrites`` argument list can be used to create a 'secret' - channel upon creation. A namedtuple of :class:`ChannelPermissions` - is exposed to create a channel-specific permission overwrite in a more - self-documenting matter. You can also use a regular tuple of ``(target, overwrite)`` - where the ``overwrite`` expected has to be of type :class:`PermissionOverwrite`. - - Examples - ---------- - - Creating a voice channel: - - .. code-block:: python - - await client.create_channel(guild, 'Voice', type=discord.ChannelType.voice) - - Creating a 'secret' text channel: - - .. code-block:: python - - everyone_perms = discord.PermissionOverwrite(read_messages=False) - my_perms = discord.PermissionOverwrite(read_messages=True) - - everyone = discord.ChannelPermissions(target=guild.default_role, overwrite=everyone_perms) - mine = discord.ChannelPermissions(target=guild.me, overwrite=my_perms) - await client.create_channel(guild, 'secret', everyone, mine) - - Or in a more 'compact' way: - - .. code-block:: python - - everyone = discord.PermissionOverwrite(read_messages=False) - mine = discord.PermissionOverwrite(read_messages=True) - await client.create_channel(guild, 'secret', (guild.default_role, everyone), (guild.me, mine)) - - Parameters - ----------- - guild : :class:`Guild` - The guild to create the channel in. - name : str - The channel's name. - type : :class:`ChannelType` - The type of channel to create. Defaults to :attr:`ChannelType.text`. - overwrites: - An argument list of channel specific overwrites to apply on the channel on - creation. Useful for creating 'secret' channels. - - Raises - ------- - Forbidden - You do not have the proper permissions to create the channel. - NotFound - The guild specified was not found. - HTTPException - Creating the channel failed. - InvalidArgument - The permission overwrite array is not in proper form. - - Returns - ------- - :class:`Channel` - The channel that was just created. This channel is - different than the one that will be added in cache. - """ - - if type is None: - type = ChannelType.text - - perms = [] - for overwrite in overwrites: - target = overwrite[0] - perm = overwrite[1] - if not isinstance(perm, PermissionOverwrite): - raise InvalidArgument('Expected PermissionOverwrite received {0.__name__}'.format(type(perm))) - - allow, deny = perm.pair() - payload = { - 'allow': allow.value, - 'deny': deny.value, - 'id': target.id - } - - if isinstance(target, User): - payload['type'] = 'member' - elif isinstance(target, Role): - payload['type'] = 'role' - else: - raise InvalidArgument('Expected Role, User, or Member target, received {0.__name__}'.format(type(target))) - - perms.append(payload) - - data = yield from self.http.create_channel(guild.id, name, str(type), permission_overwrites=perms) - channel = Channel(guild=guild, state=self.connection.ctx, data=data) - return channel - - @asyncio.coroutine - def delete_channel(self, channel): - """|coro| - - Deletes a :class:`Channel`. - - In order to delete the channel, the client must have the proper permissions - in the guild the channel belongs to. - - Parameters - ------------ - channel : :class:`Channel` - The channel to delete. - - Raises - ------- - Forbidden - You do not have proper permissions to delete the channel. - NotFound - The specified channel was not found. - HTTPException - Deleting the channel failed. - """ - yield from self.http.delete_channel(channel.id) - - # Guild management - - @asyncio.coroutine - def leave_guild(self, guild): - """|coro| - - Leaves a :class:`Guild`. - - Note - -------- - You cannot leave the guild that you own, you must delete it instead - via :meth:`delete_guild`. - - Parameters - ---------- - guild : :class:`Guild` - The guild to leave. - - Raises - -------- - HTTPException - If leaving the guild failed. - """ - yield from self.http.leave_guild(guild.id) - - @asyncio.coroutine - def delete_guild(self, guild): - """|coro| - - Deletes a :class:`Guild`. You must be the guild owner to delete the - guild. - - Parameters - ---------- - guild : :class:`Guild` - The guild to delete. - - Raises - -------- - HTTPException - If deleting the guild failed. - Forbidden - You do not have permissions to delete the guild. - """ - - yield from self.http.delete_guild(guild.id) - - @asyncio.coroutine - def create_guild(self, name, region=None, icon=None): - """|coro| - - Creates a :class:`Guild`. - - Parameters - ---------- - name : str - The name of the guild. - region : :class:`GuildRegion` - The region for the voice communication guild. - Defaults to :attr:`GuildRegion.us_west`. - icon : bytes - The *bytes-like* object representing the icon. See :meth:`edit_profile` - for more details on what is expected. - - Raises - ------ - HTTPException - Guild creation failed. - InvalidArgument - Invalid icon image format given. Must be PNG or JPG. - - Returns - ------- - :class:`Guild` - The guild created. This is not the same guild that is - added to cache. - """ - if icon is not None: - icon = utils._bytes_to_base64_data(icon) - - if region is None: - region = GuildRegion.us_west.name - else: - region = region.name - - data = yield from self.http.create_guild(name, region, icon) - return Guild(data=data, state=self.connection.ctx) - - @asyncio.coroutine - def edit_guild(self, guild, **fields): - """|coro| - - Edits a :class:`Guild`. - - You must have the proper permissions to edit the guild. - - The :class:`Guild` object is not directly modified afterwards until the - corresponding WebSocket event is received. - - Parameters - ---------- - guild: :class:`Guild` - The guild to edit. - name: str - The new name of the guild. - icon: bytes - A *bytes-like* object representing the icon. See :meth:`edit_profile` - for more details. Could be ``None`` to denote no icon. - splash: bytes - A *bytes-like* object representing the invite splash. See - :meth:`edit_profile` for more details. Could be ``None`` to denote - no invite splash. Only available for partnered servers with - ``INVITE_SPLASH`` feature. - region: :class:`GuildRegion` - The new region for the guild's voice communication. - afk_channel: :class:`Channel` - The new channel that is the AFK channel. Could be ``None`` for no AFK channel. - afk_timeout: int - The number of seconds until someone is moved to the AFK channel. - owner: :class:`Member` - The new owner of the guild to transfer ownership to. Note that you must - be owner of the guild to do this. - verification_level: :class:`VerificationLevel` - The new verification level for the guild. - - Raises - ------- - Forbidden - You do not have permissions to edit the guild. - NotFound - The guild you are trying to edit does not exist. - HTTPException - Editing the guild failed. - InvalidArgument - The image format passed in to ``icon`` is invalid. It must be - PNG or JPG. This is also raised if you are not the owner of the - guild and request an ownership transfer. - """ - - try: - icon_bytes = fields['icon'] - except KeyError: - icon = guild.icon - else: - if icon_bytes is not None: - icon = utils._bytes_to_base64_data(icon_bytes) - else: - icon = None - - try: - splash_bytes = fields['splash'] - except KeyError: - splash = server.splash - else: - if splash_bytes is not None: - splash = utils._bytes_to_base64_data(splash_bytes) - else: - splash = None - - fields['icon'] = icon - fields['splash'] = splash - if 'afk_channel' in fields: - fields['afk_channel_id'] = fields['afk_channel'].id - - if 'owner' in fields: - if guild.owner != guild.me: - raise InvalidArgument('To transfer ownership you must be the owner of the guild.') - - fields['owner_id'] = fields['owner'].id - - if 'region' in fields: - fields['region'] = str(fields['region']) - - level = fields.get('verification_level', guild.verification_level) - if not isinstance(level, VerificationLevel): - raise InvalidArgument('verification_level field must of type VerificationLevel') - - fields['verification_level'] = level.value - yield from self.http.edit_guild(guild.id, **fields) - - @asyncio.coroutine - def get_bans(self, guild): - """|coro| - - Retrieves all the :class:`User` s that are banned from the specified - guild. - - You must have proper permissions to get this information. - - Parameters - ---------- - guild : :class:`Guild` - The guild to get ban information from. - - Raises - ------- - Forbidden - You do not have proper permissions to get the information. - HTTPException - An error occurred while fetching the information. - - Returns - -------- - list - A list of :class:`User` that have been banned. - """ - - data = yield from self.http.get_bans(guild.id) - return [self.connection.store_user(user) for user in data] - - @asyncio.coroutine - def prune_members(self, guild, *, days): - """|coro| - - Prunes a :class:`Guild` from its inactive members. - - The inactive members are denoted if they have not logged on in - ``days`` number of days and they have no roles. - - You must have the "Kick Members" permission to use this. - - To check how many members you would prune without actually pruning, - see the :meth:`estimate_pruned_members` function. - - Parameters - ----------- - guild: :class:`Guild` - The guild to prune from. - days: int - The number of days before counting as inactive. - - Raises - ------- - Forbidden - You do not have permissions to prune members. - HTTPException - An error occurred while pruning members. - InvalidArgument - An integer was not passed for ``days``. - - Returns - --------- - int - The number of members pruned. - """ - - if not isinstance(days, int): - raise InvalidArgument('Expected int for ``days``, received {0.__class__.__name__} instead.'.format(days)) - - data = yield from self.http.prune_members(guild.id, days) - return data['pruned'] - - @asyncio.coroutine - def estimate_pruned_members(self, guild, *, days): - """|coro| - - Similar to :meth:`prune_members` except instead of actually - pruning members, it returns how many members it would prune - from the guild had it been called. - - Parameters - ----------- - guild: :class:`Guild` - The guild to estimate a prune from. - days: int - The number of days before counting as inactive. - - Raises - ------- - Forbidden - You do not have permissions to prune members. - HTTPException - An error occurred while fetching the prune members estimate. - InvalidArgument - An integer was not passed for ``days``. - - Returns - --------- - int - The number of members estimated to be pruned. - """ - - if not isinstance(days, int): - raise InvalidArgument('Expected int for ``days``, received {0.__class__.__name__} instead.'.format(days)) - - data = yield from self.http.estimate_pruned_members(guild.id, days) - return data['pruned'] - - @asyncio.coroutine - def create_custom_emoji(self, guild, *, name, image): - """|coro| - - Creates a custom :class:`Emoji` for a :class:`Guild`. - - This endpoint is only allowed for user bots or white listed - bots. If this is done by a user bot then this is a local - emoji that can only be used inside that guild. - - There is currently a limit of 50 local emotes per guild. - - Parameters - ----------- - guild: :class:`Guild` - The guild to add the emoji to. - name: str - The emoji name. Must be at least 2 characters. - image: bytes - The *bytes-like* object representing the image data to use. - Only JPG and PNG images are supported. - - Returns - -------- - :class:`Emoji` - The created emoji. - - Raises - ------- - Forbidden - You are not allowed to create emojis. - HTTPException - An error occurred creating an emoji. - """ - - img = utils._bytes_to_base64_data(image) - data = yield from self.http.create_custom_emoji(guild.id, name, img) - return Emoji(guild=guild, data=data, state=self.connection.ctx) - - @asyncio.coroutine - def delete_custom_emoji(self, emoji): - """|coro| - - Deletes a custom :class:`Emoji` from a :class:`Guild`. - - This follows the same rules as :meth:`create_custom_emoji`. - - Parameters - ----------- - emoji: :class:`Emoji` - The emoji to delete. - - Raises - ------- - Forbidden - You are not allowed to delete emojis. - HTTPException - An error occurred deleting the emoji. - """ - - yield from self.http.delete_custom_emoji(emoji.guild.id, emoji.id) - - @asyncio.coroutine - def edit_custom_emoji(self, emoji, *, name): - """|coro| - - Edits a :class:`Emoji`. - - Parameters - ----------- - emoji: :class:`Emoji` - The emoji to edit. - name: str - The new emoji name. - - Raises - ------- - Forbidden - You are not allowed to edit emojis. - HTTPException - An error occurred editing the emoji. - """ - - yield from self.http.edit_custom_emoji(emoji.guild.id, emoji.id, name=name) - - - # Invite management - - def _fill_invite_data(self, data): - guild = self.connection._get_guild(data['guild']['id']) - if guild is not None: - ch_id = data['channel']['id'] - channel = guild.get_channel(ch_id) - else: - guild = Object(id=data['guild']['id']) - guild.name = data['guild']['name'] - channel = Object(id=data['channel']['id']) - channel.name = data['channel']['name'] - data['guild'] = guild - data['channel'] = channel - - @asyncio.coroutine - def create_invite(self, destination, **options): - """|coro| - - Creates an invite for the destination which could be either a - :class:`Guild` or :class:`Channel`. - - Parameters - ------------ - destination - The :class:`Guild` or :class:`Channel` to create the invite to. - max_age : int - How long the invite should last. If it's 0 then the invite - doesn't expire. Defaults to 0. - max_uses : int - How many uses the invite could be used for. If it's 0 then there - are unlimited uses. Defaults to 0. - temporary : bool - Denotes that the invite grants temporary membership - (i.e. they get kicked after they disconnect). Defaults to False. - xkcd : bool - Indicates if the invite URL is human readable. Defaults to False. - - Raises - ------- - HTTPException - Invite creation failed. - - Returns - -------- - :class:`Invite` - The invite that was created. - """ - - data = yield from self.http.create_invite(destination.id, **options) - self._fill_invite_data(data) - return Invite(**data) - - @asyncio.coroutine - def get_invite(self, url): - """|coro| - - Gets a :class:`Invite` from a discord.gg URL or ID. - - Note - ------ - If the invite is for a guild you have not joined, the guild and channel - attributes of the returned invite will be :class:`Object` with the names - patched in. - - Parameters - ----------- - url : str - The discord invite ID or URL (must be a discord.gg URL). - - Raises - ------- - NotFound - The invite has expired or is invalid. - HTTPException - Getting the invite failed. - - Returns - -------- - :class:`Invite` - The invite from the URL/ID. - """ - - invite_id = self._resolve_invite(url) - data = yield from self.http.get_invite(invite_id) - self._fill_invite_data(data) - return Invite(**data) - - @asyncio.coroutine - def invites_from(self, guild): - """|coro| - - Returns a list of all active instant invites from a :class:`Guild`. - - You must have proper permissions to get this information. - - Parameters - ---------- - guild : :class:`Guild` - The guild to get invites from. - - Raises - ------- - Forbidden - You do not have proper permissions to get the information. - HTTPException - An error occurred while fetching the information. - - Returns - ------- - list of :class:`Invite` - The list of invites that are currently active. - """ - - data = yield from self.http.invites_from(guild.id) - result = [] - for invite in data: - channel = guild.get_channel(invite['channel']['id']) - invite['channel'] = channel - invite['guild'] = guild - result.append(Invite(**invite)) - - return result - - @asyncio.coroutine - def accept_invite(self, invite): - """|coro| - - Accepts an :class:`Invite`, URL or ID to an invite. - - The URL must be a discord.gg URL. e.g. "http://discord.gg/codehere". - An ID for the invite is just the "codehere" portion of the invite URL. + The URL must be a discord.gg URL. e.g. "http://discord.gg/codehere". + An ID for the invite is just the "codehere" portion of the invite URL. Parameters ----------- @@ -2739,261 +1180,6 @@ class Client: invite_id = self._resolve_invite(invite) yield from self.http.delete_invite(invite_id) - # Role management - - @asyncio.coroutine - def move_role(self, guild, role, position): - """|coro| - - Moves the specified :class:`Role` to the given position in the :class:`Guild`. - - The :class:`Role` object is not directly modified afterwards until the - corresponding WebSocket event is received. - - Parameters - ----------- - guild : :class:`Guild` - The guild the role belongs to. - role : :class:`Role` - The role to edit. - position : int - The position to insert the role to. - - Raises - ------- - InvalidArgument - If position is 0, or role is guild.default_role - Forbidden - You do not have permissions to change role order. - HTTPException - If moving the role failed, or you are of too low rank to move the role. - """ - - if position == 0: - raise InvalidArgument("Cannot move role to position 0") - - if role == guild.default_role: - raise InvalidArgument("Cannot move default role") - - if role.position == position: - return # Save discord the extra request. - - url = '{0}/{1.id}/roles'.format(self.http.GUILDS, guild) - - change_range = range(min(role.position, position), max(role.position, position) + 1) - - roles = [r.id for r in sorted(filter(lambda x: (x.position in change_range) and x != role, guild.roles), key=lambda x: x.position)] - - if role.position > position: - roles.insert(0, role.id) - else: - roles.append(role.id) - - payload = [{"id": z[0], "position": z[1]} for z in zip(roles, change_range)] - yield from self.http.patch(url, json=payload, bucket='move_role') - - @asyncio.coroutine - def edit_role(self, guild, role, **fields): - """|coro| - - Edits the specified :class:`Role` for the entire :class:`Guild`. - - The :class:`Role` object is not directly modified afterwards until the - corresponding WebSocket event is received. - - All fields except ``guild`` and ``role`` are optional. To change - the position of a role, use :func:`move_role` instead. - - .. versionchanged:: 0.8.0 - Editing now uses keyword arguments instead of editing the :class:`Role` object directly. - - Parameters - ----------- - guild : :class:`Guild` - The guild the role belongs to. - role : :class:`Role` - The role to edit. - name : str - The new role name to change to. - permissions : :class:`Permissions` - The new permissions to change to. - colour : :class:`Colour` - The new colour to change to. (aliased to color as well) - hoist : bool - Indicates if the role should be shown separately in the online list. - mentionable : bool - Indicates if the role should be mentionable by others. - - Raises - ------- - Forbidden - You do not have permissions to change the role. - HTTPException - Editing the role failed. - """ - - colour = fields.get('colour') - if colour is None: - colour = fields.get('color', role.colour) - - payload = { - 'name': fields.get('name', role.name), - 'permissions': fields.get('permissions', role.permissions).value, - 'color': colour.value, - 'hoist': fields.get('hoist', role.hoist), - 'mentionable': fields.get('mentionable', role.mentionable) - } - - yield from self.http.edit_role(guild.id, role.id, **payload) - - @asyncio.coroutine - def delete_role(self, guild, role): - """|coro| - - Deletes the specified :class:`Role` for the entire :class:`Guild`. - - Parameters - ----------- - guild : :class:`Guild` - The guild the role belongs to. - role : :class:`Role` - The role to delete. - - Raises - -------- - Forbidden - You do not have permissions to delete the role. - HTTPException - Deleting the role failed. - """ - - yield from self.http.delete_role(guild.id, role.id) - - @asyncio.coroutine - def _replace_roles(self, member, roles): - yield from self.http.replace_roles(member.id, member.guild.id, roles) - - @asyncio.coroutine - def add_roles(self, member, *roles): - """|coro| - - Gives the specified :class:`Member` a number of :class:`Role` s. - - You must have the proper permissions to use this function. - - The :class:`Member` object is not directly modified afterwards until the - corresponding WebSocket event is received. - - Parameters - ----------- - member : :class:`Member` - The member to give roles to. - \*roles - An argument list of :class:`Role` s to give the member. - - Raises - ------- - Forbidden - You do not have permissions to add roles. - HTTPException - Adding roles failed. - """ - - new_roles = utils._unique(role.id for role in itertools.chain(member.roles, roles)) - yield from self._replace_roles(member, new_roles) - - @asyncio.coroutine - def remove_roles(self, member, *roles): - """|coro| - - Removes the :class:`Role` s from the :class:`Member`. - - You must have the proper permissions to use this function. - - The :class:`Member` object is not directly modified afterwards until the - corresponding WebSocket event is received. - - Parameters - ----------- - member : :class:`Member` - The member to revoke roles from. - \*roles - An argument list of :class:`Role` s to revoke the member. - - Raises - ------- - Forbidden - You do not have permissions to revoke roles. - HTTPException - Removing roles failed. - """ - new_roles = [x.id for x in member.roles] - for role in roles: - try: - new_roles.remove(role.id) - except ValueError: - pass - - yield from self._replace_roles(member, new_roles) - - @asyncio.coroutine - def replace_roles(self, member, *roles): - """|coro| - - Replaces the :class:`Member`'s roles. - - You must have the proper permissions to use this function. - - This function **replaces** all roles that the member has. - For example if the member has roles ``[a, b, c]`` and the - call is ``client.replace_roles(member, d, e, c)`` then - the member has the roles ``[d, e, c]``. - - The :class:`Member` object is not directly modified afterwards until the - corresponding WebSocket event is received. - - Parameters - ----------- - member : :class:`Member` - The member to replace roles from. - \*roles - An argument list of :class:`Role` s to replace the roles with. - - Raises - ------- - Forbidden - You do not have permissions to revoke roles. - HTTPException - Removing roles failed. - """ - - new_roles = utils._unique(role.id for role in roles) - yield from self._replace_roles(member, new_roles) - - @asyncio.coroutine - def create_role(self, guild, **fields): - """|coro| - - Creates a :class:`Role`. - - This function is similar to :class:`edit_role` in both - the fields taken and exceptions thrown. - - Returns - -------- - :class:`Role` - The newly created role. This not the same role that - is stored in cache. - """ - - data = yield from self.http.create_role(guild.id) - role = Role(guild=guild, data=data, state=self.connection.ctx) - - # we have to call edit because you can't pass a payload to the - # http request currently. - yield from self.edit_role(guild, role, **fields) - return role - @asyncio.coroutine def edit_channel_permissions(self, channel, target, overwrite=None): """|coro| @@ -3086,41 +1272,6 @@ class Client: # Voice management - @asyncio.coroutine - def move_member(self, member, channel): - """|coro| - - Moves a :class:`Member` to a different voice channel. - - You must have proper permissions to do this. - - Note - ----- - You cannot pass in a :class:`Object` instead of a :class:`Channel` - object in this function. - - Parameters - ----------- - member : :class:`Member` - The member to move to another voice channel. - channel : :class:`Channel` - The voice channel to move the member to. - - Raises - ------- - InvalidArgument - The channel provided is not a voice channel. - HTTPException - Moving the member failed. - Forbidden - You do not have permissions to move the member. - """ - - if getattr(channel, 'type', ChannelType.text) != ChannelType.voice: - raise InvalidArgument('The channel provided must be a voice channel.') - - yield from self.http.move_member(member.id, member.guild.id, channel.id) - @asyncio.coroutine def join_voice_channel(self, channel): """|coro|