"use strict";
module.exports = function(Joi){
//const Joi = require('joi');
const actionSchema = Joi.object().keys({
type: Joi.string().required().valid('postback', 'call', 'url', 'share', 'location'),
postback: Joi.when('type', { is: 'postback', then: Joi.alternatives().try([Joi.string(), Joi.object()]), otherwise: Joi.any().forbidden() }),
phoneNumber: Joi.when('type', { is: 'call', then: Joi.string().required(), otherwise: Joi.any().forbidden() }),
url: Joi.when('type', { is: 'url', then: Joi.string().required().uri(), otherwise: Joi.any().forbidden() }),
label: Joi.string().optional().trim(),
imageUrl: Joi.string().uri().optional(),
channelExtensions: Joi.object().optional()
});
const actionsSchema = Joi.array().items(actionSchema);
const attachmentSchema = Joi.object().keys({
type: Joi.string().required().valid('file', 'video', 'audio', 'image'),
url: Joi.string().uri()
});
const cardSchema = Joi.object().keys({
title: Joi.string().required(),
description: Joi.string().optional(),
imageUrl: Joi.string().uri().optional(),
url: Joi.string().uri().optional(),
actions: actionsSchema.optional(),
channelExtensions: Joi.object().optional()
});
const locationSchema = Joi.object().keys({
title: Joi.string().optional(),
url: Joi.string().uri().optional(),
latitude: Joi.number().required(),
longitude: Joi.number().required()
});
const textConversationMessageSchema = Joi.object().keys({
type: Joi.string().required().valid('text'),
text: Joi.string().required().trim(),
actions: actionsSchema.optional(),
globalActions: actionsSchema.optional(),
channelExtensions: Joi.object().optional()
});
const cardConversationMessageSchema = Joi.object().keys({
type: Joi.string().required().valid('card'),
layout: Joi.string().required().valid('horizontal', 'vertical'),
cards: Joi.array().items(cardSchema).min(1),
actions: actionsSchema.optional(),
globalActions: actionsSchema.optional(),
channelExtensions: Joi.object().optional()
});
const attachmentConversationMessageSchema = Joi.object().keys({
type: Joi.string().required().valid('attachment'),
attachment: attachmentSchema.required(),
actions: actionsSchema.optional(),
globalActions: actionsSchema.optional(),
channelExtensions: Joi.object().optional()
});
const locationConversationMessageSchema = Joi.object().keys({
type: Joi.string().required().valid('location'),
location: locationSchema.required(),
actions: actionsSchema.optional(),
globalActions: actionsSchema.optional(),
channelExtensions: Joi.object().optional()
});
const rawConversationMessageSchema = Joi.object().keys({
type: Joi.string().required().valid('raw'),
payload: Joi.required(),
});
const postbackConversationMessageSchema = Joi.object().keys({
type: Joi.string().required().valid('postback'),
postback: Joi.alternatives().try([Joi.string(), Joi.object()]).required(),
text: Joi.string().optional().trim(),
actions: actionsSchema.optional(),
globalActions: actionsSchema.optional(),
channelExtensions: Joi.object().optional()
});
const agentConversationMessageSchema = Joi.object().keys({
type: Joi.string().required().valid('agentRequest', 'agentRequestResponse', 'agentConversationHistory', 'agentJoined', 'agentLeft', 'botConversationEnded', 'agent', 'botToAgentText')
}).options({'allowUnknown': true});
const conversationMessageSchema = Joi.alternatives().try(textConversationMessageSchema, cardConversationMessageSchema, attachmentConversationMessageSchema, locationConversationMessageSchema, postbackConversationMessageSchema, rawConversationMessageSchema, agentConversationMessageSchema);
/**
* The Bots MessageModel is a utility class that helps creating and validating
* a message structure representing a bot message. This utility is used by the Bots Custom Components Conversation SDK, and can also
* be used independently of the SDK.
*
* This utility can be used in a server side Nodejs environment, or in the browser.
* When used in Nodejs, require the module 'joi' use it to initialize the class. When used in browser, require the module 'joi-browser' instead.
*
* A MessageModel class instance can be instantiated using the constructor taking the payload
* that represents the message. The payload is then parsed and validated.
*
* The payload can be created using the various static utility methods for creating different
* response types including TextConversationMessage, CardConversationMessage, AttachmentConversationMessage, etc.
*/
const MessageModel = class {
/**
* To create a MessageModel object using a string or an object representing the message.
* The object is one of a known Common Message Model message type such as Text, Card, Attachment, Location, Postback, Agent or Raw type. This object can be created
* using the static methods in this class. To support older message format, the object can also be of the 'choice' type or
* text.
*
* The input will be parsed. If it is a valid message, messagePayload() will return the valid message object. If not, the message content can be retrieved via payload().
* @constructor
* @param {string|object} payload - The payload to be parsed into a MessageModel object
*/
constructor(payload){
this._payload = payload;
this._messagePayload = null;
this._validationError = null;
this._parse();
}
_parse(){
if (this._payload){
if (this._payload.type) {
if (this._payload.type === 'choice'){
this._payload = MessageModel._parseLegacyChoice(this._payload);
}
} else {
if (typeof this._payload === 'string') {
this._payload = MessageModel.textConversationMessage(this._payload);
} else {
if (this._payload.choices) {
this._payload = MessageModel._parseLegacyChoice(this._payload);
} else if (this._payload.text) {
this._payload = MessageModel.textConversationMessage(this._payload.text);
}
}
}
var result = MessageModel.validateConversationMessage(this._payload);
if (result === true) {
this._messagePayload = Object.assign({},this._payload);
} else {
this._validationError = result;
}
}
}
/**
* Retrieves the validated common message model payload.
* @return {object} The common message model payload.
*/
messagePayload() {
return this._messagePayload;
}
/**
* If messagePayload() returns null or if isValid() is false, this method can be used
* to retrieve the payload that could not be converted to a Conversation Message Model payload.
* @return {object} The payload which may not comply to Conversation Message Model
*/
rawPayload() {
return this._payload;
}
/**
* returns if the instance contains a valid message according to Conversation Message Model
* @return {boolean} if the message is valid common message model.
*/
isValid() {
return (!!this._messagePayload);
}
/**
* Retrieves the validation error messages, if any. Use if messagePayload() returns null or isValid() is false, signifying validation errors;
* @return {object} The validation error(s) encountered when converting the payload to the Conversation Message Model.
*/
validationError() {
return this._validationError;
}
static _parseLegacyChoice(payload) {
if (payload.choices && payload.choices instanceof Array && payload.choices.length > 0) {
var postbacks = payload.choices.map(function(choice){
return MessageModel.postbackActionObject(choice, null, choice);
});
return MessageModel.textConversationMessage(payload.text, postbacks);
} else {
return payload;
}
}
/**
* Static utility method to create a TextConversationMessage
* @return {object} A TextConversationMessage.
* @param {string} text - The text of the message payload.
* @param {object[]} [actions] - A list of actions related to the text.
*/
static textConversationMessage(text, actions) {
var instance = {
type: 'text',
text: text
};
if (actions) {
instance.actions = actions;
}
return instance;
}
static _baseActionObject(type, label, imageUrl){
var instance = {
type: type
};
if (label){
instance.label = label;
}
if (imageUrl){
instance.imageUrl = imageUrl;
}
return instance;
}
/**
* Static utility method to create a postback Action. A label or an imageUrl is required.
* @return {object} A postbackActionObject.
* @param {string} [label] - label of the action.
* @param {string} [imageUrl] - image to show for the action.
* @param {object|string} postback - object or string to send as postback if action is taken.
*/
static postbackActionObject(label, imageUrl, postback){
var instance = this._baseActionObject('postback', label, imageUrl);
instance.postback = postback;
return instance;
}
/**
* Static utility method to create a url Action. A label or an imageUrl is required.
* @return {object} A urlActionObject.
* @param {string} [label] - label of the action.
* @param {string} [imageUrl] - image to show for the action.
* @param {string} url - url to open if action is taken.
*/
static urlActionObject(label, imageUrl, url){
var instance = this._baseActionObject('url', label, imageUrl);
instance.url = url;
return instance;
}
/**
* Static utility method to create a call Action. A label or an imageUrl is required.
* @return {object} A callActionObject.
* @param {string} [label] - label of the action.
* @param {string} [imageUrl] - image to show for the action.
* @param {string} phoneNumber - phoneNumber to call if action is taken.
*/
static callActionObject(label, imageUrl, phoneNumber){
var instance = this._baseActionObject('call', label, imageUrl);
instance.phoneNumber = phoneNumber;
return instance;
}
/**
* Static utility method to create a location Action. A label or an imageUrl is required.
* @return {object} A locationActionObject.
* @param {string} [label] - label of the action.
* @param {string} [imageUrl] - image to show for the action.
*/
static locationActionObject(label, imageUrl){
return this._baseActionObject('location', label, imageUrl);
}
/**
* Static utility method to create a share Action. A label or an imageUrl is required.
* @return {object} A shareActionObject.
* @param {string} [label] - label of the action.
* @param {string} [imageUrl] - image to show for the action.
*/
static shareActionObject(label, imageUrl){
return this._baseActionObject('share', label, imageUrl);
}
/**
* Static utility method to create a card object for CardConversationMessage
* @return {object} A Card.
* @param {string} title - The title of the card.
* @param {string} [description] - The description of the card.
* @param {string} [imageUrl] - URL of the image.
* @param {string} [url] - URL for a hyperlink of the card.
* @param {object[]} [actions] - A list of actions available for this card.
*/
static cardObject(title, description, imageUrl, url, actions){
var instance = {
title: title
};
if (description) {
instance.description = description;
}
if (imageUrl) {
instance.imageUrl = imageUrl;
}
if (url) {
instance.url = url;
}
if (actions) {
instance.actions = actions;
}
return instance;
}
/**
* Static utility method to create a CardConversationMessage
* @return {object} A CardConversationMessage.
* @param {string} [layout] - 'vertical' or 'horizontal'. Whether to display the cards horizontally or vertically. Default is vertical.
* @param {object[]} cards - The list of cards to be rendered.
* @param {object[]} [actions] - A list of actions for the cardConversationMessage.
*/
static cardConversationMessage(layout, cards, actions) {
var response = {
type: 'card',
layout: layout||'vertical',
cards: cards
};
if (actions) {
response.actions = actions;
}
return response;
}
/**
* Static utility method to create an AttachmentConversationMessage
* @return {object} An AttachmentConversationMessage.
* @param {string} type - type of attachment - file, image, video or audio.
* @param {string} url - the url of the attachment.
* @param {object[]} [actions] - A list of actions for the attachmentConversationMessage.
*/
static attachmentConversationMessage(type, url, actions) {
var attachment = {
type: type,
url: url
};
var response = {
type: 'attachment',
attachment: attachment
};
if (actions) {
response.actions = actions;
}
return response;
}
/**
* Static utility method to create a LocationConversationMessage
* @return {object} A LocationConversationMessage.
* @param {number} latitude - The latitude.
* @param {number} longitude - The longitude.
* @param {string} [title] - The title for the location.
* @param {string} [url] - A url for displaying a map of the location.
* @param {object[]} [actions] - A list of actions for the locationConversationMessage.
*/
static locationConversationMessage(latitude, longitude, title, url, actions) {
var location = {
latitude: latitude,
longitude: longitude
};
if (title) {
location.title = title;
}
if (url) {
location.url = url;
}
var response = {
type: 'location',
location: location
};
if (actions) {
response.actions = actions;
}
return response;
}
/**
* Static utility method to create a postackConversationMessage
* @return {object} A PostbackConversationMessage.
* @param {object|string} postback - object or string to send as postback
* @param {string} [label] - The label associated with the postback.
* @param {object[]} [actions] - A list of actions for the postbackConversationMessage.
*/
static postbackConversationMessage(postback, label, actions) {
var response = {
type: 'postback',
postback: postback
};
if (label) {
response.text = label;
}
if (actions) {
response.actions = actions;
}
return response;
}
/**
* Static utility method to create a RawConversationMessage
* @return {object} A RawConversationMessage.
* @param {object} payload - The raw (channel-specific) payload to send.
*/
static rawConversationMessage(payload) {
return {
type: 'raw',
payload: payload
};
}
/**
* Static utility method to add channel extensions to a payload
* @return {object} A ConversationMessage with channel extensions.
* @param {object} message - The message to add channel extensions to.
* @param {string} channel - The channel type ('facebook', 'webhook', etc) to set extensions on
* @param {object} extensions - The channel-specific extensions to be added.
*/
static addChannelExtensions(message, channel, extensions) {
if (message && channel && extensions){
if (!message.channelExensions) {
message.channelExtensions = {};
}
message.channelExtensions[channel] = (message.channelExtensions[channel] ? Object.assign(message.channelExtensions[channel], extensions) : extensions);
}
return message;
}
/**
* Static utility method to add global actions to a payload
* @return {object} A ConversationMessage with global actions.
* @param {object} message - The message to add global actions to.
* @param {object} globalActions - The global actions to be added.
*/
static addGlobalActions(message, globalActions) {
if (message && globalActions){
message.globalActions = globalActions;
}
return message;
}
/**
* Static utility method to validate a common ConversationMessage
* @return {boolean|object} true if valid; return Validation Error object (error & value) if invalid
* @param {object} payload - The payload object to be verified
*/
static validateConversationMessage(payload) {
var result = conversationMessageSchema.validate(payload);
if (result && !result.error) {
return true;
} else {
return result;
}
}
};
return MessageModel;
};