diff --git a/changelog/1113.feature.rst b/changelog/1113.feature.rst index effdde8e52..079ad452e1 100644 --- a/changelog/1113.feature.rst +++ b/changelog/1113.feature.rst @@ -2,4 +2,4 @@ Support application subscriptions and one-time purchases (see the :ddocs:`offici - New types: :class:`SKU`, :class:`Entitlement`. - New :attr:`Interaction.entitlements` attribute, and :meth:`InteractionResponse.require_premium` response type. - New events: :func:`on_entitlement_create`, :func:`on_entitlement_update`, :func:`on_entitlement_delete`. -- New :class:`Client` methods: :meth:`~Client.skus`, :meth:`~Client.entitlements`, :meth:`~Client.create_entitlement`. +- New :class:`Client` methods: :meth:`~Client.skus`, :meth:`~Client.entitlements`, :meth:`~Client.fetch_entitlement`, :meth:`~Client.create_entitlement`. diff --git a/changelog/1186.feature.rst b/changelog/1186.feature.rst index effdde8e52..079ad452e1 100644 --- a/changelog/1186.feature.rst +++ b/changelog/1186.feature.rst @@ -2,4 +2,4 @@ Support application subscriptions and one-time purchases (see the :ddocs:`offici - New types: :class:`SKU`, :class:`Entitlement`. - New :attr:`Interaction.entitlements` attribute, and :meth:`InteractionResponse.require_premium` response type. - New events: :func:`on_entitlement_create`, :func:`on_entitlement_update`, :func:`on_entitlement_delete`. -- New :class:`Client` methods: :meth:`~Client.skus`, :meth:`~Client.entitlements`, :meth:`~Client.create_entitlement`. +- New :class:`Client` methods: :meth:`~Client.skus`, :meth:`~Client.entitlements`, :meth:`~Client.fetch_entitlement`, :meth:`~Client.create_entitlement`. diff --git a/changelog/1245.feature.rst b/changelog/1245.feature.rst new file mode 100644 index 0000000000..03bb9cfd27 --- /dev/null +++ b/changelog/1245.feature.rst @@ -0,0 +1 @@ +Implement :attr:`~MemberFlags.is_guest`, :attr:`~MemberFlags.started_home_actions`, :attr:`~MemberFlags.completed_home_actions`, :attr:`~MemberFlags.automod_quarantined_username`, :attr:`~MemberFlags.dm_settings_upsell_acknowledged` new member flags. diff --git a/changelog/1249.feature.rst b/changelog/1249.feature.rst new file mode 100644 index 0000000000..079ad452e1 --- /dev/null +++ b/changelog/1249.feature.rst @@ -0,0 +1,5 @@ +Support application subscriptions and one-time purchases (see the :ddocs:`official docs ` for more info). +- New types: :class:`SKU`, :class:`Entitlement`. +- New :attr:`Interaction.entitlements` attribute, and :meth:`InteractionResponse.require_premium` response type. +- New events: :func:`on_entitlement_create`, :func:`on_entitlement_update`, :func:`on_entitlement_delete`. +- New :class:`Client` methods: :meth:`~Client.skus`, :meth:`~Client.entitlements`, :meth:`~Client.fetch_entitlement`, :meth:`~Client.create_entitlement`. diff --git a/changelog/1252.bugfix.rst b/changelog/1252.bugfix.rst new file mode 100644 index 0000000000..f73d8d537d --- /dev/null +++ b/changelog/1252.bugfix.rst @@ -0,0 +1 @@ +|commands| Fix incorrect exception when using the :func:`~ext.commands.default_member_permissions` decorator on a :func:`~ext.commands.user_command` while also using the cog-level ``user_command_attrs`` field. diff --git a/disnake/client.py b/disnake/client.py index 2c4e2c7da4..990e19c201 100644 --- a/disnake/client.py +++ b/disnake/client.py @@ -3147,6 +3147,7 @@ def entitlements( guild: Optional[Snowflake] = None, skus: Optional[Sequence[Snowflake]] = None, exclude_ended: bool = False, + exclude_deleted: bool = True, oldest_first: bool = False, ) -> EntitlementIterator: """Retrieves an :class:`.AsyncIterator` that enables receiving entitlements for the application. @@ -3186,6 +3187,8 @@ def entitlements( The SKUs for which entitlements are retrieved. exclude_ended: :class:`bool` Whether to exclude ended/expired entitlements. Defaults to ``False``. + exclude_deleted: :class:`bool` + Whether to exclude deleted entitlements. Defaults to ``True``. oldest_first: :class:`bool` If set to ``True``, return entries in oldest->newest order. Defaults to ``False``. @@ -3209,9 +3212,40 @@ def entitlements( guild_id=guild.id if guild is not None else None, sku_ids=[sku.id for sku in skus] if skus else None, exclude_ended=exclude_ended, + exclude_deleted=exclude_deleted, oldest_first=oldest_first, ) + async def fetch_entitlement(self, entitlement_id: int, /) -> Entitlement: + """|coro| + + Retrieves a :class:`.Entitlement` for the given ID. + + .. note:: + + This method is an API call. To get the entitlements of the invoking user/guild + in interactions, consider using :attr:`.Interaction.entitlements`. + + .. versionadded:: 2.10 + + Parameters + ---------- + entitlement_id: :class:`int` + The ID of the entitlement to retrieve. + + Raises + ------ + HTTPException + Retrieving the entitlement failed. + + Returns + ------- + :class:`.Entitlement` + The retrieved entitlement. + """ + data = await self.http.get_entitlement(self.application_id, entitlement_id=entitlement_id) + return Entitlement(data=data, state=self._connection) + async def create_entitlement( self, sku: Snowflake, owner: Union[abc.User, Guild] ) -> Entitlement: diff --git a/disnake/ext/commands/ctx_menus_core.py b/disnake/ext/commands/ctx_menus_core.py index 0e84b5a89d..9f59f03fc5 100644 --- a/disnake/ext/commands/ctx_menus_core.py +++ b/disnake/ext/commands/ctx_menus_core.py @@ -86,15 +86,9 @@ def __init__( self.auto_sync: bool = True if auto_sync is None else auto_sync try: - default_perms: int = func.__default_member_permissions__ + default_member_permissions = func.__default_member_permissions__ except AttributeError: pass - else: - if default_member_permissions is not None: - raise ValueError( - "Cannot set `default_member_permissions` in both parameter and decorator" - ) - default_member_permissions = default_perms dm_permission = True if dm_permission is None else dm_permission diff --git a/disnake/flags.py b/disnake/flags.py index da3cba6904..de753b30ba 100644 --- a/disnake/flags.py +++ b/disnake/flags.py @@ -2343,9 +2343,14 @@ class MemberFlags(BaseFlags): def __init__( self, *, + automod_quarantined_username: bool = ..., bypasses_verification: bool = ..., + completed_home_actions: bool = ..., completed_onboarding: bool = ..., did_rejoin: bool = ..., + dm_settings_upsell_acknowledged: bool = ..., + is_guest: bool = ..., + started_home_actions: bool = ..., started_onboarding: bool = ..., ) -> None: ... @@ -2370,6 +2375,46 @@ def started_onboarding(self): """:class:`bool`: Returns ``True`` if the member has started onboarding.""" return 1 << 3 + @flag_value + def is_guest(self): + """:class:`bool`: Returns ``True`` if the member is a guest and can only access the voice channel they were invited to. + + .. versionadded:: 2.10 + """ + return 1 << 4 + + @flag_value + def started_home_actions(self): + """:class:`bool`: Returns ``True`` if the member has started the Server Guide actions. + + .. versionadded:: 2.10 + """ + return 1 << 5 + + @flag_value + def completed_home_actions(self): + """:class:`bool`: Returns ``True`` if the member has completed the Server Guide actions. + + .. versionadded:: 2.10 + """ + return 1 << 6 + + @flag_value + def automod_quarantined_username(self): + """:class:`bool`: Returns ``True`` if the member's username, display name, or nickname is blocked by AutoMod. + + .. versionadded:: 2.10 + """ + return 1 << 7 + + @flag_value + def dm_settings_upsell_acknowledged(self): + """:class:`bool`: Returns ``True`` if the member has dismissed the DM settings upsell. + + .. versionadded:: 2.10 + """ + return 1 << 9 + class RoleFlags(BaseFlags): """Wraps up Discord Role flags. diff --git a/disnake/http.py b/disnake/http.py index fe9c7d6afc..89dfe30724 100644 --- a/disnake/http.py +++ b/disnake/http.py @@ -2376,10 +2376,12 @@ def get_entitlements( guild_id: Optional[Snowflake] = None, sku_ids: Optional[SnowflakeList] = None, exclude_ended: bool = False, + exclude_deleted: bool = False, ) -> Response[List[entitlement.Entitlement]]: params: Dict[str, Any] = { "limit": limit, "exclude_ended": int(exclude_ended), + "exclude_deleted": int(exclude_deleted), } if before is not None: params["before"] = before @@ -2397,6 +2399,18 @@ def get_entitlements( ) return self.request(r, params=params) + def get_entitlement( + self, application_id: Snowflake, entitlement_id: int + ) -> Response[entitlement.Entitlement]: + return self.request( + Route( + "GET", + "/applications/{application_id}/entitlements/{entitlement_id}", + application_id=application_id, + entitlement_id=entitlement_id, + ) + ) + def create_test_entitlement( self, application_id: Snowflake, diff --git a/disnake/iterators.py b/disnake/iterators.py index 6d629066af..86c311e39f 100644 --- a/disnake/iterators.py +++ b/disnake/iterators.py @@ -1044,6 +1044,7 @@ def __init__( before: Optional[Union[Snowflake, datetime.datetime]] = None, after: Optional[Union[Snowflake, datetime.datetime]] = None, exclude_ended: bool = False, + exclude_deleted: bool = True, oldest_first: bool = False, ) -> None: if isinstance(before, datetime.datetime): @@ -1059,6 +1060,7 @@ def __init__( self.guild_id: Optional[int] = guild_id self.sku_ids: Optional[List[int]] = sku_ids self.exclude_ended: bool = exclude_ended + self.exclude_deleted: bool = exclude_deleted self.state: ConnectionState = state self.request = state.http.get_entitlements @@ -1116,6 +1118,7 @@ async def _before_strategy(self, retrieve: int) -> List[EntitlementPayload]: user_id=self.user_id, guild_id=self.guild_id, exclude_ended=self.exclude_ended, + exclude_deleted=self.exclude_deleted, ) if len(data): @@ -1133,6 +1136,7 @@ async def _after_strategy(self, retrieve: int) -> List[EntitlementPayload]: user_id=self.user_id, guild_id=self.guild_id, exclude_ended=self.exclude_ended, + exclude_deleted=self.exclude_deleted, ) if len(data): diff --git a/tests/ext/commands/test_base_core.py b/tests/ext/commands/test_base_core.py index 734353c5a4..bdedeb0fe0 100644 --- a/tests/ext/commands/test_base_core.py +++ b/tests/ext/commands/test_base_core.py @@ -7,19 +7,33 @@ from disnake.ext import commands +class DecoratorMeta: + def __init__(self, type: str) -> None: + self.decorator = { + "slash": commands.slash_command, + "user": commands.user_command, + "message": commands.message_command, + }[type] + self.attr_key = f"{type}_command_attrs" + + class TestDefaultPermissions: - def test_decorator(self) -> None: + @pytest.fixture(params=["slash", "user", "message"]) + def meta(self, request): + return DecoratorMeta(request.param) + + def test_decorator(self, meta: DecoratorMeta) -> None: class Cog(commands.Cog): - @commands.slash_command(default_member_permissions=64) + @meta.decorator(default_member_permissions=64) async def cmd(self, _) -> None: ... @commands.default_member_permissions(64) - @commands.slash_command() + @meta.decorator() async def above(self, _) -> None: ... - @commands.slash_command() + @meta.decorator() @commands.default_member_permissions(64) async def below(self, _) -> None: ... @@ -29,22 +43,22 @@ async def below(self, _) -> None: assert c.above.default_member_permissions == Permissions(64) assert c.below.default_member_permissions == Permissions(64) - def test_decorator_overwrite(self) -> None: + def test_decorator_overwrite(self, meta: DecoratorMeta) -> None: # putting the decorator above should fail with pytest.raises(ValueError, match="Cannot set `default_member_permissions`"): class Cog(commands.Cog): @commands.default_member_permissions(32) - @commands.slash_command(default_member_permissions=64) + @meta.decorator(default_member_permissions=64) async def above(self, _) -> None: ... - # putting the decorator below shouldn't fail - # (this is a side effect of how command copying works, + # putting the decorator below shouldn't fail, for now + # FIXME: (this is a side effect of how command copying works, # and while this *should* probably fail, we're just testing # for regressions for now) class Cog2(commands.Cog): - @commands.slash_command(default_member_permissions=64) + @meta.decorator(default_member_permissions=64) @commands.default_member_permissions(32) async def below(self, _) -> None: ... @@ -52,22 +66,24 @@ async def below(self, _) -> None: for c in (Cog2, Cog2()): assert c.below.default_member_permissions == Permissions(32) - def test_attrs(self) -> None: - class Cog(commands.Cog, slash_command_attrs={"default_member_permissions": 32}): - @commands.slash_command() + def test_attrs(self, meta: DecoratorMeta) -> None: + kwargs = {meta.attr_key: {"default_member_permissions": 32}} + + class Cog(commands.Cog, **kwargs): + @meta.decorator() async def no_overwrite(self, _) -> None: ... - @commands.slash_command(default_member_permissions=64) + @meta.decorator(default_member_permissions=64) async def overwrite(self, _) -> None: ... @commands.default_member_permissions(64) - @commands.slash_command() + @meta.decorator() async def overwrite_decorator_above(self, _) -> None: ... - @commands.slash_command() + @meta.decorator() @commands.default_member_permissions(64) async def overwrite_decorator_below(self, _) -> None: ... @@ -75,6 +91,7 @@ async def overwrite_decorator_below(self, _) -> None: assert Cog.no_overwrite.default_member_permissions is None assert Cog().no_overwrite.default_member_permissions == Permissions(32) + # all of these should overwrite the cog-level attr assert Cog.overwrite.default_member_permissions == Permissions(64) assert Cog().overwrite.default_member_permissions == Permissions(64)