|
|
@ -24,14 +24,16 @@ DEALINGS IN THE SOFTWARE. |
|
|
|
|
|
|
|
from __future__ import annotations |
|
|
|
|
|
|
|
from typing import Optional, TYPE_CHECKING, Union |
|
|
|
import asyncio |
|
|
|
from typing import TYPE_CHECKING, Optional, Union |
|
|
|
|
|
|
|
from .enums import InteractionType, try_enum |
|
|
|
from .errors import InvalidData |
|
|
|
from .mixins import Hashable |
|
|
|
from .utils import cached_slot_property, find, MISSING |
|
|
|
from .utils import MISSING, cached_slot_property, find |
|
|
|
|
|
|
|
if TYPE_CHECKING: |
|
|
|
from .channel import DMChannel, GroupChannel, TextChannel, VoiceChannel |
|
|
|
from .channel import DMChannel, TextChannel, VoiceChannel |
|
|
|
from .guild import Guild |
|
|
|
from .message import Message |
|
|
|
from .modal import Modal |
|
|
@ -41,7 +43,7 @@ if TYPE_CHECKING: |
|
|
|
from .types.user import User as UserPayload |
|
|
|
from .user import BaseUser, ClientUser |
|
|
|
|
|
|
|
MessageableChannel = Union[TextChannel, Thread, DMChannel, GroupChannel, VoiceChannel] |
|
|
|
MessageableChannel = Union[TextChannel, Thread, DMChannel, VoiceChannel] |
|
|
|
|
|
|
|
# fmt: off |
|
|
|
__all__ = ( |
|
|
@ -69,30 +71,36 @@ class Interaction(Hashable): |
|
|
|
|
|
|
|
Return the interaction's hash. |
|
|
|
|
|
|
|
.. describe:: str(x) |
|
|
|
|
|
|
|
Returns a string representation of the interaction, if any. |
|
|
|
|
|
|
|
Attributes |
|
|
|
------------ |
|
|
|
id: :class:`int` |
|
|
|
The interaction ID. |
|
|
|
type: :class:`InteractionType` |
|
|
|
The type of interaction. |
|
|
|
nonce: Optional[Union[:class:`int`, :class:`str`]] |
|
|
|
The interaction's nonce. Not always present. |
|
|
|
channel: Union[:class:`TextChannel`, :class:`VoiceChannel`, :class:`Thread`, :class:`DMChannel`] |
|
|
|
The channel this interaction originated from. |
|
|
|
user: Union[:class:`Member`, :class:`abc.User`] |
|
|
|
The :class:`Member` who initiated the interaction. |
|
|
|
If :attr:`channel` is a private channel or the |
|
|
|
user has the left the guild, then it is a :class:`User` instead. |
|
|
|
name: Optional[:class:`str`] |
|
|
|
The name of the application command, if applicable. |
|
|
|
type: :class:`InteractionType` |
|
|
|
The type of interaction. |
|
|
|
successful: :class:`bool` |
|
|
|
Whether the interaction succeeded. |
|
|
|
If this is your interaction, this is not immediately available. |
|
|
|
It is filled when Discord notifies us about the outcome of the interaction. |
|
|
|
user: Union[:class:`Member`, :class:`abc.User`] |
|
|
|
The :class:`Member` who initiated the interaction. |
|
|
|
If :attr:`channel` is a private channel or the |
|
|
|
user has the left the guild, then it is a :class:`User` instead. |
|
|
|
modal: Optional[:class:`Modal`] |
|
|
|
The modal that is in response to this interaction. |
|
|
|
This is not immediately available and is filled when the modal is dispatched. |
|
|
|
""" |
|
|
|
|
|
|
|
__slots__ = ('id', 'type', 'nonce', 'user', 'name', 'successful', 'modal', '_cs_message', '_cs_channel', '_state') |
|
|
|
__slots__ = ('id', 'type', 'nonce', 'channel', 'user', 'name', 'successful', 'modal', '_cs_message', '_state') |
|
|
|
|
|
|
|
def __init__( |
|
|
|
self, |
|
|
@ -100,15 +108,16 @@ class Interaction(Hashable): |
|
|
|
type: int, |
|
|
|
nonce: Optional[Snowflake] = None, |
|
|
|
*, |
|
|
|
channel: MessageableChannel, |
|
|
|
user: BaseUser, |
|
|
|
state: ConnectionState, |
|
|
|
name: Optional[str] = None, |
|
|
|
message: Optional[Message] = None, |
|
|
|
channel: Optional[MessageableChannel] = None, |
|
|
|
) -> None: |
|
|
|
self.id = id |
|
|
|
self.nonce = nonce |
|
|
|
self.type = try_enum(InteractionType, type) |
|
|
|
self.nonce = nonce |
|
|
|
self.channel = channel |
|
|
|
self.user = user |
|
|
|
self.name = name |
|
|
|
self.successful: bool = MISSING |
|
|
@ -116,8 +125,6 @@ class Interaction(Hashable): |
|
|
|
self._state = state |
|
|
|
if message is not None: |
|
|
|
self._cs_message = message |
|
|
|
if channel is not None: |
|
|
|
self._cs_channel = channel |
|
|
|
|
|
|
|
@classmethod |
|
|
|
def _from_self( |
|
|
@ -137,13 +144,28 @@ class Interaction(Hashable): |
|
|
|
state = message._state |
|
|
|
name = data.get('name') |
|
|
|
user_cls = state.store_user(user) |
|
|
|
self = cls(int(id), type, user=user_cls, name=name, message=message, state=state) |
|
|
|
self = cls( |
|
|
|
int(id), |
|
|
|
type, |
|
|
|
channel=message.channel, # type: ignore # message.channel is always correct here |
|
|
|
user=user_cls, |
|
|
|
name=name, |
|
|
|
message=message, |
|
|
|
state=state, |
|
|
|
) |
|
|
|
self.successful = True |
|
|
|
return self |
|
|
|
|
|
|
|
def __repr__(self) -> str: |
|
|
|
s = self.successful |
|
|
|
return f'<Interaction id={self.id} type={self.type}{f" successful={s}" if s is not None else ""} user={self.user!r}>' |
|
|
|
return ( |
|
|
|
f'<Interaction id={self.id} type={self.type!r}{f" successful={s}" if s is not MISSING else ""} user={self.user!r}>' |
|
|
|
) |
|
|
|
|
|
|
|
def __str__(self) -> str: |
|
|
|
if self.name: |
|
|
|
return f'{self.user.name} used **{"/" if self.type == InteractionType.application_command else ""}{self.name}**' |
|
|
|
return '' |
|
|
|
|
|
|
|
def __bool__(self) -> bool: |
|
|
|
if self.successful is not MISSING: |
|
|
@ -164,11 +186,37 @@ class Interaction(Hashable): |
|
|
|
@property |
|
|
|
def guild(self) -> Optional[Guild]: |
|
|
|
"""Optional[:class:`Guild`]: Returns the guild the interaction originated from.""" |
|
|
|
return getattr(self.channel, 'guild', getattr(self.message, 'guild', None)) |
|
|
|
|
|
|
|
@cached_slot_property('_cs_channel') |
|
|
|
def channel(self) -> MessageableChannel: |
|
|
|
"""Union[:class:`TextChannel`, :class:`Thread`, :class:`DMChannel`, :class:`GroupChannel`]: |
|
|
|
Returns the channel this interaction originated from. |
|
|
|
""" |
|
|
|
return getattr(self.message, 'channel', None) |
|
|
|
return getattr(self.channel, 'guild', None) |
|
|
|
|
|
|
|
|
|
|
|
async def _wrapped_interaction( |
|
|
|
state: ConnectionState, |
|
|
|
nonce: str, |
|
|
|
type: InteractionType, |
|
|
|
name: Optional[str], |
|
|
|
channel: MessageableChannel, |
|
|
|
data: dict, |
|
|
|
**kwargs, |
|
|
|
) -> Interaction: |
|
|
|
state._interaction_cache[nonce] = (type.value, name, channel) |
|
|
|
|
|
|
|
try: |
|
|
|
await state.http.interact(type, data, channel, nonce=nonce, **kwargs) |
|
|
|
# The maximum possible time a response can take is 3 seconds, |
|
|
|
# +/- a few milliseconds for network latency |
|
|
|
# However, people have been getting errors because their gateway |
|
|
|
# disconnects while waiting for the interaction, causing the |
|
|
|
# response to be delayed until the gateway is reconnected |
|
|
|
# 12 seconds should be enough to account for this |
|
|
|
i = await state.client.wait_for( |
|
|
|
'interaction_finish', |
|
|
|
check=lambda d: d.nonce == nonce, |
|
|
|
timeout=12, |
|
|
|
) |
|
|
|
except (asyncio.TimeoutError, asyncio.CancelledError) as exc: |
|
|
|
raise InvalidData('Did not receive a response from Discord') from exc |
|
|
|
finally: |
|
|
|
# Cleanup even if we failed |
|
|
|
state._interaction_cache.pop(nonce, None) |
|
|
|
|
|
|
|
return i |
|
|
|