From e971e2f16cba22decd25db6b44e9cc84adf08555 Mon Sep 17 00:00:00 2001 From: Nadir Chowdhury Date: Wed, 1 Jul 2020 04:35:42 +0100 Subject: [PATCH] Allow more methods to set an audit log reason --- discord/channel.py | 8 ++++++-- discord/http.py | 12 ++++++------ discord/message.py | 22 ++++++++++++++++++---- discord/webhook.py | 40 +++++++++++++++++++++++++++++----------- 4 files changed, 59 insertions(+), 23 deletions(-) diff --git a/discord/channel.py b/discord/channel.py index a554d0d5f..c02c00f36 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -471,7 +471,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable): data = await self._state.http.create_webhook(self.id, name=str(name), avatar=avatar, reason=reason) return Webhook.from_state(data, state=self._state) - async def follow(self, *, destination): + async def follow(self, *, destination, reason=None): """ Follows a channel using a webhook. @@ -488,6 +488,10 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable): ----------- destination: :class:`TextChannel` The channel you would like to follow from. + reason: Optional[:class:`str`] + The reason for following the channel. Shows up on the destination guild's audit log. + + .. versionadded:: 1.4 Raises ------- @@ -508,7 +512,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable): if not isinstance(destination, TextChannel): raise InvalidArgument('Expected TextChannel received {0.__name__}'.format(type(destination))) - data = await self._state.http.follow_webhook(self.id, webhook_channel_id=destination.id) + data = await self._state.http.follow_webhook(self.id, webhook_channel_id=destination.id, reason=reason) return Webhook._as_follower(data, channel=destination, user=self._state.user) class VoiceChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable): diff --git a/discord/http.py b/discord/http.py index 1ad5df260..07bed8cc5 100644 --- a/discord/http.py +++ b/discord/http.py @@ -443,13 +443,13 @@ class HTTPClient: return self.request(Route('POST', '/channels/{channel_id}/messages/{message_id}/crosspost', channel_id=channel_id, message_id=message_id)) - def pin_message(self, channel_id, message_id): + def pin_message(self, channel_id, message_id, reason=None): return self.request(Route('PUT', '/channels/{channel_id}/pins/{message_id}', - channel_id=channel_id, message_id=message_id)) + channel_id=channel_id, message_id=message_id), reason=reason) - def unpin_message(self, channel_id, message_id): + def unpin_message(self, channel_id, message_id, reason=None): return self.request(Route('DELETE', '/channels/{channel_id}/pins/{message_id}', - channel_id=channel_id, message_id=message_id)) + channel_id=channel_id, message_id=message_id), reason=reason) def pins_from(self, channel_id): return self.request(Route('GET', '/channels/{channel_id}/pins', channel_id=channel_id)) @@ -578,11 +578,11 @@ class HTTPClient: def get_webhook(self, webhook_id): return self.request(Route('GET', '/webhooks/{webhook_id}', webhook_id=webhook_id)) - def follow_webhook(self, channel_id, webhook_channel_id): + def follow_webhook(self, channel_id, webhook_channel_id, reason=None): payload = { 'webhook_channel_id': str(webhook_channel_id) } - return self.request(Route('POST', '/channels/{channel_id}/followers', channel_id=channel_id), json=payload) + return self.request(Route('POST', '/channels/{channel_id}/followers', channel_id=channel_id), json=payload, reason=reason) # Guild management diff --git a/discord/message.py b/discord/message.py index f6565ba14..5b4d79cb1 100644 --- a/discord/message.py +++ b/discord/message.py @@ -856,7 +856,7 @@ class Message: await self._state.http.publish_message(self.channel.id, self.id) - async def pin(self): + async def pin(self, *, reason=None): """|coro| Pins the message. @@ -864,6 +864,13 @@ class Message: You must have the :attr:`~Permissions.manage_messages` permission to do this in a non-private channel context. + Parameters + ----------- + reason: Optional[:class:`str`] + The reason for pinning the message. Shows up on the audit log. + + .. versionadded:: 1.4 + Raises ------- Forbidden @@ -875,10 +882,10 @@ class Message: having more than 50 pinned messages. """ - await self._state.http.pin_message(self.channel.id, self.id) + await self._state.http.pin_message(self.channel.id, self.id, reason=reason) self.pinned = True - async def unpin(self): + async def unpin(self, *, reason=None): """|coro| Unpins the message. @@ -886,6 +893,13 @@ class Message: You must have the :attr:`~Permissions.manage_messages` permission to do this in a non-private channel context. + Parameters + ----------- + reason: Optional[:class:`str`] + The reason for pinning the message. Shows up on the audit log. + + .. versionadded:: 1.4 + Raises ------- Forbidden @@ -896,7 +910,7 @@ class Message: Unpinning the message failed. """ - await self._state.http.unpin_message(self.channel.id, self.id) + await self._state.http.unpin_message(self.channel.id, self.id, reason=reason) self.pinned = False async def add_reaction(self, emoji): diff --git a/discord/webhook.py b/discord/webhook.py index 64136b9ed..317c54c4e 100644 --- a/discord/webhook.py +++ b/discord/webhook.py @@ -28,6 +28,7 @@ import asyncio import json import time import re +from urllib.parse import quote as _uriquote import aiohttp @@ -84,11 +85,11 @@ class WebhookAdapter: """ raise NotImplementedError() - def delete_webhook(self): - return self.request('DELETE', self._request_url) + def delete_webhook(self, *, reason=None): + return self.request('DELETE', self._request_url, reason=reason) - def edit_webhook(self, **payload): - return self.request('PATCH', self._request_url, payload=payload) + def edit_webhook(self, *, reason=None, **payload): + return self.request('PATCH', self._request_url, payload=payload, reason=reason) def handle_execution_response(self, data, *, wait): """Transforms the webhook execution response into something @@ -174,13 +175,16 @@ class AsyncWebhookAdapter(WebhookAdapter): self.session = session self.loop = asyncio.get_event_loop() - async def request(self, verb, url, payload=None, multipart=None, *, files=None): + async def request(self, verb, url, payload=None, multipart=None, *, files=None, reason=None): headers = {} data = None files = files or [] if payload: headers['Content-Type'] = 'application/json' data = utils.to_json(payload) + + if reason: + headers['X-Audit-Log-Reason'] = _uriquote(reason, safe='/ ') if multipart: data = aiohttp.FormData() @@ -260,13 +264,16 @@ class RequestsWebhookAdapter(WebhookAdapter): self.session = session or requests self.sleep = sleep - def request(self, verb, url, payload=None, multipart=None, *, files=None): + def request(self, verb, url, payload=None, multipart=None, *, files=None, reason=None): headers = {} data = None files = files or [] if payload: headers['Content-Type'] = 'application/json' data = utils.to_json(payload) + + if reason: + headers['X-Audit-Log-Reason'] = _uriquote(reason, safe='/ ') if multipart is not None: data = {'payload_json': multipart.pop('payload_json')} @@ -640,7 +647,7 @@ class Webhook(Hashable): url = '/avatars/{0.id}/{0.avatar}.{1}?size={2}'.format(self, format, size) return Asset(self._state, url) - def delete(self): + def delete(self, *, reason=None): """|maybecoro| Deletes this Webhook. @@ -648,6 +655,13 @@ class Webhook(Hashable): If the webhook is constructed with a :class:`RequestsWebhookAdapter` then this is not a coroutine. + Parameters + ------------ + reason: Optional[:class:`str`] + The reason for deleting this webhook. Shows up on the audit log. + + .. versionadded:: 1.4 + Raises ------- HTTPException @@ -662,9 +676,9 @@ class Webhook(Hashable): if self.token is None: raise InvalidArgument('This webhook does not have a token associated with it') - return self._adapter.delete_webhook() + return self._adapter.delete_webhook(reason=reason) - def edit(self, **kwargs): + def edit(self, *, reason=None, **kwargs): """|maybecoro| Edits this Webhook. @@ -673,11 +687,15 @@ class Webhook(Hashable): not a coroutine. Parameters - ------------- + ------------ name: Optional[:class:`str`] The webhook's new default name. avatar: Optional[:class:`bytes`] A :term:`py:bytes-like object` representing the webhook's new default avatar. + reason: Optional[:class:`str`] + The reason for deleting this webhook. Shows up on the audit log. + + .. versionadded:: 1.4 Raises ------- @@ -713,7 +731,7 @@ class Webhook(Hashable): else: payload['avatar'] = None - return self._adapter.edit_webhook(**payload) + return self._adapter.edit_webhook(reason=reason, **payload) def send(self, content=None, *, wait=False, username=None, avatar_url=None, tts=False, file=None, files=None, embed=None, embeds=None, allowed_mentions=None):