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 ..channel import TextChannel
from ..abc import Snowflake
from ..ui.view import View
import datetime
MISSING = utils.MISSING
@ -123,11 +122,11 @@ class AsyncWebhookAdapter:
headers['Content-Type'] = 'application/json'
to_send = utils._to_json(payload)
if auth_token is not None:
headers['Authorization'] = f'Bot {auth_token}'
if auth_token is not None: # TODO: same as sync.py
headers['Authorization'] = f'{auth_token}'
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
data: Optional[Union[Dict[str, Any], str]] = None
@ -149,7 +148,7 @@ class AsyncWebhookAdapter:
try:
async with session.request(method, url, data=to_send, headers=headers, params=params) as response:
_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,
method,
url,
@ -163,7 +162,7 @@ class AsyncWebhookAdapter:
if remaining == '0' and response.status != 429:
delta = utils._parse_ratelimit_header(response)
_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)
@ -175,7 +174,7 @@ class AsyncWebhookAdapter:
raise HTTPException(response, data)
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)
continue
@ -341,79 +340,6 @@ class AsyncWebhookAdapter:
route = Route('GET', '/webhooks/{webhook_id}/{webhook_token}', webhook_id=webhook_id, webhook_token=token)
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):
payload: Optional[Dict[str, Any]]
@ -427,12 +353,10 @@ def handle_message_parameters(
username: str = MISSING,
avatar_url: Any = MISSING,
tts: bool = False,
ephemeral: bool = False,
file: File = MISSING,
files: List[File] = MISSING,
embed: Optional[Embed] = MISSING,
embeds: List[Embed] = MISSING,
view: Optional[View] = MISSING,
allowed_mentions: Optional[AllowedMentions] = MISSING,
previous_allowed_mentions: Optional[AllowedMentions] = None,
) -> ExecuteWebhookParameters:
@ -441,7 +365,7 @@ def handle_message_parameters(
if embeds is not MISSING and embed is not MISSING:
raise TypeError('Cannot mix embed and embeds keyword arguments.')
payload = {}
payload = {'tts': tts}
if embeds is not MISSING:
if len(embeds) > 10:
raise InvalidArgument('embeds has a maximum of 10 elements.')
@ -459,19 +383,10 @@ def handle_message_parameters(
else:
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:
payload['avatar_url'] = str(avatar_url)
if username:
payload['username'] = username
if ephemeral:
payload['flags'] = 64
if allowed_mentions:
if previous_allowed_mentions is not None:
@ -614,14 +529,14 @@ class _WebhookState:
return self._parent.http
# 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()
def __getattr__(self, attr):
if self._parent is not None:
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):
@ -645,7 +560,6 @@ class WebhookMessage(Message):
embed: Optional[Embed] = MISSING,
file: File = MISSING,
files: List[File] = MISSING,
view: Optional[View] = MISSING,
allowed_mentions: Optional[AllowedMentions] = None,
) -> WebhookMessage:
"""|coro|
@ -678,11 +592,6 @@ class WebhookMessage(Message):
allowed_mentions: :class:`AllowedMentions`
Controls the mentions being processed in this message.
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
-------
@ -709,7 +618,6 @@ class WebhookMessage(Message):
embed=embed,
file=file,
files=files,
view=view,
allowed_mentions=allowed_mentions,
)
@ -1208,13 +1116,11 @@ class Webhook(BaseWebhook):
username: str = MISSING,
avatar_url: Any = MISSING,
tts: bool = MISSING,
ephemeral: bool = MISSING,
file: File = MISSING,
files: List[File] = MISSING,
embed: Embed = MISSING,
embeds: List[Embed] = MISSING,
allowed_mentions: AllowedMentions = MISSING,
view: View = MISSING,
thread: Snowflake = MISSING,
wait: Literal[True],
) -> WebhookMessage:
@ -1228,13 +1134,11 @@ class Webhook(BaseWebhook):
username: str = MISSING,
avatar_url: Any = MISSING,
tts: bool = MISSING,
ephemeral: bool = MISSING,
file: File = MISSING,
files: List[File] = MISSING,
embed: Embed = MISSING,
embeds: List[Embed] = MISSING,
allowed_mentions: AllowedMentions = MISSING,
view: View = MISSING,
thread: Snowflake = MISSING,
wait: Literal[False] = ...,
) -> None:
@ -1247,13 +1151,11 @@ class Webhook(BaseWebhook):
username: str = MISSING,
avatar_url: Any = MISSING,
tts: bool = False,
ephemeral: bool = False,
file: File = MISSING,
files: List[File] = MISSING,
embed: Embed = MISSING,
embeds: List[Embed] = MISSING,
allowed_mentions: AllowedMentions = MISSING,
view: View = MISSING,
thread: Snowflake = MISSING,
wait: bool = False,
) -> Optional[WebhookMessage]:
@ -1288,13 +1190,6 @@ class Webhook(BaseWebhook):
string then it is explicitly cast using ``str``.
tts: :class:`bool`
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`
The file to upload. This cannot be mixed with ``files`` parameter.
files: List[:class:`File`]
@ -1310,13 +1205,6 @@ class Webhook(BaseWebhook):
Controls the mentions being processed in this message.
.. 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`
The thread to send this webhook to.
@ -1335,9 +1223,7 @@ class Webhook(BaseWebhook):
ValueError
The length of ``embeds`` was invalid.
InvalidArgument
There was no token associated with this webhook or ``ephemeral``
was passed with the improper webhook type or there was no state
attached with this webhook when giving it a view.
There was no token associated with this webhook.
Returns
---------
@ -1352,19 +1238,9 @@ class Webhook(BaseWebhook):
if content is None:
content = MISSING
application_webhook = 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:
if self.type is WebhookType.application
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(
content=content,
username=username,
@ -1374,8 +1250,6 @@ class Webhook(BaseWebhook):
files=files,
embed=embed,
embeds=embeds,
ephemeral=ephemeral,
view=view,
allowed_mentions=allowed_mentions,
previous_allowed_mentions=previous_mentions,
)
@ -1399,10 +1273,6 @@ class Webhook(BaseWebhook):
if wait:
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
async def fetch_message(self, id: int) -> WebhookMessage:
@ -1455,7 +1325,6 @@ class Webhook(BaseWebhook):
embed: Optional[Embed] = MISSING,
file: File = MISSING,
files: List[File] = MISSING,
view: Optional[View] = MISSING,
allowed_mentions: Optional[AllowedMentions] = None,
) -> WebhookMessage:
"""|coro|
@ -1493,12 +1362,6 @@ class Webhook(BaseWebhook):
allowed_mentions: :class:`AllowedMentions`
Controls the mentions being processed in this message.
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
-------
@ -1523,12 +1386,6 @@ class Webhook(BaseWebhook):
if self.token is None:
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)
params = handle_message_parameters(
content=content,
@ -1536,7 +1393,6 @@ class Webhook(BaseWebhook):
files=files,
embed=embed,
embeds=embeds,
view=view,
allowed_mentions=allowed_mentions,
previous_allowed_mentions=previous_mentions,
)
@ -1552,8 +1408,6 @@ class Webhook(BaseWebhook):
)
message = self._create_message(data)
if view and not view.is_finished():
self._state.store_view(view, message_id)
return message
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.
"""
# 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
# a bit easier to write. It's an unfortunate design. Originally, these types were
# 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.
"""
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
a bit easier to write. It's an unfortunate design. Originally, these types were
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
@ -119,11 +121,11 @@ class WebhookAdapter:
headers['Content-Type'] = 'application/json'
to_send = utils._to_json(payload)
if auth_token is not None:
headers['Authorization'] = f'Bot {auth_token}'
if auth_token is not None: # TODO: is this possible with users?
headers['Authorization'] = f'{auth_token}'
if reason is not None:
headers['X-Audit-Log-Reason'] = urlquote(reason, safe='/ ')
headers['X-Audit-Log-Reason'] = urlquote(reason)
response: Optional[Response] = 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
) as response:
_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,
method,
url,
@ -169,7 +171,7 @@ class WebhookAdapter:
if remaining == '0' and response.status_code != 429:
delta = utils._parse_ratelimit_header(response)
_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)
@ -181,7 +183,7 @@ class WebhookAdapter:
raise HTTPException(response, data)
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)
continue

Loading…
Cancel
Save