UNPKG

voxa

Version:

A fsm (state machine) framework for Alexa, Dialogflow, Facebook Messenger and Botframework apps using Node.js

484 lines (409 loc) 12.9 kB
import * as _ from "lodash"; import { IDirective, IDirectiveClass } from "../../../directives"; import { ITransition } from "../../../StateMachine"; import { IVoxaEvent } from "../../../VoxaEvent"; import { IVoxaReply } from "../../../VoxaReply"; import { DialogflowEvent } from "../DialogflowEvent"; import { FacebookReply } from "./FacebookReply"; function createQuickReplyDirective( contentType: string, key: string, ): IDirectiveClass { return class implements IDirective { public static platform: string = "facebook"; public static key: string = key; constructor( public message: string, public replyArray: IFacebookQuickReply | IFacebookQuickReply[], ) {} public async writeToReply( reply: IVoxaReply, event: IVoxaEvent, transition?: ITransition, ): Promise<void> { const dialogflowReply = reply as FacebookReply; const quickReplies: any[] = []; if (_.isEmpty(this.replyArray)) { quickReplies.push({ content_type: contentType, }); } else { if (!_.isArray(this.replyArray)) { this.replyArray = [this.replyArray]; } _.forEach(this.replyArray, (item) => { quickReplies.push({ content_type: contentType, image_url: item.imageUrl, payload: item.payload, title: item.title, }); }); } let text; try { text = await event.renderer.renderPath(this.message, event); } catch (err) { // THE VALUE SENT IS A REAL STRING AND NOT A PATH TO A VIEW text = this.message; } const facebookPayload = { payload: { facebook: { quick_replies: quickReplies, text, }, }, }; dialogflowReply.fulfillmentMessages.push(facebookPayload); dialogflowReply.source = event.platform.name; } }; } function createGenericTemplateDirective( key: string, templateType: string, ): IDirectiveClass { return class implements IDirective { public static platform: string = "facebook"; public static key: string = key; constructor(public config: string|IFacebookPayloadTemplate) {} public async writeToReply( reply: IVoxaReply, event: IVoxaEvent, transition?: ITransition, ): Promise<void> { const dialogflowReply = (reply as FacebookReply); let configElements: IFacebookElementTemplate[]|undefined; let configButtons: IFacebookGenericButtonTemplate[]|undefined; let configSharable: boolean|undefined; let configText: string|undefined; let configTopElementStyle: FACEBOOK_TOP_ELEMENT_STYLE|undefined; if (_.isString(this.config)) { const payloadTemplate: IFacebookPayloadTemplate = await event.renderer.renderPath(this.config, event); configButtons = payloadTemplate.buttons; configElements = payloadTemplate.elements; configSharable = payloadTemplate.sharable; configText = payloadTemplate.text; configTopElementStyle = payloadTemplate.topElementStyle; } else { configButtons = this.config.buttons; configElements = this.config.elements; configSharable = this.config.sharable; configText = this.config.text; configTopElementStyle = this.config.topElementStyle; } const elements = getTemplateElements(configElements); const facebookPayload: IVoxaFacebookPayloadTemplate = { buttons: configButtons, sharable: configSharable, template_type: templateType, text: configText, top_element_style: configTopElementStyle, }; if (!_.isEmpty(elements)) { facebookPayload.elements = elements; } const customFacebookPayload = { payload: { facebook: { attachment: { payload: _.omitBy(facebookPayload, _.isNil), type: "template", }, }, }, }; dialogflowReply.fulfillmentMessages.push(customFacebookPayload); dialogflowReply.source = event.platform.name; } }; } function getTemplateElements(configElements: IFacebookElementTemplate[]|undefined) { const elements: any[] = []; _.forEach(configElements, (item) => { let defaultAction; if (item.defaultActionUrl) { defaultAction = { fallback_url: item.defaultActionFallbackUrl, messenger_extensions: item.defaultMessengerExtensions, type: "web_url", url: item.defaultActionUrl, webview_height_ratio: item.defaultWebviewHeightRatio, }; defaultAction = _.omitBy(defaultAction, _.isNil); } const buttons = _.map(item.buttons, (x) => { const buttonFormatted: any = _.pick(x, ["payload", "title", "type", "url"]); buttonFormatted.fallback_url = x.fallbackUrl; buttonFormatted.messenger_extensions = x.messengerExtensions; buttonFormatted.webview_height_ratio = x.webviewHeightRatio; return _.omitBy(buttonFormatted, _.isNil); }) as IVoxaFacebookGenericButtonTemplate[]; const elementItem: IVoxaFacebookElementTemplate = { buttons, default_action: defaultAction, image_url: item.imageUrl, subtitle: item.subtitle, title: item.title, url: item.url, }; elements.push(_.omitBy(elementItem, _.isEmpty)); }); return elements; } export class FacebookAccountLink implements IDirective { public static platform: string = "facebook"; public static key: string = "facebookAccountLink"; constructor(public url: string) {} public async writeToReply( reply: IVoxaReply, event: IVoxaEvent, transition?: ITransition, ): Promise<void> { const dialogflowReply = reply as FacebookReply; let renderedUrl; try { renderedUrl = await event.renderer.renderPath(this.url, event); } catch (err) { // THE VALUE SENT IS A REAL STRING AND NOT A PATH TO A VIEW renderedUrl = this.url; } const fulfillmentText = getTextFromTextTemplate(dialogflowReply); const facebookPayload = this.getFacebookPayload(renderedUrl, fulfillmentText); dialogflowReply.source = event.platform.name; dialogflowReply.fulfillmentMessages.push(facebookPayload); } private getFacebookPayload( renderedUrl: string, fulfillmentText: string, ) { return { payload: { facebook: { attachment: { payload: { buttons: [ { type: FACEBOOK_BUTTONS.ACCOUNT_LINK, url: renderedUrl, }, ], template_type: "button", text: fulfillmentText, }, type: "template", }, }, }, }; } } export class FacebookAccountUnlink implements IDirective { public static platform: string = "facebook"; public static key: string = "facebookAccountUnlink"; public async writeToReply( reply: IVoxaReply, event: IVoxaEvent, transition?: ITransition, ): Promise<void> { const dialogflowReply = reply as FacebookReply; const facebookPayload = { payload: { facebook: { attachment: { payload: { buttons: [ { type: FACEBOOK_BUTTONS.ACCOUNT_UNLINK, }, ], template_type: "button", text: getTextFromTextTemplate(dialogflowReply), }, type: "template", }, }, }, }; dialogflowReply.fulfillmentMessages.push(facebookPayload); dialogflowReply.source = event.platform.name; } } export class FacebookSuggestionChips implements IDirective { public static platform: string = "facebook"; public static key: string = "facebookSuggestionChips"; constructor(public suggestions: string | string[]) {} public async writeToReply( reply: IVoxaReply, event: IVoxaEvent, transition?: ITransition, ): Promise<void> { const suggestionChips: any[] = await this.getSuggestionChips(event); const dialogflowReply = reply as FacebookReply; const facebookPayload = { payload: { facebook: { attachment: { payload: { buttons: suggestionChips, template_type: "button", text: getTextFromTextTemplate(dialogflowReply), }, type: "template", }, }, }, }; dialogflowReply.fulfillmentMessages.push(facebookPayload); dialogflowReply.source = event.platform.name; } private async getSuggestionChips( event: IVoxaEvent, ): Promise<string[]> { let options = this.suggestions; if (_.isString(options)) { options = await event.renderer.renderPath(options, event); } const suggestionChips: any[] = []; _.forEach(options, (item) => { const button = { payload: item, title: item, type: "postback", }; suggestionChips.push(button); }); return suggestionChips; } } function getTextFromTextTemplate(dialogflowReply: FacebookReply): string { const textTemplate = _.findLast(dialogflowReply.fulfillmentMessages, (x) => _.has(x, "payload.facebook.text")); if (textTemplate) { _.pull(dialogflowReply.fulfillmentMessages, textTemplate); return textTemplate.payload.facebook.text; } return dialogflowReply.fulfillmentText; } export interface IFacebookQuickReply { imageUrl: string; title: string; payload: string; } export const FacebookQuickReplyLocation = createQuickReplyDirective( "location", "facebookQuickReplyLocation", ); export const FacebookQuickReplyPhoneNumber = createQuickReplyDirective( "user_phone_number", "facebookQuickReplyPhoneNumber", ); export const FacebookQuickReplyText = createQuickReplyDirective( "text", "facebookQuickReplyText", ); export const FacebookQuickReplyUserEmail = createQuickReplyDirective( "user_email", "facebookQuickReplyUserEmail", ); export enum FACEBOOK_BUTTONS { ACCOUNT_LINK = "account_link", ACCOUNT_UNLINK = "account_unlink", ELEMENT_SHARE = "element_share", GAME_PLAY = "game_play", PAYMENT = "payment", PHONE_NUMBER = "phone_number", POSTBACK = "postback", WEB_URL = "web_url", } export enum FACEBOOK_IMAGE_ASPECT_RATIO { HORIZONTAL = "horizontal", SQUARE = "square", } export enum FACEBOOK_WEBVIEW_HEIGHT_RATIO { COMPACT = "compact", TALL = "tall", FULL = "full", } export enum FACEBOOK_TOP_ELEMENT_STYLE { COMPACT = "compact", LARGE = "large", } export interface IFacebookGenericButtonTemplate { fallbackUrl?: string; messengerExtensions?: boolean; payload?: string; title: string; type: FACEBOOK_BUTTONS; url?: string; webviewHeightRatio?: FACEBOOK_WEBVIEW_HEIGHT_RATIO; } export interface IFacebookElementTemplate { buttons?: IFacebookGenericButtonTemplate[]; imageUrl?: string; subtitle?: string; title?: string; defaultActionUrl?: string; defaultActionFallbackUrl?: string; defaultMessengerExtensions?: boolean; defaultWebviewHeightRatio?: FACEBOOK_WEBVIEW_HEIGHT_RATIO; sharable?: boolean; url?: string; } export interface IFacebookPayloadTemplate { buttons?: IFacebookGenericButtonTemplate[]; elements?: IFacebookElementTemplate[]; imageAspectRatio?: FACEBOOK_IMAGE_ASPECT_RATIO; sharable?: boolean; text?: string; topElementStyle?: FACEBOOK_TOP_ELEMENT_STYLE; } export interface IVoxaFacebookGenericButtonTemplate { fallback_url?: string; messenger_extensions?: boolean; payload?: string; title: string; type: FACEBOOK_BUTTONS; url?: string; webview_height_ratio?: FACEBOOK_WEBVIEW_HEIGHT_RATIO; } export interface IVoxaFacebookElementTemplate { buttons?: IFacebookGenericButtonTemplate[]; default_action?: { fallback_url?: string; messenger_extensions?: boolean; type?: FACEBOOK_BUTTONS; url?: string; webview_height_ratio?: FACEBOOK_WEBVIEW_HEIGHT_RATIO; }; image_url?: string; subtitle?: string; title?: string; sharable?: boolean; url?: string; } export interface IVoxaFacebookPayloadTemplate { buttons?: IFacebookGenericButtonTemplate[]; elements?: IFacebookElementTemplate[]; image_aspect_ratio?: FACEBOOK_IMAGE_ASPECT_RATIO; sharable?: boolean; template_type: string; text?: string; top_element_style?: FACEBOOK_TOP_ELEMENT_STYLE; } export const FacebookButtonTemplate = createGenericTemplateDirective( "facebookButtonTemplate", "button", ); export const FacebookCarousel = createGenericTemplateDirective( "facebookCarousel", "generic", ); export const FacebookList = createGenericTemplateDirective( "facebookList", "list", ); export const FacebookOpenGraphTemplate = createGenericTemplateDirective( "facebookOpenGraphTemplate", "open_graph", );