Browse Source

Migrate webhooks/

pull/10109/head
dolfies 4 years ago
parent
commit
4f09c51500
  1. 168
      discord/webhook/async_.py
  2. 24
      discord/webhook/sync.py

168
discord/webhook/async_.py

@ -69,7 +69,6 @@ if TYPE_CHECKING:
from ..guild import Guild from ..guild import Guild
from ..channel import TextChannel from ..channel import TextChannel
from ..abc import Snowflake from ..abc import Snowflake
from ..ui.view import View
import datetime import datetime
MISSING = utils.MISSING MISSING = utils.MISSING
@ -123,11 +122,11 @@ class AsyncWebhookAdapter:
headers['Content-Type'] = 'application/json' headers['Content-Type'] = 'application/json'
to_send = utils._to_json(payload) to_send = utils._to_json(payload)
if auth_token is not None: if auth_token is not None: # TODO: same as sync.py
headers['Authorization'] = f'Bot {auth_token}' headers['Authorization'] = f'{auth_token}'
if reason is not None: if reason is not None:
headers['X-Audit-Log-Reason'] = urlquote(reason, safe='/ ') headers['X-Audit-Log-Reason'] = urlquote(reason)
response: Optional[aiohttp.ClientResponse] = None response: Optional[aiohttp.ClientResponse] = None
data: Optional[Union[Dict[str, Any], str]] = None data: Optional[Union[Dict[str, Any], str]] = None
@ -149,7 +148,7 @@ class AsyncWebhookAdapter:
try: try:
async with session.request(method, url, data=to_send, headers=headers, params=params) as response: async with session.request(method, url, data=to_send, headers=headers, params=params) as response:
_log.debug( _log.debug(
'Webhook ID %s with %s %s has returned status code %s', 'Webhook ID %s with %s %s has returned status code %s.',
webhook_id, webhook_id,
method, method,
url, url,
@ -163,7 +162,7 @@ class AsyncWebhookAdapter:
if remaining == '0' and response.status != 429: if remaining == '0' and response.status != 429:
delta = utils._parse_ratelimit_header(response) delta = utils._parse_ratelimit_header(response)
_log.debug( _log.debug(
'Webhook ID %s has been pre-emptively rate limited, waiting %.2f seconds', webhook_id, delta 'Webhook ID %s has been pre-emptively rate limited, waiting %.2f seconds.', webhook_id, delta
) )
lock.delay_by(delta) lock.delay_by(delta)
@ -175,7 +174,7 @@ class AsyncWebhookAdapter:
raise HTTPException(response, data) raise HTTPException(response, data)
retry_after: float = data['retry_after'] # type: ignore retry_after: float = data['retry_after'] # type: ignore
_log.warning('Webhook ID %s is rate limited. Retrying in %.2f seconds', webhook_id, retry_after) _log.warning('Webhook ID %s is rate limited. Retrying in %.2f seconds.', webhook_id, retry_after)
await asyncio.sleep(retry_after) await asyncio.sleep(retry_after)
continue continue
@ -341,79 +340,6 @@ class AsyncWebhookAdapter:
route = Route('GET', '/webhooks/{webhook_id}/{webhook_token}', webhook_id=webhook_id, webhook_token=token) route = Route('GET', '/webhooks/{webhook_id}/{webhook_token}', webhook_id=webhook_id, webhook_token=token)
return self.request(route, session=session) return self.request(route, session=session)
def create_interaction_response(
self,
interaction_id: int,
token: str,
*,
session: aiohttp.ClientSession,
type: int,
data: Optional[Dict[str, Any]] = None,
) -> Response[None]:
payload: Dict[str, Any] = {
'type': type,
}
if data is not None:
payload['data'] = data
route = Route(
'POST',
'/interactions/{webhook_id}/{webhook_token}/callback',
webhook_id=interaction_id,
webhook_token=token,
)
return self.request(route, session=session, payload=payload)
def get_original_interaction_response(
self,
application_id: int,
token: str,
*,
session: aiohttp.ClientSession,
) -> Response[MessagePayload]:
r = Route(
'GET',
'/webhooks/{webhook_id}/{webhook_token}/messages/@original',
webhook_id=application_id,
webhook_token=token,
)
return self.request(r, session=session)
def edit_original_interaction_response(
self,
application_id: int,
token: str,
*,
session: aiohttp.ClientSession,
payload: Optional[Dict[str, Any]] = None,
multipart: Optional[List[Dict[str, Any]]] = None,
files: Optional[List[File]] = None,
) -> Response[MessagePayload]:
r = Route(
'PATCH',
'/webhooks/{webhook_id}/{webhook_token}/messages/@original',
webhook_id=application_id,
webhook_token=token,
)
return self.request(r, session, payload=payload, multipart=multipart, files=files)
def delete_original_interaction_response(
self,
application_id: int,
token: str,
*,
session: aiohttp.ClientSession,
) -> Response[None]:
r = Route(
'DELETE',
'/webhooks/{webhook_id}/{wehook_token}/messages/@original',
webhook_id=application_id,
wehook_token=token,
)
return self.request(r, session=session)
class ExecuteWebhookParameters(NamedTuple): class ExecuteWebhookParameters(NamedTuple):
payload: Optional[Dict[str, Any]] payload: Optional[Dict[str, Any]]
@ -427,12 +353,10 @@ def handle_message_parameters(
username: str = MISSING, username: str = MISSING,
avatar_url: Any = MISSING, avatar_url: Any = MISSING,
tts: bool = False, tts: bool = False,
ephemeral: bool = False,
file: File = MISSING, file: File = MISSING,
files: List[File] = MISSING, files: List[File] = MISSING,
embed: Optional[Embed] = MISSING, embed: Optional[Embed] = MISSING,
embeds: List[Embed] = MISSING, embeds: List[Embed] = MISSING,
view: Optional[View] = MISSING,
allowed_mentions: Optional[AllowedMentions] = MISSING, allowed_mentions: Optional[AllowedMentions] = MISSING,
previous_allowed_mentions: Optional[AllowedMentions] = None, previous_allowed_mentions: Optional[AllowedMentions] = None,
) -> ExecuteWebhookParameters: ) -> ExecuteWebhookParameters:
@ -441,7 +365,7 @@ def handle_message_parameters(
if embeds is not MISSING and embed is not MISSING: if embeds is not MISSING and embed is not MISSING:
raise TypeError('Cannot mix embed and embeds keyword arguments.') raise TypeError('Cannot mix embed and embeds keyword arguments.')
payload = {} payload = {'tts': tts}
if embeds is not MISSING: if embeds is not MISSING:
if len(embeds) > 10: if len(embeds) > 10:
raise InvalidArgument('embeds has a maximum of 10 elements.') raise InvalidArgument('embeds has a maximum of 10 elements.')
@ -459,19 +383,10 @@ def handle_message_parameters(
else: else:
payload['content'] = None payload['content'] = None
if view is not MISSING:
if view is not None:
payload['components'] = view.to_components()
else:
payload['components'] = []
payload['tts'] = tts
if avatar_url: if avatar_url:
payload['avatar_url'] = str(avatar_url) payload['avatar_url'] = str(avatar_url)
if username: if username:
payload['username'] = username payload['username'] = username
if ephemeral:
payload['flags'] = 64
if allowed_mentions: if allowed_mentions:
if previous_allowed_mentions is not None: if previous_allowed_mentions is not None:
@ -614,14 +529,14 @@ class _WebhookState:
return self._parent.http return self._parent.http
# Some data classes assign state.http and that should be kosher # Some data classes assign state.http and that should be kosher
# however, using it should result in a late-binding error. # However, using it should result in a late-binding error
return _FriendlyHttpAttributeErrorHelper() return _FriendlyHttpAttributeErrorHelper()
def __getattr__(self, attr): def __getattr__(self, attr):
if self._parent is not None: if self._parent is not None:
return getattr(self._parent, attr) return getattr(self._parent, attr)
raise AttributeError(f'PartialWebhookState does not support {attr!r}.') raise AttributeError(f'PartialWebhookState does not support {attr!r}')
class WebhookMessage(Message): class WebhookMessage(Message):
@ -645,7 +560,6 @@ class WebhookMessage(Message):
embed: Optional[Embed] = MISSING, embed: Optional[Embed] = MISSING,
file: File = MISSING, file: File = MISSING,
files: List[File] = MISSING, files: List[File] = MISSING,
view: Optional[View] = MISSING,
allowed_mentions: Optional[AllowedMentions] = None, allowed_mentions: Optional[AllowedMentions] = None,
) -> WebhookMessage: ) -> WebhookMessage:
"""|coro| """|coro|
@ -678,11 +592,6 @@ class WebhookMessage(Message):
allowed_mentions: :class:`AllowedMentions` allowed_mentions: :class:`AllowedMentions`
Controls the mentions being processed in this message. Controls the mentions being processed in this message.
See :meth:`.abc.Messageable.send` for more information. See :meth:`.abc.Messageable.send` for more information.
view: Optional[:class:`~discord.ui.View`]
The updated view to update this message with. If ``None`` is passed then
the view is removed.
.. versionadded:: 2.0
Raises Raises
------- -------
@ -709,7 +618,6 @@ class WebhookMessage(Message):
embed=embed, embed=embed,
file=file, file=file,
files=files, files=files,
view=view,
allowed_mentions=allowed_mentions, allowed_mentions=allowed_mentions,
) )
@ -1208,13 +1116,11 @@ class Webhook(BaseWebhook):
username: str = MISSING, username: str = MISSING,
avatar_url: Any = MISSING, avatar_url: Any = MISSING,
tts: bool = MISSING, tts: bool = MISSING,
ephemeral: bool = MISSING,
file: File = MISSING, file: File = MISSING,
files: List[File] = MISSING, files: List[File] = MISSING,
embed: Embed = MISSING, embed: Embed = MISSING,
embeds: List[Embed] = MISSING, embeds: List[Embed] = MISSING,
allowed_mentions: AllowedMentions = MISSING, allowed_mentions: AllowedMentions = MISSING,
view: View = MISSING,
thread: Snowflake = MISSING, thread: Snowflake = MISSING,
wait: Literal[True], wait: Literal[True],
) -> WebhookMessage: ) -> WebhookMessage:
@ -1228,13 +1134,11 @@ class Webhook(BaseWebhook):
username: str = MISSING, username: str = MISSING,
avatar_url: Any = MISSING, avatar_url: Any = MISSING,
tts: bool = MISSING, tts: bool = MISSING,
ephemeral: bool = MISSING,
file: File = MISSING, file: File = MISSING,
files: List[File] = MISSING, files: List[File] = MISSING,
embed: Embed = MISSING, embed: Embed = MISSING,
embeds: List[Embed] = MISSING, embeds: List[Embed] = MISSING,
allowed_mentions: AllowedMentions = MISSING, allowed_mentions: AllowedMentions = MISSING,
view: View = MISSING,
thread: Snowflake = MISSING, thread: Snowflake = MISSING,
wait: Literal[False] = ..., wait: Literal[False] = ...,
) -> None: ) -> None:
@ -1247,13 +1151,11 @@ class Webhook(BaseWebhook):
username: str = MISSING, username: str = MISSING,
avatar_url: Any = MISSING, avatar_url: Any = MISSING,
tts: bool = False, tts: bool = False,
ephemeral: bool = False,
file: File = MISSING, file: File = MISSING,
files: List[File] = MISSING, files: List[File] = MISSING,
embed: Embed = MISSING, embed: Embed = MISSING,
embeds: List[Embed] = MISSING, embeds: List[Embed] = MISSING,
allowed_mentions: AllowedMentions = MISSING, allowed_mentions: AllowedMentions = MISSING,
view: View = MISSING,
thread: Snowflake = MISSING, thread: Snowflake = MISSING,
wait: bool = False, wait: bool = False,
) -> Optional[WebhookMessage]: ) -> Optional[WebhookMessage]:
@ -1288,13 +1190,6 @@ class Webhook(BaseWebhook):
string then it is explicitly cast using ``str``. string then it is explicitly cast using ``str``.
tts: :class:`bool` tts: :class:`bool`
Indicates if the message should be sent using text-to-speech. Indicates if the message should be sent using text-to-speech.
ephemeral: :class:`bool`
Indicates if the message should only be visible to the user.
This is only available to :attr:`WebhookType.application` webhooks.
If a view is sent with an ephemeral message and it has no timeout set
then the timeout is set to 15 minutes.
.. versionadded:: 2.0
file: :class:`File` file: :class:`File`
The file to upload. This cannot be mixed with ``files`` parameter. The file to upload. This cannot be mixed with ``files`` parameter.
files: List[:class:`File`] files: List[:class:`File`]
@ -1310,13 +1205,6 @@ class Webhook(BaseWebhook):
Controls the mentions being processed in this message. Controls the mentions being processed in this message.
.. versionadded:: 1.4 .. versionadded:: 1.4
view: :class:`discord.ui.View`
The view to send with the message. You can only send a view
if this webhook is not partial and has state attached. A
webhook has state attached if the webhook is managed by the
library.
.. versionadded:: 2.0
thread: :class:`~discord.abc.Snowflake` thread: :class:`~discord.abc.Snowflake`
The thread to send this webhook to. The thread to send this webhook to.
@ -1335,9 +1223,7 @@ class Webhook(BaseWebhook):
ValueError ValueError
The length of ``embeds`` was invalid. The length of ``embeds`` was invalid.
InvalidArgument InvalidArgument
There was no token associated with this webhook or ``ephemeral`` There was no token associated with this webhook.
was passed with the improper webhook type or there was no state
attached with this webhook when giving it a view.
Returns Returns
--------- ---------
@ -1352,19 +1238,9 @@ class Webhook(BaseWebhook):
if content is None: if content is None:
content = MISSING content = MISSING
application_webhook = self.type is WebhookType.application if self.type is WebhookType.application
if ephemeral and not application_webhook:
raise InvalidArgument('ephemeral messages can only be sent from application webhooks')
if application_webhook:
wait = True wait = True
if view is not MISSING:
if isinstance(self._state, _WebhookState):
raise InvalidArgument('Webhook views require an associated state with the webhook')
if ephemeral is True and view.timeout is None:
view.timeout = 15 * 60.0
params = handle_message_parameters( params = handle_message_parameters(
content=content, content=content,
username=username, username=username,
@ -1374,8 +1250,6 @@ class Webhook(BaseWebhook):
files=files, files=files,
embed=embed, embed=embed,
embeds=embeds, embeds=embeds,
ephemeral=ephemeral,
view=view,
allowed_mentions=allowed_mentions, allowed_mentions=allowed_mentions,
previous_allowed_mentions=previous_mentions, previous_allowed_mentions=previous_mentions,
) )
@ -1399,10 +1273,6 @@ class Webhook(BaseWebhook):
if wait: if wait:
msg = self._create_message(data) msg = self._create_message(data)
if view is not MISSING and not view.is_finished():
message_id = None if msg is None else msg.id
self._state.store_view(view, message_id)
return msg return msg
async def fetch_message(self, id: int) -> WebhookMessage: async def fetch_message(self, id: int) -> WebhookMessage:
@ -1455,7 +1325,6 @@ class Webhook(BaseWebhook):
embed: Optional[Embed] = MISSING, embed: Optional[Embed] = MISSING,
file: File = MISSING, file: File = MISSING,
files: List[File] = MISSING, files: List[File] = MISSING,
view: Optional[View] = MISSING,
allowed_mentions: Optional[AllowedMentions] = None, allowed_mentions: Optional[AllowedMentions] = None,
) -> WebhookMessage: ) -> WebhookMessage:
"""|coro| """|coro|
@ -1493,12 +1362,6 @@ class Webhook(BaseWebhook):
allowed_mentions: :class:`AllowedMentions` allowed_mentions: :class:`AllowedMentions`
Controls the mentions being processed in this message. Controls the mentions being processed in this message.
See :meth:`.abc.Messageable.send` for more information. See :meth:`.abc.Messageable.send` for more information.
view: Optional[:class:`~discord.ui.View`]
The updated view to update this message with. If ``None`` is passed then
the view is removed. The webhook must have state attached, similar to
:meth:`send`.
.. versionadded:: 2.0
Raises Raises
------- -------
@ -1523,12 +1386,6 @@ class Webhook(BaseWebhook):
if self.token is None: if self.token is None:
raise InvalidArgument('This webhook does not have a token associated with it') raise InvalidArgument('This webhook does not have a token associated with it')
if view is not MISSING:
if isinstance(self._state, _WebhookState):
raise InvalidArgument('This webhook does not have state associated with it')
self._state.prevent_view_updates_for(message_id)
previous_mentions: Optional[AllowedMentions] = getattr(self._state, 'allowed_mentions', None) previous_mentions: Optional[AllowedMentions] = getattr(self._state, 'allowed_mentions', None)
params = handle_message_parameters( params = handle_message_parameters(
content=content, content=content,
@ -1536,7 +1393,6 @@ class Webhook(BaseWebhook):
files=files, files=files,
embed=embed, embed=embed,
embeds=embeds, embeds=embeds,
view=view,
allowed_mentions=allowed_mentions, allowed_mentions=allowed_mentions,
previous_allowed_mentions=previous_mentions, previous_allowed_mentions=previous_mentions,
) )
@ -1552,8 +1408,6 @@ class Webhook(BaseWebhook):
) )
message = self._create_message(data) message = self._create_message(data)
if view and not view.is_finished():
self._state.store_view(view, message_id)
return message return message
async def delete_message(self, message_id: int, /) -> None: async def delete_message(self, message_id: int, /) -> None:

24
discord/webhook/sync.py

@ -22,11 +22,13 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
""" """
# If you're wondering why this is essentially copy pasted from the async_.py """
# file, then it's due to needing two separate types to make the typing shenanigans If you're wondering why this is essentially copy pasted from the async_.py
# a bit easier to write. It's an unfortunate design. Originally, these types were file, then it's due to needing two separate types to make the typing shenanigans
# merged and an adapter was used to differentiate between the async and sync versions. a bit easier to write. It's an unfortunate design. Originally, these types were
# However, this proved to be difficult to provide typings for, so here we are. merged and an adapter was used to differentiate between the async and sync versions.
However, this proved to be difficult to provide typings for, so here we are.
"""
from __future__ import annotations from __future__ import annotations
@ -119,11 +121,11 @@ class WebhookAdapter:
headers['Content-Type'] = 'application/json' headers['Content-Type'] = 'application/json'
to_send = utils._to_json(payload) to_send = utils._to_json(payload)
if auth_token is not None: if auth_token is not None: # TODO: is this possible with users?
headers['Authorization'] = f'Bot {auth_token}' headers['Authorization'] = f'{auth_token}'
if reason is not None: if reason is not None:
headers['X-Audit-Log-Reason'] = urlquote(reason, safe='/ ') headers['X-Audit-Log-Reason'] = urlquote(reason)
response: Optional[Response] = None response: Optional[Response] = None
data: Optional[Union[Dict[str, Any], str]] = None data: Optional[Union[Dict[str, Any], str]] = None
@ -151,7 +153,7 @@ class WebhookAdapter:
method, url, data=to_send, files=file_data, headers=headers, params=params method, url, data=to_send, files=file_data, headers=headers, params=params
) as response: ) as response:
_log.debug( _log.debug(
'Webhook ID %s with %s %s has returned status code %s', 'Webhook ID %s with %s %s has returned status code %s.',
webhook_id, webhook_id,
method, method,
url, url,
@ -169,7 +171,7 @@ class WebhookAdapter:
if remaining == '0' and response.status_code != 429: if remaining == '0' and response.status_code != 429:
delta = utils._parse_ratelimit_header(response) delta = utils._parse_ratelimit_header(response)
_log.debug( _log.debug(
'Webhook ID %s has been pre-emptively rate limited, waiting %.2f seconds', webhook_id, delta 'Webhook ID %s has been pre-emptively rate limited, waiting %.2f seconds.', webhook_id, delta
) )
lock.delay_by(delta) lock.delay_by(delta)
@ -181,7 +183,7 @@ class WebhookAdapter:
raise HTTPException(response, data) raise HTTPException(response, data)
retry_after: float = data['retry_after'] # type: ignore retry_after: float = data['retry_after'] # type: ignore
_log.warning('Webhook ID %s is rate limited. Retrying in %.2f seconds', webhook_id, retry_after) _log.warning('Webhook ID %s is rate limited. Retrying in %.2f seconds.', webhook_id, retry_after)
time.sleep(retry_after) time.sleep(retry_after)
continue continue

Loading…
Cancel
Save