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,
CategoryChannel as CategoryChannelPayload,
GroupDMChannel as GroupChannelPayload,
GroupDMNickname as GroupDMNicknamePayload,
ForumChannel as ForumChannelPayload,
MediaChannel as MediaChannelPayload,
ForumTag as ForumTagPayload,
@ -3815,6 +3816,48 @@ class DMChannel(discord.abc.Messageable, discord.abc.Connectable, discord.abc.Pr
"""
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:
"""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`
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)
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.
.. 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__ = (
@ -4012,6 +4061,7 @@ class GroupChannel(discord.abc.Messageable, discord.abc.Connectable, discord.abc
'managed',
'application_id',
'nicks',
'origin_channel_id',
'_icon',
'name',
'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.managed: bool = data.get('managed', False)
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]:
return self.me.id, 'self_id'
@ -4110,6 +4170,17 @@ class GroupChannel(discord.abc.Messageable, discord.abc.Connectable, discord.abc
return None
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
def created_at(self) -> datetime.datetime:
""":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.
"""
nicknames = {k.id: v for k, v in nicks.items()} if nicks else {}
await self._get_channel()
req = self._state.http.add_group_recipient
for recipient in recipients:
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
Removing a recipient from this group failed.
"""
await self._get_channel()
req = self._state.http.remove_group_recipient
for recipient in recipients:
await req(self.id, recipient.id)
@ -4354,8 +4423,6 @@ class GroupChannel(discord.abc.Messageable, discord.abc.Connectable, discord.abc
HTTPException
Editing the group failed.
"""
await self._get_channel()
payload = {}
if name is not MISSING:
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,
ring: bool = True,
) -> T:
await self._get_channel()
ret = await super().connect(timeout=timeout, reconnect=reconnect, cls=cls)
if ring:

8
discord/http.py

@ -1137,6 +1137,14 @@ class HTTPClient:
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]:
return self.request(
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'])
channel.recipients.append(user) # type: ignore
if 'nick' in data:
channel.nicks[user] = data['nick']
self.dispatch('group_join', channel, user)
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.
"""
from typing import List, Literal, Optional, TypedDict, Union
from typing import Dict, List, Literal, Optional, TypedDict, Union
from typing_extensions import NotRequired
from .user import PartialUser
@ -191,12 +191,21 @@ class DMChannel(_BaseChannel):
is_spam: NotRequired[bool]
class GroupDMNickname(TypedDict):
id: Snowflake
nick: str
class GroupDMChannel(_BaseChannel):
type: Literal[3]
name: Optional[str]
icon: Optional[str]
owner_id: Snowflake
application_id: NotRequired[Snowflake]
managed: NotRequired[bool]
nicks: NotRequired[List[GroupDMNickname]]
recipients: List[PartialUser]
origin_channel_id: NotRequired[Snowflake] # Only present in CHANNEL_CREATE
Channel = Union[GuildChannel, DMChannel, GroupDMChannel]

1
discord/types/gateway.py

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

Loading…
Cancel
Save