Rocket.Chat Apps

Making a Rocket.Chat App

Rohan Lekhwani
6 min readApr 10, 2021

Rocket.Chat can be easily extended with powerful Apps.

Here is a step by step of how I created MemeBuddy for Rocket.Chat.

MemeBuddy in action on Rocket.Chat

Setting up shop

First you will need a server to test the app, there exists a multitude of setup options available.

On your development machine, you will need to install the Rocket.Chat Apps-Engine CLI. Follow these docs.

Creating MemeBuddy App

Generate a starter for the app using the CLI:

Starter generation using Apps-Engine CLI

When chatting, you can trigger MemeBuddy at any time to generate a Meme. This is done through a slash command.

Crafting Slash commands

To code the slash command, create a commands directory and a meme.ts file within it. This file would have an executor function that tells the app what to do when someone sends a /meme within a channel.

public async executor( context: SlashCommandContext, read: IRead, modify: IModify, http: IHttp, persistence: IPersistence): Promise<void> {console.log(“A small step for man, a giant leap for mankind”)}

Register the MemeCommand in the generated MemeBuddyApp.ts file. Do it within the extendConfiguration method with the following line of code.

await configuration.slashCommands.provideSlashCommand(new MemeCommand(this));

Congratulations! Your first Rocket.Chat App is completed

Loading App on server

Get it onto your server, run the following command:

rc-apps deploy --url http://localhost:3000 --username <username> --password <password>

See it in action. Run the /meme command in any channel. You should see a log message from Neil Armstrong.

A small step for man, a giant leap for mankind.

OK, buddy! That was simple, but the app doesn’t do much. Let’s add the memes.

Note — You can sideload apps directly onto a production server without going through the Marketplace. Sideloading apps requires enabling development mode on the server. This can be done by navigating to Administration -> General and scrolling down to the Apps section to switch on the “Enable Development Mode” radio button.

Calling APIs of external systems — Gimme the Memes

Apps in Rocket.Chat often reach out to large external systems to get data or have work done. Apps call out on RESTful APIs. MemeBuddy calls D3vd’s Meme API for fetching memes.

In MemeBuddy, we use http.get() and supply REST arguments subreddit and number of memes to a hosted server at:

https://meme-api.herokuapp.com/gimme/wholesomememes/1

The result is:

JSON response from the Meme API

Adding interactive UI Components Into Chat Flow

Time to get fancy, let’s send that message into the channel together with interactive clickable buttons for the users to select their memes.

After all everyone has a unique meme-o-scope. Right? (bad joke, noted)

Rocket.Chat Apps use UIKit to build rich message blocks. If you’re already familiar with Slack’s BlockKit , the IModify interface is where everything happens.

Create a file named initiatorMessage.ts in a lib directory. Add an initiatorMessage function:

export async function initiatorMessage({ data, read, persistence, modify, http}: { data; read: IRead; persistence: IPersistence; modify: IModify; http: IHttp;})
{
// Function Body}

To send a simple text message into a channel is 2 lines of code:

const greetBuilder = await modify
.getCreator()
.startMessage()
.setRoom(data.room)
.setText(`Hey _${data.sender.username}_ !`);
await modify.getCreator().finish(greetBuilder);

IModify helps to send rich messages that can contain interactive user interface elements. Use its getBlockBuilder() to get the BlockBuilder class and insert block elements within messages. Here, I add three buttons for the categories.

block.addActionsBlock({
blockId: “subreddits”,
elements: [
block.newButtonElement({
actionId: “memeSelect”,
text: block.newPlainTextObject(“Programmer”),
value: “programmerhumor”,
style: ButtonStyle.PRIMARY,}),
block.newButtonElement({
// … Other keys similar to above
value: “dankmemes”,}),
block.newButtonElement({
// … Other keys similar to above
value: “wholesomememes”,}),
],});

Making interactive messages visible only to sender

We are in a public channel but only the user interacting with MemeBuddy should see the buttoned message. Use the getNotifier and notifyUser methods for this.

await modify
.getNotifier()
.notifyUser(data.sender, builder.getMessage());

The message stays around only for the user session. A /meme would now give us the following:

Sending an interactive message to the channel

The buttons don’t do anything yet. Let’s fix it.

Wiring button actions to make REST API calls

Clicking the button should trigger its category of meme to be fetched from Reddit via the REST APIs.

Wire it up with executeBlockActionHandler() in the MemeBuddyApp class.

Calling context.getInteractionData() we get the action details. If the actionId is memeSelect it implies that one of the three buttons was clicked.

data.value is returned by the button upon clicking (the subreddit to fetch memes from in our case). Use it to get the memes via REST API.

public async executeBlockActionHandler(context: UIKitBlockInteractionContext, http: IHttp, //… Other parameters){const data = context.getInteractionData();
const { actionId } = data;
switch (actionId) {case “memeSelect”: {try {const memeResponse = await http.get(`https://meme-api.herokuapp.com/gimme/${data.value}/1`);// Send memeResponse through a message herereturn {
success: true,
};
} catch (err){// Handle Error }} // end of case block} // end of switch block} // end of function block

Sending graphics via message attachment

Memes are sent in-channel via message attachments.

Create a class MemeAsImageAttachment within the lib directory that implements IMessageAttachment.

import { IMessageAttachment } from ‘@rocket.chat/apps-engine/definition/messages’;export class MemeAsImageAttachment implements IMessageAttachment
{
imageUrl?: string;
constructor(imgUrl: string){
this.imageUrl = imgUrl;
}

We can now build an attachment into the message using IMesssageExtender::addAttachment().

const memeSender = await modify
.getCreator()
.startLivechatMessage()
.setVisitor(data.visitor)
.setText(`*${memeResponse.data.memes[0].title}*`)
.addAttachment(
new MemeAsImageAttachment(`${memeResponse.data.memes[0].url}`)
);
MemeBuddy! Completed for team-chat channels on Rocket.Chat 🚀

This completes MemeBuddy, ready for action in any team-chat channel.

Adapting App for LiveChat — Omnichannel Operation

Rocket.Chat is also a great platform for building real-time omnichannel customer service websites and community engagement portals. Its LiveChat widget is used by millions of users on a daily basis.

LiveChat customer service bot in action

Adapting MemeBuddy to work with the LiveChat widget is straightforward.

There is no slash command in LiveChat, but recognition of “trigger words” in messages can take its place.

Parsing trigger words in LiveChat messages

To access every message sent, make the MemeBuddyApp class implement IPostMessageSent interface and hook executePostMessageSent(). Here, I check for the trigger word :meme:

if (message.room.type !== “l”) {
return;
}
if (message.text === “:meme:”) {const data = {
room: message.room,
sender: message.sender,
};
await initiatorMessage({ data, read, persistence, modify, http });} // end of if block} // end of function block

Interactive button clicks within LiveChat messages are handled through the executeLiveChatBlockActionHandler() method.

The differences when sending messages to LiveChat are:

  1. Use of startLiveChatMessage instead of startMessage.
  2. Setting the visitor for sending the message within LiveChat.
  3. Notifiers are not applicable for LiveChat messages.

Now MemeBuddy works on both the team chat and the omnichannel LiveChat widget!

Calling MemeBuddy within LiveChat

Publish your amazing app for the world

You now know everything there is to know about building Rocket.Chat apps.

If you need to review anything, find the MemeBuddy repo here.

Rocket.Chat operates a Rocket.Chat Apps Marketplace that can make your (free or paid) app available to every single Rocket.Chat installation in the universe.

I can’t wait to see what innovative new app you will build!

Rohan Lekhwani is an open source contributor and enthusiast. You can connect with him on LinkedIn, GitHub, Twitter, Rocket.Chat and his website.

This article wouldn’t have been possible without the invaluable guidance of Sing Li and Murtaza Patrawala from Rocket.Chat. I’m extremely thankful to them for being generous with their time.

--

--

Rohan Lekhwani
Rohan Lekhwani

Written by Rohan Lekhwani

Open source enthusiast and contributor | Contributing @ Rocket.Chat | Previously @ Gojek, DRDO, C-DAC