Browse Source

Fix group dm nickname implementation, add dm migration

pull/10109/head
dolfies 4 months ago
parent
commit
d784e6dcab
  1. 80
      discord/channel.py
  2. 8
      discord/http.py
  3. 2
      discord/state.py
  4. 11
      discord/types/channel.py
  5. 1
      discord/types/gateway.py

80
discord/channel.py

@ -115,6 +115,7 @@ if TYPE_CHECKING:
DMChannel as DMChannelPayload, DMChannel as DMChannelPayload,
CategoryChannel as CategoryChannelPayload, CategoryChannel as CategoryChannelPayload,
GroupDMChannel as GroupChannelPayload, GroupDMChannel as GroupChannelPayload,
GroupDMNickname as GroupDMNicknamePayload,
ForumChannel as ForumChannelPayload, ForumChannel as ForumChannelPayload,
MediaChannel as MediaChannelPayload, MediaChannel as MediaChannelPayload,
ForumTag as ForumTagPayload, ForumTag as ForumTagPayload,
@ -3815,6 +3816,48 @@ class DMChannel(discord.abc.Messageable, discord.abc.Connectable, discord.abc.Pr
""" """
return Permissions._dm_permissions() return Permissions._dm_permissions()
async def add_recipients(self, *recipients: Snowflake) -> GroupChannel:
r"""|coro|
Adds recipients to this DM. This spawns a new group with the existing DM
recipient and the new recipients.
A group can only have a maximum of 10 members.
Attempting to add more ends up in an exception. To
add a recipient to the group, you must have a relationship
with the user of type :attr:`RelationshipType.friend`.
.. versionadded:: 2.1
Parameters
-----------
\*recipients: :class:`~discord.abc.Snowflake`
An argument list of users to add to this group.
Raises
-------
TypeError
No recipients were provided.
Forbidden
You do not have permissions to add a recipient to this group.
HTTPException
Adding a recipient to this group failed.
Returns
--------
:class:`GroupChannel`
The newly created group channel. Due to a Discord limitation,
this will not contain complete recipient data.
"""
if len(recipients) < 1:
raise TypeError('add_recipients() missing 1 required positional argument')
state = self._state
data = await state.http.convert_dm(self.id, recipients[0].id)
channel = GroupChannel(state=state, data=data, me=self.me)
await channel.add_recipients(*[r for r in recipients[1:]])
return channel
def get_partial_message(self, message_id: int, /) -> PartialMessage: def get_partial_message(self, message_id: int, /) -> PartialMessage:
"""Creates a :class:`PartialMessage` from the message ID. """Creates a :class:`PartialMessage` from the message ID.
@ -3899,7 +3942,6 @@ class DMChannel(discord.abc.Messageable, discord.abc.Connectable, discord.abc.Pr
:class:`~discord.VoiceProtocol` :class:`~discord.VoiceProtocol`
A voice client that is fully connected to the voice server. A voice client that is fully connected to the voice server.
""" """
await self._get_channel()
ret = await super().connect(timeout=timeout, reconnect=reconnect, cls=cls) ret = await super().connect(timeout=timeout, reconnect=reconnect, cls=cls)
if ring: if ring:
@ -4001,6 +4043,13 @@ class GroupChannel(discord.abc.Messageable, discord.abc.Connectable, discord.abc
A mapping of users to their respective nicknames in the group channel. A mapping of users to their respective nicknames in the group channel.
.. versionadded:: 2.0 .. versionadded:: 2.0
origin_channel_id: Optional[:class:`int`]
The ID of the DM this group channel originated from, if any.
This can only be accurately received in :func:`on_private_channel_create`
due to a Discord limitation.
.. versionadded:: 2.1
""" """
__slots__ = ( __slots__ = (
@ -4012,6 +4061,7 @@ class GroupChannel(discord.abc.Messageable, discord.abc.Connectable, discord.abc
'managed', 'managed',
'application_id', 'application_id',
'nicks', 'nicks',
'origin_channel_id',
'_icon', '_icon',
'name', 'name',
'me', 'me',
@ -4033,7 +4083,17 @@ class GroupChannel(discord.abc.Messageable, discord.abc.Connectable, discord.abc
self.last_pin_timestamp: Optional[datetime.datetime] = utils.parse_time(data.get('last_pin_timestamp')) self.last_pin_timestamp: Optional[datetime.datetime] = utils.parse_time(data.get('last_pin_timestamp'))
self.managed: bool = data.get('managed', False) self.managed: bool = data.get('managed', False)
self.application_id: Optional[int] = utils._get_as_snowflake(data, 'application_id') self.application_id: Optional[int] = utils._get_as_snowflake(data, 'application_id')
self.nicks: Dict[User, str] = {utils.get(self.recipients, id=int(k)): v for k, v in data.get('nicks', {}).items()} # type: ignore self.nicks: Dict[User, str] = self._unroll_nicks(data.get('nicks', []))
self.origin_channel_id: Optional[int] = utils._get_as_snowflake(data, 'origin_channel_id')
def _unroll_nicks(self, data: List[GroupDMNicknamePayload]) -> Dict[User, str]:
ret = {}
for entry in data:
user_id = int(entry['id'])
user = utils.get(self.recipients, id=user_id)
if user:
ret[user] = entry['nick']
return ret
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'
@ -4110,6 +4170,17 @@ class GroupChannel(discord.abc.Messageable, discord.abc.Connectable, discord.abc
return None return None
return Asset._from_icon(self._state, self.id, self._icon, path='channel') return Asset._from_icon(self._state, self.id, self._icon, path='channel')
@property
def origin_channel(self) -> Optional[DMChannel]:
"""Optional[:class:`DMChannel`]: The DM this group channel originated from, if any.
This can only be accurately received in :func:`on_private_channel_create`
due to a Discord limitation.
.. versionadded:: 2.1
"""
return self._state._get_private_channel(self.origin_channel_id) if self.origin_channel_id else None # type: ignore
@property @property
def created_at(self) -> datetime.datetime: def created_at(self) -> datetime.datetime:
""":class:`datetime.datetime`: Returns the channel's creation time in UTC.""" """:class:`datetime.datetime`: Returns the channel's creation time in UTC."""
@ -4295,7 +4366,6 @@ class GroupChannel(discord.abc.Messageable, discord.abc.Connectable, discord.abc
Adding a recipient to this group failed. Adding a recipient to this group failed.
""" """
nicknames = {k.id: v for k, v in nicks.items()} if nicks else {} nicknames = {k.id: v for k, v in nicks.items()} if nicks else {}
await self._get_channel()
req = self._state.http.add_group_recipient req = self._state.http.add_group_recipient
for recipient in recipients: for recipient in recipients:
await req(self.id, recipient.id, getattr(recipient, 'nick', (nicknames.get(recipient.id) if nicks else None))) await req(self.id, recipient.id, getattr(recipient, 'nick', (nicknames.get(recipient.id) if nicks else None)))
@ -4317,7 +4387,6 @@ class GroupChannel(discord.abc.Messageable, discord.abc.Connectable, discord.abc
HTTPException HTTPException
Removing a recipient from this group failed. Removing a recipient from this group failed.
""" """
await self._get_channel()
req = self._state.http.remove_group_recipient req = self._state.http.remove_group_recipient
for recipient in recipients: for recipient in recipients:
await req(self.id, recipient.id) await req(self.id, recipient.id)
@ -4354,8 +4423,6 @@ class GroupChannel(discord.abc.Messageable, discord.abc.Connectable, discord.abc
HTTPException HTTPException
Editing the group failed. Editing the group failed.
""" """
await self._get_channel()
payload = {} payload = {}
if name is not MISSING: if name is not MISSING:
payload['name'] = name payload['name'] = name
@ -4471,7 +4538,6 @@ class GroupChannel(discord.abc.Messageable, discord.abc.Connectable, discord.abc
cls: Callable[[Client, discord.abc.VocalChannel], T] = VoiceClient, cls: Callable[[Client, discord.abc.VocalChannel], T] = VoiceClient,
ring: bool = True, ring: bool = True,
) -> T: ) -> T:
await self._get_channel()
ret = await super().connect(timeout=timeout, reconnect=reconnect, cls=cls) ret = await super().connect(timeout=timeout, reconnect=reconnect, cls=cls)
if ring: if ring:

8
discord/http.py

@ -1137,6 +1137,14 @@ class HTTPClient:
context_properties=props, context_properties=props,
) )
def convert_dm(self, channel_id: Snowflake, user_id: Snowflake) -> Response[channel.GroupDMChannel]:
props = ContextProperties.from_add_friends_to_dm()
return self.request(
Route('PUT', '/channels/{channel_id}/recipients/{user_id}', channel_id=channel_id, user_id=user_id),
context_properties=props,
)
def remove_group_recipient(self, channel_id: Snowflake, user_id: Snowflake) -> Response[None]: def remove_group_recipient(self, channel_id: Snowflake, user_id: Snowflake) -> Response[None]:
return self.request( return self.request(
Route('DELETE', '/channels/{channel_id}/recipients/{user_id}', channel_id=channel_id, user_id=user_id) Route('DELETE', '/channels/{channel_id}/recipients/{user_id}', channel_id=channel_id, user_id=user_id)

2
discord/state.py

@ -2296,6 +2296,8 @@ class ConnectionState:
user = self.store_user(data['user']) user = self.store_user(data['user'])
channel.recipients.append(user) # type: ignore channel.recipients.append(user) # type: ignore
if 'nick' in data:
channel.nicks[user] = data['nick']
self.dispatch('group_join', channel, user) self.dispatch('group_join', channel, user)
def parse_channel_recipient_remove(self, data: gw.ChannelRecipientEvent) -> None: def parse_channel_recipient_remove(self, data: gw.ChannelRecipientEvent) -> None:

11
discord/types/channel.py

@ -22,7 +22,7 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
""" """
from typing import List, Literal, Optional, TypedDict, Union from typing import Dict, List, Literal, Optional, TypedDict, Union
from typing_extensions import NotRequired from typing_extensions import NotRequired
from .user import PartialUser from .user import PartialUser
@ -191,12 +191,21 @@ class DMChannel(_BaseChannel):
is_spam: NotRequired[bool] is_spam: NotRequired[bool]
class GroupDMNickname(TypedDict):
id: Snowflake
nick: str
class GroupDMChannel(_BaseChannel): class GroupDMChannel(_BaseChannel):
type: Literal[3] type: Literal[3]
name: Optional[str] name: Optional[str]
icon: Optional[str] icon: Optional[str]
owner_id: Snowflake owner_id: Snowflake
application_id: NotRequired[Snowflake]
managed: NotRequired[bool]
nicks: NotRequired[List[GroupDMNickname]]
recipients: List[PartialUser] recipients: List[PartialUser]
origin_channel_id: NotRequired[Snowflake] # Only present in CHANNEL_CREATE
Channel = Union[GuildChannel, DMChannel, GroupDMChannel] Channel = Union[GuildChannel, DMChannel, GroupDMChannel]

1
discord/types/gateway.py

@ -253,6 +253,7 @@ ChannelCreateEvent = ChannelUpdateEvent = ChannelDeleteEvent = _ChannelEvent
class ChannelRecipientEvent(TypedDict): class ChannelRecipientEvent(TypedDict):
channel_id: Snowflake channel_id: Snowflake
user: PartialUser user: PartialUser
nick: str
class ChannelPinsUpdateEvent(TypedDict): class ChannelPinsUpdateEvent(TypedDict):

Loading…
Cancel
Save