At the heart of RedBot there’s chat-platform.js, a library to translate messages from different platform (Telegram, Slack, Facebook Messenger, etc) to common payloads in order to be used inside Node-RED. In this way a text message containing “Hello world!” from Facebook or Telegram will be translated into the same payload by the receiver nodes and injected into the flow, the answer to this message (“Hello to you!”) will be then translated and sent back to the platform using the appropriate API call.

The chat-platform.js it’s a kind of framework to build connectors for different platforms, it provides basic functionalities (like handling the chat context, etc) while the implementation details for the specific platform is delegated to chunk of codes called middlewares. It’s very similar to Express.js in it’s philosophy, a middleware is something like this

node.chat.in(message => {  
  return new Promise((resolve, reject) => {    
    if (message.type === 'my-type') {      
      // do something      
      resolve(message);    
     }  
  });
});

A middleware is a function that returns a promise, it receives a message as argument and - in general - resolves the promise with a message or, if an error occurred during the computation, it rejects with an error.

The implementation of chat connector with chat-platform.js consists in two chains of middlewares, one for inbound messages and one for outbound messages, that are executed sequentially when a message is received from the chat platform and when a message is to be sent to the chat platform.

Each middleware generally takes care of one type of message. Keeping up with all chat platform’s API is hard, for this reason keeping the code small, modular and easy to maintain is vital. For example a middleware that handles incoming photo message could be something like this:

node.chat.in(message => {  
  return new Promise((resolve, reject) => {    
    if (message.originalMessage.type === 'picture' && 
        message.originalMessage.url != null
		) {      
			fetch(message.originalMessage.url)        
				.then(
					buffer => {            
						message.payload.content = buffer;            
						message.payload.type = 'photo';            
						resolve(message);          
					}, 
					error => reject(`Error downloading ${message.originalMessage.url}`)        
				);    
		} else {      
			resolve(message);    
		}  
	});
});

The message variable contains the Node-RED message that will go out of the node receiver, this middleware has to detect if the incoming message is a picture, in that case it has to download the photo file and store the retrieved data in a message attribute otherwise let the message flow through the next middleware (the .resolve() in the else block).

The received payload is always stored in the originalMessage key of the message, the content depends on the specific chat platform we’re dealing with and it’s usually where the middleware would sniff for particular keys to try to understand which kind of message is arrived. In case the middleware decides to handle the message, it has to 1. store the content of the message (a string, a buffer, etc.) in the key message.payload.content 2. assign a type to the message, RedBot supports a number of types out of the box (audio, buttons, command, contact, dialog, document, inline-buttons, inline-query, invoice, invoice-shipping, location, message, photo, payment, request, response, video), but new types can be defined in order to expand the platform. 3. resolve the promise with the new message

In case a middleware has handled a message (it has changed the type with message.payload.type = 'a-type';), the rest of the inbound chain of middlewares will be skipped since the incoming message has been already resolved. In case of fatal error, like a broken link, a proper error must be raised with reject(my_error): the error will be shown in the system console and the Node-RED debug panel, so it should be verbose enough to understand want went wrong.

In case the Extend node is used to add a custom message type (like in the first example), it’s recommended to register the message type with

node.chat.registerMessageType('my-type', 'My Type');

in this way the new message type will appear in the drop down menu of the Rules node.

In a very similar way works the outbound chain: in order to extend the chat platform to support a new message type for example bitcoin: