Skip to content

Update to support new pin endpoints #10205

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 72 additions & 7 deletions discord/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
T = TypeVar('T', bound=VoiceProtocol)

if TYPE_CHECKING:
from typing_extensions import Self

Check warning on line 76 in discord/abc.py

View workflow job for this annotation

GitHub Actions / check 3.x

Import "typing_extensions" could not be resolved from source (reportMissingModuleSource)

from .client import Client
from .user import ClientUser
Expand Down Expand Up @@ -1710,17 +1710,48 @@
data = await self._state.http.get_message(channel.id, id)
return self._state.create_message(channel=channel, data=data)

async def pins(self) -> List[Message]:
"""|coro|
async def pins(
self,
*,
limit: Optional[int] = None,
before: SnowflakeTime = MISSING,
oldest_first: bool = False,
) -> AsyncIterator[Message]:
"""Retrieves an :term:`asynchronous iterator` of the pinned messages in the channel.

Retrieves all messages that are currently pinned in the channel.
You must have the following permissions to get these:
:attr:`~discord.Permissions.view_channel`, :attr:`~discord.Permissions.read_message_history`.

.. versionchanged:: 2.6
Due to a change in Discord's API, this now returns a paginated iterator instead of a list.

.. note::

Due to a limitation with the Discord API, the :class:`.Message`
objects returned by this method do not contain complete
:attr:`.Message.reactions` data.

Parameters
-----------
limit: Optional[int]
The number of pinned messages to retrieve. If ``None``, it retrieves
every pinned message in the channel. Note, however, that this would
make it a slow operation.
Defaults to ``50``.

.. versionadded:: 2.6
before: Union[:class:`datetime.datetime`, :class:`.abc.Snowflake`]
Retrieve pinned messages before this time or snowflake.
If a datetime is provided, it is recommended to use a UTC aware datetime.
If the datetime is naive, it is assumed to be local time.

.. versionadded:: 2.6
oldest_first: :class:`bool`
If set to ``True``, return messages in oldest->newest order.
Defaults to ``False``.

.. versionadded:: 2.6

Raises
-------
~discord.Forbidden
Expand All @@ -1730,14 +1761,48 @@

Returns
--------
List[:class:`~discord.Message`]
The messages that are currently pinned.
:class:`~discord.Message`
The pinned message with :attr:`.Message.pinned_at` set.
"""

channel = await self._get_channel()
state = self._state
data = await state.http.pins_from(channel.id)
return [state.create_message(channel=channel, data=m) for m in data]
max_limit: int = 50

time: Optional[str] = (
(before if isinstance(before, datetime) else utils.snowflake_time(before.id)).isoformat()
if before is not None
else None
)
while True:
retrieve = max_limit if limit is None else min(limit, max_limit)
if retrieve < 1:
return

data = await self._state.http.pins_from(
channel_id=channel.id,
limit=retrieve,
before=time,
)

items = data and data["items"]
if items:
if limit is not None:
limit -= len(items)

time = items[-1]['pinned_at']

# Terminate loop on next iteration; there's no data left after this
if len(items) < max_limit or not data['has_more']:
limit = 0

if oldest_first:
reversed(items)

for m in items:
message = state.create_message(channel=channel, data=m['message'])
message._pinned_at = utils.parse_time(m['pinned_at'])
yield message

async def history(
self,
Expand Down
19 changes: 15 additions & 4 deletions discord/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -1041,7 +1041,7 @@ def publish_message(self, channel_id: Snowflake, message_id: Snowflake) -> Respo
def pin_message(self, channel_id: Snowflake, message_id: Snowflake, reason: Optional[str] = None) -> Response[None]:
r = Route(
'PUT',
'/channels/{channel_id}/pins/{message_id}',
'/channels/{channel_id}/messages/pins/{message_id}',
channel_id=channel_id,
message_id=message_id,
)
Expand All @@ -1050,14 +1050,25 @@ def pin_message(self, channel_id: Snowflake, message_id: Snowflake, reason: Opti
def unpin_message(self, channel_id: Snowflake, message_id: Snowflake, reason: Optional[str] = None) -> Response[None]:
r = Route(
'DELETE',
'/channels/{channel_id}/pins/{message_id}',
'/channels/{channel_id}/messages/pins/{message_id}',
channel_id=channel_id,
message_id=message_id,
)
return self.request(r, reason=reason)

def pins_from(self, channel_id: Snowflake) -> Response[List[message.Message]]:
return self.request(Route('GET', '/channels/{channel_id}/pins', channel_id=channel_id))
def pins_from(
self,
channel_id: Snowflake,
limit: Optional[int] = None,
before: Optional[str] = None,
) -> Response[message.ChannelPins]:
params = {}
if before is not None:
params['before'] = before
if limit is not None:
params['limit'] = limit

return self.request(Route('GET', '/channels/{channel_id}/messages/pins', channel_id=channel_id), params=params)

# Member management

Expand Down
15 changes: 15 additions & 0 deletions discord/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -2174,6 +2174,7 @@ class Message(PartialMessage, Hashable):
'call',
'purchase_notification',
'message_snapshots',
'_pinned_at',
)

if TYPE_CHECKING:
Expand Down Expand Up @@ -2213,6 +2214,8 @@ def __init__(
self.application_id: Optional[int] = utils._get_as_snowflake(data, 'application_id')
self.stickers: List[StickerItem] = [StickerItem(data=d, state=state) for d in data.get('sticker_items', [])]
self.message_snapshots: List[MessageSnapshot] = MessageSnapshot._from_value(state, data.get('message_snapshots'))
# Set by Messageable.pins
self._pinned_at: Optional[datetime.datetime] = None

self.poll: Optional[Poll] = None
try:
Expand Down Expand Up @@ -2633,6 +2636,18 @@ def thread(self) -> Optional[Thread]:
# Fall back to guild threads in case one was created after the message
return self._thread or self.guild.get_thread(self.id)

@property
def pinned_at(self) -> Optional[datetime.datetime]:
"""Optional[:class:`datetime.datetime`]: An aware UTC datetime object containing the time
when the message was pinned.

.. note::
This is only set for messages that are returned by :meth:`abc.Messageable.pins`.

.. versionadded:: 2.6
"""
return self._pinned_at

@property
@deprecated('interaction_metadata')
def interaction(self) -> Optional[MessageInteraction]:
Expand Down
10 changes: 10 additions & 0 deletions discord/types/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,3 +237,13 @@ class AllowedMentions(TypedDict):
roles: SnowflakeList
users: SnowflakeList
replied_user: bool


class MessagePin(TypedDict):
pinned_at: str
message: Message


class ChannelPins(TypedDict):
items: List[MessagePin]
has_more: bool
Loading