Browse Source

Add support for audit log reasons.

Most routes now have a 'reason' keyword argument.
pull/572/head
Rapptz 8 years ago
parent
commit
dff6bcc745
  1. 35
      discord/abc.py
  2. 38
      discord/channel.py
  3. 15
      discord/emoji.py
  4. 62
      discord/guild.py
  5. 127
      discord/http.py
  6. 9
      discord/invite.py
  7. 30
      discord/member.py
  8. 9
      discord/message.py
  9. 21
      discord/role.py

35
discord/abc.py

@ -121,7 +121,7 @@ class GuildChannel:
return self.name
@asyncio.coroutine
def _move(self, position):
def _move(self, position, *, reason):
if position < 0:
raise InvalidArgument('Channel position cannot be less than 0.')
@ -145,7 +145,7 @@ class GuildChannel:
channels.insert(position, self)
payload = [{'id': c.id, 'position': index } for index, c in enumerate(channels)]
yield from http.move_channel_position(self.guild.id, payload)
yield from http.move_channel_position(self.guild.id, payload, reason=reason)
def _fill_overwrites(self, data):
self._overwrites = []
@ -351,13 +351,19 @@ class GuildChannel:
return base
@asyncio.coroutine
def delete(self):
def delete(self, *, reason=None):
"""|coro|
Deletes the channel.
You must have Manage Channel permission to use this.
Parameters
-----------
reason: Optional[str]
The reason for deleting this channel.
Shows up on the audit log.
Raises
-------
Forbidden
@ -367,10 +373,10 @@ class GuildChannel:
HTTPException
Deleting the channel failed.
"""
yield from self._state.http.delete_channel(self.id)
yield from self._state.http.delete_channel(self.id, reason=reason)
@asyncio.coroutine
def set_permissions(self, target, *, overwrite=_undefined, **permissions):
def set_permissions(self, target, *, overwrite=_undefined, reason=None, **permissions):
"""|coro|
Sets the channel specific permission overwrites for a target in the
@ -418,6 +424,8 @@ class GuildChannel:
\*\*permissions
A keyword argument list of permissions to set for ease of use.
Cannot be mixed with ``overwrite``.
reason: Optional[str]
The reason for doing this action. Shows up on the audit log.
Raises
-------
@ -453,15 +461,15 @@ class GuildChannel:
# TODO: wait for event
if overwrite is None:
yield from http.delete_channel_permissions(self.id, target.id)
yield from 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)
yield from 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, **fields):
def create_invite(self, *, reason=None, **fields):
"""|coro|
Creates an instant invite.
@ -481,6 +489,8 @@ class GuildChannel:
Indicates if a unique invite URL should be created. Defaults to True.
If this is set to False then it will return a previously created
invite.
reason: Optional[str]
The reason for creating this invite. Shows up on the audit log.
Raises
-------
@ -493,7 +503,7 @@ class GuildChannel:
The invite that was created.
"""
data = yield from self._state.http.create_invite(self.id, **fields)
data = yield from self._state.http.create_invite(self.id, reason=reason, **fields)
return Invite.from_incomplete(data=data, state=self._state)
@asyncio.coroutine
@ -537,7 +547,7 @@ class Messageable(metaclass=abc.ABCMeta):
raise NotImplementedError
@asyncio.coroutine
def send(self, content=None, *, tts=False, embed=None, file=None, files=None, delete_after=None):
def send(self, content=None, *, tts=False, embed=None, file=None, files=None, reason=None, delete_after=None):
"""|coro|
Sends a message to the destination with the content given.
@ -571,6 +581,9 @@ class Messageable(metaclass=abc.ABCMeta):
If provided, the number of seconds to wait in the background
before deleting the message we just sent. If the deletion fails,
then it is silently ignored.
reason: Optional[str]
The reason for deleting the message, if necessary.
Shows up on the audit log.
Raises
--------
@ -626,7 +639,7 @@ class Messageable(metaclass=abc.ABCMeta):
def delete():
yield from asyncio.sleep(delete_after, loop=state.loop)
try:
yield from ret.delete()
yield from ret.delete(reason=reason)
except:
pass
compat.create_task(delete(), loop=state.loop)

38
discord/channel.py

@ -37,9 +37,9 @@ import asyncio
__all__ = ('TextChannel', 'VoiceChannel', 'DMChannel', 'GroupChannel', '_channel_factory')
@asyncio.coroutine
def _single_delete_strategy(messages):
def _single_delete_strategy(messages, *, reason):
for m in messages:
yield from m.delete()
yield from m.delete(reason=reason)
class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
"""Represents a Discord guild text channel.
@ -116,7 +116,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
return n == 'nsfw' or n[:5] == 'nsfw-'
@asyncio.coroutine
def edit(self, **options):
def edit(self, *, reason=None, **options):
"""|coro|
Edits the channel.
@ -132,6 +132,8 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
The new channel's topic.
position: int
The new channel's position.
reason: Optional[str]
The reason for editing this channel. Shows up on the audit log.
Raises
------
@ -147,15 +149,15 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
except KeyError:
pass
else:
yield from self._move(position)
yield from self._move(position, reason=reason)
self.position = position
if options:
data = yield from self._state.http.edit_channel(self.id, **options)
data = yield from self._state.http.edit_channel(self.id, reason=reason, **options)
self._update(self.guild, data)
@asyncio.coroutine
def delete_messages(self, messages):
def delete_messages(self, messages, *, reason=None):
"""|coro|
Deletes a list of messages. This is similar to :meth:`Message.delete`
@ -167,6 +169,8 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
-----------
messages: iterable of :class:`Message`
An iterable of messages denoting which ones to bulk delete.
reason: Optional[str]
The reason for bulk deleting these messages. Shows up on the audit log.
Raises
------
@ -186,10 +190,10 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
message_ids = [m.id for m in messages]
channel = yield from self._get_channel()
yield from self._state.http.delete_messages(channel.id, message_ids)
yield from self._state.http.delete_messages(channel.id, message_ids, reason=reason)
@asyncio.coroutine
def purge(self, *, limit=100, check=None, before=None, after=None, around=None):
def purge(self, *, limit=100, check=None, before=None, after=None, around=None, reason=None):
"""|coro|
Purges a list of messages that meet the criteria given by the predicate
@ -219,6 +223,8 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
Same as ``after`` in :meth:`history`.
around
Same as ``around`` in :meth:`history`.
reason: Optional[str]
The reason for doing this action. Shows up on the audit log.
Raises
-------
@ -262,17 +268,17 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
if count >= 2:
# more than 2 messages -> bulk delete
to_delete = ret[-count:]
yield from strategy(to_delete)
yield from strategy(to_delete, reason=reason)
elif count == 1:
# delete a single message
yield from ret[-1].delete()
yield from ret[-1].delete(reason=reason)
return ret
else:
if count == 100:
# we've reached a full 'queue'
to_delete = ret[-100:]
yield from strategy(to_delete)
yield from strategy(to_delete, reason=reason)
count = 0
yield from asyncio.sleep(1)
@ -283,7 +289,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
yield from ret[-1].delete()
elif count >= 2:
to_delete = ret[-count:]
yield from strategy(to_delete)
yield from strategy(to_delete, reason=reason)
count = 0
strategy = _single_delete_strategy
@ -362,7 +368,7 @@ class VoiceChannel(discord.abc.Callable, discord.abc.GuildChannel, Hashable):
return ret
@asyncio.coroutine
def edit(self, **options):
def edit(self, *, reason=None, **options):
"""|coro|
Edits the channel.
@ -378,6 +384,8 @@ class VoiceChannel(discord.abc.Callable, discord.abc.GuildChannel, Hashable):
The new channel's user limit.
position: int
The new channel's position.
reason: Optional[str]
The reason for editing this channel. Shows up on the audit log.
Raises
------
@ -392,11 +400,11 @@ class VoiceChannel(discord.abc.Callable, discord.abc.GuildChannel, Hashable):
except KeyError:
pass
else:
yield from self._move(position)
yield from self._move(position, reason=reason)
self.position = position
if options:
data = yield from self._state.http.edit_channel(self.id, **options)
data = yield from self._state.http.edit_channel(self.id, reason=reason, **options)
self._update(self.guild, data)
class DMChannel(discord.abc.Messageable, Hashable):

15
discord/emoji.py

@ -118,7 +118,7 @@ class Emoji(Hashable):
@asyncio.coroutine
def delete(self):
def delete(self, *, reason=None):
"""|coro|
Deletes the custom emoji.
@ -128,6 +128,11 @@ class Emoji(Hashable):
Guild local emotes can only be deleted by user bots.
Parameters
-----------
reason: Optional[str]
The reason for deleting this emoji. Shows up on the audit log.
Raises
-------
Forbidden
@ -136,10 +141,10 @@ class Emoji(Hashable):
An error occurred deleting the emoji.
"""
yield from self._state.http.delete_custom_emoji(self.guild.id, self.id)
yield from self._state.http.delete_custom_emoji(self.guild.id, self.id, reason=reason)
@asyncio.coroutine
def edit(self, *, name):
def edit(self, *, name, reason=None):
"""|coro|
Edits the custom emoji.
@ -153,6 +158,8 @@ class Emoji(Hashable):
-----------
name: str
The new emoji name.
reason: Optional[str]
The reason for editing this emoji. Shows up on the audit log.
Raises
-------
@ -162,4 +169,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)
yield from self._state.http.edit_custom_emoji(self.guild.id, self.id, name=name, reason=reason)

62
discord/guild.py

@ -427,7 +427,7 @@ class Guild(Hashable):
return utils.find(pred, members)
def _create_channel(self, name, overwrites, type):
def _create_channel(self, name, overwrites, type, reason):
if overwrites is None:
overwrites = {}
elif not isinstance(overwrites, dict):
@ -452,10 +452,10 @@ class Guild(Hashable):
perms.append(payload)
return self._state.http.create_channel(self.id, name, str(type), permission_overwrites=perms)
return self._state.http.create_channel(self.id, name, str(type), permission_overwrites=perms, reason=reason)
@asyncio.coroutine
def create_text_channel(self, name, *, overwrites=None):
def create_text_channel(self, name, *, overwrites=None, reason=None):
"""|coro|
Creates a :class:`TextChannel` for the guild.
@ -495,6 +495,8 @@ class Guild(Hashable):
A `dict` of target (either a role or a member) to
:class:`PermissionOverwrite` to apply upon creation of a channel.
Useful for creating secret channels.
reason: Optional[str]
The reason for creating this channel. Shows up on the audit log.
Raises
-------
@ -510,17 +512,17 @@ class Guild(Hashable):
:class:`TextChannel`
The channel that was just created.
"""
data = yield from self._create_channel(name, overwrites, ChannelType.text)
data = yield from self._create_channel(name, overwrites, ChannelType.text, reason=reason)
return TextChannel(state=self._state, guild=self, data=data)
@asyncio.coroutine
def create_voice_channel(self, name, *, overwrites=None):
def create_voice_channel(self, name, *, overwrites=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)
data = yield from self._create_channel(name, overwrites, ChannelType.voice, reason=reason)
return VoiceChannel(state=self._state, guild=self, data=data)
@asyncio.coroutine
@ -559,7 +561,7 @@ class Guild(Hashable):
yield from self._state.http.delete_guild(self.id)
@asyncio.coroutine
def edit(self, **fields):
def edit(self, *, reason=None, **fields):
"""|coro|
Edits the guild.
@ -590,6 +592,8 @@ class Guild(Hashable):
be owner of the guild to do this.
verification_level: :class:`VerificationLevel`
The new verification level for the guild.
reason: Optional[str]
The reason for editing this guild. Shows up on the audit log.
Raises
-------
@ -642,7 +646,8 @@ class Guild(Hashable):
raise InvalidArgument('verification_level field must of type VerificationLevel')
fields['verification_level'] = level.value
yield from self._state.http.edit_guild(self.id, **fields)
yield from self._state.http.edit_guild(self.id, reason=reason, **fields)
@asyncio.coroutine
@ -678,7 +683,7 @@ class Guild(Hashable):
for e in data]
@asyncio.coroutine
def prune_members(self, *, days):
def prune_members(self, *, days, reason=None):
"""|coro|
Prunes the guild from its inactive members.
@ -696,6 +701,8 @@ class Guild(Hashable):
-----------
days: int
The number of days before counting as inactive.
reason: Optional[str]
The reason for doing this action. Shows up on the audit log.
Raises
-------
@ -715,7 +722,7 @@ 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)
data = yield from self._state.http.prune_members(self.id, days, reason=reason)
return data['pruned']
@asyncio.coroutine
@ -784,7 +791,7 @@ class Guild(Hashable):
return result
@asyncio.coroutine
def create_invite(self, **fields):
def create_invite(self, *, reason=None, **fields):
"""|coro|
Creates an instant invite.
@ -804,6 +811,8 @@ class Guild(Hashable):
Indicates if a unique invite URL should be created. Defaults to True.
If this is set to False then it will return a previously created
invite.
reason: Optional[str]
The reason for creating this invite. Shows up on the audit log.
Raises
-------
@ -816,11 +825,11 @@ class Guild(Hashable):
The invite that was created.
"""
data = yield from self._state.http.create_invite(self.id, **fields)
data = yield from self._state.http.create_invite(self.id, reason=reason, **fields)
return Invite.from_incomplete(data=data, state=self._state)
@asyncio.coroutine
def create_custom_emoji(self, *, name, image):
def create_custom_emoji(self, *, name, image, reason=None):
"""|coro|
Creates a custom :class:`Emoji` for the guild.
@ -839,6 +848,8 @@ class Guild(Hashable):
image: bytes
The *bytes-like* object representing the image data to use.
Only JPG and PNG images are supported.
reason: Optional[str]
The reason for creating this emoji. Shows up on the audit log.
Returns
--------
@ -854,11 +865,11 @@ class Guild(Hashable):
"""
img = utils._bytes_to_base64_data(image)
data = yield from self._state.http.create_custom_emoji(self.id, name, img)
data = yield from 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, **fields):
def create_role(self, *, reason=None, **fields):
"""|coro|
Creates a :class:`Role` for the guild.
@ -880,6 +891,8 @@ class Guild(Hashable):
mentionable: bool
Indicates if the role should be mentionable by others.
Defaults to False.
reason: Optional[str]
The reason for creating this role. Shows up on the audit log.
Returns
--------
@ -915,7 +928,7 @@ 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, **fields)
data = yield from self._state.http.create_role(self.id, reason=reason, **fields)
role = Role(guild=self, data=data, state=self._state)
# TODO: add to cache
@ -979,7 +992,7 @@ class Guild(Hashable):
yield from self._state.http.ban(user.id, self.id, delete_message_days, reason=reason)
@asyncio.coroutine
def unban(self, user):
def unban(self, user, *, reason=None):
"""|coro|
Unbans a user from the guild.
@ -993,6 +1006,8 @@ class Guild(Hashable):
-----------
user: :class:`abc.Snowflake`
The user to unban.
reason: Optional[str]
The reason for doing this action. Shows up on the audit log.
Raises
-------
@ -1001,7 +1016,7 @@ class Guild(Hashable):
HTTPException
Unbanning failed.
"""
yield from self._state.http.unban(user.id, self.id)
yield from self._state.http.unban(user.id, self.id, reason=reason)
@asyncio.coroutine
def vanity_invite(self):
@ -1038,7 +1053,7 @@ class Guild(Hashable):
return Invite(state=self._state, data=payload)
@asyncio.coroutine
def change_vanity_invite(self, new_code):
def change_vanity_invite(self, new_code, *, reason=None):
"""|coro|
Changes the guild's special vanity invite.
@ -1048,6 +1063,13 @@ class Guild(Hashable):
You must have :attr:`Permissions.manage_guild` to use this as well.
Parameters
-----------
new_code: str
The new vanity URL code.
reason: Optional[str]
The reason for changing the vanity invite. Shows up on the audit log.
Raises
-------
Forbidden
@ -1056,7 +1078,7 @@ class Guild(Hashable):
Setting the vanity invite failed.
"""
yield from self._state.http.change_vanity_code(self.id, new_code)
yield from self._state.http.change_vanity_code(self.id, new_code, reason=reason)
def ack(self):
"""|coro|

127
discord/http.py

@ -125,6 +125,14 @@ class HTTPClient:
headers['Content-Type'] = 'application/json'
kwargs['data'] = utils.to_json(kwargs.pop('json'))
try:
reason = kwargs.pop('reason')
except KeyError:
pass
else:
if reason:
headers['X-Audit-Log-Reason'] = reason
kwargs['headers'] = headers
if not self._global_over.is_set():
@ -336,18 +344,18 @@ class HTTPClient:
def ack_guild(self, guild_id):
return self.request(Route('POST', '/guilds/{guild_id}/ack', guild_id=guild_id))
def delete_message(self, channel_id, message_id):
def delete_message(self, channel_id, message_id, *, reason=None):
r = Route('DELETE', '/channels/{channel_id}/messages/{message_id}', channel_id=channel_id,
message_id=message_id)
return self.request(r)
return self.request(r, reason=reason)
def delete_messages(self, channel_id, message_ids):
def delete_messages(self, channel_id, message_ids, *, reason=None):
r = Route('POST', '/channels/{channel_id}/messages/bulk_delete', channel_id=channel_id)
payload = {
'messages': message_ids
}
return self.request(r, json=payload)
return self.request(r, json=payload, reason=reason)
def edit_message(self, message_id, channel_id, **fields):
r = Route('PATCH', '/channels/{channel_id}/messages/{message_id}', channel_id=channel_id,
@ -426,11 +434,11 @@ class HTTPClient:
return self.request(r, params=params)
def unban(self, user_id, guild_id):
def unban(self, user_id, guild_id, *, reason=None):
r = Route('DELETE', '/guilds/{guild_id}/bans/{user_id}', guild_id=guild_id, user_id=user_id)
return self.request(r)
return self.request(r, reason=reason)
def guild_voice_state(self, user_id, guild_id, *, mute=None, deafen=None):
def guild_voice_state(self, user_id, guild_id, *, mute=None, deafen=None, reason=None):
r = Route('PATCH', '/guilds/{guild_id}/members/{user_id}', guild_id=guild_id, user_id=user_id)
payload = {}
if mute is not None:
@ -439,7 +447,7 @@ class HTTPClient:
if deafen is not None:
payload['deaf'] = deafen
return self.request(r, json=payload)
return self.request(r, json=payload, reason=reason)
def edit_profile(self, password, username, avatar, **fields):
payload = {
@ -456,38 +464,40 @@ class HTTPClient:
return self.request(Route('PATCH', '/users/@me'), json=payload)
def change_my_nickname(self, guild_id, nickname):
def change_my_nickname(self, guild_id, nickname, *, reason=None):
r = Route('PATCH', '/guilds/{guild_id}/members/@me/nick', guild_id=guild_id)
payload = {
'nick': nickname
}
return self.request(Route('PATCH', '/guilds/{guild_id}/members/@me/nick', guild_id=guild_id), json=payload)
return self.request(r, json=payload, reason=reason)
def change_nickname(self, guild_id, user_id, nickname):
def change_nickname(self, guild_id, user_id, nickname, *, reason=None):
r = Route('PATCH', '/guilds/{guild_id}/members/{user_id}', guild_id=guild_id, user_id=user_id)
payload = {
'nick': nickname
}
return self.request(r, json=payload)
return self.request(r, json=payload, reason=reason)
def edit_member(self, guild_id, user_id, **fields):
def edit_member(self, guild_id, user_id, *, reason=None, **fields):
r = Route('PATCH', '/guilds/{guild_id}/members/{user_id}', guild_id=guild_id, user_id=user_id)
return self.request(r, json=fields)
return self.request(r, json=fields, reason=reason)
# Channel management
def edit_channel(self, channel_id, **options):
def edit_channel(self, channel_id, *, reason=None, **options):
r = Route('PATCH', '/channels/{channel_id}', channel_id=channel_id)
valid_keys = ('name', 'topic', 'bitrate', 'user_limit', 'position')
payload = {
k: v for k, v in options.items() if k in valid_keys
}
return self.request(Route('PATCH', '/channels/{channel_id}', channel_id=channel_id), json=payload)
return self.request(r, reason=reason, json=payload)
def move_channel_position(self, guild_id, positions):
def move_channel_position(self, guild_id, positions, *, reason=None):
r = Route('PATCH', '/guilds/{guild_id}/channels', guild_id=guild_id)
return self.request(r, json=positions)
return self.request(r, json=positions, reason=reason)
def create_channel(self, guild_id, name, channe_type, permission_overwrites=None):
def create_channel(self, guild_id, name, channe_type, permission_overwrites=None, *, reason=None):
payload = {
'name': name,
'type': channe_type
@ -496,10 +506,10 @@ class HTTPClient:
if permission_overwrites is not None:
payload['permission_overwrites'] = permission_overwrites
return self.request(Route('POST', '/guilds/{guild_id}/channels', guild_id=guild_id), json=payload)
return self.request(Route('POST', '/guilds/{guild_id}/channels', guild_id=guild_id), json=payload, reason=reason)
def delete_channel(self, channel_id):
return self.request(Route('DELETE', '/channels/{channel_id}', channel_id=channel_id))
def delete_channel(self, channel_id, *, reason=None):
return self.request(Route('DELETE', '/channels/{channel_id}', channel_id=channel_id), reason=reason)
# Guild management
@ -518,7 +528,7 @@ class HTTPClient:
return self.request(Route('POST', '/guilds'), json=payload)
def edit_guild(self, guild_id, **fields):
def edit_guild(self, guild_id, *, reason=None, **fields):
valid_keys = ('name', 'region', 'icon', 'afk_timeout', 'owner_id',
'afk_channel_id', 'splash', 'verification_level')
@ -526,7 +536,7 @@ class HTTPClient:
k: v for k, v in fields.items() if k in valid_keys
}
return self.request(Route('PATCH', '/guilds/{guild_id}', guild_id=guild_id), json=payload)
return self.request(Route('PATCH', '/guilds/{guild_id}', guild_id=guild_id), json=payload, reason=reason)
def get_bans(self, guild_id):
return self.request(Route('GET', '/guilds/{guild_id}/bans', guild_id=guild_id))
@ -534,15 +544,15 @@ class HTTPClient:
def get_vanity_code(self, guild_id):
return self.request(Route('GET', '/guilds/{guild_id}/vanity-url', guild_id=guild_id))
def change_vanity_code(self, guild_id, code):
def change_vanity_code(self, guild_id, code, *, reason=None):
payload = { 'code': code }
return self.request(Route('PATCH', '/guilds/{guild_id}/vanity-url', guild_id=guild_id), json=payload)
return self.request(Route('PATCH', '/guilds/{guild_id}/vanity-url', guild_id=guild_id), json=payload, reason=reason)
def prune_members(self, guild_id, days):
def prune_members(self, guild_id, days, *, reason=None):
params = {
'days': days
}
return self.request(Route('POST', '/guilds/{guild_id}/prune', guild_id=guild_id), params=params)
return self.request(Route('POST', '/guilds/{guild_id}/prune', guild_id=guild_id), params=params, reason=reason)
def estimate_pruned_members(self, guild_id, days):
params = {
@ -550,24 +560,25 @@ class HTTPClient:
}
return self.request(Route('GET', '/guilds/{guild_id}/prune', guild_id=guild_id), params=params)
def create_custom_emoji(self, guild_id, name, image):
def create_custom_emoji(self, guild_id, name, image, *, reason=None):
payload = {
'name': name,
'image': image
}
r = Route('POST', '/guilds/{guild_id}/emojis', guild_id=guild_id)
return self.request(r, json=payload)
return self.request(r, json=payload, reason=reason)
def delete_custom_emoji(self, guild_id, emoji_id):
return self.request(Route('DELETE', '/guilds/{guild_id}/emojis/{emoji_id}', guild_id=guild_id, emoji_id=emoji_id))
def delete_custom_emoji(self, guild_id, emoji_id, *, reason=None):
r = Route('DELETE', '/guilds/{guild_id}/emojis/{emoji_id}', guild_id=guild_id, emoji_id=emoji_id)
return self.request(r, reason=reason)
def edit_custom_emoji(self, guild_id, emoji_id, *, name):
def edit_custom_emoji(self, guild_id, emoji_id, *, name, reason=None):
payload = {
'name': name
}
r = Route('PATCH', '/guilds/{guild_id}/emojis/{emoji_id}', guild_id=guild_id, emoji_id=emoji_id)
return self.request(r, json=payload)
return self.request(r, json=payload, reason=reason)
def get_audit_logs(self, guild_id, limit=100, before=None, after=None, user_id=None, action_type=None):
params = { 'limit': limit }
@ -585,7 +596,7 @@ class HTTPClient:
# Invite management
def create_invite(self, channel_id, **options):
def create_invite(self, channel_id, *, reason=None, **options):
r = Route('POST', '/channels/{channel_id}/invites', channel_id=channel_id)
payload = {
'max_age': options.get('max_age', 0),
@ -594,7 +605,7 @@ class HTTPClient:
'unique': options.get('unique', True)
}
return self.request(r, json=payload)
return self.request(r, reason=reason, json=payload)
def get_invite(self, invite_id):
return self.request(Route('GET', '/invite/{invite_id}', invite_id=invite_id))
@ -605,45 +616,45 @@ class HTTPClient:
def invites_from_channel(self, channel_id):
return self.request(Route('GET', '/channels/{channel_id}/invites', channel_id=channel_id))
def delete_invite(self, invite_id):
return self.request(Route('DELETE', '/invite/{invite_id}', invite_id=invite_id))
def delete_invite(self, invite_id, *, reason=None):
return self.request(Route('DELETE', '/invite/{invite_id}', invite_id=invite_id), reason=reason)
# Role management
def edit_role(self, guild_id, role_id, **fields):
def edit_role(self, guild_id, role_id, *, reason=None, **fields):
r = Route('PATCH', '/guilds/{guild_id}/roles/{role_id}', guild_id=guild_id, role_id=role_id)
valid_keys = ('name', 'permissions', 'color', 'hoist', 'mentionable')
payload = {
k: v for k, v in fields.items() if k in valid_keys
}
return self.request(r, json=payload)
return self.request(r, json=payload, reason=reason)
def delete_role(self, guild_id, role_id):
def delete_role(self, guild_id, role_id, *, reason=None):
r = Route('DELETE', '/guilds/{guild_id}/roles/{role_id}', guild_id=guild_id, role_id=role_id)
return self.request(r)
return self.request(r, reason=reason)
def replace_roles(self, user_id, guild_id, role_ids):
return self.edit_member(guild_id=guild_id, user_id=user_id, roles=role_ids)
def replace_roles(self, user_id, guild_id, role_ids, *, reason=None):
return self.edit_member(guild_id=guild_id, user_id=user_id, roles=role_ids, reason=reason)
def create_role(self, guild_id, **fields):
def create_role(self, guild_id, *, reason=None, **fields):
r = Route('POST', '/guilds/{guild_id}/roles', guild_id=guild_id)
return self.request(r, json=fields)
return self.request(r, json=fields, reason=reason)
def move_role_position(self, guild_id, positions):
def move_role_position(self, guild_id, positions, *, reason=None):
r = Route('PATCH', '/guilds/{guild_id}/roles', guild_id=guild_id)
return self.request(r, json=positions)
return self.request(r, json=positions, reason=reason)
def add_role(self, guild_id, user_id, role_id):
def add_role(self, guild_id, user_id, role_id, *, reason=None):
r = Route('PUT', '/guilds/{guild_id}/members/{user_id}/roles/{role_id}',
guild_id=guild_id, user_id=user_id, role_id=role_id)
return self.request(r)
return self.request(r, reason=reason)
def remove_role(self, guild_id, user_id, role_id):
def remove_role(self, guild_id, user_id, role_id, *, reason=None):
r = Route('DELETE', '/guilds/{guild_id}/members/{user_id}/roles/{role_id}',
guild_id=guild_id, user_id=user_id, role_id=role_id)
return self.request(r)
return self.request(r, reason=reason)
def edit_channel_permissions(self, channel_id, target, allow, deny, type):
def edit_channel_permissions(self, channel_id, target, allow, deny, type, *, reason=None):
payload = {
'id': target,
'allow': allow,
@ -651,16 +662,16 @@ class HTTPClient:
'type': type
}
r = Route('PUT', '/channels/{channel_id}/permissions/{target}', channel_id=channel_id, target=target)
return self.request(r, json=payload)
return self.request(r, json=payload, reason=reason)
def delete_channel_permissions(self, channel_id, target):
def delete_channel_permissions(self, channel_id, target, *, reason=None):
r = Route('DELETE', '/channels/{channel_id}/permissions/{target}', channel_id=channel_id, target=target)
return self.request(r)
return self.request(r, reason=reason)
# Voice management
def move_member(self, user_id, guild_id, channel_id):
return self.edit_member(guild_id=guild_id, user_id=user_id, channel_id=channel_id)
def move_member(self, user_id, guild_id, channel_id, *, reason=None):
return self.edit_member(guild_id=guild_id, user_id=user_id, channel_id=channel_id, reason=reason)
# Relationship related

9
discord/invite.py

@ -147,11 +147,16 @@ class Invite(Hashable):
yield from self._state.http.accept_invite(self.code)
@asyncio.coroutine
def delete(self):
def delete(self, *, reason=None):
"""|coro|
Revokes the instant invite.
Parameters
-----------
reason: Optional[str]
The reason for deleting this invite. Shows up on the audit log.
Raises
-------
Forbidden
@ -162,4 +167,4 @@ class Invite(Hashable):
Revoking the invite failed.
"""
yield from self._state.http.delete_invite(self.code)
yield from self._state.http.delete_invite(self.code, reason=reason)

30
discord/member.py

@ -349,12 +349,12 @@ class Member(discord.abc.Messageable):
yield from self.guild.ban(self, **kwargs)
@asyncio.coroutine
def unban(self):
def unban(self, *, reason=None):
"""|coro|
Unbans this member. Equivalent to :meth:`Guild.unban`
"""
yield from self.guild.unban(self)
yield from self.guild.unban(self, reason=reason)
@asyncio.coroutine
def kick(self, *, reason=None):
@ -365,7 +365,7 @@ class Member(discord.abc.Messageable):
yield from self.guild.kick(self, reason=reason)
@asyncio.coroutine
def edit(self, **fields):
def edit(self, *, reason=None, **fields):
"""|coro|
Edits the member's data.
@ -400,6 +400,8 @@ class Member(discord.abc.Messageable):
The member's new list of roles. This *replaces* the roles.
voice_channel: :class:`VoiceChannel`
The voice channel to move the member to.
reason: Optional[str]
The reason for editing this member. Shows up on the audit log.
Raises
-------
@ -420,7 +422,7 @@ class Member(discord.abc.Messageable):
else:
nick = nick if nick else ''
if self._state.self_id == self.id:
yield from http.change_my_nickname(guild_id, nick)
yield from http.change_my_nickname(guild_id, nick, reason=reason)
else:
payload['nick'] = nick
@ -446,12 +448,12 @@ class Member(discord.abc.Messageable):
else:
payload['roles'] = tuple(r.id for r in roles)
yield from http.edit_member(guild_id, self.id, **payload)
yield from 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):
def move_to(self, channel, *, reason=None):
"""|coro|
Moves a member to a new voice channel (they must be connected first).
@ -465,11 +467,13 @@ class Member(discord.abc.Messageable):
-----------
channel: :class:`VoiceChannel`
The new voice channel to move the member to.
reason: Optional[str]
The reason for doing this action. Shows up on the audit log.
"""
yield from self.edit(voice_channel=channel)
yield from self.edit(voice_channel=channel, reason=reason)
@asyncio.coroutine
def add_roles(self, *roles):
def add_roles(self, *roles, reason=None):
"""|coro|
Gives the member a number of :class:`Role`\s.
@ -481,6 +485,8 @@ class Member(discord.abc.Messageable):
-----------
\*roles
An argument list of :class:`Role`\s to give the member.
reason: Optional[str]
The reason for adding these roles. Shows up on the audit log.
Raises
-------
@ -491,10 +497,10 @@ class Member(discord.abc.Messageable):
"""
new_roles = utils._unique(r for s in (self.roles[1:], roles) for r in s)
yield from self.edit(roles=new_roles)
yield from self.edit(roles=new_roles, reason=reason)
@asyncio.coroutine
def remove_roles(self, *roles):
def remove_roles(self, *roles, reason=None):
"""|coro|
Removes :class:`Role`\s from this member.
@ -506,6 +512,8 @@ class Member(discord.abc.Messageable):
-----------
\*roles
An argument list of :class:`Role`\s to remove from the member.
reason: Optional[str]
The reason for removing these roles. Shows up on the audit log.
Raises
-------
@ -522,4 +530,4 @@ class Member(discord.abc.Messageable):
except ValueError:
pass
yield from self.edit(roles=new_roles)
yield from self.edit(roles=new_roles, reason=reason)

9
discord/message.py

@ -407,7 +407,7 @@ class Message:
return '{0.author.name} started a call \N{EM DASH} Join the call.'.format(self)
@asyncio.coroutine
def delete(self):
def delete(self, *, reason=None):
"""|coro|
Deletes the message.
@ -416,6 +416,11 @@ class Message:
delete other people's messages, you need the :attr:`Permissions.manage_messages`
permission.
Parameters
------------
reason: Optional[str]
The reason for deleting this message. Shows up on the audit log.
Raises
------
Forbidden
@ -423,7 +428,7 @@ class Message:
HTTPException
Deleting the message failed.
"""
yield from self._state.http.delete_message(self.channel.id, self.id)
yield from self._state.http.delete_message(self.channel.id, self.id, reason=reason)
@asyncio.coroutine
def edit(self, **fields):

21
discord/role.py

@ -160,7 +160,7 @@ class Role(Hashable):
return [member for member in all_members if self in member.roles]
@asyncio.coroutine
def _move(self, position):
def _move(self, position, reason):
if position <= 0:
raise InvalidArgument("Cannot move role to position 0 or below")
@ -184,10 +184,10 @@ 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)
yield from http.move_role_position(self.guild.id, payload, reason=reason)
@asyncio.coroutine
def edit(self, **fields):
def edit(self, *, reason=None, **fields):
"""|coro|
Edits the role.
@ -212,6 +212,8 @@ class Role(Hashable):
position: int
The new role's position. This must be below your top role's
position or it will fail.
reason: Optional[str]
The reason for editing this role. Shows up on the audit log.
Raises
-------
@ -226,7 +228,7 @@ class Role(Hashable):
position = fields.get('position')
if position is not None:
yield from self._move(position)
yield from self._move(position, reason=reason)
self.position = position
try:
@ -242,11 +244,11 @@ class Role(Hashable):
'mentionable': fields.get('mentionable', self.mentionable)
}
data = yield from self._state.http.edit_role(self.guild.id, self.id, **payload)
data = yield from self._state.http.edit_role(self.guild.id, self.id, reason=reason, **payload)
self._update(data)
@asyncio.coroutine
def delete(self):
def delete(self, *, reason=None):
"""|coro|
Deletes the role.
@ -254,6 +256,11 @@ class Role(Hashable):
You must have the :attr:`Permissions.manage_roles` permission to
use this.
Parameters
-----------
reason: Optional[str]
The reason for deleting this role. Shows up on the audit log.
Raises
--------
Forbidden
@ -262,4 +269,4 @@ class Role(Hashable):
Deleting the role failed.
"""
yield from self._state.http.delete_role(self.guild.id, self.id)
yield from self._state.http.delete_role(self.guild.id, self.id, reason=reason)

Loading…
Cancel
Save