aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRapptz <[email protected]>2021-08-10 09:24:49 -0400
committerRapptz <[email protected]>2021-08-10 09:24:49 -0400
commit1279510194441135070a294254365db3dbf03897 (patch)
treed12b5f2323e1e650ebc22ae7e9d161439e18ea08
parentFill in ConnectionState.user via HTTPClient.static_login (diff)
downloaddiscord.py-1279510194441135070a294254365db3dbf03897.tar.xz
discord.py-1279510194441135070a294254365db3dbf03897.zip
Add support for PartialMessageable instances
This allows library users to send messages to channels without fetching it first.
-rw-r--r--discord/abc.py4
-rw-r--r--discord/channel.py110
-rw-r--r--discord/client.py22
-rw-r--r--discord/message.py6
-rw-r--r--discord/state.py4
-rw-r--r--docs/api.rst9
6 files changed, 125 insertions, 30 deletions
diff --git a/discord/abc.py b/discord/abc.py
index 0c077dae..54f776c2 100644
--- a/discord/abc.py
+++ b/discord/abc.py
@@ -78,7 +78,7 @@ if TYPE_CHECKING:
from .channel import CategoryChannel
from .embeds import Embed
from .message import Message, MessageReference, PartialMessage
- from .channel import TextChannel, DMChannel, GroupChannel
+ from .channel import TextChannel, DMChannel, GroupChannel, PartialMessageable
from .threads import Thread
from .enums import InviteTarget
from .ui.view import View
@@ -88,7 +88,7 @@ if TYPE_CHECKING:
OverwriteType,
)
- PartialMessageableChannel = Union[TextChannel, Thread, DMChannel]
+ PartialMessageableChannel = Union[TextChannel, Thread, DMChannel, PartialMessageable]
MessageableChannel = Union[PartialMessageableChannel, GroupChannel]
SnowflakeTime = Union["Snowflake", datetime]
diff --git a/discord/channel.py b/discord/channel.py
index 21b87c1b..6d0fe081 100644
--- a/discord/channel.py
+++ b/discord/channel.py
@@ -26,13 +26,28 @@ from __future__ import annotations
import time
import asyncio
-from typing import Any, Callable, Dict, Iterable, List, Mapping, Optional, TYPE_CHECKING, Tuple, Type, TypeVar, Union, overload
+from typing import (
+ Any,
+ Callable,
+ Dict,
+ Iterable,
+ List,
+ Mapping,
+ Optional,
+ TYPE_CHECKING,
+ Tuple,
+ Type,
+ TypeVar,
+ Union,
+ overload,
+)
import datetime
import discord.abc
from .permissions import PermissionOverwrite, Permissions
from .enums import ChannelType, StagePrivacyLevel, try_enum, VoiceRegion, VideoQualityMode
from .mixins import Hashable
+from .object import Object
from . import utils
from .utils import MISSING
from .asset import Asset
@@ -49,6 +64,7 @@ __all__ = (
'CategoryChannel',
'StoreChannel',
'GroupChannel',
+ 'PartialMessageable',
)
if TYPE_CHECKING:
@@ -648,7 +664,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
message: Optional[Snowflake] = None,
auto_archive_duration: ThreadArchiveDuration = 1440,
type: Optional[ChannelType] = None,
- reason: Optional[str] = None
+ reason: Optional[str] = None,
) -> Thread:
"""|coro|
@@ -1147,7 +1163,9 @@ class StageChannel(VocalGuildChannel):
"""
return utils.get(self.guild.stage_instances, channel_id=self.id)
- async def create_instance(self, *, topic: str, privacy_level: StagePrivacyLevel = MISSING, reason: Optional[str] = None) -> StageInstance:
+ async def create_instance(
+ self, *, topic: str, privacy_level: StagePrivacyLevel = MISSING, reason: Optional[str] = None
+ ) -> StageInstance:
"""|coro|
Create a stage instance.
@@ -1651,9 +1669,6 @@ class StoreChannel(discord.abc.GuildChannel, Hashable):
await self._edit(options, reason=reason)
-DMC = TypeVar('DMC', bound='DMChannel')
-
-
class DMChannel(discord.abc.Messageable, Hashable):
"""Represents a Discord direct message channel.
@@ -1677,10 +1692,8 @@ class DMChannel(discord.abc.Messageable, Hashable):
Attributes
----------
- recipient: Optional[:class:`User`]
+ recipient: :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`
@@ -1691,7 +1704,7 @@ class DMChannel(discord.abc.Messageable, Hashable):
def __init__(self, *, me: ClientUser, state: ConnectionState, data: DMChannelPayload):
self._state: ConnectionState = state
- self.recipient: Optional[User] = state.store_user(data['recipients'][0])
+ self.recipient: User = state.store_user(data['recipients'][0])
self.me: ClientUser = me
self.id: int = int(data['id'])
@@ -1699,22 +1712,11 @@ class DMChannel(discord.abc.Messageable, Hashable):
return self
def __str__(self) -> str:
- if self.recipient:
- return f'Direct Message with {self.recipient}'
- return 'Direct Message with Unknown User'
+ return f'Direct Message with {self.recipient}'
def __repr__(self) -> str:
return f'<DMChannel id={self.id} recipient={self.recipient!r}>'
- @classmethod
- def _from_message(cls: Type[DMC], state: ConnectionState, channel_id: int) -> DMC:
- self: DMC = cls.__new__(cls)
- self._state = state
- self.id = channel_id
- self.recipient = None
- self.me = state.user # type: ignore
- return self
-
@property
def type(self) -> ChannelType:
""":class:`ChannelType`: The channel's Discord type."""
@@ -1922,6 +1924,69 @@ class GroupChannel(discord.abc.Messageable, Hashable):
await self._state.http.leave_group(self.id)
+class PartialMessageable(discord.abc.Messageable, Hashable):
+ """Represents a partial messageable to aid with working messageable channels when
+ only a channel ID are present.
+
+ The only way to construct this class is through :meth:`Client.get_partial_messageable`.
+
+ Note that this class is trimmed down and has no rich attributes.
+
+ .. versionadded:: 2.0
+
+ .. container:: operations
+
+ .. describe:: x == y
+
+ Checks if two partial messageables are equal.
+
+ .. describe:: x != y
+
+ Checks if two partial messageables are not equal.
+
+ .. describe:: hash(x)
+
+ Returns the partial messageable's hash.
+
+ Attributes
+ -----------
+ id: :class:`int`
+ The channel ID associated with this partial messageable.
+ type: Optional[:class:`ChannelType`]
+ The channel type associated with this partial messageable, if given.
+ """
+
+ def __init__(self, state: ConnectionState, id: int, type: Optional[ChannelType] = None):
+ self._state: ConnectionState = state
+ self._channel: Object = Object(id=id)
+ self.id: int = id
+ self.type: Optional[ChannelType] = type
+
+ async def _get_channel(self) -> Object:
+ return self._channel
+
+ def get_partial_message(self, message_id: int, /) -> PartialMessage:
+ """Creates a :class:`PartialMessage` from the message ID.
+
+ This is useful if you want to work with a message and only have its ID without
+ doing an unnecessary API call.
+
+ Parameters
+ ------------
+ message_id: :class:`int`
+ The message ID to create a partial message for.
+
+ Returns
+ ---------
+ :class:`PartialMessage`
+ The partial message.
+ """
+
+ from .message import PartialMessage
+
+ return PartialMessage(channel=self, id=message_id)
+
+
def _guild_channel_factory(channel_type: int):
value = try_enum(ChannelType, channel_type)
if value is ChannelType.text:
@@ -1949,6 +2014,7 @@ def _channel_factory(channel_type: int):
else:
return cls, value
+
def _threaded_channel_factory(channel_type: int):
cls, value = _channel_factory(channel_type)
if value in (ChannelType.private_thread, ChannelType.public_thread, ChannelType.news_thread):
diff --git a/discord/client.py b/discord/client.py
index 81c86d7f..cdfc6bdb 100644
--- a/discord/client.py
+++ b/discord/client.py
@@ -39,7 +39,7 @@ from .template import Template
from .widget import Widget
from .guild import Guild
from .emoji import Emoji
-from .channel import _threaded_channel_factory
+from .channel import _threaded_channel_factory, PartialMessageable
from .enums import ChannelType
from .mentions import AllowedMentions
from .errors import *
@@ -729,6 +729,26 @@ class Client:
"""
return self._connection.get_channel(id)
+ def get_partial_messageable(self, id: int, *, type: Optional[ChannelType] = None) -> PartialMessageable:
+ """Returns a partial messageable with the given channel ID.
+
+ This is useful if you have a channel_id but don't want to do an API call
+ to send messages to it.
+
+ Parameters
+ -----------
+ id: :class:`int`
+ The channel ID to create a partial messageable for.
+ type: Optional[:class:`ChannelType`]
+ The underlying channel type for the partial messageable.
+
+ Returns
+ --------
+ :class:`PartialMessageable`
+ The partial messageable
+ """
+ return PartialMessageable(state=self._connection, id=id, type=type)
+
def get_stage_instance(self, id) -> Optional[StageInstance]:
"""Returns a stage instance with the given stage channel ID.
diff --git a/discord/message.py b/discord/message.py
index 270ac9e9..d752c8c7 100644
--- a/discord/message.py
+++ b/discord/message.py
@@ -70,7 +70,7 @@ if TYPE_CHECKING:
from .abc import GuildChannel, PartialMessageableChannel, MessageableChannel
from .components import Component
from .state import ConnectionState
- from .channel import TextChannel, GroupChannel, DMChannel
+ from .channel import TextChannel, GroupChannel, DMChannel, PartialMessageable
from .mentions import AllowedMentions
from .user import User
from .role import Role
@@ -520,7 +520,7 @@ class Message(Hashable):
This is not stored long term within Discord's servers and is only used ephemerally.
embeds: List[:class:`Embed`]
A list of embeds the message has.
- channel: Union[:class:`TextChannel`, :class:`Thread`, :class:`DMChannel`, :class:`GroupChannel`]
+ channel: Union[:class:`TextChannel`, :class:`Thread`, :class:`DMChannel`, :class:`GroupChannel`, :class:`PartialMessageable`]
The :class:`TextChannel` or :class:`Thread` that the message was sent from.
Could be a :class:`DMChannel` or :class:`GroupChannel` if it's a private message.
reference: Optional[:class:`~discord.MessageReference`]
@@ -646,7 +646,7 @@ class Message(Hashable):
self,
*,
state: ConnectionState,
- channel: Union[TextChannel, Thread, DMChannel, GroupChannel],
+ channel: Union[TextChannel, Thread, DMChannel, GroupChannel, PartialMessageable],
data: MessagePayload,
):
self._state: ConnectionState = state
diff --git a/discord/state.py b/discord/state.py
index ca33df3d..4a09a9cc 100644
--- a/discord/state.py
+++ b/discord/state.py
@@ -405,12 +405,12 @@ class ConnectionState:
try:
guild = self._get_guild(int(data['guild_id']))
except KeyError:
- channel = DMChannel._from_message(self, channel_id)
+ channel = PartialMessageable(state=self, id=channel_id, type=ChannelType.private)
guild = None
else:
channel = guild and guild._resolve_channel(channel_id)
- return channel or Object(id=channel_id), guild
+ return channel or PartialMessageable(state=self, id=channel_id), guild
async def chunker(self, guild_id, query='', limit=0, presences=False, *, nonce=None):
ws = self._get_websocket(guild_id) # This is ignored upstream
diff --git a/docs/api.rst b/docs/api.rst
index c8a09d88..82978e7c 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -3574,6 +3574,15 @@ RoleTags
.. autoclass:: RoleTags()
:members:
+PartialMessageable
+~~~~~~~~~~~~~~~~~~~~
+
+.. attributetable:: PartialMessageable
+
+.. autoclass:: PartialMessageable()
+ :members:
+ :inherited-members:
+
TextChannel
~~~~~~~~~~~~