In this example, we will create an app that increments the value of a number. Users will send a slash command with a number as an argument. Initially, the number’s value is incremented from 0 and stored. After that, each time the slash command is used, the new stored value is incremented with the specified number.
Create the slash command class
Create a TypeScript slash command file in your app folder. You can use the following command to generate a template file:
rc-apps generate
Select the Slash command extension option and provide a name for the file, for example numberPersis
. The file is automatically created in a slashCommands
folder located at the root of your app folder.
First, we will give a name to the slash command and create an association using the
MISC
enumeration model. The class looks like this initially:
import { IHttp, IModify, IPersistence, IPersistenceRead, IRead } from '@rocket.chat/apps-engine/definition/accessors';
import { IApp } from '@rocket.chat/apps-engine/definition/IApp';
import { RocketChatAssociationModel, RocketChatAssociationRecord } from '@rocket.chat/apps-engine/definition/metadata';
import { ISlashCommand, SlashCommandContext } from '@rocket.chat/apps-engine/definition/slashcommands/index';
import { IUser } from '@rocket.chat/apps-engine/definition/users';
import { IRoom } from '@rocket.chat/apps-engine/definition/rooms';
export class numberPersis implements ISlashCommand {
app: IApp;
constructor(app) {
this.app = app;
}
public command = 'numberpersis';
public i18nDescription = 'Increment a number';
public providesPreview = false;
public i18nParamsExample = '';
public associations: Array<RocketChatAssociationRecord> = [ // this is the association that will be used to store the value
new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, 'increment_command'),
];
}
In this class, we will add a function called
getValue
to read the value of the number:
public async getValue(read: IRead): Promise<any> { // this function will read the value from the database
let result: number;
const persistenceRead = read.getPersistenceReader();
try {
const records: any = (await persistenceRead.readByAssociations(this.associations));
if (records.length) {
result = records[0].value;
} else {
result = 0;
}
} catch (err) {
console.error(err);
return ({
success: false,
error: err,
});
}
return ({
success: true,
result,
});
}
From the IRead interface, the getPersistenceReader
method is used to read the number. If a valid number exists in the record, it is stored in the variable result
. Otherwise, the result
is zero.
Now we will add an executor function. In this, we will add subcommands to the slash command so that we can get the number from the user or reset its value to zero. We will also add a help subcommand to display the slash command options. The number calculation will be carried out within this function.
public async executor(
context: SlashCommandContext,
read: IRead,
modify: IModify,
http: IHttp,
persistence: IPersistence,
): Promise<void> {
const sender = context.getSender(); // the user who sent the command
const room = context.getRoom(); // the room where the command was sent
const [subcommand] = context.getArguments(); // the subcommand
const appBot = await read.getUserReader().getAppUser() as IUser; // the user(bot) who sends relevant message
const helpText = `use \`/numberpersis [number]\` to increment the value by a given integer. \n` +
`use \`/numberpersis [reset|r]\` to reset the value to 0\n` +
`use \`/numberpersis [help|h]\` to display this message`;
if (!subcommand) { // if no argument is provided
var message = `No Subcommand :thinking: \n ${helpText}`;
await sendNotification(modify, room, sender, message);
} else {
switch (subcommand) {
case 'reset': // if the user enters the reset argument
case 'r':
await persistence.updateByAssociations(this.associations, { value: 0 }, true); // update the value to 0
message = `@${sender.username} reset the value to 0.`;
await sendMessage(modify, room, appBot, message); // calling the sendMessage function
return;
case 'help': // if the user enters the help argument
case 'h':
await sendNotification(modify, room, sender, helpText); // calling the sendNotification function to display the helptext
return;
default:
if (isNaN(parseInt(subcommand, 10))) { // if the subcommand is not a number
message = `@${sender.username} you need to provide a number to increment the value by.`;
await sendNotification(modify, room, sender, message); // calling the sendNotification function to display the helptext
return;
} else {
const initialValue = parseInt(await this.getValue(read).then((value) => value.result), 10); // get the current value
const finalValue = await initialValue + parseInt(subcommand, 10); // calculate the new value
await persistence.updateByAssociations(this.associations, { value: finalValue }, true); // update the value
message = `@${sender.username} incremented the value of ${initialValue} by ${subcommand}.\n` +
`The new value is ${finalValue} `;
await sendMessage(modify, room, appBot, message);
}
}
}
}
}
The updateByAssociations
method is used to update the record. For details, see the IPersistence interface.
Finally, we will define the
sendMessage
andsendNotification
functions to send messages to the channel. These functions are added after thenumberPersis
class.
export async function sendMessage(
modify: IModify,
room: IRoom,
sender: IUser,
message: string,
): Promise<string> {
const msg = modify.getCreator().startMessage()
.setSender(sender)
.setRoom(room)
.setText(message);
return await modify.getCreator().finish(msg);
}
export async function sendNotification(
modify: IModify,
room: IRoom,
sender: IUser,
message: string
): Promise<void> {
let msg = modify.getCreator().startMessage().setRoom(room).setText(message);
// uncomment below if you want the notification to be sent by the sender instead of the app bot user
// msg = msg.setSender(sender);
// lets build a really simple block
const block = modify.getCreator().startMessage();
// we want this block to have a Text supporting MarkDown
block.setBlocks([
{
type: 'section',
text: {
type: 'mrkdwn',
text: message
}
}
])
// now let's set the blocks in our message
block.setRoom(room).setText(message);
// and finally, notify the user with the IMessage
return await modify.getNotifier().notifyUser(sender, msg.getMessage());
}
Update the main app class
Next, we will update the app’s main class to call the slash command class.
// ...import commands...
import { numberPersis } from './numberPersis'; // remember to import the class here
export class PersistenceSampleApp extends App {
constructor(info: IAppInfo, logger: ILogger, accessors: IAppAccessors) {
super(info, logger, accessors);
}
public async extendConfiguration(configuration: IConfigurationExtend, environmentRead: IEnvironmentRead): Promise<void> {
await configuration.slashCommands.provideSlashCommand(
new numberPersis(this)
)
}
}
Test the app
Deploy the app to your workspace or package the app and upload it to the workspace.
In any channel, send the slash command
/numberPersis
. Try sending it without arguments. The app bot should send a message like this:
Now send the slash command with numbers. For example,
/numberPersis 5
, and then again with/numberPersis 6
. The app bot will return the updated values:
If you send
/numberPersis r
or/numberPersis reset
, the value will be reset to 0:
If you send
/numberPersis h
or/numberPersis help
, the help menu will be displayed:
With this example, we can see how the value is stored and updated using associations.