Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for nsfw commands #1775

Merged
merged 11 commits into from
Nov 10, 2022
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ These changes are available on the `master` branch, but have not yet been releas
- New select types `user`, `role`, `mentionable`, and `channel` - Along with their
respective types and shortcut decorators.
([#1702](https://github.com/Pycord-Development/pycord/pull/1702))
- Added support for age-restricted (NSFW) commands.
([#1775](https://github.com/Pycord-Development/pycord/pull/1775))

### Fixed

Expand Down
1 change: 1 addition & 0 deletions discord/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ def _check_command(cmd: ApplicationCommand, match: Mapping[str, Any]) -> bool:
as_dict = cmd.to_dict()
to_check = {
"dm_permission": None,
"nsfw": None,
"default_member_permissions": None,
"name": None,
"description": None,
Expand Down
23 changes: 23 additions & 0 deletions discord/commands/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ def __init__(self, func: Callable, **kwargs) -> None:
self.guild_only: bool | None = getattr(
func, "__guild_only__", kwargs.get("guild_only", None)
)
self.nsfw: bool | None = getattr(func, "__nsfw__", kwargs.get("nsfw", None))

def __repr__(self) -> str:
return f"<discord.commands.{self.__class__.__name__} name={self.name}>"
Expand Down Expand Up @@ -629,6 +630,9 @@ class SlashCommand(ApplicationCommand):
Returns a string that allows you to mention the slash command.
guild_only: :class:`bool`
Whether the command should only be usable inside a guild.
nsfw: :class:`bool`
Whether the command should be restricted to 18+ channels and users.
Apps intending to be listed in the App Directory cannot have NSFW commands.
default_member_permissions: :class:`~discord.Permissions`
The default permissions a member needs to be able to run the command.
cog: Optional[:class:`Cog`]
Expand Down Expand Up @@ -849,6 +853,9 @@ def to_dict(self) -> dict:
if self.guild_only is not None:
as_dict["dm_permission"] = not self.guild_only

if self.nsfw is not None:
as_dict["nsfw"] = self.nsfw

if self.default_member_permissions is not None:
as_dict[
"default_member_permissions"
Expand Down Expand Up @@ -1060,6 +1067,9 @@ class SlashCommandGroup(ApplicationCommand):
isn't one.
guild_only: :class:`bool`
Whether the command should only be usable inside a guild.
nsfw: :class:`bool`
Whether the command should be restricted to 18+ channels and users.
Apps intending to be listed in the App Directory cannot have NSFW commands.
default_member_permissions: :class:`~discord.Permissions`
The default permissions a member needs to be able to run the command.
checks: List[Callable[[:class:`.ApplicationContext`], :class:`bool`]]
Expand Down Expand Up @@ -1132,6 +1142,7 @@ def __init__(
"default_member_permissions", None
)
self.guild_only: bool | None = kwargs.get("guild_only", None)
self.nsfw: bool | None = kwargs.get("nsfw", None)

self.name_localizations: dict[str, str] | None = kwargs.get(
"name_localizations", None
Expand Down Expand Up @@ -1161,6 +1172,9 @@ def to_dict(self) -> dict:
if self.guild_only is not None:
as_dict["dm_permission"] = not self.guild_only

if self.nsfw is not None:
as_dict["nsfw"] = self.nsfw

if self.default_member_permissions is not None:
as_dict[
"default_member_permissions"
Expand Down Expand Up @@ -1208,6 +1222,9 @@ def create_subgroup(
This will be a global command if ``None`` is passed.
guild_only: :class:`bool`
Whether the command should only be usable inside a guild.
nsfw: :class:`bool`
Whether the command should be restricted to 18+ channels and users.
Apps intending to be listed in the App Directory cannot have NSFW commands.
default_member_permissions: :class:`~discord.Permissions`
The default permissions a member needs to be able to run the command.
checks: List[Callable[[:class:`.ApplicationContext`], :class:`bool`]]
Expand Down Expand Up @@ -1377,6 +1394,9 @@ class ContextMenuCommand(ApplicationCommand):
The ids of the guilds where this command will be registered.
guild_only: :class:`bool`
Whether the command should only be usable inside a guild.
nsfw: :class:`bool`
Whether the command should be restricted to 18+ channels and users.
Apps intending to be listed in the App Directory cannot have NSFW commands.
default_member_permissions: :class:`~discord.Permissions`
The default permissions a member needs to be able to run the command.
cog: Optional[:class:`Cog`]
Expand Down Expand Up @@ -1476,6 +1496,9 @@ def to_dict(self) -> dict[str, str | int]:
if self.guild_only is not None:
as_dict["dm_permission"] = not self.guild_only

if self.nsfw is not None:
as_dict["nsfw"] = self.nsfw

if self.default_member_permissions is not None:
as_dict[
"default_member_permissions"
Expand Down
36 changes: 32 additions & 4 deletions discord/commands/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@
from ..permissions import Permissions
from .core import ApplicationCommand

__all__ = (
"default_permissions",
"guild_only",
)
__all__ = ("default_permissions", "guild_only", "is_nsfw")


def default_permissions(**perms: bool) -> Callable:
Expand Down Expand Up @@ -108,3 +105,34 @@ def inner(command: Callable):
return command

return inner


def is_nsfw() -> Callable:
"""A decorator that limits the usage of a slash command to 18+ channels and users.
In guilds, the command will only be able to be used in channels marked as NSFW.
In DMs, users must have opted into age-restricted commands via privacy settings.

Note that apps intending to be listed in the App Directory cannot have NSFW commands.

Example
-------

.. code-block:: python3

from discord import is_nsfw

@bot.slash_command()
@is_nsfw()
async def test(ctx):
await ctx.respond("This command is age restricted.")
"""

def inner(command: Callable):
if isinstance(command, ApplicationCommand):
command.nsfw = True
else:
command.__nsfw__ = True

return command

return inner
25 changes: 25 additions & 0 deletions discord/ext/bridge/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"map_to",
"guild_only",
"has_permissions",
"is_nsfw",
)


Expand Down Expand Up @@ -437,6 +438,30 @@ def predicate(func: Callable | ApplicationCommand):
return predicate


def is_nsfw():
"""Intended to work with :class:`.ApplicationCommand` and :class:`BridgeCommand`, adds a :func:`~ext.commands.check`
that locks the command to only run in nsfw contexts, and also registers the command as nsfw client-side (on discord).

Basically a utility function that wraps both :func:`discord.ext.commands.is_nsfw` and :func:`discord.commands.is_nsfw`.

.. warning::

In DMs, the prefixed-based command will always run as the user's privacy settings cannot be checked directly.
"""

def predicate(func: Callable | ApplicationCommand):
if isinstance(func, ApplicationCommand):
func.nsfw = True
else:
func.__nsfw__ = True

from ..commands import is_nsfw

return is_nsfw()(func)

return predicate


def has_permissions(**perms: dict[str, bool]):
r"""Intended to work with :class:`.SlashCommand` and :class:`BridgeCommand`, adds a
:func:`~ext.commands.check` that locks the command to be run by people with certain
Expand Down
3 changes: 3 additions & 0 deletions docs/api/application_commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ Command Permission Decorators
.. autofunction:: discord.commands.guild_only
:decorator:

.. autofunction:: discord.commands.is_nsfw
:decorator:


Commands
--------
Expand Down
3 changes: 3 additions & 0 deletions docs/ext/bridge/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ Decorators
.. automethod:: discord.ext.bridge.guild_only()
:decorator:

.. automethod:: discord.ext.bridge.is_nsfw()
:decorator:

.. automethod:: discord.ext.bridge.has_permissions()
:decorator:

Expand Down