mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-06-07 12:18:59 +00:00
parent
b00aaab0b2
commit
44dc7a8e02
@ -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',
|
||||||
@ -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`.
|
A value of `0` disables slowmode. The maximum value possible is `21600`.
|
||||||
type: :class:`ChannelType`
|
type: :class:`ChannelType`
|
||||||
Change the type of this text channel. Currently, only conversion between
|
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`.
|
is only available to guilds that contain ``NEWS`` in :attr:`Guild.features`.
|
||||||
reason: Optional[:class:`str`]
|
reason: Optional[:class:`str`]
|
||||||
The reason for editing this channel. Shows up on the audit log.
|
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.
|
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
|
||||||
@ -482,7 +481,7 @@ class Guild(Hashable):
|
|||||||
@property
|
@property
|
||||||
def public_updates_channel(self):
|
def public_updates_channel(self):
|
||||||
"""Optional[:class:`TextChannel`]: Return's the guild's channel where admins and
|
"""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.
|
Community guild.
|
||||||
|
|
||||||
If no channel is set, then this returns ``None``.
|
If no channel is set, then this returns ``None``.
|
||||||
@ -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)
|
||||||
|
@ -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.
|
Collects items into chunks of up to a given maximum size.
|
||||||
Another :class:`AsyncIterator` is returned which collects items into
|
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.
|
:class:`list`\s of a given size. The maximum chunk size must be a positive integer.
|
||||||
|
|
||||||
.. versionadded:: 1.6
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
Collecting groups of users: ::
|
Collecting groups of users: ::
|
||||||
|
|
||||||
async for leader, *users in reaction.users().chunk(3):
|
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
|
.. autoclass:: Webhook
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: WebhookMessage
|
||||||
|
:members:
|
||||||
|
|
||||||
Adapters
|
Adapters
|
||||||
~~~~~~~~~
|
~~~~~~~~~
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user