Skip to content

Commit 2640e57

Browse files
Snipy7374shiftinv
andauthored
feat: update the message pin endpoints (#1306)
Signed-off-by: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Co-authored-by: vi <8530778+shiftinv@users.noreply.github.com>
1 parent 3bd5a6e commit 2640e57

File tree

9 files changed

+168
-20
lines changed

9 files changed

+168
-20
lines changed

changelog/1305.deprecate.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Deprecate awaiting :meth:`.Messageable.pins` in favour of ``async for msg in channel.pins()``.

changelog/1305.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Update the :meth:`.Messageable.pins`, :meth:`Message.pin` and :meth:`Message.unpin` methods to use the new API endpoints. :meth:`.Messageable.pins` returns now an asynchronous iterator to yield all pinned messages.

disnake/abc.py

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
from .enums import InviteTarget
7171
from .guild import Guild, GuildChannel as AnyGuildChannel, GuildMessageable
7272
from .guild_scheduled_event import GuildScheduledEvent
73-
from .iterators import HistoryIterator
73+
from .iterators import ChannelPinsIterator, HistoryIterator
7474
from .member import Member
7575
from .message import Message, MessageReference, PartialMessage
7676
from .poll import Poll
@@ -1863,31 +1863,64 @@ async def fetch_message(self, id: int, /) -> Message:
18631863
data = await self._state.http.get_message(channel.id, id)
18641864
return self._state.create_message(channel=channel, data=data)
18651865

1866-
async def pins(self) -> List[Message]:
1867-
"""|coro|
1866+
def pins(
1867+
self, *, limit: Optional[int] = 50, before: Optional[SnowflakeTime] = None
1868+
) -> ChannelPinsIterator:
1869+
"""Returns an :class:`.AsyncIterator` that enables receiving the destination's pinned messages.
18681870
1869-
Retrieves all messages that are currently pinned in the channel.
1871+
You must have the :attr:`.Permissions.read_message_history` and :attr:`.Permissions.view_channel` permissions to use this.
18701872
18711873
.. note::
18721874
18731875
Due to a limitation with the Discord API, the :class:`.Message`
18741876
objects returned by this method do not contain complete
18751877
:attr:`.Message.reactions` data.
18761878
1879+
.. versionchanged:: 2.11
1880+
Now returns an :class:`.AsyncIterator` to support changes in Discord's API.
1881+
``await``\\ing the result of this method remains supported, but only returns the
1882+
last 50 pins and is deprecated in favor of ``async for msg in channel.pins()``.
1883+
1884+
Examples
1885+
--------
1886+
Usage ::
1887+
1888+
counter = 0
1889+
async for message in channel.pins(limit=100):
1890+
if message.author == client.user:
1891+
counter += 1
1892+
1893+
Flattening to a list ::
1894+
1895+
pinned_messages = await channel.pins(limit=100).flatten()
1896+
# pinned_messages is now a list of Message...
1897+
1898+
All parameters are optional.
1899+
1900+
Parameters
1901+
----------
1902+
limit: Optional[:class:`int`]
1903+
The number of pinned messages to retrieve.
1904+
If ``None``, retrieves every pinned message in the channel. Note, however,
1905+
that this would make it a slow operation.
1906+
before: Optional[Union[:class:`.abc.Snowflake`, :class:`datetime.datetime`]]
1907+
Retrieve messages pinned before this date or message.
1908+
If a datetime is provided, it is recommended to use a UTC aware datetime.
1909+
If the datetime is naive, it is assumed to be local time.
1910+
18771911
Raises
18781912
------
18791913
HTTPException
18801914
Retrieving the pinned messages failed.
18811915
1882-
Returns
1883-
-------
1884-
List[:class:`.Message`]
1885-
The messages that are currently pinned.
1916+
Yields
1917+
------
1918+
:class:`.Message`
1919+
The pinned message from the parsed message data.
18861920
"""
1887-
channel = await self._get_channel()
1888-
state = self._state
1889-
data = await state.http.pins_from(channel.id)
1890-
return [state.create_message(channel=channel, data=m) for m in data]
1921+
from .iterators import ChannelPinsIterator # due to cyclic imports
1922+
1923+
return ChannelPinsIterator(self, limit=limit, before=before)
18911924

18921925
def history(
18931926
self,

disnake/http.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -876,7 +876,7 @@ def pin_message(
876876
) -> Response[None]:
877877
r = Route(
878878
"PUT",
879-
"/channels/{channel_id}/pins/{message_id}",
879+
"/channels/{channel_id}/messages/pins/{message_id}",
880880
channel_id=channel_id,
881881
message_id=message_id,
882882
)
@@ -887,14 +887,29 @@ def unpin_message(
887887
) -> Response[None]:
888888
r = Route(
889889
"DELETE",
890-
"/channels/{channel_id}/pins/{message_id}",
890+
"/channels/{channel_id}/messages/pins/{message_id}",
891891
channel_id=channel_id,
892892
message_id=message_id,
893893
)
894894
return self.request(r, reason=reason)
895895

896-
def pins_from(self, channel_id: Snowflake) -> Response[List[message.Message]]:
897-
return self.request(Route("GET", "/channels/{channel_id}/pins", channel_id=channel_id))
896+
def get_pins(
897+
self,
898+
channel_id: Snowflake,
899+
limit: int,
900+
before: Optional[Snowflake] = None,
901+
) -> Response[channel.ChannelPins]:
902+
r = Route(
903+
"GET",
904+
"/channels/{channel_id}/messages/pins",
905+
channel_id=channel_id,
906+
)
907+
params: Dict[str, Any] = {"limit": limit}
908+
909+
if before is not None:
910+
params["before"] = before
911+
912+
return self.request(r, params=params)
898913

899914
# Member management
900915

disnake/iterators.py

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
Awaitable,
1212
Callable,
1313
Dict,
14+
Generator,
1415
List,
1516
Optional,
1617
TypeVar,
@@ -29,7 +30,7 @@
2930
from .object import Object
3031
from .subscription import Subscription
3132
from .threads import Thread
32-
from .utils import maybe_coroutine, snowflake_time, time_snowflake
33+
from .utils import deprecated, maybe_coroutine, parse_time, snowflake_time, time_snowflake
3334

3435
__all__ = (
3536
"ReactionIterator",
@@ -1308,3 +1309,76 @@ async def fill_users(self) -> None:
13081309
if not (self.guild is None or isinstance(self.guild, Object)):
13091310
member = self.guild.get_member(int(element["id"]))
13101311
await self.users.put(member or self.state.create_user(data=element))
1312+
1313+
1314+
class ChannelPinsIterator(_AsyncIterator["Message"]):
1315+
def __init__(
1316+
self,
1317+
messageable: Messageable,
1318+
*,
1319+
limit: Optional[int],
1320+
before: Optional[Union[Snowflake, datetime.datetime]] = None,
1321+
) -> None:
1322+
before_ = None
1323+
if before is not None:
1324+
if isinstance(before, datetime.datetime):
1325+
before_ = before.isoformat()
1326+
elif isinstance(before, Object):
1327+
before_ = snowflake_time(before.id).isoformat()
1328+
else:
1329+
raise TypeError(
1330+
f"Expected either `disnake.Snowflake` or `datetime.datetime` for `before`. Got `{before.__class__.__name__!r}`."
1331+
)
1332+
1333+
self.messageable = messageable
1334+
self._state = messageable._state
1335+
self.limit = limit
1336+
self.before: Optional[str] = before_
1337+
1338+
self.getter = self._state.http.get_pins
1339+
self.messages: asyncio.Queue[Message] = asyncio.Queue()
1340+
1341+
# defined to maintain backward compatibility with the old `pins` method
1342+
@deprecated("async for msg in channel.pins()")
1343+
def __await__(self) -> Generator[None, None, List[Message]]:
1344+
return self.flatten().__await__()
1345+
1346+
async def next(self) -> Message:
1347+
if self.messages.empty():
1348+
await self.fill_messages()
1349+
1350+
try:
1351+
return self.messages.get_nowait()
1352+
except asyncio.QueueEmpty:
1353+
raise NoMoreItems from None
1354+
1355+
def _get_retrieve(self) -> bool:
1356+
self.retrieve = min(self.limit, 50) if self.limit is not None else 50
1357+
return self.retrieve > 0
1358+
1359+
async def fill_messages(self) -> None:
1360+
if not hasattr(self, "channel"):
1361+
channel = await self.messageable._get_channel()
1362+
self.channel = channel
1363+
1364+
if self._get_retrieve():
1365+
data = await self.getter(
1366+
channel_id=self.channel.id,
1367+
before=self.before,
1368+
limit=self.retrieve,
1369+
)
1370+
1371+
if len(data):
1372+
if self.limit is not None:
1373+
self.limit -= self.retrieve
1374+
1375+
if data["items"]:
1376+
self.before = data["items"][-1]["pinned_at"]
1377+
1378+
if not data["has_more"]:
1379+
self.limit = 0 # terminate loop
1380+
1381+
for element in data["items"]:
1382+
message = self._state.create_message(channel=self.channel, data=element["message"])
1383+
message._pinned_at = parse_time(element["pinned_at"])
1384+
await self.messages.put(message)

disnake/message.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,6 +1157,7 @@ class Message(Hashable):
11571157
"poll",
11581158
"_edited_timestamp",
11591159
"_role_subscription_data",
1160+
"_pinned_at",
11601161
)
11611162

11621163
if TYPE_CHECKING:
@@ -1197,6 +1198,7 @@ def __init__(
11971198
)
11981199
self.type: MessageType = try_enum(MessageType, data["type"])
11991200
self.pinned: bool = data["pinned"]
1201+
self._pinned_at: Optional[datetime.datetime] = None
12001202
self.flags: MessageFlags = MessageFlags._from_value(data.get("flags", 0))
12011203
self.mention_everyone: bool = data["mention_everyone"]
12021204
self.tts: bool = data["tts"]
@@ -1556,6 +1558,17 @@ def edited_at(self) -> Optional[datetime.datetime]:
15561558
"""Optional[:class:`datetime.datetime`]: An aware UTC datetime object containing the edited time of the message."""
15571559
return self._edited_timestamp
15581560

1561+
@property
1562+
def pinned_at(self) -> Optional[datetime.datetime]:
1563+
"""Optional[:class:`datetime.datetime`]: An aware UTC datetime object containing the pin time of the message.
1564+
1565+
.. note::
1566+
This is only set on messages retrieved using :meth:`abc.Messageable.pins`.
1567+
1568+
.. versionadded:: 2.11
1569+
"""
1570+
return self._pinned_at
1571+
15591572
@property
15601573
def jump_url(self) -> str:
15611574
""":class:`str`: Returns a URL that allows the client to jump to this message."""
@@ -2137,7 +2150,7 @@ async def pin(self, *, reason: Optional[str] = None) -> None:
21372150
21382151
Pins the message.
21392152
2140-
You must have the :attr:`~Permissions.manage_messages` permission to do
2153+
You must have the :attr:`~Permissions.pin_messages` permission to do
21412154
this in a non-private channel context.
21422155
21432156
This does not work with messages sent in a :class:`VoiceChannel` or :class:`StageChannel`.
@@ -2167,7 +2180,7 @@ async def unpin(self, *, reason: Optional[str] = None) -> None:
21672180
21682181
Unpins the message.
21692182
2170-
You must have the :attr:`~Permissions.manage_messages` permission to do
2183+
You must have the :attr:`~Permissions.pin_messages` permission to do
21712184
this in a non-private channel context.
21722185
21732186
Parameters

disnake/permissions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -760,7 +760,7 @@ def send_tts_messages(self) -> int:
760760

761761
@flag_value
762762
def manage_messages(self) -> int:
763-
""":class:`bool`: Returns ``True`` if a user can delete or pin messages in a text channel.
763+
""":class:`bool`: Returns ``True`` if a user can delete messages in a text channel.
764764
765765
.. note::
766766

disnake/types/channel.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from typing_extensions import NotRequired
66

7+
from .message import MessagePin
78
from .snowflake import Snowflake
89
from .threads import ForumTag, ThreadArchiveDurationLiteral, ThreadMember, ThreadMetadata
910
from .user import PartialUser
@@ -196,3 +197,8 @@ class CreateGuildChannel(TypedDict):
196197
rtc_region: NotRequired[Optional[str]]
197198
video_quality_mode: NotRequired[Optional[VideoQualityMode]]
198199
default_auto_archive_duration: NotRequired[Optional[ThreadArchiveDurationLiteral]]
200+
201+
202+
class ChannelPins(TypedDict):
203+
items: List[MessagePin]
204+
has_more: bool

disnake/types/message.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,8 @@ class AllowedMentions(TypedDict):
159159
roles: SnowflakeList
160160
users: SnowflakeList
161161
replied_user: bool
162+
163+
164+
class MessagePin(TypedDict):
165+
pinned_at: str
166+
message: Message

0 commit comments

Comments
 (0)