UNPKG

@botonic/plugin-contentful

Version:

## What Does This Plugin Do?

212 lines (192 loc) 6.14 kB
import * as cms from '../cms' import { ButtonStyle, CmsException, TopContent } from '../cms' export class RenderOptions { followUpDelaySeconds = 4 maxButtons = 3 maxQuickReplies = 5 /** Some integrations fail when a field is empty*/ replaceEmptyStringsWith?: string defaultButtonsStyle?: ButtonStyle = ButtonStyle.BUTTON } // TODO consider moving it to @botonic/core export interface BotonicMsg { type: 'carousel' | 'text' | 'image' | 'document' | 'video' delay?: number data: any } // https://stackoverflow.com/a/45999529/145289 export type BotonicMsgs = BotonicMsg | BotonicMsgArray // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface BotonicMsgArray extends Array<BotonicMsgs> {} export interface BotonicText extends BotonicMsg { buttons: any } export interface BotonicDocument extends BotonicMsg { src: string } export class BotonicMsgConverter { readonly options: RenderOptions constructor(options: Partial<RenderOptions> = {}) { this.options = { ...new RenderOptions(), ...options } } convert(content: cms.MessageContent, delayS = 0): BotonicMsgs { if (content instanceof cms.Carousel) { return this.carousel(content, delayS) } if (content instanceof cms.Text) { return this.text(content, delayS) } if (content instanceof cms.StartUp) { return this.startUp(content, delayS) } if (content instanceof cms.Image) { return this.image(content, delayS) } if (content instanceof cms.Video) { return this.video(content, delayS) } if (content instanceof cms.Document) { return this.document(content, delayS) } throw new CmsException(`Unsupported content type ${content.contentType}`) } carousel(carousel: cms.Carousel, delayS = 0): BotonicMsgs { const msg = { type: 'carousel', delay: delayS, data: { elements: carousel.elements.map(e => this.element(e)), }, } as BotonicMsg return this.appendFollowUp(msg, carousel) } private element(cmsElement: cms.Element): any { return { img: cmsElement.imgUrl, title: this.str(cmsElement.title), subtitle: this.str(cmsElement.subtitle), buttons: this.convertButtons(cmsElement.buttons, ButtonStyle.BUTTON), } } private convertButtons(cmsButtons: cms.Button[], style: ButtonStyle): any[] { const maxButtons = style == ButtonStyle.BUTTON ? this.options.maxButtons : this.options.maxQuickReplies if (cmsButtons.length > maxButtons) { console.error('Content has more buttons than maximum. Trimming') cmsButtons = cmsButtons.slice(0, maxButtons) } return cmsButtons.map(cmsButton => { const msgButton = { payload: cmsButton.callback.payload, url: cmsButton.callback.url, } as any if (style == ButtonStyle.BUTTON) { msgButton['title'] = this.str(cmsButton.text) } else { msgButton['text'] = this.str(cmsButton.text) } return msgButton }) } text(text: cms.Text, delayS = 0): BotonicMsgs { const msg: any = { type: 'text', delay: delayS, data: { text: this.str(text.text) }, } const buttonsStyle = text.buttonsStyle || this.options.defaultButtonsStyle const buttons = this.convertButtons(text.buttons, buttonsStyle!) if (buttonsStyle == ButtonStyle.QUICK_REPLY) { msg['replies'] = buttons } else { msg['buttons'] = buttons } return this.appendFollowUp(msg, text) } startUp(startUp: cms.StartUp, delayS = 0): BotonicMsgs { const img: BotonicMsg = { type: 'image', delay: delayS, data: { image: startUp.imgUrl }, } const text: BotonicText = { type: 'text', data: { text: this.str(startUp.text) }, buttons: this.convertButtons(startUp.buttons, ButtonStyle.BUTTON), } return this.appendFollowUp([img, text], startUp) } image(img: cms.Image, delayS = 0): BotonicMsgs { const msg: BotonicMsg = { type: 'image', delay: delayS, data: { image: img.imgUrl, }, } return this.appendFollowUp(msg, img) } video(video: cms.Video, delayS = 0): BotonicMsgs { const msg: BotonicMsg = { type: 'video', delay: delayS, data: { video: video.videoUrl, }, } return this.appendFollowUp(msg, video) } document(doc: cms.Document, delayS = 0): BotonicMsgs { const msg: BotonicMsg = { type: 'document', delay: delayS, data: { document: doc.docUrl, }, } return this.appendFollowUp(msg, doc) } private appendFollowUp( contentMsgs: BotonicMsgs, content: TopContent ): BotonicMsgs { if (content.common.followUp) { const followUp = this.followUp(content.common.followUp) const followUps = Array.isArray(followUp) ? followUp : [followUp] if (Array.isArray(contentMsgs)) { contentMsgs.push(...followUps) } else { contentMsgs = [contentMsgs, ...followUps] } return contentMsgs } return contentMsgs } private followUp(followUp: cms.FollowUp): BotonicMsgs { if (followUp instanceof cms.Text) { // give user time to read the initial text return this.text(followUp, this.options.followUpDelaySeconds) } else if (followUp instanceof cms.Carousel) { // for carousels, the previous text usually introduces the carousel. So, we set a smaller delay return this.carousel(followUp, 2) } else if (followUp instanceof cms.Image) { return this.image(followUp) } else if (followUp instanceof cms.Video) { return this.video(followUp) } else if (followUp instanceof cms.StartUp) { return this.startUp(followUp) } else if (followUp instanceof cms.Document) { return this.document(followUp) } else { throw new Error('Unexpected followUp type ' + typeof followUp) } } private str(str: string): string { if (this.options.replaceEmptyStringsWith == undefined) { return str } return str || this.options.replaceEmptyStringsWith } }