| @@ -34,7 +34,6 @@ from .mixins import Hashable | |||||||
| from . import utils | from . import utils | ||||||
| from .asset import Asset | from .asset import Asset | ||||||
| from .errors import ClientException, NoMoreItems, InvalidArgument | from .errors import ClientException, NoMoreItems, InvalidArgument | ||||||
| from .webhook import Webhook |  | ||||||
|  |  | ||||||
| __all__ = ( | __all__ = ( | ||||||
|     'TextChannel', |     'TextChannel', | ||||||
| @@ -429,6 +428,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable): | |||||||
|             The webhooks for this channel. |             The webhooks for this channel. | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|  |         from .webhook import Webhook | ||||||
|         data = await self._state.http.channel_webhooks(self.id) |         data = await self._state.http.channel_webhooks(self.id) | ||||||
|         return [Webhook.from_state(d, state=self._state) for d in data] |         return [Webhook.from_state(d, state=self._state) for d in data] | ||||||
|  |  | ||||||
| @@ -465,6 +465,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable): | |||||||
|             The created webhook. |             The created webhook. | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|  |         from .webhook import Webhook | ||||||
|         if avatar is not None: |         if avatar is not None: | ||||||
|             avatar = utils._bytes_to_base64_data(avatar) |             avatar = utils._bytes_to_base64_data(avatar) | ||||||
|  |  | ||||||
| @@ -512,6 +513,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable): | |||||||
|         if not isinstance(destination, TextChannel): |         if not isinstance(destination, TextChannel): | ||||||
|             raise InvalidArgument('Expected TextChannel received {0.__name__}'.format(type(destination))) |             raise InvalidArgument('Expected TextChannel received {0.__name__}'.format(type(destination))) | ||||||
|  |  | ||||||
|  |         from .webhook import Webhook | ||||||
|         data = await self._state.http.follow_webhook(self.id, webhook_channel_id=destination.id, reason=reason) |         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) |         return Webhook._as_follower(data, channel=destination, user=self._state.user) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -41,7 +41,6 @@ from .mixins import Hashable | |||||||
| from .user import User | from .user import User | ||||||
| from .invite import Invite | from .invite import Invite | ||||||
| from .iterators import AuditLogIterator, MemberIterator | from .iterators import AuditLogIterator, MemberIterator | ||||||
| from .webhook import Webhook |  | ||||||
| from .widget import Widget | from .widget import Widget | ||||||
| from .asset import Asset | from .asset import Asset | ||||||
| from .flags import SystemChannelFlags | from .flags import SystemChannelFlags | ||||||
| @@ -1482,6 +1481,7 @@ class Guild(Hashable): | |||||||
|             The webhooks for this guild. |             The webhooks for this guild. | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|  |         from .webhook import Webhook | ||||||
|         data = await self._state.http.guild_webhooks(self.id) |         data = await self._state.http.guild_webhooks(self.id) | ||||||
|         return [Webhook.from_state(d, state=self._state) for d in data] |         return [Webhook.from_state(d, state=self._state) for d in data] | ||||||
|  |  | ||||||
|   | |||||||
| @@ -35,6 +35,7 @@ import aiohttp | |||||||
|  |  | ||||||
| from . import utils | from . import utils | ||||||
| from .errors import InvalidArgument, HTTPException, Forbidden, NotFound, DiscordServerError | from .errors import InvalidArgument, HTTPException, Forbidden, NotFound, DiscordServerError | ||||||
|  | from .message import Message | ||||||
| from .enums import try_enum, WebhookType | from .enums import try_enum, WebhookType | ||||||
| from .user import BaseUser, User | from .user import BaseUser, User | ||||||
| from .asset import Asset | from .asset import Asset | ||||||
| @@ -45,6 +46,7 @@ __all__ = ( | |||||||
|     'AsyncWebhookAdapter', |     'AsyncWebhookAdapter', | ||||||
|     'RequestsWebhookAdapter', |     'RequestsWebhookAdapter', | ||||||
|     'Webhook', |     'Webhook', | ||||||
|  |     'WebhookMessage', | ||||||
| ) | ) | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) | log = logging.getLogger(__name__) | ||||||
| @@ -66,6 +68,9 @@ class WebhookAdapter: | |||||||
|         self._request_url = '{0.BASE}/webhooks/{1}/{2}'.format(self, webhook.id, webhook.token) |         self._request_url = '{0.BASE}/webhooks/{1}/{2}'.format(self, webhook.id, webhook.token) | ||||||
|         self.webhook = webhook |         self.webhook = webhook | ||||||
|  |  | ||||||
|  |     def is_async(self): | ||||||
|  |         return False | ||||||
|  |  | ||||||
|     def request(self, verb, url, payload=None, multipart=None): |     def request(self, verb, url, payload=None, multipart=None): | ||||||
|         """Actually does the request. |         """Actually does the request. | ||||||
|  |  | ||||||
| @@ -94,6 +99,12 @@ class WebhookAdapter: | |||||||
|     def edit_webhook(self, *, reason=None, **payload): |     def edit_webhook(self, *, reason=None, **payload): | ||||||
|         return self.request('PATCH', self._request_url, payload=payload, reason=reason) |         return self.request('PATCH', self._request_url, payload=payload, reason=reason) | ||||||
|  |  | ||||||
|  |     def edit_webhook_message(self, message_id, payload): | ||||||
|  |         return self.request('PATCH', '{}/messages/{}'.format(self._request_url, message_id), payload=payload) | ||||||
|  |  | ||||||
|  |     def delete_webhook_message(self, message_id): | ||||||
|  |         return self.request('DELETE', '{}/messages/{}'.format(self._request_url, message_id)) | ||||||
|  |  | ||||||
|     def handle_execution_response(self, data, *, wait): |     def handle_execution_response(self, data, *, wait): | ||||||
|         """Transforms the webhook execution response into something |         """Transforms the webhook execution response into something | ||||||
|         more meaningful. |         more meaningful. | ||||||
| @@ -178,6 +189,9 @@ class AsyncWebhookAdapter(WebhookAdapter): | |||||||
|         self.session = session |         self.session = session | ||||||
|         self.loop = asyncio.get_event_loop() |         self.loop = asyncio.get_event_loop() | ||||||
|  |  | ||||||
|  |     def is_async(self): | ||||||
|  |         return True | ||||||
|  |  | ||||||
|     async def request(self, verb, url, payload=None, multipart=None, *, files=None, reason=None): |     async def request(self, verb, url, payload=None, multipart=None, *, files=None, reason=None): | ||||||
|         headers = {} |         headers = {} | ||||||
|         data = None |         data = None | ||||||
| @@ -253,8 +267,9 @@ class AsyncWebhookAdapter(WebhookAdapter): | |||||||
|             return data |             return data | ||||||
|  |  | ||||||
|         # transform into Message object |         # transform into Message object | ||||||
|         from .message import Message |         # Make sure to coerce the state to the partial one to allow message edits/delete | ||||||
|         return Message(data=data, state=self.webhook._state, channel=self.webhook.channel) |         state = _PartialWebhookState(self, self.webhook) | ||||||
|  |         return WebhookMessage(data=data, state=state, channel=self.webhook.channel) | ||||||
|  |  | ||||||
| class RequestsWebhookAdapter(WebhookAdapter): | class RequestsWebhookAdapter(WebhookAdapter): | ||||||
|     """A webhook adapter suited for use with ``requests``. |     """A webhook adapter suited for use with ``requests``. | ||||||
| @@ -356,8 +371,9 @@ class RequestsWebhookAdapter(WebhookAdapter): | |||||||
|             return response |             return response | ||||||
|  |  | ||||||
|         # transform into Message object |         # transform into Message object | ||||||
|         from .message import Message |         # Make sure to coerce the state to the partial one to allow message edits/delete | ||||||
|         return Message(data=response, state=self.webhook._state, channel=self.webhook.channel) |         state = _PartialWebhookState(self, self.webhook) | ||||||
|  |         return WebhookMessage(data=response, state=state, channel=self.webhook.channel) | ||||||
|  |  | ||||||
| class _FriendlyHttpAttributeErrorHelper: | class _FriendlyHttpAttributeErrorHelper: | ||||||
|     __slots__ = () |     __slots__ = () | ||||||
| @@ -366,9 +382,10 @@ class _FriendlyHttpAttributeErrorHelper: | |||||||
|         raise AttributeError('PartialWebhookState does not support http methods.') |         raise AttributeError('PartialWebhookState does not support http methods.') | ||||||
|  |  | ||||||
| class _PartialWebhookState: | class _PartialWebhookState: | ||||||
|     __slots__ = ('loop',) |     __slots__ = ('loop', 'parent') | ||||||
|  |  | ||||||
|     def __init__(self, adapter): |     def __init__(self, adapter, parent): | ||||||
|  |         self.parent = parent | ||||||
|         # Fetch the loop from the adapter if it's there |         # Fetch the loop from the adapter if it's there | ||||||
|         try: |         try: | ||||||
|             self.loop = adapter.loop |             self.loop = adapter.loop | ||||||
| @@ -394,6 +411,98 @@ class _PartialWebhookState: | |||||||
|     def __getattr__(self, attr): |     def __getattr__(self, attr): | ||||||
|         raise AttributeError('PartialWebhookState does not support {0!r}.'.format(attr)) |         raise AttributeError('PartialWebhookState does not support {0!r}.'.format(attr)) | ||||||
|  |  | ||||||
|  | class WebhookMessage(Message): | ||||||
|  |     """Represents a message sent from your webhook. | ||||||
|  |  | ||||||
|  |     This allows you to edit or delete a message sent by your | ||||||
|  |     webhook. | ||||||
|  |  | ||||||
|  |     This inherits from :class:`discord.Message` with changes to | ||||||
|  |     :meth:`edit` and :meth:`delete` to work. | ||||||
|  |  | ||||||
|  |     .. versionadded:: 1.6 | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def edit(self, **fields): | ||||||
|  |         """|maybecoro| | ||||||
|  |  | ||||||
|  |         Edits the message. | ||||||
|  |  | ||||||
|  |         The content must be able to be transformed into a string via ``str(content)``. | ||||||
|  |  | ||||||
|  |         .. versionadded:: 1.6 | ||||||
|  |  | ||||||
|  |         Parameters | ||||||
|  |         ------------ | ||||||
|  |         content: Optional[:class:`str`] | ||||||
|  |             The content to edit the message with or ``None`` to clear it. | ||||||
|  |         embeds: List[:class:`Embed`] | ||||||
|  |             A list of embeds to edit the message with. | ||||||
|  |         embed: Optional[:class:`Embed`] | ||||||
|  |             The embed to edit the message with. ``None`` suppresses the embeds. | ||||||
|  |             This should not be mixed with the ``embeds`` parameter. | ||||||
|  |         allowed_mentions: :class:`AllowedMentions` | ||||||
|  |             Controls the mentions being processed in this message. | ||||||
|  |             See :meth:`.abc.Messageable.send` for more information. | ||||||
|  |  | ||||||
|  |         Raises | ||||||
|  |         ------- | ||||||
|  |         HTTPException | ||||||
|  |             Editing the message failed. | ||||||
|  |         Forbidden | ||||||
|  |             Edited a message that is not yours. | ||||||
|  |         InvalidArgument | ||||||
|  |             You specified both ``embed`` and ``embeds`` or the length of | ||||||
|  |             ``embeds`` was invalid or there was no token associated with | ||||||
|  |             this webhook. | ||||||
|  |         """ | ||||||
|  |         return self._state.parent.edit_message(self.id, **fields) | ||||||
|  |  | ||||||
|  |     def _delete_delay_sync(self, delay): | ||||||
|  |         time.sleep(delay) | ||||||
|  |         return self._state.parent.delete_message(self.id) | ||||||
|  |  | ||||||
|  |     async def _delete_delay_async(self, delay): | ||||||
|  |         async def inner_call(): | ||||||
|  |             await asyncio.sleep(delay) | ||||||
|  |             try: | ||||||
|  |                 await self._state.parent.delete_message(self.id) | ||||||
|  |             except HTTPException: | ||||||
|  |                 pass | ||||||
|  |  | ||||||
|  |         asyncio.ensure_future(inner_call(), loop=self._state.loop) | ||||||
|  |         return await asyncio.sleep(0) | ||||||
|  |  | ||||||
|  |     def delete(self, *, delay=None): | ||||||
|  |         """|coro| | ||||||
|  |  | ||||||
|  |         Deletes the message. | ||||||
|  |  | ||||||
|  |         Parameters | ||||||
|  |         ----------- | ||||||
|  |         delay: Optional[:class:`float`] | ||||||
|  |             If provided, the number of seconds to wait before deleting the message. | ||||||
|  |             If this is a coroutine, the waiting is done in the background and deletion failures | ||||||
|  |             are ignored. If this is not a coroutine then the delay blocks the thread. | ||||||
|  |  | ||||||
|  |         Raises | ||||||
|  |         ------ | ||||||
|  |         Forbidden | ||||||
|  |             You do not have proper permissions to delete the message. | ||||||
|  |         NotFound | ||||||
|  |             The message was deleted already | ||||||
|  |         HTTPException | ||||||
|  |             Deleting the message failed. | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         if delay is not None: | ||||||
|  |             if self._state.parent._adapter.is_async(): | ||||||
|  |                 return self._delete_delay_async(delay) | ||||||
|  |             else: | ||||||
|  |                 return self._delete_delay_sync(delay) | ||||||
|  |  | ||||||
|  |         return self._state.parent.delete_message(self.id) | ||||||
|  |  | ||||||
| class Webhook(Hashable): | class Webhook(Hashable): | ||||||
|     """Represents a Discord webhook. |     """Represents a Discord webhook. | ||||||
|  |  | ||||||
| @@ -488,7 +597,7 @@ class Webhook(Hashable): | |||||||
|         self.name = data.get('name') |         self.name = data.get('name') | ||||||
|         self.avatar = data.get('avatar') |         self.avatar = data.get('avatar') | ||||||
|         self.token = data.get('token') |         self.token = data.get('token') | ||||||
|         self._state = state or _PartialWebhookState(adapter) |         self._state = state or _PartialWebhookState(adapter, self) | ||||||
|         self._adapter = adapter |         self._adapter = adapter | ||||||
|         self._adapter._prepare(self) |         self._adapter._prepare(self) | ||||||
|  |  | ||||||
| @@ -785,7 +894,7 @@ class Webhook(Hashable): | |||||||
|         wait: :class:`bool` |         wait: :class:`bool` | ||||||
|             Whether the server should wait before sending a response. This essentially |             Whether the server should wait before sending a response. This essentially | ||||||
|             means that the return type of this function changes from ``None`` to |             means that the return type of this function changes from ``None`` to | ||||||
|             a :class:`Message` if set to ``True``. |             a :class:`WebhookMessage` if set to ``True``. | ||||||
|         username: :class:`str` |         username: :class:`str` | ||||||
|             The username to send with this message. If no username is provided |             The username to send with this message. If no username is provided | ||||||
|             then the default username for the webhook is used. |             then the default username for the webhook is used. | ||||||
| @@ -825,7 +934,7 @@ class Webhook(Hashable): | |||||||
|  |  | ||||||
|         Returns |         Returns | ||||||
|         --------- |         --------- | ||||||
|         Optional[:class:`Message`] |         Optional[:class:`WebhookMessage`] | ||||||
|             The message that was sent. |             The message that was sent. | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
| @@ -869,3 +978,115 @@ class Webhook(Hashable): | |||||||
|     def execute(self, *args, **kwargs): |     def execute(self, *args, **kwargs): | ||||||
|         """An alias for :meth:`~.Webhook.send`.""" |         """An alias for :meth:`~.Webhook.send`.""" | ||||||
|         return self.send(*args, **kwargs) |         return self.send(*args, **kwargs) | ||||||
|  |  | ||||||
|  |     def edit_message(self, message_id, **fields): | ||||||
|  |         """|maybecoro| | ||||||
|  |  | ||||||
|  |         Edits a message owned by this webhook. | ||||||
|  |  | ||||||
|  |         This is a lower level interface to :meth:`WebhookMessage.edit` in case | ||||||
|  |         you only have an ID. | ||||||
|  |  | ||||||
|  |         .. versionadded:: 1.6 | ||||||
|  |  | ||||||
|  |         Parameters | ||||||
|  |         ------------ | ||||||
|  |         message_id: :class:`int` | ||||||
|  |             The message ID to edit. | ||||||
|  |         content: Optional[:class:`str`] | ||||||
|  |             The content to edit the message with or ``None`` to clear it. | ||||||
|  |         embeds: List[:class:`Embed`] | ||||||
|  |             A list of embeds to edit the message with. | ||||||
|  |         embed: Optional[:class:`Embed`] | ||||||
|  |             The embed to edit the message with. ``None`` suppresses the embeds. | ||||||
|  |             This should not be mixed with the ``embeds`` parameter. | ||||||
|  |         allowed_mentions: :class:`AllowedMentions` | ||||||
|  |             Controls the mentions being processed in this message. | ||||||
|  |             See :meth:`.abc.Messageable.send` for more information. | ||||||
|  |  | ||||||
|  |         Raises | ||||||
|  |         ------- | ||||||
|  |         HTTPException | ||||||
|  |             Editing the message failed. | ||||||
|  |         Forbidden | ||||||
|  |             Edited a message that is not yours. | ||||||
|  |         InvalidArgument | ||||||
|  |             You specified both ``embed`` and ``embeds`` or the length of | ||||||
|  |             ``embeds`` was invalid or there was no token associated with | ||||||
|  |             this webhook. | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         payload = {} | ||||||
|  |  | ||||||
|  |         if self.token is None: | ||||||
|  |             raise InvalidArgument('This webhook does not have a token associated with it') | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             content = fields['content'] | ||||||
|  |         except KeyError: | ||||||
|  |             pass | ||||||
|  |         else: | ||||||
|  |             if content is not None: | ||||||
|  |                 content = str(content) | ||||||
|  |             payload['content'] = content | ||||||
|  |  | ||||||
|  |         # Check if the embeds interface is being used | ||||||
|  |         try: | ||||||
|  |             embeds = fields['embeds'] | ||||||
|  |         except KeyError: | ||||||
|  |             # Nope | ||||||
|  |             pass | ||||||
|  |         else: | ||||||
|  |             if embeds is None or len(embeds) > 10: | ||||||
|  |                 raise InvalidArgument('embeds has a maximum of 10 elements') | ||||||
|  |             payload['embeds'] = [e.to_dict() for e in embeds] | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             embed = fields['embed'] | ||||||
|  |         except KeyError: | ||||||
|  |             pass | ||||||
|  |         else: | ||||||
|  |             if 'embeds' in payload: | ||||||
|  |                 raise InvalidArgument('Cannot mix embed and embeds keyword arguments') | ||||||
|  |  | ||||||
|  |             if embed is None: | ||||||
|  |                 payload['embeds'] = [] | ||||||
|  |             else: | ||||||
|  |                 payload['embeds'] = [embed.to_dict()] | ||||||
|  |  | ||||||
|  |         allowed_mentions = fields.pop('allowed_mentions', None) | ||||||
|  |         previous_mentions = getattr(self._state, 'allowed_mentions', None) | ||||||
|  |  | ||||||
|  |         if allowed_mentions: | ||||||
|  |             if previous_mentions is not None: | ||||||
|  |                 payload['allowed_mentions'] = previous_mentions.merge(allowed_mentions).to_dict() | ||||||
|  |             else: | ||||||
|  |                 payload['allowed_mentions'] = allowed_mentions.to_dict() | ||||||
|  |         elif previous_mentions is not None: | ||||||
|  |             payload['allowed_mentions'] = previous_mentions.to_dict() | ||||||
|  |  | ||||||
|  |         return self._adapter.edit_webhook_message(message_id, payload=payload) | ||||||
|  |  | ||||||
|  |     def delete_message(self, message_id): | ||||||
|  |         """|maybecoro| | ||||||
|  |  | ||||||
|  |         Deletes a message owned by this webhook. | ||||||
|  |  | ||||||
|  |         This is a lower level interface to :meth:`WebhookMessage.delete` in case | ||||||
|  |         you only have an ID. | ||||||
|  |  | ||||||
|  |         .. versionadded:: 1.6 | ||||||
|  |  | ||||||
|  |         Parameters | ||||||
|  |         ------------ | ||||||
|  |         message_id: :class:`int` | ||||||
|  |             The message ID to edit. | ||||||
|  |  | ||||||
|  |         Raises | ||||||
|  |         ------- | ||||||
|  |         HTTPException | ||||||
|  |             Deleting the message failed. | ||||||
|  |         Forbidden | ||||||
|  |             Deleted a message that is not yours. | ||||||
|  |         """ | ||||||
|  |         return self._adapter.delete_webhook_message(message_id) | ||||||
|   | |||||||
| @@ -2544,6 +2544,9 @@ discord.py offers support for creating, editing, and executing webhooks through | |||||||
| .. autoclass:: Webhook | .. autoclass:: Webhook | ||||||
|     :members: |     :members: | ||||||
|  |  | ||||||
|  | .. autoclass:: WebhookMessage | ||||||
|  |     :members: | ||||||
|  |  | ||||||
| Adapters | Adapters | ||||||
| ~~~~~~~~~ | ~~~~~~~~~ | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user