Skip to content

Commit 705eb2c

Browse files
authored
Update to support new pin endpoints
1 parent 50caa3c commit 705eb2c

File tree

4 files changed

+179
-17
lines changed

4 files changed

+179
-17
lines changed

discord/abc.py

Lines changed: 139 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@
3434
AsyncIterator,
3535
Callable,
3636
Dict,
37+
Generator,
3738
Iterable,
3839
List,
40+
Literal,
3941
Optional,
4042
TYPE_CHECKING,
4143
Protocol,
@@ -61,6 +63,7 @@
6163
from .sticker import GuildSticker, StickerItem
6264
from . import utils
6365
from .flags import InviteFlags
66+
import warnings
6467

6568
__all__ = (
6669
'Snowflake',
@@ -114,6 +117,11 @@
114117
MessageableChannel = Union[PartialMessageableChannel, GroupChannel]
115118
SnowflakeTime = Union["Snowflake", datetime]
116119

120+
class PinnedMessage(Message):
121+
pinned_at: datetime
122+
pinned: Literal[True]
123+
124+
117125
MISSING = utils.MISSING
118126

119127

@@ -125,6 +133,26 @@ def __repr__(self) -> str:
125133
_undefined: Any = _Undefined()
126134

127135

136+
class _PinsIterator:
137+
def __init__(self, iterator: AsyncIterator[PinnedMessage]) -> None:
138+
self.__iterator: AsyncIterator[PinnedMessage] = iterator
139+
140+
def __await__(self) -> Generator[Any, None, List[PinnedMessage]]:
141+
warnings.warn(
142+
"`await <channel>.pins()` is deprecated; use `async for message in <channel>.pins()` instead.",
143+
DeprecationWarning,
144+
stacklevel=2,
145+
)
146+
147+
async def gather() -> List[PinnedMessage]:
148+
return [msg async for msg in self.__iterator]
149+
150+
return gather().__await__()
151+
152+
def __aiter__(self) -> AsyncIterator[PinnedMessage]:
153+
return self.__iterator
154+
155+
128156
async def _single_delete_strategy(messages: Iterable[Message], *, reason: Optional[str] = None):
129157
for m in messages:
130158
try:
@@ -1754,34 +1782,132 @@ async def fetch_message(self, id: int, /) -> Message:
17541782
data = await self._state.http.get_message(channel.id, id)
17551783
return self._state.create_message(channel=channel, data=data)
17561784

1757-
async def pins(self) -> List[Message]:
1758-
"""|coro|
1785+
async def __pins(
1786+
self,
1787+
*,
1788+
limit: Optional[int] = 50,
1789+
before: Optional[SnowflakeTime] = None,
1790+
oldest_first: bool = False,
1791+
) -> AsyncIterator[PinnedMessage]:
1792+
channel = await self._get_channel()
1793+
state = self._state
1794+
max_limit: int = 50
1795+
1796+
time: Optional[str] = (
1797+
(before if isinstance(before, datetime) else utils.snowflake_time(before.id)).isoformat()
1798+
if before is not None
1799+
else None
1800+
)
1801+
1802+
while True:
1803+
retrieve = max_limit if limit is None else min(limit, max_limit)
1804+
if retrieve < 1:
1805+
break
1806+
1807+
data = await self._state.http.pins_from(
1808+
channel_id=channel.id,
1809+
limit=retrieve,
1810+
before=time,
1811+
)
1812+
1813+
items = data and data['items']
1814+
if items:
1815+
if limit is not None:
1816+
limit -= len(items)
1817+
1818+
time = items[-1]['pinned_at']
1819+
1820+
# Terminate loop on next iteration; there's no data left after this
1821+
if len(items) < max_limit or not data['has_more']:
1822+
limit = 0
1823+
1824+
if oldest_first:
1825+
items = reversed(items)
1826+
1827+
count = 0
1828+
for count, m in enumerate(items, start=1):
1829+
message: Message = state.create_message(channel=channel, data=m['message'])
1830+
message._pinned_at = utils.parse_time(m['pinned_at'])
1831+
yield message # pyright: ignore[reportReturnType]
1832+
1833+
if count < max_limit:
1834+
break
1835+
1836+
def pins(
1837+
self,
1838+
*,
1839+
limit: Optional[int] = 50,
1840+
before: Optional[SnowflakeTime] = None,
1841+
oldest_first: bool = False,
1842+
) -> _PinsIterator:
1843+
"""Retrieves an :term:`asynchronous iterator` of the pinned messages in the channel.
17591844
1760-
Retrieves all messages that are currently pinned in the channel.
1845+
You must have :attr:`~discord.Permissions.view_channel` and
1846+
:attr:`~discord.Permissions.read_message_history` in order to use this.
1847+
1848+
.. versionchanged:: 2.6
1849+
1850+
Due to a change in Discord's API, this now returns a paginated iterator instead of a list.
1851+
1852+
For backwards compatibility, you can still retrieve a list of pinned messages by
1853+
using ``await`` on the returned object. This is however deprecated.
17611854
17621855
.. note::
17631856
17641857
Due to a limitation with the Discord API, the :class:`.Message`
1765-
objects returned by this method do not contain complete
1858+
object returned by this method does not contain complete
17661859
:attr:`.Message.reactions` data.
17671860
1861+
Examples
1862+
---------
1863+
1864+
Usage ::
1865+
1866+
counter = 0
1867+
async for message in channel.pins(limit=250):
1868+
counter += 1
1869+
1870+
Flattening into a list: ::
1871+
1872+
messages = [message async for message in channel.pins(limit=50)]
1873+
# messages is now a list of Message...
1874+
1875+
All parameters are optional.
1876+
1877+
Parameters
1878+
-----------
1879+
limit: Optional[int]
1880+
The number of pinned messages to retrieve. If ``None``, it retrieves
1881+
every pinned message in the channel. Note, however, that this would
1882+
make it a slow operation.
1883+
Defaults to ``50``.
1884+
1885+
.. versionadded:: 2.6
1886+
before: Optional[Union[:class:`datetime.datetime`, :class:`.abc.Snowflake`]]
1887+
Retrieve pinned messages before this time or snowflake.
1888+
If a datetime is provided, it is recommended to use a UTC aware datetime.
1889+
If the datetime is naive, it is assumed to be local time.
1890+
1891+
.. versionadded:: 2.6
1892+
oldest_first: :class:`bool`
1893+
If set to ``True``, return messages in oldest pin->newest pin order.
1894+
Defaults to ``False``.
1895+
1896+
.. versionadded:: 2.6
1897+
17681898
Raises
17691899
-------
17701900
~discord.Forbidden
17711901
You do not have the permission to retrieve pinned messages.
17721902
~discord.HTTPException
17731903
Retrieving the pinned messages failed.
17741904
1775-
Returns
1776-
--------
1777-
List[:class:`~discord.Message`]
1778-
The messages that are currently pinned.
1905+
Yields
1906+
-------
1907+
:class:`~discord.Message`
1908+
The pinned message with :attr:`.Message.pinned_at` set.
17791909
"""
1780-
1781-
channel = await self._get_channel()
1782-
state = self._state
1783-
data = await state.http.pins_from(channel.id)
1784-
return [state.create_message(channel=channel, data=m) for m in data]
1910+
return _PinsIterator(self.__pins(limit=limit, before=before, oldest_first=oldest_first))
17851911

17861912
async def history(
17871913
self,

discord/http.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,7 +1047,7 @@ def publish_message(self, channel_id: Snowflake, message_id: Snowflake) -> Respo
10471047
def pin_message(self, channel_id: Snowflake, message_id: Snowflake, reason: Optional[str] = None) -> Response[None]:
10481048
r = Route(
10491049
'PUT',
1050-
'/channels/{channel_id}/pins/{message_id}',
1050+
'/channels/{channel_id}/messages/pins/{message_id}',
10511051
channel_id=channel_id,
10521052
message_id=message_id,
10531053
)
@@ -1056,14 +1056,25 @@ def pin_message(self, channel_id: Snowflake, message_id: Snowflake, reason: Opti
10561056
def unpin_message(self, channel_id: Snowflake, message_id: Snowflake, reason: Optional[str] = None) -> Response[None]:
10571057
r = Route(
10581058
'DELETE',
1059-
'/channels/{channel_id}/pins/{message_id}',
1059+
'/channels/{channel_id}/messages/pins/{message_id}',
10601060
channel_id=channel_id,
10611061
message_id=message_id,
10621062
)
10631063
return self.request(r, reason=reason)
10641064

1065-
def pins_from(self, channel_id: Snowflake) -> Response[List[message.Message]]:
1066-
return self.request(Route('GET', '/channels/{channel_id}/pins', channel_id=channel_id))
1065+
def pins_from(
1066+
self,
1067+
channel_id: Snowflake,
1068+
limit: Optional[int] = None,
1069+
before: Optional[str] = None,
1070+
) -> Response[message.ChannelPins]:
1071+
params = {}
1072+
if before is not None:
1073+
params['before'] = before
1074+
if limit is not None:
1075+
params['limit'] = limit
1076+
1077+
return self.request(Route('GET', '/channels/{channel_id}/messages/pins', channel_id=channel_id), params=params)
10671078

10681079
# Member management
10691080

discord/message.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2185,6 +2185,7 @@ class Message(PartialMessage, Hashable):
21852185
'call',
21862186
'purchase_notification',
21872187
'message_snapshots',
2188+
'_pinned_at',
21882189
)
21892190

21902191
if TYPE_CHECKING:
@@ -2224,6 +2225,8 @@ def __init__(
22242225
self.application_id: Optional[int] = utils._get_as_snowflake(data, 'application_id')
22252226
self.stickers: List[StickerItem] = [StickerItem(data=d, state=state) for d in data.get('sticker_items', [])]
22262227
self.message_snapshots: List[MessageSnapshot] = MessageSnapshot._from_value(state, data.get('message_snapshots'))
2228+
# Set by Messageable.pins
2229+
self._pinned_at: Optional[datetime.datetime] = None
22272230

22282231
self.poll: Optional[Poll] = None
22292232
try:
@@ -2644,6 +2647,18 @@ def thread(self) -> Optional[Thread]:
26442647
# Fall back to guild threads in case one was created after the message
26452648
return self._thread or self.guild.get_thread(self.id)
26462649

2650+
@property
2651+
def pinned_at(self) -> Optional[datetime.datetime]:
2652+
"""Optional[:class:`datetime.datetime`]: An aware UTC datetime object containing the time
2653+
when the message was pinned.
2654+
2655+
.. note::
2656+
This is only set for messages that are returned by :meth:`abc.Messageable.pins`.
2657+
2658+
.. versionadded:: 2.6
2659+
"""
2660+
return self._pinned_at
2661+
26472662
@property
26482663
@deprecated('interaction_metadata')
26492664
def interaction(self) -> Optional[MessageInteraction]:

discord/types/message.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,3 +237,13 @@ class AllowedMentions(TypedDict):
237237
roles: SnowflakeList
238238
users: SnowflakeList
239239
replied_user: bool
240+
241+
242+
class MessagePin(TypedDict):
243+
pinned_at: str
244+
message: Message
245+
246+
247+
class ChannelPins(TypedDict):
248+
items: List[MessagePin]
249+
has_more: bool

0 commit comments

Comments
 (0)