Rocket.Chat Apps
Making a Rocket.Chat App
Rocket.Chat can be easily extended with powerful Apps.
Here is a step by step of how I created MemeBuddy for 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:
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:
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:
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}`)
);
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.
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:
- Use of startLiveChatMessage instead of startMessage.
- Setting the visitor for sending the message within LiveChat.
- Notifiers are not applicable for LiveChat messages.
Now MemeBuddy works on both the team chat and the omnichannel LiveChat widget!
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.