From e59658418c262568836ce053324d6fb19678357f Mon Sep 17 00:00:00 2001 From: Soheab_ <33902984+Soheab@users.noreply.github.com> Date: Sun, 22 Jun 2025 00:20:35 +0200 Subject: [PATCH] Make it paginate instead using the after param --- discord/abc.py | 110 ++++++++++++++++++++++++++++++++++++------------ discord/http.py | 5 ++- 2 files changed, 88 insertions(+), 27 deletions(-) diff --git a/discord/abc.py b/discord/abc.py index 39bb0d889..1486228c3 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -1710,15 +1710,20 @@ class Messageable: data = await self._state.http.get_message(channel.id, id) return self._state.create_message(channel=channel, data=data) - async def pins(self, *, before: Optional[datetime] = None, limit: Optional[int] = None) -> List[Message]: - """|coro| - - Retrieves a maximum of 50 pinned messages from the destination. + async def pins( + self, + *, + limit: Optional[int] = None, + before: SnowflakeTime = MISSING, + after: SnowflakeTime = MISSING, + ) -> AsyncIterator[Message]: + """Retrieves an :term:`asynchronous iterator` of the pinned messages in the channel. - Requires the :attr:`~discord.Permissions.view_channel` permission. + You must have :attr:`~discord.Permissions.view_channel` and + :attr:`~discord.Permissions.read_message_history` to get these. - No pins will be returned if the user is missing the - :attr:`~discord.Permissions.read_message_history` permission. + .. versionchanged:: 2.6 + Due to a change in Discord's API, this now returns a paginated iterator instead of a list. .. note:: @@ -1729,15 +1734,22 @@ class Messageable: Parameters ----------- before: Optional[:class:`datetime.datetime`] - Retrieve pinned messages before this time. + 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 - limit: Optional[int] - The maximum number of pinned messages to retrieve. Defaults to 50. + after: Optional[:class:`datetime.datetime`] + Retrieve pinned messages after 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. - This must be a number between 2 and 50. + .. versionadded:: 2.6 + 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 @@ -1755,23 +1767,69 @@ class Messageable: List[:class:`~discord.Message`] The messages that are currently pinned. """ - if before is not None: - if not isinstance(before, datetime): - raise TypeError(f'before must be a datetime object, not {before.__class__!r}') - if before.tzinfo is None: - raise TypeError( - 'before must be an aware datetime. Consider using discord.utils.utcnow() or datetime.datetime.now().astimezone() for local time.' - ) + if before is not MISSING and after is not MISSING: + raise TypeError('pins pagination does not support both before and after') - state = self._state + endpoint = self._state.http.pins_from channel = await self._get_channel() - data = await state.http.pins_from(channel.id, before=before.isoformat() if before else None, limit=limit) - ret: List[Message] = [] - for m in data["items"]: - message = state.create_message(channel=channel, data=m["message"]) - message._pinned_at = utils.parse_time(m.get("pinned_at")) - ret.append(message) - return ret + state = self._state + + async def _before_strategy(retrieve: int, before: Optional[SnowflakeTime], limit: Optional[int]): + before_time = ( + before.isoformat() + if isinstance(before, datetime) + else utils.snowflake_time(before.id).isoformat() + if before + else None + ) + data = await endpoint(channel.id, limit=retrieve, before=before_time) + + if data and data['items']: + if limit is not None: + limit -= len(data) + + before = utils.parse_time(data['items'][-1]['pinned_at']) + + return data, before, limit + + async def _after_strategy(retrieve: int, after: Optional[SnowflakeTime], limit: Optional[int]): + after_time = ( + after.isoformat() + if isinstance(after, datetime) + else utils.snowflake_time(after.id).isoformat() + if after + else None + ) + data = await endpoint(channel.id, limit=retrieve, after=after_time) + + if data and data['items']: + if limit is not None: + limit -= len(data) + + after = utils.parse_time(data['items'][-1]['pinned_at']) + + return data, after, limit + + if before: + strategy, time = _before_strategy, before + else: + strategy, time = _after_strategy, after + + while True: + retrieve = 50 if limit is None else min(limit, 50) + if retrieve < 1: + return + + data, time, limit = await strategy(retrieve, time, limit) + + # Terminate loop on next iteration; there's no data left after this + if len(data) < 50: + limit = 0 + + for m in data["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, diff --git a/discord/http.py b/discord/http.py index a907bbcd8..e6ac43113 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1059,12 +1059,15 @@ class HTTPClient: def pins_from( self, channel_id: Snowflake, - before: Optional[str] = None, limit: Optional[int] = None, + before: Optional[str] = None, + after: Optional[str] = None, ) -> Response[message.ChannelPins]: params = {} if before is not None: params['before'] = before + if after is not None: + params['after'] = after if limit is not None: params['limit'] = limit