From 4e4d111f72e96ed1b9955b4dd4c5e13b549f658e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Oct 2023 17:23:45 +0000 Subject: [PATCH 1/6] chore(deps-dev): bump codespell from 2.2.5 to 2.2.6 (#2235) Bumps [codespell](https://github.com/codespell-project/codespell) from 2.2.5 to 2.2.6. - [Release notes](https://github.com/codespell-project/codespell/releases) - [Commits](https://github.com/codespell-project/codespell/compare/v2.2.5...v2.2.6) --- updated-dependencies: - dependency-name: codespell dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index 4ab5bc5694..7eed9847a3 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -6,6 +6,6 @@ pytest-asyncio~=0.21.1 mypy~=1.5.1 coverage~=7.3 pre-commit==3.4.0 -codespell==2.2.5 +codespell==2.2.6 bandit==1.7.5 flake8==6.1.0 From 927f8cefb0a5ba8ed5f2c7ccad3f734852bef62e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Oct 2023 20:59:42 +0000 Subject: [PATCH 2/6] chore(deps-dev): bump pre-commit from 3.4.0 to 3.5.0 (#2244) Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 3.4.0 to 3.5.0. - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v3.4.0...v3.5.0) --- updated-dependencies: - dependency-name: pre-commit dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index 7eed9847a3..c2bdeb8682 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -5,7 +5,7 @@ pytest-asyncio~=0.21.1 # pytest-order~=1.0.1 mypy~=1.5.1 coverage~=7.3 -pre-commit==3.4.0 +pre-commit==3.5.0 codespell==2.2.6 bandit==1.7.5 flake8==6.1.0 From 6888b902b60066ade8724e47f68c6236f2fef1f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 14:40:01 +0000 Subject: [PATCH 3/6] chore(deps-dev): update pytest requirement from ~=7.4.2 to ~=7.4.3 (#2249) Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.4.2...7.4.3) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index c2bdeb8682..1831a6fd24 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -1,6 +1,6 @@ -r _.txt pylint~=2.17.5 -pytest~=7.4.2 +pytest~=7.4.3 pytest-asyncio~=0.21.1 # pytest-order~=1.0.1 mypy~=1.5.1 From bbb1e3260da349febb58d2c88376f6125566815b Mon Sep 17 00:00:00 2001 From: Marc13 <83296140+llamaair@users.noreply.github.com> Date: Mon, 30 Oct 2023 18:38:17 +0100 Subject: [PATCH 4/6] fix: `_bytes_to_base64_data` not defined (#2185) * fix: _bytes_to_base64_data isn't defined * Add changelog entry --------- Signed-off-by: Middledot <78228142+Middledot@users.noreply.github.com> Co-authored-by: Middledot <78228142+Middledot@users.noreply.github.com> --- CHANGELOG.md | 2 ++ discord/guild.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd7b286bce..41f9e96c11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -169,6 +169,8 @@ These changes are available on the `master` branch, but have not yet been releas ([#2156](https://github.com/Pycord-Development/pycord/pull/2156)) - Fixed `ScheduledEvent.creator_id` returning `str` instead of `int`. ([#2162](https://github.com/Pycord-Development/pycord/pull/2162)) +- Fixed `_bytes_to_base64_data` not defined. + ([#2185](https://github.com/Pycord-Development/pycord/pull/2185)) - Fixed type-hinting of `values` argument of `basic_autocomplete` to include type-hinting of `Iterable[OptionChoice]`. ([#2164](https://github.com/Pycord-Development/pycord/pull/2164)) diff --git a/discord/guild.py b/discord/guild.py index 5d74590dca..2f449beb60 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -2908,7 +2908,7 @@ async def create_role( if icon is None: fields["icon"] = None else: - fields["icon"] = _bytes_to_base64_data(icon) + fields["icon"] = utils._bytes_to_base64_data(icon) fields["unicode_emoji"] = None if unicode_emoji is not MISSING: From 9e4b3fff915a947628d0c1410dc2e188a6d98afb Mon Sep 17 00:00:00 2001 From: UK <41271523+NeloBlivion@users.noreply.github.com> Date: Mon, 30 Oct 2023 17:43:54 +0000 Subject: [PATCH 5/6] feat: Add properties for burst reactions (#1947) * Support for burst reactions * Complete implementation * style(pre-commit): auto fixes from pre-commit.com hooks * fix slots * docs fixes * style(pre-commit): auto fixes from pre-commit.com hooks * correction * return type --------- Signed-off-by: UK <41271523+NeloBlivion@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Lala Sabathil --- discord/enums.py | 8 ++++ discord/http.py | 3 ++ discord/iterators.py | 10 ++++- discord/raw_models.py | 33 +++++++++++++- discord/reaction.py | 85 ++++++++++++++++++++++++++++++++++--- discord/types/message.py | 6 +++ discord/types/raw_models.py | 6 +++ docs/api/enums.rst | 14 ++++++ docs/api/models.rst | 3 ++ 9 files changed, 158 insertions(+), 10 deletions(-) diff --git a/discord/enums.py b/discord/enums.py index f4b44623b0..f2bb6d3452 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -67,6 +67,7 @@ "AutoModActionType", "AutoModKeywordPresetType", "ApplicationRoleConnectionMetadataType", + "ReactionType", ) @@ -944,6 +945,13 @@ class ApplicationRoleConnectionMetadataType(Enum): boolean_not_equal = 8 +class ReactionType(Enum): + """The reaction type""" + + normal = 0 + burst = 1 + + T = TypeVar("T") diff --git a/discord/http.py b/discord/http.py index 9eb7837d13..40d9ebd928 100644 --- a/discord/http.py +++ b/discord/http.py @@ -757,6 +757,7 @@ def get_reaction_users( emoji: str, limit: int, after: Snowflake | None = None, + type: int | None = None, ) -> Response[list[user.User]]: r = Route( "GET", @@ -771,6 +772,8 @@ def get_reaction_users( } if after: params["after"] = after + if type: + params["type"] = type return self.request(r, params=params) def clear_reactions( diff --git a/discord/iterators.py b/discord/iterators.py index 78edb7570d..b171d70ed6 100644 --- a/discord/iterators.py +++ b/discord/iterators.py @@ -182,10 +182,11 @@ async def next(self) -> T: class ReactionIterator(_AsyncIterator[Union["User", "Member"]]): - def __init__(self, message, emoji, limit=100, after=None): + def __init__(self, message, emoji, limit=100, after=None, type=None): self.message = message self.limit = limit self.after = after + self.type = type state = message._state self.getter = state.http.get_reaction_users self.state = state @@ -212,7 +213,12 @@ async def fill_users(self): after = self.after.id if self.after else None data: list[PartialUserPayload] = await self.getter( - self.channel_id, self.message.id, self.emoji, retrieve, after=after + self.channel_id, + self.message.id, + self.emoji, + retrieve, + after=after, + type=self.type, ) if data: diff --git a/discord/raw_models.py b/discord/raw_models.py index 1cd88ea0c2..39eab47443 100644 --- a/discord/raw_models.py +++ b/discord/raw_models.py @@ -29,7 +29,7 @@ from typing import TYPE_CHECKING from .automod import AutoModAction, AutoModTriggerType -from .enums import AuditLogAction, ChannelType, try_enum +from .enums import AuditLogAction, ChannelType, ReactionType, try_enum from .types.user import User if TYPE_CHECKING: @@ -214,6 +214,15 @@ class RawReactionActionEvent(_RawReprMixin): ``REACTION_REMOVE`` for reaction removal. .. versionadded:: 1.3 + burst: :class:`bool` + Whether this reaction is a burst (super) reaction. + burst_colours: Optional[:class:`list`] + A list of hex codes this reaction can be. Only available if `event_type` is `REACTION_ADD` + and this emoji has super reactions available. + burst_colors: Optional[:class:`list`] + Alias for :attr:`burst_colours`. + type: :class:`ReactionType` + The type of reaction added. data: :class:`dict` The raw data sent by the `gateway `_. @@ -226,6 +235,10 @@ class RawReactionActionEvent(_RawReprMixin): "channel_id", "guild_id", "emoji", + "burst", + "burst_colours", + "burst_colors", + "type", "event_type", "member", "data", @@ -240,6 +253,10 @@ def __init__( self.emoji: PartialEmoji = emoji self.event_type: str = event_type self.member: Member | None = None + self.burst: bool = data.get("burst") + self.burst_colours: list = data.get("burst_colors", []) + self.burst_colors: list = self.burst_colours + self.type: ReactionType = try_enum(data.get("type", 0)) try: self.guild_id: int | None = int(data["guild_id"]) @@ -293,18 +310,30 @@ class RawReactionClearEmojiEvent(_RawReprMixin): The guild ID where the reactions got cleared. emoji: :class:`PartialEmoji` The custom or unicode emoji being removed. + burst: :class:`bool` + Whether this reaction was a burst (super) reaction. + burst_colours: :class:`list` + The available HEX codes of the removed super reaction. + burst_colors: Optional[:class:`list`] + Alias for :attr:`burst_colours`. + type: :class:`ReactionType` + The type of reaction removed. data: :class:`dict` The raw data sent by the `gateway `_. .. versionadded:: 2.5 """ - __slots__ = ("message_id", "channel_id", "guild_id", "emoji", "data") + __slots__ = ("message_id", "channel_id", "guild_id", "emoji", "burst", "data") def __init__(self, data: ReactionClearEmojiEvent, emoji: PartialEmoji) -> None: self.emoji: PartialEmoji = emoji self.message_id: int = int(data["message_id"]) self.channel_id: int = int(data["channel_id"]) + self.burst: bool = data.get("burst") + self.burst_colours: list = data.get("burst_colors", []) + self.burst_colors: list = self.burst_colours + self.type: ReactionType = try_enum(data.get("type", 0)) try: self.guild_id: int | None = int(data["guild_id"]) diff --git a/discord/reaction.py b/discord/reaction.py index ea1bcbded9..426b5474ef 100644 --- a/discord/reaction.py +++ b/discord/reaction.py @@ -27,9 +27,11 @@ from typing import TYPE_CHECKING, Any +from .colour import Colour +from .enums import ReactionType from .iterators import ReactionIterator -__all__ = ("Reaction",) +__all__ = ("Reaction", "ReactionCountDetails") if TYPE_CHECKING: from .abc import Snowflake @@ -37,6 +39,7 @@ from .message import Message from .partial_emoji import PartialEmoji from .types.message import Reaction as ReactionPayload + from .types.message import ReactionCountDetails as ReactionCountDetailsPayload class Reaction: @@ -70,14 +73,27 @@ class Reaction: emoji: Union[:class:`Emoji`, :class:`PartialEmoji`, :class:`str`] The reaction emoji. May be a custom emoji, or a unicode emoji. count: :class:`int` - Number of times this reaction was made + The combined total of normal and super reactions for this emoji. me: :class:`bool` - If the user sent this reaction. + If the user sent this as a normal reaction. + me_burst: :class:`bool` + If the user sent this as a super reaction. message: :class:`Message` Message this reaction is for. + burst: :class:`bool` + Whether this reaction is a burst (super) reaction. """ - __slots__ = ("message", "count", "emoji", "me") + __slots__ = ( + "message", + "count", + "emoji", + "me", + "burst", + "me_burst", + "_count_details", + "_burst_colours", + ) def __init__( self, @@ -91,7 +107,35 @@ def __init__( emoji or message._state.get_reaction_emoji(data["emoji"]) ) self.count: int = data.get("count", 1) + self._count_details: ReactionCountDetailsPayload = data.get("count_details", {}) self.me: bool = data.get("me") + self.burst: bool = data.get("burst") + self.me_burst: bool = data.get("me_burst") + self._burst_colours: list[Colour] = data.get("burst_colors", []) + + @property + def burst_colours(self) -> list[Colour]: + """Returns a list possible :class:`Colour` this super reaction can be. + + There is an alias for this named :attr:`burst_colors`. + """ + + # We recieve a list of #FFFFFF, so omit the # and convert to base 16 + return [Colour(int(c[1:], 16)) for c in self._burst_colours] + + @property + def burst_colors(self) -> list[Colour]: + """Returns a list possible :class:`Colour` this super reaction can be. + + There is an alias for this named :attr:`burst_colours`. + """ + + return self.burst_colours + + @property + def count_details(self): + """Returns :class:`ReactionCountDetails` for the individual counts of normal and super reactions made.""" + return ReactionCountDetails(self._count_details) # TODO: typeguard def is_custom_emoji(self) -> bool: @@ -166,7 +210,11 @@ async def clear(self) -> None: await self.message.clear_reaction(self.emoji) def users( - self, *, limit: int | None = None, after: Snowflake | None = None + self, + *, + limit: int | None = None, + after: Snowflake | None = None, + type: ReactionType | None = None, ) -> ReactionIterator: """Returns an :class:`AsyncIterator` representing the users that have reacted to the message. @@ -181,6 +229,8 @@ def users( reacted to the message. after: Optional[:class:`abc.Snowflake`] For pagination, reactions are sorted by member. + type: Optional[:class:`ReactionType`] + The type of reaction to get users for. Defaults to `normal`. Yields ------ @@ -210,6 +260,10 @@ def users( # users is now a list of User... winner = random.choice(users) await channel.send(f'{winner} has won the raffle.') + + Getting super reactors: :: + + users = await reaction.users(type=ReactionType.burst).flatten() """ if not isinstance(self.emoji, str): @@ -220,4 +274,23 @@ def users( if limit is None: limit = self.count - return ReactionIterator(self.message, emoji, limit, after) + if isinstance(type, ReactionType): + type = type.value + + return ReactionIterator(self.message, emoji, limit, after, type) + + +class ReactionCountDetails: + """Represents a breakdown of the normal and burst reaction counts for the emoji. + + Attributes + ---------- + normal: :class:`int` + The number of normal reactions for this emoji. + burst: :class:`bool` + The number of super reactions for this emoji. + """ + + def __init__(self, data: ReactionCountDetailsPayload): + self.normal = data.get("normal", 0) + self.burst = data.get("burst", 0) diff --git a/discord/types/message.py b/discord/types/message.py index b141531e9a..93e99ba7ab 100644 --- a/discord/types/message.py +++ b/discord/types/message.py @@ -54,6 +54,12 @@ class Reaction(TypedDict): count: int me: bool emoji: PartialEmoji + burst: bool + + +class ReactionCountDetails(TypedDict): + normal: int + burst: int class Attachment(TypedDict): diff --git a/discord/types/raw_models.py b/discord/types/raw_models.py index 453e75cf17..44ac45363d 100644 --- a/discord/types/raw_models.py +++ b/discord/types/raw_models.py @@ -62,6 +62,9 @@ class ReactionActionEvent(_ReactionEventOptional): channel_id: Snowflake message_id: Snowflake emoji: PartialEmoji + burst: bool + burst_colors: list + type: int class ReactionClearEvent(_ReactionEventOptional): @@ -73,6 +76,9 @@ class ReactionClearEmojiEvent(_ReactionEventOptional): channel_id: int message_id: int emoji: PartialEmoji + burst: bool + burst_colors: list + type: int class IntegrationDeleteEvent(TypedDict): diff --git a/docs/api/enums.rst b/docs/api/enums.rst index e2620ed8e5..45317a37ce 100644 --- a/docs/api/enums.rst +++ b/docs/api/enums.rst @@ -2292,3 +2292,17 @@ of :class:`enum.Enum`. .. attribute:: slurs Represents the slurs keyword preset rule. + +.. class:: ReactionType + + Represents a Reaction's type. + + .. versionadded:: 2.5 + + .. attribute:: normal + + Represents a normal reaction. + + .. attribute:: burst + + Represents a super reaction. diff --git a/docs/api/models.rst b/docs/api/models.rst index 909853a769..4d11fed63e 100644 --- a/docs/api/models.rst +++ b/docs/api/models.rst @@ -98,6 +98,9 @@ Messages .. automethod:: users :async-for: +.. autoclass:: ReactionCountDetails() + :members: + Guild ----- From 73e71737c1c06fd964a6fa7b1439982f454c6fb4 Mon Sep 17 00:00:00 2001 From: UK <41271523+NeloBlivion@users.noreply.github.com> Date: Wed, 8 Nov 2023 20:44:04 +0000 Subject: [PATCH 6/6] fix: TypeErrors raised from Reaction events (#2255) fix try_enum --- discord/raw_models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/raw_models.py b/discord/raw_models.py index 39eab47443..79c8091d98 100644 --- a/discord/raw_models.py +++ b/discord/raw_models.py @@ -256,7 +256,7 @@ def __init__( self.burst: bool = data.get("burst") self.burst_colours: list = data.get("burst_colors", []) self.burst_colors: list = self.burst_colours - self.type: ReactionType = try_enum(data.get("type", 0)) + self.type: ReactionType = try_enum(ReactionType, data.get("type", 0)) try: self.guild_id: int | None = int(data["guild_id"]) @@ -333,7 +333,7 @@ def __init__(self, data: ReactionClearEmojiEvent, emoji: PartialEmoji) -> None: self.burst: bool = data.get("burst") self.burst_colours: list = data.get("burst_colors", []) self.burst_colors: list = self.burst_colours - self.type: ReactionType = try_enum(data.get("type", 0)) + self.type: ReactionType = try_enum(ReactionType, data.get("type", 0)) try: self.guild_id: int | None = int(data["guild_id"])