diff --git a/discord/client.py b/discord/client.py index 3a6eea330..80d8dd13e 100644 --- a/discord/client.py +++ b/discord/client.py @@ -1495,3 +1495,331 @@ class Client: response = yield from self.session.delete(url, headers=self.headers) log.debug(request_logging_format.format(method='DELETE', response=response)) yield from utils._verify_successful_response(response) + + + # Role management + + @asyncio.coroutine + def edit_role(self, server, role, **fields): + """|coro| + + Edits the specified :class:`Role` for the entire :class:`Server`. + + This does **not** edit the role in place. + + All fields except ``server`` and ``role`` are optional. + + .. versionchanged:: 0.8.0 + Editing now uses keyword arguments instead of editing the :class:`Role` object directly. + + Note + ----- + At the moment, the Discord API allows you to set the colour to any + RGB value. This might change in the future so it is recommended that + you use the constants in the :class:`Colour` instead such as + :meth:`Colour.green`. + + Parameters + ----------- + server : :class:`Server` + The server 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. + + Raises + ------- + Forbidden + You do not have permissions to change the role. + HTTPException + Editing the role failed. + """ + + url = '{0}/{1.id}/roles/{2.id}'.format(endpoints.SERVERS, server, role) + color = fields.get('color') + if color is None: + color = fields.get('colour', role.colour) + + payload = { + 'name': fields.get('name', role.name), + 'permissions': fields.get('permissions', role.permissions).value, + 'color': color.value, + 'hoist': fields.get('hoist', role.hoist) + } + + r = yield from self.session.patch(url, data=to_json(payload), headers=self.headers) + log.debug(request_logging_format.format(method='PATCH', response=r)) + yield from utils._verify_successful_response(r) + + data = yield from r.json() + log.debug(request_success_log.format(json=payload, response=r, data=data)) + + @asyncio.coroutine + def delete_role(self, server, role): + """|coro| + + Deletes the specified :class:`Role` for the entire :class:`Server`. + + Works in a similar matter to :func:`edit_role`. + + Parameters + ----------- + server : :class:`Server` + The server 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. + """ + + url = '{0}/{1.id}/roles/{2.id}'.format(endpoints.SERVERS, server, role) + response = yield from self.session.delete(url, headers=self.headers) + log.debug(request_logging_format.format(method='DELETE', response=response)) + yield from utils._verify_successful_response(response) + + @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. + + This method **appends** a role to a member but does **not** do it + in-place. + + 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 = [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. + + This method does **not** do edit the member in-place. + + 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 = {role.id for role in member.roles} + new_roles = new_roles.difference(roles) + 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]``. + + This method does **not** do edit the member in-place. + + 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. + """ + + url = '{0}/{1.server.id}/members/{1.id}'.format(endpoints.SERVERS, member) + + payload = { + 'roles': [role.id for role in roles] + } + + r = yield from self.session.patch(url, headers=self.headers, data=to_json(payload)) + log.debug(request_logging_format.format(method='PATCH', response=r)) + yield from utils._verify_successful_response(r) + + @asyncio.coroutine + def create_role(self, server, **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. + """ + + url = '{0}/{1.id}/roles'.format(endpoints.SERVERS, server) + r = yield from self.session.post(url, headers=self.headers) + log.debug(request_logging_format.format(method='POST', response=r)) + yield from utils._verify_successful_response(r) + + data = yield from r.json() + everyone = server.id == data.get('id') + role = Role(everyone=everyone, **data) + + # we have to call edit because you can't pass a payload to the + # http request currently. + yield from self.edit_role(server, role, **fields) + return role + + @asyncio.coroutine + def edit_channel_permissions(self, channel, target, allow=None, deny=None): + """|coro| + + Sets the channel specific permission overwrites for a target in the + specified :class:`Channel`. + + The ``target`` parameter should either be a :class:`Member` or a + :class:`Role` that belongs to the channel's server. + + You must have the proper permissions to do this. + + Examples + ---------- + + Setting allow and deny: :: + + allow = discord.Permissions.none() + deny = discord.Permissions.none() + allow.can_mention_everyone = True + deny.can_manage_messages = True + client.set_channel_permissions(message.channel, message.author, allow, deny) + + Parameters + ----------- + channel : :class:`Channel` + The channel to give the specific permissions for. + target + The :class:`Member` or :class:`Role` to overwrite permissions for. + allow : :class:`Permissions` + The permissions to explicitly allow. (optional) + deny : :class:`Permissions` + The permissions to explicitly deny. (optional) + + Raises + ------- + Forbidden + You do not have permissions to edit channel specific permissions. + NotFound + The channel specified was not found. + HTTPException + Editing channel specific permissions failed. + InvalidArgument + The allow or deny arguments were not of type :class:`Permissions` + or the target type was not :class:`Role` or :class:`Member`. + """ + + url = '{0}/{1.id}/permissions/{2.id}'.format(endpoints.CHANNELS, channel, target) + + allow = Permissions.none() if allow is None else allow + deny = Permissions.none() if deny is None else deny + + if not (isinstance(allow, Permissions) and isinstance(deny, Permissions)): + raise InvalidArgument('allow and deny parameters must be discord.Permissions') + + deny = deny.value + allow = allow.value + + payload = { + 'id': target.id, + 'allow': allow, + 'deny': deny + } + + if isinstance(target, Member): + payload['type'] = 'member' + elif isinstance(target, Role): + payload['type'] = 'role' + else: + raise InvalidArgument('target parameter must be either discord.Member or discord.Role') + + r = yield from self.session.put(url, data=to_json(payload), headers=self.headers) + log.debug(request_logging_format.format(method='PUT', response=r)) + yield from utils._verify_successful_response(r) + + @asyncio.coroutine + def delete_channel_permissions(self, channel, target): + """|coro| + + Removes a channel specific permission overwrites for a target + in the specified :class:`Channel`. + + The target parameter follows the same rules as :meth:`set_channel_permissions`. + + You must have the proper permissions to do this. + + Parameters + ---------- + channel : :class:`Channel` + The channel to give the specific permissions for. + target + The :class:`Member` or :class:`Role` to overwrite permissions for. + + Raises + ------ + Forbidden + You do not have permissions to delete channel specific permissions. + NotFound + The channel specified was not found. + HTTPException + Deleting channel specific permissions failed. + """ + + url = '{0}/{1.id}/permissions/{2.id}'.format(endpoints.CHANNELS, channel, target) + response = yield from self.session.delete(url, headers=self.headers) + log.debug(request_logging_format.format(method='DELETE', response=response)) + yield from utils._verify_successful_response(response)