Increment Number Example

Prev Next

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

  1. 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.

  1. 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'),
    ];
}
  1. 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.

  1. 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.

  1. Finally, we will define the sendMessage and sendNotification functions to send messages to the channel. These functions are added after the numberPersis 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.