Now that we have learned about the event interfaces, let's go through some examples to implement them. On this page, we will follow three examples:
- Messaging governance
- Notify messages
- Content inspection
In this example, we will use the IPreMessageSentPrevent
interface to control whether a particular message should be sent to a channel. We are using our Hello World app to test this.
- Open your app folder in Visual Studio and select the main app file, in this case,
HelloWorldApp.ts
. - To the
HelloWorldApp
class, add theIPreMessageSentPrevent
interface as follows:
export class TestApp extends App implements IPreMessageSentPrevent {
- As soon as we add the indication of
implements IPreMessageSentPrevent
we see that Visual Studio displays an error that it cannot find the interface and shows the option to quickly fix it in a tooltip. Click it to display different import options that would fix the issue. Import the following interface:
{% code overflow="wrap" %}
import { IPreMessageSentPrevent } from '@rocket.chat/apps-engine/definition/messages/IPreMessageSentPrevent';
{% endcode %}
- Now Visual Studio will display an error that a certain method part of the interface has its implementation missing with the option to fix it quickly. Accept the suggested fix. Visual Studio adds two methods of the interface that are not implemented. The methods will throw an error when executed. But now, we have a skeleton code to work with:
checkPreMessageSentPrevent?(message: IMessage, read: IRead, http: IHttp): Promise<boolean> {
throw new Error('Method not implemented.');
}
executePreMessageSentPrevent(message: IMessage, read: IRead, http: IHttp, persistence: IPersistence): Promise<boolean> {
throw new Error('Method not implemented.');
}
- With the
checkPreMessageSentPrevent
method, we will only check the messages from channels other than general. Visual Studio helps you navigate through the APIs of the classes of the objects by suggesting the available methods. For this example, you can see that the method variablemessage
has a room attribute which has aslugifiedName
string attribute. Update the method as follows:
checkPreMessageSentPrevent?(message: IMessage, read: IRead, http: IHttp): Promise<boolean> {
return message.room.slugifiedName != 'general';
}
- Now Visual Studio displays an error that the method signature is not compatible with returning a
Promise<boolean>
. To fix this issue, addasync
to the method signature. - Next, implement the
executePreMessageSentPrevent
method. If the message equals "test", we prevent it from being published. - Your main app file should look something like this:
{% code lineNumbers="true" fullWidth="true" %}
import {
IAppAccessors,
IHttp,
ILogger,
IPersistence,
IRead,
} from '@rocket.chat/apps-engine/definition/accessors';
import { App } from '@rocket.chat/apps-engine/definition/App';
import { IMessage } from '@rocket.chat/apps-engine/definition/messages';
import { IPreMessageSentPrevent } from '@rocket.chat/apps-engine/definition/messages/IPreMessageSentPrevent';
import { IAppInfo } from '@rocket.chat/apps-engine/definition/metadata';
export class HelloWorldApp extends App implements IPreMessageSentPrevent {
constructor(info: IAppInfo, logger: ILogger, accessors: IAppAccessors) {
super(info, logger, accessors);
logger.debug('Hello, World!');
}
async checkPreMessageSentPrevent?(message: IMessage, read: IRead, http: IHttp): Promise<boolean> {
return message.room.slugifiedName != 'general';
}
async executePreMessageSentPrevent(message: IMessage, read: IRead, http: IHttp, persistence: IPersistence): Promise<boolean> {
return message.text == 'test';
}
}
{% endcode %}
- Save the file and deploy your app.
- If we send the
‘test’
message in a channel other than general, it should not be published (it will appear grayed out). If the message is something different it will get sent. As for the room general, all messages will be sent including‘test’
as shown in the following screenshots:
In this example, we will notify the general channel whenever a message is sent anywhere else. Here we will implement the IPostMessageSent
interface.
- Like the previous example, in the main app file (in this case,
HelloWorldApp.ts
), we will add thecheck
andexecute
methods from theIPostMessageSent
interface as shown in the code below. - The
check
method confirms that the general channel exists and fetches it. - In the
execute
method, for the general channel, we create a message containing information about the message published, the room, and the sender by getting the values fromsendMessage
. ThemessageBuilder
allows us to define richly formatted messages.
{% code lineNumbers="true" fullWidth="true" %}
import {
IAppAccessors,
IHttp,
ILogger,
IModify,
IPersistence,
IRead,
} from '@rocket.chat/apps-engine/definition/accessors';
import { App } from '@rocket.chat/apps-engine/definition/App';
import { IMessage, IPostMessageSent } from '@rocket.chat/apps-engine/definition/messages';
import { IAppInfo } from '@rocket.chat/apps-engine/definition/metadata';
import { IRoom } from '@rocket.chat/apps-engine/definition/rooms/IRoom';
import { IUser } from '@rocket.chat/apps-engine/definition/users/IUser';
export class HelloWorldApp extends App implements IPostMessageSent {
constructor(info: IAppInfo, logger: ILogger, accessors: IAppAccessors) {
super(info, logger, accessors);
logger.debug('Hello, World!');
}
async checkPostMessageSent?(message: IMessage, read: IRead, http: IHttp): Promise<boolean> {
return message.room.slugifiedName != 'general';
}
async executePostMessageSent(message: IMessage, read: IRead, http: IHttp, persistence: IPersistence, modify: IModify): Promise<void> {
const general = await read.getRoomReader().getByName('general');
if (!general) {
return;
}
const msg = `@${message.sender.username} said "${message.text}" in #${message.room.displayName}`;
const author = await read.getUserReader().getAppUser();
await this.sendMessage(general, msg, author?author:message.sender, modify);
}
private async sendMessage(room: IRoom, textMessage: string, author: IUser, modify: IModify) {
const messageBuilder = modify.getCreator().startMessage({
text: textMessage,
} as IMessage);
messageBuilder.setRoom(room);
messageBuilder.setSender(author);
return modify.getCreator().finish(messageBuilder);
}
}
{% endcode %}
- Now save, deploy, and test the app. You should be able to see the warning messages posted to the general channel when writing to any other channel.
A typical use case of the previous two examples is to control the content of the information being exchanged. For instance, we could use regular expressions matching inappropriate words to flag them.
Some apps that implement content inspection are:
For our Hello World app, let's look into inspecting file attachments.
- In our main app file, we will implement
IPreFileUpload
and print a message notifying the user that the attached file is inspected. - If the file type is
text/plain
, we will log the file's content also.
{% code lineNumbers="true" fullWidth="true" %}
import {
IAppAccessors,
IHttp,
ILogger,
IModify,
IPersistence,
IRead,
} from '@rocket.chat/apps-engine/definition/accessors';
import { App } from '@rocket.chat/apps-engine/definition/App';
import { AppMethod, IAppInfo } from '@rocket.chat/apps-engine/definition/metadata';
import { IRoom } from '@rocket.chat/apps-engine/definition/rooms/IRoom';
import { IFileUploadContext, IPreFileUpload } from '@rocket.chat/apps-engine/definition/uploads';
import { IUser } from '@rocket.chat/apps-engine/definition/users/IUser';
export class HelloWorldApp extends App implements IPreMessageSentPrevent, IPostMessageSent, IPreFileUpload {
public async [AppMethod.EXECUTE_PRE_FILE_UPLOAD](context: IFileUploadContext, read: IRead, http: IHttp, persis: IPersistence, modify: IModify): Promise<void> {
console.log('ContentInspectionExampleAppApp - File Uploaded - Name: ' + context.file.name);
console.log('ContentInspectionExampleAppApp - File Uploaded - Type: ' + context.file.type);
console.log('ContentInspectionExampleAppApp - File Uploaded - Size: ' + context.file.size);
if (context.file.type == 'text/plain') {
console.log('ContentInspectionExampleAppApp - File Uploaded - Content: ' +
String.fromCharCode.apply(null, context.content));
}3.
//if file was bad we could throw an exception
//throw new FileUploadNotAllowedException('File is Bad');
const user = await read.getUserReader().getById(context.file.userId);
const room = await read.getRoomReader().getById(context.file.rid);
if (room) {
await this.notifyMessage(room, read, user, 'File inspected - Check logs');
}
}
private async notifyMessage(room: IRoom, read: IRead, sender: IUser, message: string): Promise<void> {
const notifier = read.getNotifier();
const messageBuilder = notifier.getMessageBuilder();
messageBuilder.setText(message);
messageBuilder.setRoom(room);
return notifier.notifyUser(sender, messageBuilder.getMessage());
}
}
{% endcode %}
- Save the file, deploy, and test the app. The notification is like a temporary private message visible only to the user who sent the attachment (if you refresh the page, the notification is gone).
Great! With these examples, you have learned how to implement event interfaces and react to certain events. You have made significant progress in expanding your app!
In the upcoming sections, we will look at another way to extend your app's capabilities by creating interactive user experiences with the Apps-Engine UIKit.