️👤 Session
A Session
is an object that holds the state of an interaction session.
The workflow of a session goes like this:
- The session is instantiated by some object when an interaction is received.
- It's passed to
SessionManager#start()
. This ultimately starts the session viaSession#start()
. - The session replies to its
SessionStartInteraction
, with components that route back to the session. - The user uses one of these components. The bot receives it, sends it to the
SessionManager
, and it's ultimately routed toSession#update()
. - The session is updated, replying to that new interaction, and the cycle repeats.
- Eventually the session ends or expires, calling
Session#end()
. - The result is extracted via
Session#getResult()
, which is used to emit the event, and to resolve the end promise.
To avoid issues, it's recommended that the object that instantiates the session doesn't reply to the interaction.
Instead, that should be done by the session itself (on Session#start()
).
👷 Creation
You can create a session by either:
- Extending
AbstractSession
from@framework
(recommended). - Implementing the
Session
interface from@core
.
After creating it, start it via SessionManager#start()
.
- Extending AbstractSession
- Implementing Session
import { AbstractSession, SessionUpdateInteraction } from '@nyx-discord/framework';
class MySession extends AbstractSession {
public async start() {
const buttonId = this.customId.clonePush('button').build();
const updateButton = new ButtonBuilder()
.setCustomId(buttonId)
.setLabel('Update')
.setStyle(ButtonStyle.Primary);
await this.startInteraction.reply('Hello!');
}
public async end() {
const reply = await this.startInteraction.fetchReply();
await reply.edit('Goodbye!');
}
protected async handleButton(interaction: SessionUpdateInteraction) {
await interaction.reply('Hello again!');
}
}
// Somewhere in your code, like inside a command...
const sessionId = 'mySessionId'; // Ideally randomly generated
const session = new MySession(bot, sessionId, interaction);
await bot.getSessionManager().start(session);
import { Session } from '@nyx-discord/core';
class MySession implements Session {
// ...
}
const session = new MySession(/** ... */);
await bot.getSessionManager().start(session);
📝 Updating
A session is "updated" when an interaction whose customId
property refers to the session is received. The session
is then updated via Session#update()
, and it's responsible for answering to that interaction.
When extending AbstractSession
you don't need to implement Session#update()
. By default, it redirects interactions
depending on its type:
#handleButton()
forButtonInteractions
.#handleSelectMenu()
forAnySelectMenuInteractions
.#handleModal()
forModalMessageModalSubmitInteraction
.
Override these instead, depending on what components you are sending.
When the session is updated, Session#update()
(or each handle method) must return whether the session's TTL has to be
refreshed or not.
Returning true
on every update refreshes the session's TTL when updated, while returning always false
makes the
TTL to never refresh, like a "timed race" session.
💬 Custom Ids
The SessionCustomIdCodec
is the object responsible for generating and validating customIds that refer to a session,
called "Session Custom Ids".
Additionally, it can generate a CustomIdBuilder
, which you can use to append extra tokens to your customId. When
receiving it, you can use the codec again to extract the extra tokens using a StringIterator
.
const codec = bot.getSessionManager().getCustomIdCodec();
// Creating a simple customId.
const simpleCustomId = codec.deserializeToObjectId(session);
// Creating a customId builder.
const builder = codec.createCustomIdBuilder(session);
builder.push('foo').push('bar');
// Throws an error if the result has more than 100 characters.
const tokenCustomId = builder.build();
// Extracting the extra tokens.
const iterator = codec.createIteratorFromCustomId(tokenCustomId);
iterator.getTokens(); // ['foo', 'bar']
-
To avoid "magic values" where you guess the order of the tokens, you can use
CustomIdBuilder#setAt(index, value)
. You can then useStringIterator#getAt(index)
to get the value, saving the index as a constant somewhere. -
For utility purposes,
AbstractSession
saves its own customId in aAbstractSession#customId
property. However, make sure to always clone it before appending values to it. -
Discord doesn't allow duplicated customIds on a single message, even if they come from different component types. You can push values like
'button'
,'select'
,'modal'
to avoid that issue.
✨ Component Examples
- Button
- Select Menu
- Modal
const buttonId = builder.clonePush('button').build();
const button = new ButtonBuilder()
.setCustomId(buttonId)
.setLabel('A session button')
.setStyle(ButtonStyle.Primary);
The CustomIdCodec
only uses the customId from the select menu itself, not their options.
const selectId = builder.clonePush('select').build();
const select = new SelectMenuBuilder()
.setCustomId(selectId)
.setPlaceholder('A session select menu')
.addOptions([
new StringSelectMenuOptionBuilder()
.setLabel('Option 1')
.setValue('option1'),
new StringSelectMenuOptionBuilder()
.setLabel('Option 2')
.setValue('option2'),
]);
const modalId = builder.clonePush('modal').build();
const modal = new ModalBuilder()
.setCustomId(modalId)
.setTitle('A session modal')
.addComponents([
new TextInputBuilder()
.setCustomId('textInput')
.setLabel('Text input')
.setStyle(TextInputStyle.Short),
]);
🏆 Result
A session can provide a "result", the type of which is defined by the session's generic argument. When the session ends,
the result obtained via Session#getResult()
is used to emit the event, and to
resolve the end promise.
Use the result to make sessions that "aggregate" some input data from the user, which can then be used in other code.
class MySession extends AbstractSession<number> {
// ...
public end() {
this.result = 1;
}
}
// Somewhere in your code, like inside a command...
const sessionId = 'mySessionId'; // Ideally randomly generated
const session = new MySession(bot, sessionId, interaction);
await bot.getSessionManager().start(session);
const endData = await session.getEndPromise();
endData.result; // 1
⌛ Session TTL
Once a session starts, it has a given TTL until it expires. When the session is updated, Session#update()
returns
whether the TTL has to be refreshed or not.
Returning true
on every update refreshes the session's TTL when updated, while returning always false
makes the
TTL to never refresh, like a "timed race" session.
You can specify a TTL:
- Overriding
ttl
when extendingAbstractSession
from@framework
. - Returning it on
Session#getTTL()
when implementingSession
from@core
.
🚧 Filters
A SessionFilter
is an object that can be used to filter out session starts (SessionStartFilter
)
or updates (SessionUpdateFilter
). They are provided by the session, via Session#getStartFilter()
and
Session#getUpdateFilter()
.
To add a filter to your session, first create the filter:
- Extending
AbstractSessionStartFilter
orAbstractSessionUpdateFilter
from@framework
(recommended). - Implementing the
SessionStartFilter
orSessionUpdateFilter
interface from@core
.
After creating it, add it to your session class:
- Overriding
startFilter
orupdateFilter
when extendingAbstractSession
from@framework
. - Returning it on
Session#getStartFilter()
orSession#getUpdateFilter()
when implementingSession
from@core
.
For more information on filters, check the 🛡️ Session Interception documentation.