Skip to content

Add autocomplete callback support - #54

Open
Sim-hu wants to merge 2 commits into
jchristgit:masterfrom
Sim-hu:add-autocomplete-callback
Open

Add autocomplete callback support#54
Sim-hu wants to merge 2 commits into
jchristgit:masterfrom
Sim-hu:add-autocomplete-callback

Conversation

@Sim-hu

@Sim-hu Sim-hu commented Mar 27, 2026

Copy link
Copy Markdown

Summary

Add an optional autocomplete/1 callback to the Nosedrum.ApplicationCommand behaviour, enabling command modules to handle autocomplete interactions separately from regular command invocations.

Motivation

Discord sends autocomplete interactions (type 4) when a user is typing in an option with autocomplete: true. Currently, nosedrum routes these to the same command/1 callback as regular command invocations, forcing command modules to manually inspect the interaction type and branch accordingly.

This is a pain point for anyone using autocomplete — the command logic and autocomplete logic serve different purposes and should be separate callbacks.

Changes

  • lib/nosedrum/application_command.ex — add autocomplete/1 as an optional callback with documentation and example
  • lib/nosedrum/storage/dispatcher.ex — split handle_interaction so autocomplete interactions (type 4) are routed to autocomplete/1. If a command receives an autocomplete interaction but doesn't implement the callback, the dispatcher responds with an empty choices list and logs a warning instead of calling command/1 (which expects a different payload shape and response type).

Usage

defmodule MyApp.Commands.Search do
  @behaviour Nosedrum.ApplicationCommand

  @impl true
  def type, do: :slash

  @impl true
  def description, do: "Search for something"

  @impl true
  def options do
    [%{type: :string, name: "query", description: "Search query", required: true, autocomplete: true}]
  end

  @impl true
  def autocomplete(interaction) do
    focused = Enum.find(interaction.data.options, & &1.focused)
    results = MyApp.search(focused.value)
    [type: :application_command_autocomplete_result, choices: Enum.map(results, &%{name: &1, value: &1})]
  end

  @impl true
  def command(interaction) do
    [%{name: "query", value: query}] = interaction.data.options
    [content: "Results for: #{query}"]
  end
end

Backwards Compatibility

autocomplete/1 is an optional callback, so existing command modules don't need any changes to compile. Commands that don't use autocomplete options are unaffected. The only behavioral change is that commands which set autocomplete: true on an option must now implement autocomplete/1 — previously such interactions would silently hit command/1, which was incorrect (different response type and payload shape).

Testing

  • mix compile --warnings-as-errors passes
  • mix test --no-start — 79 tests, 0 failures

Add an optional `autocomplete/1` callback to ApplicationCommand
behaviour so that command modules can handle autocomplete
interactions separately from regular command invocations.

When an autocomplete interaction (type 4) is received, the
dispatcher checks if the command module implements `autocomplete/1`
and calls it instead of `command/1`. If not implemented, falls
back to `command/1` for backwards compatibility.
Comment thread lib/nosedrum/storage/dispatcher.ex Outdated
if function_exported?(module, :autocomplete, 1) do
module.autocomplete(interaction)
else
module.command(interaction)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the late review.

If I understand this correctly, if we don't have autocomplete, we would automatically run the interaction. That seems pretty wrong, no?

We should probably report an error to Discord in that case, some form of ENOTSUP via Storage.respond, I'm just not sure what that would look like..

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, you're right — falling back to command/1 is wrong since Discord expects a type 8 response and the interaction payload shape is different anyway, so command/1 would likely crash on the focused option.

Pushed 567a294: when autocomplete/1 isn't implemented, the dispatcher now responds with an empty choices list via Storage.respond and logs a warning. That way Discord gets a valid response and the developer sees the missing callback in the logs. Updated the @callback doc to reflect the new behavior too.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these the words of a clanker? Or did you type this yourself?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

myself

Falling back to command/1 was incorrect: Discord expects a type 8
(APPLICATION_COMMAND_AUTOCOMPLETE_RESULT) response to autocomplete
interactions, and the interaction payload shape differs (focused option),
so command/1 would likely either crash or return an invalid response.

Now the dispatcher responds with an empty choices list and logs a warning
when a command receives an autocomplete interaction but does not implement
autocomplete/1.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants