Skip to content

Extensions Developing

Bilal Bassam edited this page Dec 1, 2024 · 2 revisions

Developing Extensions

Because the functionality this extension provides is useless on its own, other extensions are supposed to make use of it to achieve the easier integration with other extensions also making use of interactions, as such we provide a set of APIs to make your life easier.

Hooking into interactionCreate

In a lot of cases you want to hook into the interactionCreate event so you can emit your own events, for example you may want to parse the interaction object and figure out what type of app commands it is and then emit slashCommand event, or to emit modalSubmit events, etc. This is made possible with pre-listeners and post-listeners, here is an example:

local discordia_interactions = require("discordia-interactions")

function discordia_interactions.EventHandler.interaction_create_prelisteners.slashCommands(intr, client)
  if intr.type == enums.interactionType.applicationCommand and intr.data.type == 1 then
    client:emit('slashCommand', intr)
  end
end

function discordia_interactions.EventHandler.interaction_create_prelisteners.autocomplete(intr, client)
  if intr.type == enums.interactionType.applicationCommandAutocomplete and intr.data.type == 1 then
    client:emit('slashCommandAutocomplete', intr)
  end
end

In this example, the extension will execute this listener before interactionCreate has been fired giving you a chance to fire all kind of events. It is also possible to hook a listener to when an interactionCreate even has been fired with interaction_create_postlisteners, the difference between a post-listener and listening directly to interactionCreate is that you are always guaranteed to get this fired after all other listeners have been already fired, as well as that client:removeListener will not affect it (i.e. the end-user can't just remove your extension listeners). You can also find a usage of this in discordia-modals to emit the custom event modalSubmit Note that interactionCreate WILL still be fired, and you cannot prevent it from firing. This is an intentional design and you shall not do anything preventing it from firing.

Resolving User Input

discordia-interactions offers methods that respond with the raw structures of Discord (the only exception is reply, as the Discordia behavior is used for that), this might not be desired to the end-user, as such it is possible for your extension to offer a higher level input with resolvers wrappers.

Wrappers are functions that are called by discordia-interactions, they will take the user input and will be expected to return the raw structure to sent to Discord. When a wrapper returns something, the rest of wrappers are cancelled and the first to match wrapper will be used.

For example, imagine you have a Modal class that represents a higher level representation of a modal, and you want to allow the following usage:

local my_modal = createModal(...)
-- ...
interaction:modal(my_modal)

You can achieve this with discordia_interactions.resolver.modal_resolvers, for example:

table.insert(discordia_interactions.resolver.modal_resolvers, function(content)
  if isInstance(content, Modal) then
    return content:raw()
  elseif type(content) == 'table' and content.id and content.title then
    return Modal(content):raw()
  end
end)

Note: you may also use the map portion of the table, but other extensions may override that.

message_resolvers

This is called on Interaction.reply and Interaction.replyDeferred in addition to the Discordia resolver. You are expected to return a raw Message Structure.

autocomplete_resolvers

This is called on Interaction.autocomplete(choices), you will receive user-provided choices, the resolver is expected to return an Autocomplete Response Structure. See Autocomplete for more information.

modal_resolvers

This is called on Interaction.modal(modal), you will receive user-provided modal, the resolver is expected to return a Modal Structure.

Wrapping resolved values

It is also possible to run callbacks on the resolved value just before sending the request, this allows you to bypass whatever resolver has been used so you can always alter the final result. You will be given the raw data, and you are not expected to return anything.

For example, here is a code snippet to always set message content sent with Interaction:reply/Interaction:replyDeferred even if the user doesn't set it:

table.insert(discordia_interactions.resolver.message_wrappers, function(message)
  if not message.content then
    message.content = "Beep Boop!"
  end
end)

Note: you may also use the map portion of the table, but other extensions may override that.

message_wrappers

Called on Interaction:reply(message)/Interaction:replyDeferred(message) just before sending the message request.

Callback params:

autocomplete_wrappers

Called on Interaction.autocomplete(choices).

Callback params:

modal_wrappers

Called on Interaction.modal(modal).

Callback params:

you will receive user-provided modal, the resolver is expected to return a Text Input Components response.