Browse Source

Implement message requests

pull/10109/head
dolfies 3 years ago
parent
commit
cbc5d706c2
  1. 99
      discord/channel.py
  2. 4
      discord/gateway.py
  3. 24
      discord/http.py
  4. 18
      discord/state.py
  5. 4
      discord/types/channel.py

99
discord/channel.py

@ -2213,31 +2213,45 @@ class DMChannel(discord.abc.Messageable, discord.abc.Connectable, Hashable):
Attributes Attributes
---------- ----------
id: :class:`int`
The direct message channel ID.
recipient: :class:`User`
The user you are participating with in the direct message channel.
me: :class:`ClientUser`
The user presenting yourself.
last_message_id: Optional[:class:`int`] last_message_id: Optional[:class:`int`]
The last message ID of the message sent to this channel. It may The last message ID of the message sent to this channel. It may
*not* point to an existing or valid message. *not* point to an existing or valid message.
.. versionadded:: 2.0 .. versionadded:: 2.0
recipient: Optional[:class:`User`]
The user you are participating with in the direct message channel.
If this channel is received through the gateway, the recipient information
may not be always available.
me: :class:`ClientUser`
The user presenting yourself.
id: :class:`int`
The direct message channel ID.
""" """
__slots__ = ('id', 'recipient', 'me', 'last_message_id', '_state', '_accessed') __slots__ = (
'id',
'recipient',
'me',
'last_message_id',
'_message_request',
'_requested_at',
'_spam',
'_state',
'_accessed',
)
def __init__(self, *, me: ClientUser, state: ConnectionState, data: DMChannelPayload): def __init__(self, *, me: ClientUser, state: ConnectionState, data: DMChannelPayload):
self._state: ConnectionState = state self._state: ConnectionState = state
self.last_message_id: Optional[int] = utils._get_as_snowflake(data, 'last_message_id')
self.recipient: User = state.store_user(data['recipients'][0]) self.recipient: User = state.store_user(data['recipients'][0])
self.me: ClientUser = me self.me: ClientUser = me
self.id: int = int(data['id']) self.id: int = int(data['id'])
self._update(data)
self._accessed: bool = False self._accessed: bool = False
def _update(self, data: DMChannelPayload) -> None:
self.last_message_id: Optional[int] = utils._get_as_snowflake(data, 'last_message_id')
self._message_request: Optional[bool] = data.get('is_message_request')
self._requested_at: Optional[datetime.datetime] = utils.parse_time(data.get('is_message_request_timestamp'))
self._spam: bool = data.get('is_spam', False)
def _get_voice_client_key(self) -> Tuple[int, str]: def _get_voice_client_key(self) -> Tuple[int, str]:
return self.me.id, 'self_id' return self.me.id, 'self_id'
@ -2249,7 +2263,7 @@ class DMChannel(discord.abc.Messageable, discord.abc.Connectable, Hashable):
async def _get_channel(self) -> Self: async def _get_channel(self) -> Self:
if not self._accessed: if not self._accessed:
await self._state.access_private_channel(self.id) await self._state.call_connect(self.id)
self._accessed = True self._accessed = True
return self return self
@ -2327,6 +2341,24 @@ class DMChannel(discord.abc.Messageable, discord.abc.Connectable, Hashable):
""" """
return self._state._get_message(self.last_message_id) if self.last_message_id else None return self._state._get_message(self.last_message_id) if self.last_message_id else None
@property
def accepted(self) -> bool:
""":class:`bool`: Indicates if the message request is accepted. For regular direct messages, this is always ``True``."""
return self._message_request or True
@property
def requested_at(self) -> Optional[datetime.datetime]:
"""Optional[:class:`datetime.datetime`]: Returns the message request's creation time in UTC, if applicable."""
return self._requested_at
def is_message_request(self) -> bool:
""":class:`bool`: Indicates if the direct message is/was a message request."""
return self._message_request is not None
def is_spam(self) -> bool:
""":class:`bool`: Indicates if the direct message is a spam message."""
return self._spam
def permissions_for(self, obj: Any = None, /) -> Permissions: def permissions_for(self, obj: Any = None, /) -> Permissions:
"""Handles permission resolution for a :class:`User`. """Handles permission resolution for a :class:`User`.
@ -2391,7 +2423,7 @@ class DMChannel(discord.abc.Messageable, discord.abc.Connectable, Hashable):
async def close(self): async def close(self):
"""|coro| """|coro|
"Deletes" the channel. Closes/"deletes" the channel.
In reality, if you recreate a DM with the same user, In reality, if you recreate a DM with the same user,
all your message history will be there. all your message history will be there.
@ -2401,7 +2433,7 @@ class DMChannel(discord.abc.Messageable, discord.abc.Connectable, Hashable):
HTTPException HTTPException
Closing the channel failed. Closing the channel failed.
""" """
await self._state.http.delete_channel(self.id) await self._state.http.delete_channel(self.id, silent=False)
async def connect( async def connect(
self, self,
@ -2451,6 +2483,41 @@ class DMChannel(discord.abc.Messageable, discord.abc.Connectable, Hashable):
await self._initial_ring() await self._initial_ring()
return await super().connect(timeout=timeout, reconnect=reconnect, cls=cls) return await super().connect(timeout=timeout, reconnect=reconnect, cls=cls)
async def accept(self) -> DMChannel:
"""|coro|
Accepts a message request.
Raises
-------
HTTPException
Accepting the message request failed.
TypeError
The channel is not a message request or the request is already accepted.
"""
data = await self._state.http.accept_message_request(self.id)
# Of course Discord does not actually include these fields
data['is_message_request'] = False
if self._requested_at:
data['is_message_request_timestamp'] = utils.utcnow().isoformat()
data['is_spam'] = self._spam
return DMChannel(state=self._state, data=data, me=self.me)
async def decline(self) -> None:
"""|coro|
Declines a message request. This closes the channel.
Raises
-------
HTTPException
Declining the message request failed.
TypeError
The channel is not a message request or the request is already accepted.
"""
await self._state.http.decline_message_request(self.id)
class GroupChannel(discord.abc.Messageable, discord.abc.Connectable, Hashable): class GroupChannel(discord.abc.Messageable, discord.abc.Connectable, Hashable):
"""Represents a Discord group channel. """Represents a Discord group channel.
@ -2500,10 +2567,10 @@ class GroupChannel(discord.abc.Messageable, discord.abc.Connectable, Hashable):
self._state: ConnectionState = state self._state: ConnectionState = state
self.id: int = int(data['id']) self.id: int = int(data['id'])
self.me: ClientUser = me self.me: ClientUser = me
self._update_group(data) self._update(data)
self._accessed: bool = False self._accessed: bool = False
def _update_group(self, data: GroupChannelPayload) -> None: def _update(self, data: GroupChannelPayload) -> None:
self.owner_id: Optional[int] = utils._get_as_snowflake(data, 'owner_id') self.owner_id: Optional[int] = utils._get_as_snowflake(data, 'owner_id')
self._icon: Optional[str] = data.get('icon') self._icon: Optional[str] = data.get('icon')
self.name: Optional[str] = data.get('name') self.name: Optional[str] = data.get('name')
@ -2518,7 +2585,7 @@ class GroupChannel(discord.abc.Messageable, discord.abc.Connectable, Hashable):
async def _get_channel(self) -> Self: async def _get_channel(self) -> Self:
if not self._accessed: if not self._accessed:
await self._state.access_private_channel(self.id) await self._state.call_connect(self.id)
self._accessed = True self._accessed = True
return self return self

4
discord/gateway.py

@ -770,10 +770,10 @@ class DiscordWebSocket:
_log.debug('Updating %s voice state to %s.', guild_id or 'client', payload) _log.debug('Updating %s voice state to %s.', guild_id or 'client', payload)
await self.send_as_json(payload) await self.send_as_json(payload)
async def access_dm(self, channel_id: Snowflake): async def call_connect(self, channel_id: Snowflake):
payload = {'op': self.CALL_CONNECT, 'd': {'channel_id': str(channel_id)}} payload = {'op': self.CALL_CONNECT, 'd': {'channel_id': str(channel_id)}}
_log.debug('Sending ACCESS_DM for channel %s.', channel_id) _log.debug('Requesting call connect for channel %s.', channel_id)
await self.send_as_json(payload) await self.send_as_json(payload)
async def request_commands( async def request_commands(

24
discord/http.py

@ -698,6 +698,30 @@ class HTTPClient:
return self.request(Route('POST', '/users/@me/channels'), json=payload, context_properties=props) return self.request(Route('POST', '/users/@me/channels'), json=payload, context_properties=props)
def accept_message_request(self, channel_id: Snowflake) -> Response[channel.DMChannel]:
payload = {
'consent_status': 2,
}
return self.request(Route('PUT', '/channels/{channel_id}/recipients/@me', channel_id=channel_id), json=payload)
def decline_message_request(self, channel_id: Snowflake) -> Response[channel.DMChannel]:
return self.request(Route('DELETE', '/channels/{channel_id}/recipients/@me', channel_id=channel_id))
def mark_message_request(self, channel_id: Snowflake) -> Response[channel.DMChannel]:
payload = {
'consent_status': 1,
}
return self.request(Route('PUT', '/channels/{channel_id}/recipients/@me', channel_id=channel_id), json=payload)
def reset_message_request(self, channel_id: Snowflake) -> Response[channel.DMChannel]:
payload = {
'consent_status': 0,
}
return self.request(Route('PUT', '/channels/{channel_id}/recipients/@me', channel_id=channel_id), json=payload)
# Message management # Message management
def send_message( def send_message(

18
discord/state.py

@ -668,14 +668,11 @@ class ConnectionState:
def private_channels(self) -> List[PrivateChannel]: def private_channels(self) -> List[PrivateChannel]:
return list(self._private_channels.values()) return list(self._private_channels.values())
async def access_private_channel(self, channel_id: int) -> None: async def call_connect(self, channel_id: int) -> None:
if (ws := self.ws) is None: if self.ws is None:
return return
try: await self.ws.call_connect(channel_id)
await ws.access_dm(channel_id)
except Exception as exc:
_log.warning('Sending ACCESS_DM failed for channel %s, (%s).', channel_id, exc)
def _get_private_channel(self, channel_id: Optional[int]) -> Optional[PrivateChannel]: def _get_private_channel(self, channel_id: Optional[int]) -> Optional[PrivateChannel]:
# The keys of self._private_channels are ints # The keys of self._private_channels are ints
@ -879,10 +876,10 @@ class ConnectionState:
self._relationships[r_id] = Relationship(state=self, data=relationship) self._relationships[r_id] = Relationship(state=self, data=relationship)
# Private channel parsing # Private channel parsing
for pm in data.get('private_channels', []): for pm in data.get('private_channels', []) + extra_data.get('lazy_private_channels', []):
factory, _ = _private_channel_factory(pm['type']) factory, _ = _private_channel_factory(pm['type'])
if 'recipients' not in pm: if 'recipients' not in pm:
pm['recipients'] = [temp_users[int(u_id)] for u_id in pm.pop('recipient_ids')] # type: ignore pm['recipients'] = [temp_users[int(u_id)] for u_id in pm.pop('recipient_ids')]
self._add_private_channel(factory(me=user, data=pm, state=self)) # type: ignore self._add_private_channel(factory(me=user, data=pm, state=self)) # type: ignore
# Extras # Extras
@ -1182,12 +1179,11 @@ class ConnectionState:
def parse_channel_update(self, data: gw.ChannelUpdateEvent) -> None: def parse_channel_update(self, data: gw.ChannelUpdateEvent) -> None:
channel_type = try_enum(ChannelType, data.get('type')) channel_type = try_enum(ChannelType, data.get('type'))
channel_id = int(data['id']) channel_id = int(data['id'])
if channel_type is ChannelType.group: if channel_type in (ChannelType.private, ChannelType.group):
channel = self._get_private_channel(channel_id) channel = self._get_private_channel(channel_id)
if channel is not None: if channel is not None:
old_channel = copy.copy(channel) old_channel = copy.copy(channel)
# The channel is a GroupChannel channel._update(data)
channel._update_group(data) # type: ignore
self.dispatch('private_channel_update', old_channel, channel) self.dispatch('private_channel_update', old_channel, channel)
return return
else: else:

4
discord/types/channel.py

@ -130,12 +130,16 @@ class DMChannel(_BaseChannel):
type: Literal[1] type: Literal[1]
last_message_id: Optional[Snowflake] last_message_id: Optional[Snowflake]
recipients: List[PartialUser] recipients: List[PartialUser]
is_message_request: NotRequired[bool]
is_message_request_timestamp: NotRequired[str]
is_spam: NotRequired[bool]
class GroupDMChannel(_BaseChannel): class GroupDMChannel(_BaseChannel):
type: Literal[3] type: Literal[3]
icon: Optional[str] icon: Optional[str]
owner_id: Snowflake owner_id: Snowflake
recipients: List[PartialUser]
Channel = Union[GuildChannel, DMChannel, GroupDMChannel] Channel = Union[GuildChannel, DMChannel, GroupDMChannel]

Loading…
Cancel
Save