Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4094424
Refactored optional field logic, added nsfw, started work on contexts
polyjitter Aug 22, 2025
7b11f1c
Added some global-only handling (hopefully?)
polyjitter Aug 22, 2025
5d99a18
Refactor mappings. Screw it, this is a refactor.
polyjitter Aug 22, 2025
f1a80d7
More refactoring of the adding fields
polyjitter Aug 22, 2025
714d843
Changed types in ApplicationCommand
polyjitter Aug 30, 2025
8c3e18e
Changed hexdocs
polyjitter Aug 30, 2025
a53abdf
Refactored optional field logic, added nsfw, started work on contexts
polyjitter Aug 22, 2025
07b3638
Added some global-only handling (hopefully?)
polyjitter Aug 22, 2025
15348ee
Refactor mappings. Screw it, this is a refactor.
polyjitter Aug 22, 2025
4be32d7
More refactoring of the adding fields
polyjitter Aug 22, 2025
f6ba2a8
Changed types in ApplicationCommand
polyjitter Aug 30, 2025
9a0883a
Changed hexdocs
polyjitter Aug 30, 2025
d25d067
Merge branch 'feature/all_app_command_flags' of https://github.com/po…
polyjitter Aug 30, 2025
a18efb8
Readability improvements
polyjitter Aug 30, 2025
919222a
Removed global handling (not needed), fixed oversights with optional …
polyjitter Aug 30, 2025
f361228
Re-privated add_optional_fields (unprivated for testing)
polyjitter Aug 30, 2025
1dad529
Added missing documentation for integration_types
polyjitter Aug 30, 2025
7b12e67
Fixes for global handling removal
polyjitter Aug 30, 2025
195e74a
Removed logger requirement - unintentional debugging leftover.
polyjitter Aug 30, 2025
d76af21
I have no idea why the formatter did that to the moduledoc.
polyjitter Aug 30, 2025
b0b8956
Don't use apply unless it's necessary
polyjitter Aug 30, 2025
ebbe59c
Putting `alias Nosedrum.Storage` on its own line to hopefully make th…
polyjitter Aug 30, 2025
cf66795
Correcting the right alias...
polyjitter Aug 30, 2025
7adb8c5
Hopefully made add_optional_fields easier to read
polyjitter Aug 31, 2025
413506d
More readability improvements
polyjitter Aug 31, 2025
5abe7c0
Final readability improvements
polyjitter Aug 31, 2025
8e08b23
Consistency to align with map names across codebase.
polyjitter Sep 8, 2025
b958611
Fixing comments, backwards compatibility for permissions, minor doc fix.
polyjitter Sep 16, 2025
3e4e40a
Merge branch 'feature/all_app_command_flags' of https://github.com/po…
polyjitter Sep 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 69 additions & 9 deletions lib/nosedrum/application_command.ex
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ defmodule Nosedrum.ApplicationCommand do
"""
@moduledoc since: "0.4.0"

alias Nostrum.Permission
alias Nostrum.Struct.{Embed, Interaction}

@typedoc """
Expand Down Expand Up @@ -225,6 +226,21 @@ defmodule Nosedrum.ApplicationCommand do
value: String.t() | number()
}

@typedoc """
An interaction context, which dictates where a command may be used (servers, DMs directly with
the bot user, or all other DMS).

See callback `c:contexts/0` for more examples.
"""
@type context :: :guild | :bot_dms | :private_channel

@typedoc """
An installation context for a command (guild or user install).

See callback `c:integration_types/0` for more examples.
"""
@type installation_context :: :guild_install | :user_install

@doc """
Returns one of `:slash`, `:message`, or `:user`, indicating what kind of application command this module represents.
"""
Expand Down Expand Up @@ -281,16 +297,54 @@ defmodule Nosedrum.ApplicationCommand do
@callback options() :: [option]

@doc """
An optional callback that returns a bitset for the required default permissions to run this command.
An optional callback that returns a list of atoms for the required default permissions to run this command.

Example callback that requires that the user has the permission to ban members to be able to see and execute this command
## Example

```elixir
def default_member_permissions, do:
Nostrum.Permission.to_bitset([:ban_members])
```
```elixir
def default_member_permissions, do: [:ban_members, :kick_members, :manage_roles]
```
"""
@callback default_member_permissions() :: String.t()
@callback default_member_permissions() :: [Permission.t()]

@doc """
An optional callback that returns a boolean, determining whether a command is
[age-restricted](https://discord.com/developers/docs/interactions/application-commands#agerestricted-commands).

Defaults to `false`.

## Example
```elixir
def nsfw, do: true
```
"""
@callback nsfw() :: boolean()

@doc """
An optional callback that returns a list of interaction contexts.

Only applies to globally-scoped commands.

If not set, this will default to guild and DMs directly with the bot user.

# Example
```elixir
def contexts, do: [:guild, :private_channel, :bot_dms]
```
"""
@callback contexts() :: [context]

@doc """
An optional callback that determines which installation context the command may be used in.

If not set, this will default to all installation contexts.

# Example
```elixir
def integration_types, do: [:guild_install, :user_install]
```
"""
@callback integration_types() :: [installation_context]

@doc """
Execute the command invoked by the given `t:Nostrum.Struct.Interaction.t/0`. Returns a `t:response/0`
Expand All @@ -317,8 +371,14 @@ defmodule Nosedrum.ApplicationCommand do
`Nostrum.Api.create_global_application_command/2` or
`Nostrum.Api.create_guild_application_command/3`
"""

@callback update_command_payload(map) :: map

@optional_callbacks [options: 0, default_member_permissions: 0, update_command_payload: 1]
@optional_callbacks [
options: 0,
default_member_permissions: 0,
nsfw: 0,
contexts: 0,
integration_types: 0,
update_command_payload: 1
]
end
2 changes: 1 addition & 1 deletion lib/nosedrum/component_interaction.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ defmodule Nosedrum.ComponentInteraction do
@doc """
Handle message component interactions.

Behaves the same way as the `Nosedrum.ApplicationCommand.command/1` callback.
Behaves the same way as the `c:Nosedrum.ApplicationCommand.command/1` callback.
"""
@callback message_component_interaction(
interaction :: Nostrum.Struct.Interaction.t(),
Expand Down
134 changes: 103 additions & 31 deletions lib/nosedrum/storage/dispatcher.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
defmodule Nosedrum.Storage.Dispatcher do
@moduledoc """
An implementation of `Nosedrum.Storage`, dispatching Application Command Interactions to the appropriate modules.



"""
@moduledoc since: "0.4.0"
@behaviour Nosedrum.Storage
Expand All @@ -11,18 +14,43 @@ defmodule Nosedrum.Storage.Dispatcher do
alias Nostrum.Api.ApplicationCommand
alias Nostrum.Struct.Interaction

@option_type_map %{
sub_command: 1,
sub_command_group: 2,
string: 3,
integer: 4,
boolean: 5,
user: 6,
channel: 7,
role: 8,
mentionable: 9,
number: 10,
attachment: 11
require Logger

@type_map %{
options: %{
sub_command: 1,
sub_command_group: 2,
string: 3,
integer: 4,
boolean: 5,
user: 6,
channel: 7,
role: 8,
mentionable: 9,
number: 10,
attachment: 11
},
contexts: %{
guild: 0,
bot_dms: 1,
private_channel: 2
},
integration_types: %{
guild_install: 0,
user_install: 1
},
commands: %{
slash: 1,
user: 2,
message: 3
}
}

@optional_fields %{
nsfw: 0,
default_member_permissions: 0,
contexts: 0,
integration_types: 0
}

## Api
Expand Down Expand Up @@ -227,19 +255,13 @@ defmodule Nosedrum.Storage.Dispatcher do
[]
end

payload =
%{
type: parse_type(command.type()),
name: name
}
|> put_type_specific_fields(command, options)
|> apply_payload_updates(command)

if function_exported?(command, :default_member_permissions, 0) do
Map.put(payload, :default_member_permissions, command.default_member_permissions())
else
payload
end
%{
type: parse_type(command.type()),
name: name
}
|> put_type_specific_fields(command, options)
|> add_optional_fields(command)
|> apply_payload_updates(command)
end

# This seems like a hacky way to unwrap the outer list...
Expand Down Expand Up @@ -276,19 +298,15 @@ defmodule Nosedrum.Storage.Dispatcher do

defp parse_type(type) do
Map.fetch!(
%{
slash: 1,
user: 2,
message: 3
},
@type_map.commands,
type
)
end

defp parse_option_types(options) do
Enum.map(options, fn
map when is_map_key(map, :type) ->
updated_map = Map.update!(map, :type, &Map.fetch!(@option_type_map, &1))
updated_map = Map.update!(map, :type, &Map.fetch!(@type_map.options, &1))

if is_map_key(updated_map, :options) do
parsed_options = parse_option_types(updated_map[:options])
Expand Down Expand Up @@ -336,4 +354,58 @@ defmodule Nosedrum.Storage.Dispatcher do
payload
end
end

defp add_optional_fields(payload, command) do
fun = fn {name, arity}, acc ->
if command |> function_exported?(name, arity) do
acc |> add_field(command, name)
else
acc
end
end

Enum.reduce(@optional_fields, payload, fun)
end

defp normalize_permissions(permissions) when is_list(permissions) do
Nostrum.Permission.to_bitset(permissions)
end

defp normalize_permissions(permissions) when is_integer(permissions) do
Logger.warning("""
DEPRECATION: Returning a bitset integer from default_member_permissions is deprecated.
Please return a list of Nostrum.Permission.t() atoms instead.

For compatibility, integer bitsets will still be accepted and translated internally,
but this may be removed in a future release.
""")

permissions
end

defp add_field(payload, command, :default_member_permissions) do
Map.put(
payload,
:default_member_permissions,
command.default_members_permissions() |> normalize_permissions()
)
end

defp add_field(payload, command, :contexts) do
Map.put(
payload,
:contexts,
command.contexts() |> Enum.map(&@type_map.contexts[&1])
)
end

defp add_field(payload, command, :integration_types) do
Map.put(
payload,
:integration_types,
command.integration_types() |> Enum.map(&@type_map.integration_types[&1])
)
end

defp add_field(payload, command, name), do: Map.put(payload, name, apply(command, name, []))
end
Loading