Fergun.Interactive is an addon that provides interactive functionality to Discord commands.
This is a fork of Discord.InteractivityAddon that adds several features, including more customization and support for interactions (buttons and select menus).
-
Methods for sending and deleting a message after a timeout
-
Methods for receiving incoming messages, reactions, or interactions
-
Fully customizable paginator:
- Uses pages that can be navigated through reactions or buttons
- Supports button customization (emote, label, style, etc.)
- Includes two types of paginators: static and lazy-loaded
- Supports restricting usage to specific users
- Provides canceled and timeout pages
- Supports timeout and cancellation via a special option or cancellation token
- Supports actions that are executed when a paginator stops, such as modifying/deleting the message or removing/disabling the reactions/components
- Supports extension methods that can be used in any paginator builder
- Supports custom paginators, inheriting from the
Paginator
andPaginatorBuilder
classes - Allows jumping (skipping) to a specific page using message input or modals (more info here)
-
Fully customizable selection:
- Uses a list of options for users to select from
- Supports messages, reactions, buttons, and select menus
- Supports restricting usage to specific users
- Provides success, canceled, and timeout pages
- Supports timeout and cancellation via a special option or cancellation token
- Supports actions that are executed when a selection stops, such as modifying/deleting the message or removing/disabling the reactions/components
- Supports extension methods that can be used in any selection builder
- Fully generic, supports any type of option (by providing a string/emote converter for that type)
- Supports custom selections, inheriting from the
BaseSelection
andBaseSelectionBuilder
classes
-
Install via NuGet
-
Add the
InteractiveService
into your service provider:
using Fergun.Interactive;
...
var provider = new ServiceCollection()
.AddSingleton(new InteractiveConfig { DefaultTimeout = TimeSpan.FromMinutes(5) }) // Optional config
.AddSingleton<InteractiveService>()
...
- Inject the service via DI (constructor/property injection).
The Example Bot contains multiple examples with comments. The default prefix is !
.
Example modules:
-
Waiting for socket entities (messages, reactions, etc.)
- Wait for a message (
!next message
) - Wait for a reaction (
!next reaction
) - Wait for an interaction (
!next interaction
)
- Wait for a message (
-
Selection
- Simple selection (
!select simple
) - Emote selection (
!select emote
) (for selections using reactions/buttons as input) - Emote selection 2 (
!select emote2
) - Selection that allows choosing multiple options (
!select multi
) - Selection with extra features (
!select extra
) - Menu (
!select menu
) (How to reuse a selection message)
- Simple selection (
-
Pagination
- Static paginator (
!paginator static
) - Lazy paginator (
!paginator lazy
) - Image paginator (
!paginator img [query]
)
- Static paginator (
-
Customization
- Selection with custom button colors (
!custom button
) - Multi selection (
!custom select
) (Selection message with multiple select menus) - Extension methods in builders (
!custom extension
)
- Selection with custom button colors (
Q: Why the paginator/selection doesn't do anything after I press a reaction/button? / I'm getting an "A MessageReceived handler is blocking the gateway task." message in the console
A: You're blocking the gateway task with a method from the interactive service. Make sure your command is running in a different thread using RunMode.Async
:
[Command("command", RunMode = RunMode.Async)]
public async Task Command()
...
Q: Why is my reaction/message to the paginator/selection not automatically deleted even if I specified to delete valid or invalid responses?
A: The bot doesn't have the ManageMessages
permission in the channel you're using the paginator/selection. This is required to delete messages and reactions.
Q: When responding an interaction with a paginator/selection, Why does the response message have no components even if I specified to use buttons or select menus?
- A: Your paginator only has one page. The library doesn't include components in this case.
- A: You're not passing the correct response type. The default value is
ChannelMessageWithSource
, but if you're deferring the interaction explicitly or implicitly (viaAlwaysAcknowledgeInteractions
in the client config), you'll have to use eitherDeferredChannelMessageWithSource
(send a message) orDeferredUpdateMessage
(update a message).
A: Discord doesn't support support reactions in ephemeral messages. Why would you do that anyways?
A: Currently these actions are not supported with ephemeral messages. More info here.
A: Ephemeral messages now support the cancellation/timeout/success actions with some limitations.
There's more info about the limitations in the description of the ephemeral
parameter, in SendPaginatorAsync()
and SendSelectionAsync()
.
A: Currently no, but I'm planning to add support for multiple input types.
A: Yes, update to the latest version.
Q: How can I get the last interaction of a paginator/selection to use it somewhere else, eg. send a modal?
A: You can get the interaction that stopped the paginator/selection through the StopInteraction
property, in InteractiveMessageResult
.
Note that this interaction will already be deferred by default (if nothing else has already been done to the interaction before, like update the message).
You can prevent the library from doing this, use the following options in InteractiveService
s' config:
var collection = new ServiceCollection()
...
.AddSingleton(new InteractiveConfig
{
DeferStopPaginatorInteractions = false,
DeferStopSelectionInteractions = false
})
.AddSingleton<InteractiveService>()
...
-
Paginators now support buttons.
-
Selections now support buttons and select menus.
-
Merged
MessageSelection
andReactionSelection
intoSelection
. -
Added
EmoteConverter
andStringConverter
toSelectionBuilder
. These properties are used to properly convert the generic options in the selection into the options that can be used to receive the incoming inputs, like messages (fromStringConverter
), reactions (fromEmoteConverter
), buttons and select menus (emotes and labels) -
Added
EqualityComparer
toSelectionBuilder
. This is used to determine there are no duplicate options. -
Added
EmoteSelectionBuilder
andEmoteSelectionBuilder<TValue>
, they are a variant ofSelectionBuilder
that uses emotes as input and provides overriden properties with default values, making them ready to use in selections using reactions and buttons. -
Added
InteractiveStatus
toInteractiveResult
, containing all the possible status of an interactive result. -
In
PaginatorBuilder
, nowEmotes
,WithEmotes()
andAddEmote()
are namedOptions
,WithOptions()
andAddOption()
, respectively. -
In the methods that waits for a socket entity (
NextMessageAsync
,NextReactionAsync
, etc.), now thebool
parameter inaction
returns whether the entity passed the filter (the previous behavior was the same but inverted, not sure if this was intended). -
Now the paginator/selection builders implement the fluent builder pattern using recursive generics. This makes creating custom builders much easier.
-
Now multiple input types (messages, reactions, buttons, etc.) can be used in a single paginator/selection.
-
Now
SendPaginatorAsync()
andSendSelectionAsync()
returns anInteractiveMessageResult
. This is the same asInteractiveResult
but contains the message that has the paginator/selection. -
Added a
messageAction
parameter toSendPaginatorAsync()
andSendSelectionAsync()
. This allows to execute any action after the message containing the paginator/selection is sent or modified. -
Now the paginators don't reset their internal timeout timer when a valid input is received. This option can be enabled again using the
resetTimeoutOnInput
parameter. -
Now
SendPaginatorAsync()
only sends a message and returns when a paginator with a single page is passed. -
When using paginators/selections with reactions, now the process of adding the initial reactions will be canceled if the paginator/selection is canceled.
-
Added more post-execution actions to paginators and selections. Now it's possible to tell the paginator/selection to do the following after a cancellation/timeout (or a valid input in case of selections):
DeleteInput
(remove the reactions/buttons/select menu from the message)DisableInput
(disable the buttons/select menu from the message)
Plus the previously existing options:
ModifyMessage
(Modifies the message to the Cancelled/Timeout/Success page)DeleteMessage
These options can also be combined, so you can modify the message and delete/disable the input. Note that using the option
DeleteMessage
will override any other option, andDeleteInput
/DisableInput
can't be used at the same time (for obvious reasons). -
Now the incoming inputs (messages, reactions, interactions) are handled via callbacks. These callbacks are stored in a dictionary. This eliminates the need to subscribe to a local event handler each time an input is received, since now they are received in a single event handler, and also allows to cancel/remove/dispose any callback.
-
The following methods now wait for completion:
DelayedSendMessageAndDeleteAsync()
DelayedDeleteMessageAsync()
DelayedSendFileAndDeleteAsync()
If you don't want to wait for completion, simply discard the Task:
_ = Interactive.DelayedSendMessageAndDeleteAsync(...);