how to pass variables from one interaction into the next?

this is my current workflow concept: 1. user runs slash command 2. modal pops up, user enters email 3. bot validates email against regex, API service 4. if no account is found on service that matches the email, a message is provided with the option for the bot to create an account on the service on behalf of the user using the provided email 5. user selects the "Create Account" button when the user enters their email in the initial modal, it is stored as a variable in the modal interaction handler. how do i pass that variable into the button handler? is there a way to do this internally without explicitly exporting the variable?
Solution:
npm
@sapphire/string-store
High-capacity raw data storage in UTF-16 strings. Latest version: 1.0.0, last published: 4 days ago. Start using @sapphire/string-store in your project by running npm i @sapphire/string-store. There are no other projects in the npm registry using @sapphire/string-store.
Jump to solution
32 Replies
Ben
Ben2mo ago
You can store the information in the buttons custom id. I'm pretty sure the recently released string store package can help with getting lots of info into an out of button ids
FM
FMOP2mo ago
oh no way. how does that work? is it something like
new ButtonBuilder()
.setCustomId('myButton', 'myVar')
new ButtonBuilder()
.setCustomId('myButton', 'myVar')
i went to look at the sapphire docs, but not sure if I was looking in the right spot for this.
Solution
kyra
kyra2mo ago
npm
@sapphire/string-store
High-capacity raw data storage in UTF-16 strings. Latest version: 1.0.0, last published: 4 days ago. Start using @sapphire/string-store in your project by running npm i @sapphire/string-store. There are no other projects in the npm registry using @sapphire/string-store.
kyra
kyra2mo ago
It can store 1600 bits unaligned, this means it can fit from 1600 booleans to 25 snowflakes in custom_id You can fit an array of 100 16-bit integers (5 digits)
FM
FMOP2mo ago
interesting. so i create the store, save it to a variable, and pass that variable as the .setCustomId argument?
kyra
kyra2mo ago
Strings are stored very efficiently in half the size as well, so a string of 10 characters gets stored as 5 You first define the schema somewhere:
// Require the store classes
import { SchemaStore, Schema, t } from '@sapphire/string-store';

const Id = {
AgeUpdate: 0,
StrengthUpdate: 1,
Planet: 2,
User: 3
};

// Create the store
export const store = new SchemaStore()
// Add a schema with an age field stored as a int32:
// Schema<Id.AgeUpdate, { age: number }>
.add(new Schema(Id.AgeUpdate).int32('age'))
// Add a schema with a strength field stored as a float32:
// Schema<Id.StrengthUpdate, { strength: number }>
.add(new Schema(Id.StrengthUpdate).float32('strength'));
// Require the store classes
import { SchemaStore, Schema, t } from '@sapphire/string-store';

const Id = {
AgeUpdate: 0,
StrengthUpdate: 1,
Planet: 2,
User: 3
};

// Create the store
export const store = new SchemaStore()
// Add a schema with an age field stored as a int32:
// Schema<Id.AgeUpdate, { age: number }>
.add(new Schema(Id.AgeUpdate).int32('age'))
// Add a schema with a strength field stored as a float32:
// Schema<Id.StrengthUpdate, { strength: number }>
.add(new Schema(Id.StrengthUpdate).float32('strength'));
Then you use it:
import { store } from '#lib/custom-id'; // wherever you export the file

// Serialize an `Id.AgeUpdate` object into a string containing:
// - The schema ID (0)
// - The age field (20)
const customId = store.serialize(Id.AgeUpdate, { age: 20 }).toString();
new ButtonBuilder()
.setCustomId(customId);
import { store } from '#lib/custom-id'; // wherever you export the file

// Serialize an `Id.AgeUpdate` object into a string containing:
// - The schema ID (0)
// - The age field (20)
const customId = store.serialize(Id.AgeUpdate, { age: 20 }).toString();
new ButtonBuilder()
.setCustomId(customId);
(That'll take just 3 characters of the 100, if you're curious. 1 for the schema ID and 2 for the 20)
FM
FMOP2mo ago
i see. and then i just deserialize in the button handler?
kyra
kyra2mo ago
Yup!
FM
FMOP2mo ago
nice.
kyra
kyra2mo ago
It's strongly typed so you'll get that as:
import { store } from '#lib/custom-id'; // wherever you export the file

const data = store.deserialize(customId);
// ^? { id: Id.AgeUpdate, age: number } | { id: Id.StrengthUpdate, strength: number }
import { store } from '#lib/custom-id'; // wherever you export the file

const data = store.deserialize(customId);
// ^? { id: Id.AgeUpdate, age: number } | { id: Id.StrengthUpdate, strength: number }
FM
FMOP2mo ago
that's perfect. this is going to simplify things so much for me. thank you so much @Ben @kyra 🩵🩷🤍🩷🩵 🙏
kyra
kyra2mo ago
You're welcome! And yeah, it makes many things easier, I'm still trying to migrate my bots to string-store
kyra
kyra2mo ago
In one of my bots, I want to store the entirety of a Connect Four game state, which is: - 42 cells of 3 states - 2 snowflakes (both players) - The turn And I can store it as
new Schema(Id.ConnectFour)
.fixedLengthArray('board', t.uint2, 42)
.fixedLengthArray('players', t.snowflake, 2)
.bit('turn');
new Schema(Id.ConnectFour)
.fixedLengthArray('board', t.uint2, 42)
.fixedLengthArray('players', t.snowflake, 2)
.bit('turn');
And that'll use... 28 characters (27 bytes and 5 bits) Meaning I can pretty much store the entire C4 state 3 times in custom_id, which is very hard to accomplish otherwise, and still have space left
FM
FMOP2mo ago
that's impressive. even more so in its simplicity.
kyra
kyra2mo ago
Even more reasons to migrate to it! Wait my bad, 28 bytes is merely 14 characters, so there's space for 7.5 times that in custom_id
FM
FMOP2mo ago
holy fuck. well then! i'm not moving around a whole lot of data, but this is insanely useful even for the little bit i'm moving around. this is the first bot i've built that makes use of modals and buttons, tbh. I may be doing something wrong. This is my store, StripeSchemaStore.js:
import { SchemaStore, Schema, t } from '@sapphire/string-store';

const Id = {
Email: 0
}

export const stripeStore = new SchemaStore()
.add(new Schema(Id.Email).string('email'));
import { SchemaStore, Schema, t } from '@sapphire/string-store';

const Id = {
Email: 0
}

export const stripeStore = new SchemaStore()
.add(new Schema(Id.Email).string('email'));
and i use it in the modal interaction handler, StripeModalSubmit.js:
import { stripeStore } from '../service/stripe/StripeSchemaStore.js';

// a bunch of other logic

// email is captured

const stripeEmail = interaction.fields.getTextInputValue('stripeModalEmailEntry');

// time to bundle it up and send it
// to the button handler

const stripeAccountEmailStore = stripeStore
.serialize(**Id**.Email, { email: stripeEmail })
.toString();
import { stripeStore } from '../service/stripe/StripeSchemaStore.js';

// a bunch of other logic

// email is captured

const stripeEmail = interaction.fields.getTextInputValue('stripeModalEmailEntry');

// time to bundle it up and send it
// to the button handler

const stripeAccountEmailStore = stripeStore
.serialize(**Id**.Email, { email: stripeEmail })
.toString();
except now when i run it, i get an error that comes back saying Id is not defined. if this works like i'm understanding from your example, i should be able to use the store anywhere necessary, correct? i shouldn't have to define the store every time, i just need to pass in values to the store and serialize them as needed. do i understand this correctly?
kyra
kyra2mo ago
You need to export the Id object By the way, are you using TS? Or JS?
FM
FMOP2mo ago
gotcha. i shall give that a go. i'm using js.
kyra
kyra2mo ago
So basically in #SchemaStore narrowed to never, I noticed that in TS you may need to use enum or as const, but in JS you have neither, so it's either you make a JSDoc to strict type the values, or you export a list of constants rather than an object
FM
FMOP2mo ago
🤔 interesting. thank you for the insight. i'll try the jsdoc way first. what do i use for the parse string for the button handler to pick up the interaction? i pass the variable into the .setCustomId method, but i'm stuck getting the button handler to pick it up:
const stripeAccountEmailStore = stripeStore.serialize(Id.Email, { email: stripeEmail }).toString();

const stripeAccountCreate = new ButtonBuilder()
.setCustomId(stripeAccountEmailStore)
.setLabel('Create Account')
.setStyle(ButtonStyle.Primary);
const stripeAccountEmailStore = stripeStore.serialize(Id.Email, { email: stripeEmail }).toString();

const stripeAccountCreate = new ButtonBuilder()
.setCustomId(stripeAccountEmailStore)
.setLabel('Create Account')
.setStyle(ButtonStyle.Primary);
and in the button handler:
parse(interaction) {
// Log.info(interaction.customId);
if (interaction.customId !== 'stripeAccountEmailStore') return this.none();
return this.some();
}
parse(interaction) {
// Log.info(interaction.customId);
if (interaction.customId !== 'stripeAccountEmailStore') return this.none();
return this.some();
}
it wants a normal string so bad. i feel like i'm missing something totally rookie.
kyra
kyra2mo ago
Uhmmmmm I didn’t take into account this problem in the framework Once I get back home, among the other things I want to PR to string-store, I’ll get something to fast-get the IDs
FM
FMOP2mo ago
okay cool. i'm glad i'm not missing something stupid lol hi kyra! is there an update on this? i perused the #GitHub Logs and didn't see anything. with regard to how the framework currently handles things, should i just build the button logic directly in the modal interaction-handler for the time being? i don't want to export the variable as that just seems ... messy. open to suggestions though.
Amgelo
Amgelo2mo ago
I'm pretty sure you'd simply just need to parse the string and see if it matches? I haven't used sapphire but it sounds like
parse(interaction) {
try {
const deserialized = store.deserialize(interaction.customId);
return deserialized.id === Id.SomeId ? this.some() : this.none();
} catch (e) {
return this.none();
}
}
parse(interaction) {
try {
const deserialized = store.deserialize(interaction.customId);
return deserialized.id === Id.SomeId ? this.some() : this.none();
} catch (e) {
return this.none();
}
}
I assume kyra meant that with some change to fast-get ids you wouldn't need the try catch block but it's still possible
FM
FMOP2mo ago
took a little tweaking, but this works, thank you!
kyra
kyra2mo ago
Yeah sorry, I got focused on /result since that needed some tweaks
FM
FMOP2mo ago
no apologies needed. i'm super grateful these packages exist, and these conversations help identify potential improvements. thank you for your efforts, kyra 🙏
WhacK
WhacK2mo ago
This is a really cool package can’t wait to try it. Going to migrate my sapphire bot to incorporate it
Favna
Favna2mo ago
I have to do the same for @Dragonite still :rolling_eyes:
kyra
kyra4w ago
Hello there
kyra
kyra4w ago
I opened a PR to add a method to get the ID of the schema: https://github.com/sapphiredev/utilities/pull/838 -# @Amgelo, @FM
kyra
kyra4w ago
This way you can replace the method with:
parse(interaction) {
return store.getIdentifier(interaction.customId) === Id.SomeId
? this.some()
: this.none();
}
parse(interaction) {
return store.getIdentifier(interaction.customId) === Id.SomeId
? this.some()
: this.none();
}
The only time in which it'd return an unexpected result is when you pass in an empty string, which is never the case here
FM
FMOP4w ago
awesome! thank you. just subscribed to the pr.
Want results from more Discord servers?
Add your server