Skip to content

Commit

Permalink
feat: Add support for nsfw commands (#1775)
Browse files Browse the repository at this point in the history
* Add @is_nsfw decorator

* Add @is_nsfw bridge decorator

* Update bot.py

* Add nsfw attribute and documentation

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* App Directory warning

* Update docs

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update bridge docs

* Add changelog entry

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: BobDotCom <71356958+BobDotCom@users.noreply.github.com>
  • Loading branch information
3 people committed Nov 10, 2022
1 parent 4fb327c commit df85b81
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 4 deletions.
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

0 comments on commit df85b81

Please sign in to comment.