parent
b00aaab0b2
commit
44dc7a8e02
@ -34,7 +34,6 @@ from .mixins import Hashable
|
||||
from . import utils
|
||||
from .asset import Asset
|
||||
from .errors import ClientException, NoMoreItems, InvalidArgument
|
||||
from .webhook import Webhook
|
||||
|
||||
__all__ = (
|
||||
'TextChannel',
|
||||
@ -221,7 +220,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
|
||||
A value of `0` disables slowmode. The maximum value possible is `21600`.
|
||||
type: :class:`ChannelType`
|
||||
Change the type of this text channel. Currently, only conversion between
|
||||
:attr:`ChannelType.text` and :attr:`ChannelType.news` is supported. This
|
||||
:attr:`ChannelType.text` and :attr:`ChannelType.news` is supported. This
|
||||
is only available to guilds that contain ``NEWS`` in :attr:`Guild.features`.
|
||||
reason: Optional[:class:`str`]
|
||||
The reason for editing this channel. Shows up on the audit log.
|
||||
@ -429,6 +428,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
|
||||
The webhooks for this channel.
|
||||
"""
|
||||
|
||||
from .webhook import Webhook
|
||||
data = await self._state.http.channel_webhooks(self.id)
|
||||
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.
|
||||
"""
|
||||
|
||||
from .webhook import Webhook
|
||||
if avatar is not None:
|
||||
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):
|
||||
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)
|
||||
return Webhook._as_follower(data, channel=destination, user=self._state.user)
|
||||
|
||||
|
@ -41,7 +41,6 @@ from .mixins import Hashable
|
||||
from .user import User
|
||||
from .invite import Invite
|
||||
from .iterators import AuditLogIterator, MemberIterator
|
||||
from .webhook import Webhook
|
||||
from .widget import Widget
|
||||
from .asset import Asset
|
||||
from .flags import SystemChannelFlags
|
||||
@ -482,7 +481,7 @@ class Guild(Hashable):
|
||||
@property
|
||||
def public_updates_channel(self):
|
||||
"""Optional[:class:`TextChannel`]: Return's the guild's channel where admins and
|
||||
moderators of the guilds receive notices from Discord. The guild must be a
|
||||
moderators of the guilds receive notices from Discord. The guild must be a
|
||||
Community guild.
|
||||
|
||||
If no channel is set, then this returns ``None``.
|
||||
@ -1482,6 +1481,7 @@ class Guild(Hashable):
|
||||
The webhooks for this guild.
|
||||
"""
|
||||
|
||||
from .webhook import Webhook
|
||||
data = await self._state.http.guild_webhooks(self.id)
|
||||
return [Webhook.from_state(d, state=self._state) for d in data]
|
||||
|
||||
|
@ -35,6 +35,7 @@ import aiohttp
|
||||
|
||||
from . import utils
|
||||
from .errors import InvalidArgument, HTTPException, Forbidden, NotFound, DiscordServerError
|
||||
from .message import Message
|
||||
from .enums import try_enum, WebhookType
|
||||
from .user import BaseUser, User
|
||||
from .asset import Asset
|
||||
@ -45,6 +46,7 @@ __all__ = (
|
||||
'AsyncWebhookAdapter',
|
||||
'RequestsWebhookAdapter',
|
||||
'Webhook',
|
||||
'WebhookMessage',
|
||||
)
|
||||
|
||||
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.webhook = webhook
|
||||
|
||||
def is_async(self):
|
||||
return False
|
||||
|
||||
def request(self, verb, url, payload=None, multipart=None):
|
||||
"""Actually does the request.
|
||||
|
||||
@ -94,6 +99,12 @@ class WebhookAdapter:
|
||||
def edit_webhook(self, *, reason=None, **payload):
|
||||
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):
|
||||
"""Transforms the webhook execution response into something
|
||||
more meaningful.
|
||||
@ -178,6 +189,9 @@ class AsyncWebhookAdapter(WebhookAdapter):
|
||||
self.session = session
|
||||
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):
|
||||
headers = {}
|
||||
data = None
|
||||
@ -253,8 +267,9 @@ class AsyncWebhookAdapter(WebhookAdapter):
|
||||
return data
|
||||
|
||||
# transform into Message object
|
||||
from .message import Message
|
||||
return Message(data=data, state=self.webhook._state, channel=self.webhook.channel)
|
||||
# Make sure to coerce the state to the partial one to allow message edits/delete
|
||||
state = _PartialWebhookState(self, self.webhook)
|
||||
return WebhookMessage(data=data, state=state, channel=self.webhook.channel)
|
||||
|
||||
class RequestsWebhookAdapter(WebhookAdapter):
|
||||
"""A webhook adapter suited for use with ``requests``.
|
||||
@ -356,8 +371,9 @@ class RequestsWebhookAdapter(WebhookAdapter):
|
||||
return response
|
||||
|
||||
# transform into Message object
|
||||
from .message import Message
|
||||
return Message(data=response, state=self.webhook._state, channel=self.webhook.channel)
|
||||
# Make sure to coerce the state to the partial one to allow message edits/delete
|
||||
state = _PartialWebhookState(self, self.webhook)
|
||||
return WebhookMessage(data=response, state=state, channel=self.webhook.channel)
|
||||
|
||||
class _FriendlyHttpAttributeErrorHelper:
|
||||
__slots__ = ()
|
||||
@ -366,9 +382,10 @@ class _FriendlyHttpAttributeErrorHelper:
|
||||
raise AttributeError('PartialWebhookState does not support http methods.')
|
||||
|
||||
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
|
||||
try:
|
||||
self.loop = adapter.loop
|
||||
@ -394,6 +411,98 @@ class _PartialWebhookState:
|
||||
def __getattr__(self, 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):
|
||||
"""Represents a Discord webhook.
|
||||
|
||||
@ -488,7 +597,7 @@ class Webhook(Hashable):
|
||||
self.name = data.get('name')
|
||||
self.avatar = data.get('avatar')
|
||||
self.token = data.get('token')
|
||||
self._state = state or _PartialWebhookState(adapter)
|
||||
self._state = state or _PartialWebhookState(adapter, self)
|
||||
self._adapter = adapter
|
||||
self._adapter._prepare(self)
|
||||
|
||||
@ -785,7 +894,7 @@ class Webhook(Hashable):
|
||||
wait: :class:`bool`
|
||||
Whether the server should wait before sending a response. This essentially
|
||||
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`
|
||||
The username to send with this message. If no username is provided
|
||||
then the default username for the webhook is used.
|
||||
@ -825,7 +934,7 @@ class Webhook(Hashable):
|
||||
|
||||
Returns
|
||||
---------
|
||||
Optional[:class:`Message`]
|
||||
Optional[:class:`WebhookMessage`]
|
||||
The message that was sent.
|
||||
"""
|
||||
|
||||
@ -869,3 +978,115 @@ class Webhook(Hashable):
|
||||
def execute(self, *args, **kwargs):
|
||||
"""An alias for :meth:`~.Webhook.send`."""
|
||||
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)
|
||||
|
@ -2090,9 +2090,9 @@ Certain utilities make working with async iterators easier, detailed below.
|
||||
Collects items into chunks of up to a given maximum size.
|
||||
Another :class:`AsyncIterator` is returned which collects items into
|
||||
:class:`list`\s of a given size. The maximum chunk size must be a positive integer.
|
||||
|
||||
|
||||
.. versionadded:: 1.6
|
||||
|
||||
|
||||
Collecting groups of users: ::
|
||||
|
||||
async for leader, *users in reaction.users().chunk(3):
|
||||
@ -2544,6 +2544,9 @@ discord.py offers support for creating, editing, and executing webhooks through
|
||||
.. autoclass:: Webhook
|
||||
:members:
|
||||
|
||||
.. autoclass:: WebhookMessage
|
||||
:members:
|
||||
|
||||
Adapters
|
||||
~~~~~~~~~
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user