├── .gitignore ├── nodemon.json ├── tsconfig.json ├── package.json ├── src ├── index.ts ├── components │ ├── Message.ts │ ├── QuickReply.ts │ ├── Attachment.ts │ ├── Button.ts │ ├── TemplateElement.ts │ └── Template.ts ├── interfaces.d.ts ├── FacebookConnector.ts └── helpers.ts ├── LICENSE ├── lib ├── components │ ├── Message.js │ ├── QuickReply.js │ ├── Attachment.js │ ├── Button.js │ ├── TemplateElement.js │ └── Template.js ├── index.js ├── FacebookConnector.js └── helpers.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | logs 3 | npm-debug.log 4 | package-lock.json 5 | .idea 6 | .vscode 7 | .DS_Store -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": "ts", 4 | "ignore": ["src/**/*.spec.ts"], 5 | "exec": "ts-node ./src/app.ts" 6 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": "src", 4 | "outDir": "lib", 5 | "module": "commonjs", 6 | "noImplicitAny": true, 7 | "sourceMap": false, 8 | "removeComments": true, 9 | "target": "es5" 10 | } 11 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "botbuilder-facebook-connector", 3 | "version": "0.1.5", 4 | "scripts": { 5 | "build": "tsc" 6 | }, 7 | "main": "./lib/index.js", 8 | "author": "Mihail Cristian Dumitru", 9 | "license": "MIT", 10 | "repository": "Xzya/botbuilder-facebook-connector", 11 | "dependencies": { 12 | "async": "^2.6.0", 13 | "botbuilder": "^3.13.1", 14 | "messenger-bot": "^2.4.0" 15 | }, 16 | "devDependencies": { 17 | "@types/async": "^2.0.46", 18 | "@types/node": "^9.3.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { FacebookConnector, IFacebookConnectorSettings } from "./FacebookConnector"; 2 | export { Attachment, AttachmentTypes } from "./components/Attachment"; 3 | export { Button, ButtonTypes, WebviewHeightRatioTypes } from "./components/Button"; 4 | export { FBMessage } from "./components/Message"; 5 | export { QuickReply, QuickReplyTypes } from "./components/QuickReply"; 6 | export { Template, TemplateTypes, ImageAspectRatioTypes, TopElementStyles } from "./components/Template"; 7 | export { TemplateElement, MediaTypes } from "./components/TemplateElement"; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Mihail Cristian Dumitru 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/components/Message.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var FBMessage = (function () { 4 | function FBMessage() { 5 | this.data = {}; 6 | } 7 | FBMessage.prototype.text = function (text) { 8 | if (text) { 9 | this.data.text = text; 10 | } 11 | return this; 12 | }; 13 | FBMessage.prototype.attachment = function (attachment) { 14 | if (attachment) { 15 | this.data.attachment = attachment.toAttachment ? attachment.toAttachment() : attachment; 16 | } 17 | return this; 18 | }; 19 | FBMessage.prototype.quickReplies = function (list) { 20 | if (list) { 21 | this.data.quick_replies = []; 22 | for (var i = 0; i < list.length; i++) { 23 | var item = list[i]; 24 | this.data.quick_replies.push(item.toQuickReply ? item.toQuickReply() : item); 25 | } 26 | } 27 | else { 28 | delete this.data.quick_replies; 29 | } 30 | return this; 31 | }; 32 | FBMessage.prototype.toMessage = function () { 33 | return this.data; 34 | }; 35 | return FBMessage; 36 | }()); 37 | exports.FBMessage = FBMessage; 38 | -------------------------------------------------------------------------------- /src/components/Message.ts: -------------------------------------------------------------------------------- 1 | import { IIsFBMessage, IFBMessage, IIsAttachment, IQuickReply, IIsQuickReply, IAttachment } from "../interfaces"; 2 | 3 | export class FBMessage implements IIsFBMessage { 4 | protected data = {}; 5 | 6 | text(text: string): this { 7 | if (text) { 8 | this.data.text = text; 9 | } 10 | return this; 11 | } 12 | 13 | attachment(attachment: IAttachment | IIsAttachment): this { 14 | if (attachment) { 15 | this.data.attachment = (attachment).toAttachment ? (attachment).toAttachment() : attachment; 16 | } 17 | return this; 18 | } 19 | 20 | quickReplies(list: IQuickReply[] | IIsQuickReply[]): this { 21 | if (list) { 22 | this.data.quick_replies = []; 23 | for (let i = 0; i < list.length; i++) { 24 | let item = list[i]; 25 | this.data.quick_replies.push((item).toQuickReply ? (item).toQuickReply() : item); 26 | } 27 | } else { 28 | delete this.data.quick_replies; 29 | } 30 | return this; 31 | } 32 | 33 | toMessage(): IFBMessage { 34 | return this.data; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var FacebookConnector_1 = require("./FacebookConnector"); 4 | exports.FacebookConnector = FacebookConnector_1.FacebookConnector; 5 | var Attachment_1 = require("./components/Attachment"); 6 | exports.Attachment = Attachment_1.Attachment; 7 | exports.AttachmentTypes = Attachment_1.AttachmentTypes; 8 | var Button_1 = require("./components/Button"); 9 | exports.Button = Button_1.Button; 10 | exports.ButtonTypes = Button_1.ButtonTypes; 11 | exports.WebviewHeightRatioTypes = Button_1.WebviewHeightRatioTypes; 12 | var Message_1 = require("./components/Message"); 13 | exports.FBMessage = Message_1.FBMessage; 14 | var QuickReply_1 = require("./components/QuickReply"); 15 | exports.QuickReply = QuickReply_1.QuickReply; 16 | exports.QuickReplyTypes = QuickReply_1.QuickReplyTypes; 17 | var Template_1 = require("./components/Template"); 18 | exports.Template = Template_1.Template; 19 | exports.TemplateTypes = Template_1.TemplateTypes; 20 | exports.ImageAspectRatioTypes = Template_1.ImageAspectRatioTypes; 21 | exports.TopElementStyles = Template_1.TopElementStyles; 22 | var TemplateElement_1 = require("./components/TemplateElement"); 23 | exports.TemplateElement = TemplateElement_1.TemplateElement; 24 | exports.MediaTypes = TemplateElement_1.MediaTypes; 25 | -------------------------------------------------------------------------------- /src/components/QuickReply.ts: -------------------------------------------------------------------------------- 1 | import { IQuickReply, IIsQuickReply } from "../interfaces"; 2 | 3 | export var QuickReplyTypes = { 4 | text: "text", 5 | location: "location", 6 | } 7 | 8 | export class QuickReply implements IIsQuickReply { 9 | protected data = {}; 10 | 11 | constructor() { 12 | this.data.content_type = QuickReplyTypes.text; 13 | } 14 | 15 | contentType(text: string): this { 16 | if (text) { 17 | this.data.content_type = text; 18 | } 19 | return this; 20 | } 21 | 22 | title(text: string): this { 23 | if (text) { 24 | this.data.title = text; 25 | } 26 | return this; 27 | } 28 | 29 | payload(payload: string | number): this { 30 | if (payload) { 31 | this.data.payload = payload; 32 | } 33 | return this; 34 | } 35 | 36 | imageURL(text: string): this { 37 | if (text) { 38 | this.data.image_url = text; 39 | } 40 | return this; 41 | } 42 | 43 | toQuickReply(): IQuickReply { 44 | return this.data; 45 | } 46 | 47 | static text(title: string, payload: string, imageURL?: string): QuickReply { 48 | return new QuickReply().contentType(QuickReplyTypes.text).title(title).payload(payload).imageURL(imageURL) 49 | } 50 | 51 | static location(): QuickReply { 52 | return new QuickReply().contentType(QuickReplyTypes.location) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/components/QuickReply.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.QuickReplyTypes = { 4 | text: "text", 5 | location: "location", 6 | }; 7 | var QuickReply = (function () { 8 | function QuickReply() { 9 | this.data = {}; 10 | this.data.content_type = exports.QuickReplyTypes.text; 11 | } 12 | QuickReply.prototype.contentType = function (text) { 13 | if (text) { 14 | this.data.content_type = text; 15 | } 16 | return this; 17 | }; 18 | QuickReply.prototype.title = function (text) { 19 | if (text) { 20 | this.data.title = text; 21 | } 22 | return this; 23 | }; 24 | QuickReply.prototype.payload = function (payload) { 25 | if (payload) { 26 | this.data.payload = payload; 27 | } 28 | return this; 29 | }; 30 | QuickReply.prototype.imageURL = function (text) { 31 | if (text) { 32 | this.data.image_url = text; 33 | } 34 | return this; 35 | }; 36 | QuickReply.prototype.toQuickReply = function () { 37 | return this.data; 38 | }; 39 | QuickReply.text = function (title, payload, imageURL) { 40 | return new QuickReply().contentType(exports.QuickReplyTypes.text).title(title).payload(payload).imageURL(imageURL); 41 | }; 42 | QuickReply.location = function () { 43 | return new QuickReply().contentType(exports.QuickReplyTypes.location); 44 | }; 45 | return QuickReply; 46 | }()); 47 | exports.QuickReply = QuickReply; 48 | -------------------------------------------------------------------------------- /src/components/Attachment.ts: -------------------------------------------------------------------------------- 1 | import { IAttachment, IIsAttachmentPayload, IAttachmentPayload, IIsAttachment } from "../interfaces"; 2 | 3 | export var AttachmentTypes = { 4 | image: "image", 5 | video: "video", 6 | audio: "audio", 7 | file: "file", 8 | } 9 | 10 | export class Attachment implements IIsAttachment { 11 | protected data = {}; 12 | 13 | type(text: string): this { 14 | if (text) { 15 | this.data.type = text; 16 | } 17 | return this; 18 | } 19 | 20 | payload(payload: IAttachmentPayload): this { 21 | if (payload) { 22 | this.data.payload = (payload).toPayload ? (payload).toPayload() : payload; 23 | } 24 | return this; 25 | } 26 | 27 | toAttachment(): IAttachment { 28 | return this.data; 29 | } 30 | 31 | static image(url?: string, is_reusable?: boolean): Attachment { 32 | return new Attachment().type(AttachmentTypes.image).payload({ url, is_reusable }) 33 | } 34 | 35 | static video(url?: string, is_reusable?: boolean): Attachment { 36 | return new Attachment().type(AttachmentTypes.video).payload({ url, is_reusable }) 37 | } 38 | 39 | static audio(url?: string, is_reusable?: boolean): Attachment { 40 | return new Attachment().type(AttachmentTypes.audio).payload({ url, is_reusable }) 41 | } 42 | 43 | static file(url?: string, is_reusable?: boolean): Attachment { 44 | return new Attachment().type(AttachmentTypes.file).payload({ url, is_reusable }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/components/Attachment.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.AttachmentTypes = { 4 | image: "image", 5 | video: "video", 6 | audio: "audio", 7 | file: "file", 8 | }; 9 | var Attachment = (function () { 10 | function Attachment() { 11 | this.data = {}; 12 | } 13 | Attachment.prototype.type = function (text) { 14 | if (text) { 15 | this.data.type = text; 16 | } 17 | return this; 18 | }; 19 | Attachment.prototype.payload = function (payload) { 20 | if (payload) { 21 | this.data.payload = payload.toPayload ? payload.toPayload() : payload; 22 | } 23 | return this; 24 | }; 25 | Attachment.prototype.toAttachment = function () { 26 | return this.data; 27 | }; 28 | Attachment.image = function (url, is_reusable) { 29 | return new Attachment().type(exports.AttachmentTypes.image).payload({ url: url, is_reusable: is_reusable }); 30 | }; 31 | Attachment.video = function (url, is_reusable) { 32 | return new Attachment().type(exports.AttachmentTypes.video).payload({ url: url, is_reusable: is_reusable }); 33 | }; 34 | Attachment.audio = function (url, is_reusable) { 35 | return new Attachment().type(exports.AttachmentTypes.audio).payload({ url: url, is_reusable: is_reusable }); 36 | }; 37 | Attachment.file = function (url, is_reusable) { 38 | return new Attachment().type(exports.AttachmentTypes.file).payload({ url: url, is_reusable: is_reusable }); 39 | }; 40 | return Attachment; 41 | }()); 42 | exports.Attachment = Attachment; 43 | -------------------------------------------------------------------------------- /src/interfaces.d.ts: -------------------------------------------------------------------------------- 1 | import { ICardAction } from "botbuilder"; 2 | 3 | type ButtonType = IButton | IIsButton; 4 | type TemplateElementType = ITemplateElement | IIsTemplateElement; 5 | 6 | export interface IIsAttachmentPayload { 7 | toPayload(): IAttachmentPayload; 8 | } 9 | 10 | export interface IAttachmentPayload { 11 | } 12 | 13 | export interface IIsAttachment { 14 | toAttachment(): IAttachment; 15 | } 16 | 17 | export interface IAttachment { 18 | type: string; 19 | payload: IAttachmentPayload; 20 | } 21 | 22 | export interface IMediaPayload extends IAttachmentPayload { 23 | url?: string; 24 | is_reusable?: boolean; 25 | } 26 | 27 | export interface IIsQuickReply { 28 | toQuickReply(): IQuickReply; 29 | } 30 | 31 | export interface IQuickReply { 32 | content_type: string; 33 | title?: string; 34 | payload?: string | number; 35 | image_url?: string; 36 | } 37 | 38 | export interface IIsFBMessage { 39 | toMessage(): IFBMessage; 40 | } 41 | 42 | export interface IFBMessage { 43 | text?: string; 44 | attachment?: IAttachment; 45 | quick_replies?: IQuickReply[]; 46 | } 47 | 48 | export interface IIsButton { 49 | toButton(): IButton; 50 | } 51 | 52 | export interface IButton { 53 | type: string; 54 | title: string; 55 | url?: string; 56 | payload?: string; 57 | share_contents?: IAttachment; 58 | messenger_extensions?: boolean; 59 | webview_height_ratio?: string; 60 | fallback_url?: string; 61 | } 62 | 63 | export interface ITemplatePayload extends IAttachmentPayload { 64 | template_type: string; 65 | elements?: ITemplateElement[]; 66 | sharable?: boolean; 67 | image_aspect_ratio?: string; 68 | top_element_style?: string; 69 | buttons?: ButtonType[]; 70 | } 71 | 72 | export interface IIsTemplateElement { 73 | toElement(): ITemplateElement; 74 | } 75 | 76 | export interface ITemplateElement { 77 | title: string; 78 | subtitle?: string; 79 | image_url?: string; 80 | default_action?: IButton; 81 | buttons?: IButton[]; 82 | media_type?: string; 83 | attachment_id?: string; 84 | url?: string; 85 | } 86 | 87 | interface IKeyboard { 88 | buttons: ICardAction[]; 89 | } 90 | -------------------------------------------------------------------------------- /src/components/Button.ts: -------------------------------------------------------------------------------- 1 | import { IAttachment, IButton, IIsButton } from "../interfaces"; 2 | 3 | export var ButtonTypes = { 4 | web_url: "web_url", 5 | postback: "postback", 6 | element_share: "element_share", 7 | } 8 | 9 | export var WebviewHeightRatioTypes = { 10 | compact: "compact", 11 | tall: "tall", 12 | full: "full", 13 | } 14 | 15 | export class Button implements IIsButton { 16 | protected data = {}; 17 | 18 | public type(type: string): this { 19 | if (type) { 20 | this.data.type = type; 21 | } 22 | return this; 23 | } 24 | 25 | public url(url: string): this { 26 | if (url) { 27 | this.data.url = url; 28 | } 29 | return this; 30 | } 31 | 32 | public title(title: string): this { 33 | if (title) { 34 | this.data.title = title; 35 | } 36 | return this; 37 | } 38 | 39 | public payload(payload: string): this { 40 | if (payload) { 41 | this.data.payload = payload; 42 | } 43 | return this; 44 | } 45 | 46 | public shareContents(content: IAttachment): this { 47 | if (content) { 48 | this.data.share_contents = content; 49 | } 50 | return this; 51 | } 52 | 53 | public messengerExtensions(enabled: boolean): this { 54 | this.data.messenger_extensions = enabled; 55 | return this; 56 | } 57 | 58 | public webviewHeightRatio(text: string): this { 59 | if (text) { 60 | this.data.webview_height_ratio = text; 61 | } 62 | return this; 63 | } 64 | 65 | public fallbackURL(text: string): this { 66 | if (text) { 67 | this.data.fallback_url = text; 68 | } 69 | return this; 70 | } 71 | 72 | public toButton(): IButton { 73 | return this.data; 74 | } 75 | 76 | static webURL(title: string, url: string): Button { 77 | return new Button().type(ButtonTypes.web_url).title(title).url(url) 78 | } 79 | 80 | static postback(title: string, payload: string): Button { 81 | return new Button().type(ButtonTypes.postback).title(title).payload(payload) 82 | } 83 | 84 | static share(contents?: IAttachment): Button { 85 | return new Button().type(ButtonTypes.element_share).shareContents(contents) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lib/components/Button.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.ButtonTypes = { 4 | web_url: "web_url", 5 | postback: "postback", 6 | element_share: "element_share", 7 | }; 8 | exports.WebviewHeightRatioTypes = { 9 | compact: "compact", 10 | tall: "tall", 11 | full: "full", 12 | }; 13 | var Button = (function () { 14 | function Button() { 15 | this.data = {}; 16 | } 17 | Button.prototype.type = function (type) { 18 | if (type) { 19 | this.data.type = type; 20 | } 21 | return this; 22 | }; 23 | Button.prototype.url = function (url) { 24 | if (url) { 25 | this.data.url = url; 26 | } 27 | return this; 28 | }; 29 | Button.prototype.title = function (title) { 30 | if (title) { 31 | this.data.title = title; 32 | } 33 | return this; 34 | }; 35 | Button.prototype.payload = function (payload) { 36 | if (payload) { 37 | this.data.payload = payload; 38 | } 39 | return this; 40 | }; 41 | Button.prototype.shareContents = function (content) { 42 | if (content) { 43 | this.data.share_contents = content; 44 | } 45 | return this; 46 | }; 47 | Button.prototype.messengerExtensions = function (enabled) { 48 | this.data.messenger_extensions = enabled; 49 | return this; 50 | }; 51 | Button.prototype.webviewHeightRatio = function (text) { 52 | if (text) { 53 | this.data.webview_height_ratio = text; 54 | } 55 | return this; 56 | }; 57 | Button.prototype.fallbackURL = function (text) { 58 | if (text) { 59 | this.data.fallback_url = text; 60 | } 61 | return this; 62 | }; 63 | Button.prototype.toButton = function () { 64 | return this.data; 65 | }; 66 | Button.webURL = function (title, url) { 67 | return new Button().type(exports.ButtonTypes.web_url).title(title).url(url); 68 | }; 69 | Button.postback = function (title, payload) { 70 | return new Button().type(exports.ButtonTypes.postback).title(title).payload(payload); 71 | }; 72 | Button.share = function (contents) { 73 | return new Button().type(exports.ButtonTypes.element_share).shareContents(contents); 74 | }; 75 | return Button; 76 | }()); 77 | exports.Button = Button; 78 | -------------------------------------------------------------------------------- /lib/components/TemplateElement.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.MediaTypes = { 4 | image: "image", 5 | video: "video", 6 | }; 7 | var TemplateElement = (function () { 8 | function TemplateElement() { 9 | this.data = {}; 10 | } 11 | TemplateElement.prototype.title = function (text) { 12 | if (text) { 13 | this.data.title = text; 14 | } 15 | return this; 16 | }; 17 | TemplateElement.prototype.subtitle = function (text) { 18 | if (text) { 19 | this.data.subtitle = text; 20 | } 21 | return this; 22 | }; 23 | TemplateElement.prototype.imageURL = function (text) { 24 | if (text) { 25 | this.data.image_url = text; 26 | } 27 | return this; 28 | }; 29 | TemplateElement.prototype.defaultAction = function (action) { 30 | if (action) { 31 | this.data.default_action = action.toButton ? action.toButton() : action; 32 | } 33 | return this; 34 | }; 35 | TemplateElement.prototype.buttons = function (list) { 36 | if (list) { 37 | this.data.buttons = []; 38 | for (var i = 0; i < list.length; i++) { 39 | var item = list[i]; 40 | this.data.buttons.push(item.toButton ? item.toButton() : item); 41 | } 42 | } 43 | else { 44 | delete this.data.buttons; 45 | } 46 | return this; 47 | }; 48 | TemplateElement.prototype.mediaType = function (text) { 49 | if (text) { 50 | this.data.media_type = text; 51 | } 52 | return this; 53 | }; 54 | TemplateElement.prototype.attachmentID = function (text) { 55 | if (text) { 56 | this.data.attachment_id = text; 57 | } 58 | return this; 59 | }; 60 | TemplateElement.prototype.url = function (text) { 61 | if (text) { 62 | this.data.url = text; 63 | } 64 | return this; 65 | }; 66 | TemplateElement.prototype.toElement = function () { 67 | return this.data; 68 | }; 69 | TemplateElement.generic = function (title, subtitle, imageURL, defaultAction, buttons) { 70 | return new TemplateElement().title(title).subtitle(subtitle).imageURL(imageURL).defaultAction(defaultAction).buttons(buttons); 71 | }; 72 | TemplateElement.list = function (title, subtitle, imageURL, defaultAction, buttons) { 73 | return new TemplateElement().title(title).subtitle(subtitle).imageURL(imageURL).defaultAction(defaultAction).buttons(buttons); 74 | }; 75 | TemplateElement.imageID = function (attachmentID, buttons) { 76 | return new TemplateElement().mediaType(exports.MediaTypes.image).attachmentID(attachmentID).buttons(buttons); 77 | }; 78 | TemplateElement.imageURL = function (url, buttons) { 79 | return new TemplateElement().mediaType(exports.MediaTypes.image).url(url).buttons(buttons); 80 | }; 81 | TemplateElement.videoID = function (attachmentID, buttons) { 82 | return new TemplateElement().mediaType(exports.MediaTypes.video).attachmentID(attachmentID).buttons(buttons); 83 | }; 84 | TemplateElement.videoURL = function (url, buttons) { 85 | return new TemplateElement().mediaType(exports.MediaTypes.video).url(url).buttons(buttons); 86 | }; 87 | TemplateElement.openGraph = function (url, buttons) { 88 | return new TemplateElement().url(url).buttons(buttons); 89 | }; 90 | return TemplateElement; 91 | }()); 92 | exports.TemplateElement = TemplateElement; 93 | -------------------------------------------------------------------------------- /lib/components/Template.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var Message_1 = require("./Message"); 4 | exports.TemplateTypes = { 5 | generic: "generic", 6 | list: "list", 7 | media: "media", 8 | open_graph: "open_graph", 9 | }; 10 | exports.ImageAspectRatioTypes = { 11 | horizontal: "horizontal", 12 | square: "square", 13 | }; 14 | exports.TopElementStyles = { 15 | compact: "compact", 16 | large: "large", 17 | }; 18 | var Template = (function () { 19 | function Template() { 20 | this.data = {}; 21 | this.data.type = "template"; 22 | this.data.payload = {}; 23 | } 24 | Template.prototype.templateType = function (text) { 25 | if (text) { 26 | this.data.payload.template_type = text; 27 | } 28 | return this; 29 | }; 30 | Template.prototype.elements = function (list) { 31 | if (list) { 32 | this.data.payload.elements = []; 33 | for (var i = 0; i < list.length; i++) { 34 | var item = list[i]; 35 | this.data.payload.elements.push(item.toElement ? item.toElement() : item); 36 | } 37 | } 38 | else { 39 | delete this.data.payload.elements; 40 | } 41 | return this; 42 | }; 43 | Template.prototype.sharable = function (enabled) { 44 | if (enabled != null) { 45 | this.data.payload.sharable = enabled; 46 | } 47 | return this; 48 | }; 49 | Template.prototype.imageAspectRatio = function (text) { 50 | if (text) { 51 | this.data.payload.image_aspect_ratio = text; 52 | } 53 | return this; 54 | }; 55 | Template.prototype.topElementStyle = function (text) { 56 | if (text) { 57 | this.data.payload.top_element_style = text; 58 | } 59 | return this; 60 | }; 61 | Template.prototype.buttons = function (list) { 62 | if (list) { 63 | this.data.payload.buttons = []; 64 | for (var i = 0; i < list.length; i++) { 65 | var item = list[i]; 66 | this.data.payload.buttons.push(item.toButton ? item.toButton() : item); 67 | } 68 | } 69 | else { 70 | delete this.data.payload.buttons; 71 | } 72 | return this; 73 | }; 74 | Template.prototype.toMessage = function () { 75 | return new Message_1.FBMessage().attachment(this.data).toMessage(); 76 | }; 77 | Template.generic = function (elements, sharable, imageAspectRatio) { 78 | return new Template() 79 | .templateType(exports.TemplateTypes.generic) 80 | .elements(elements) 81 | .imageAspectRatio(imageAspectRatio) 82 | .sharable(sharable); 83 | }; 84 | Template.list = function (elements, topElementStyle, buttons) { 85 | return new Template() 86 | .templateType(exports.TemplateTypes.list) 87 | .elements(elements) 88 | .topElementStyle(topElementStyle) 89 | .buttons(buttons); 90 | }; 91 | Template.media = function (elements) { 92 | return new Template() 93 | .templateType(exports.TemplateTypes.media) 94 | .elements(elements); 95 | }; 96 | Template.openGraph = function (elements) { 97 | return new Template() 98 | .templateType(exports.TemplateTypes.open_graph) 99 | .elements(elements); 100 | }; 101 | return Template; 102 | }()); 103 | exports.Template = Template; 104 | -------------------------------------------------------------------------------- /src/components/TemplateElement.ts: -------------------------------------------------------------------------------- 1 | import { IIsTemplateElement, ITemplateElement, IButton, IIsButton, ButtonType } from "../interfaces"; 2 | import { Button } from "./Button"; 3 | 4 | export var MediaTypes = { 5 | image: "image", 6 | video: "video", 7 | } 8 | 9 | export class TemplateElement implements IIsTemplateElement { 10 | protected data = {}; 11 | 12 | public title(text: string): this { 13 | if (text) { 14 | this.data.title = text; 15 | } 16 | return this; 17 | } 18 | 19 | public subtitle(text: string): this { 20 | if (text) { 21 | this.data.subtitle = text; 22 | } 23 | return this; 24 | } 25 | 26 | public imageURL(text: string): this { 27 | if (text) { 28 | this.data.image_url = text; 29 | } 30 | return this; 31 | } 32 | 33 | public defaultAction(action: ButtonType): this { 34 | if (action) { 35 | this.data.default_action = (action).toButton ? (action).toButton() : action; 36 | } 37 | return this; 38 | } 39 | 40 | public buttons(list: ButtonType[]): this { 41 | if (list) { 42 | this.data.buttons = []; 43 | for (let i = 0; i < list.length; i++) { 44 | let item = list[i]; 45 | this.data.buttons.push((item).toButton ? (item).toButton() : item); 46 | } 47 | } else { 48 | delete this.data.buttons; 49 | } 50 | return this; 51 | } 52 | 53 | public mediaType(text: string): this { 54 | if (text) { 55 | this.data.media_type = text; 56 | } 57 | return this; 58 | } 59 | 60 | public attachmentID(text: string): this { 61 | if (text) { 62 | this.data.attachment_id = text; 63 | } 64 | return this; 65 | } 66 | 67 | public url(text: string): this { 68 | if (text) { 69 | this.data.url = text; 70 | } 71 | return this; 72 | } 73 | 74 | public toElement(): ITemplateElement { 75 | return this.data; 76 | } 77 | 78 | static generic(title: string, subtitle?: string, imageURL?: string, defaultAction?: IButton, buttons?: ButtonType[]): TemplateElement { 79 | return new TemplateElement().title(title).subtitle(subtitle).imageURL(imageURL).defaultAction(defaultAction).buttons(buttons) 80 | } 81 | 82 | static list(title: string, subtitle?: string, imageURL?: string, defaultAction?: IButton, buttons?: ButtonType[]): TemplateElement { 83 | return new TemplateElement().title(title).subtitle(subtitle).imageURL(imageURL).defaultAction(defaultAction).buttons(buttons) 84 | } 85 | 86 | static imageID(attachmentID: string, buttons?: ButtonType[]): TemplateElement { 87 | return new TemplateElement().mediaType(MediaTypes.image).attachmentID(attachmentID).buttons(buttons) 88 | } 89 | 90 | static imageURL(url: string, buttons?: ButtonType[]): TemplateElement { 91 | return new TemplateElement().mediaType(MediaTypes.image).url(url).buttons(buttons) 92 | } 93 | 94 | static videoID(attachmentID: string, buttons?: ButtonType[]): TemplateElement { 95 | return new TemplateElement().mediaType(MediaTypes.video).attachmentID(attachmentID).buttons(buttons) 96 | } 97 | 98 | static videoURL(url: string, buttons?: ButtonType[]): TemplateElement { 99 | return new TemplateElement().mediaType(MediaTypes.video).url(url).buttons(buttons) 100 | } 101 | 102 | static openGraph(url: string, buttons?: ButtonType[]): TemplateElement { 103 | return new TemplateElement().url(url).buttons(buttons) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BotBuilder Facebook Connector 2 | 3 | Library for connecting to Facebook directly using BotBuilder. 4 | 5 | # Usage 6 | 7 | Install 8 | 9 | ``` 10 | npm install botbuilder-facebook-connector --save 11 | ``` 12 | 13 | You can create the connector by passing the settings for connecting to Facebook, or by passing a [messenger-bot](https://github.com/remixz/messenger-bot) instance 14 | 15 | ```javascript 16 | // passing settings 17 | let connector = new FacebookConnector(null, { 18 | token: process.env.FACEBOOK_TOKEN, 19 | verify: process.env.FACEBOOK_VERIFY, 20 | }); 21 | 22 | // passing messenger-bot instance 23 | import { FacebookConnector } from "botbuilder-facebook-connector"; 24 | import * as Bot from "messenger-bot"; 25 | let messengerBot = new Bot({ 26 | token: process.env.FACEBOOK_TOKEN, 27 | verify: process.env.FACEBOOK_VERIFY, 28 | }); 29 | let connector = new FacebookConnector(messengerBot); 30 | ``` 31 | 32 | # Supported send components 33 | 34 | - [ ] Cards 35 | - [x] Hero card 36 | - [x] Thumbnail card 37 | - [ ] Receipt card 38 | - [ ] Sign-in card 39 | - [x] Animation card 40 | - [x] Audio card 41 | - [ ] Video card (I get no response from facebook on this one, no idea why) 42 | - [ ] Adaptive card, not supported by facebook, Microsoft just renders to an image, [more info](https://github.com/Microsoft/AdaptiveCards/issues/367) 43 | 44 | # Supported receive components 45 | 46 | - [x] Quick replies 47 | - [x] Text [ref](https://developers.facebook.com/docs/messenger-platform/reference/send-api/quick-replies) 48 | - [x] Location [ref](https://developers.facebook.com/docs/messenger-platform/reference/send-api/quick-replies) 49 | - [ ] Buttons 50 | - [ ] Buy button [ref](https://developers.facebook.com/docs/messenger-platform/reference/buttons/buy) 51 | - [ ] Call button [ref](https://developers.facebook.com/docs/messenger-platform/reference/buttons/call) 52 | - [ ] Game play button [ref](https://developers.facebook.com/docs/messenger-platform/reference/buttons/game-play) 53 | - [ ] Login button [ref](https://developers.facebook.com/docs/messenger-platform/reference/buttons/login) 54 | - [ ] Logout button [ref](https://developers.facebook.com/docs/messenger-platform/reference/buttons/logout) 55 | - [x] Postback button [ref](https://developers.facebook.com/docs/messenger-platform/reference/buttons/postback) 56 | - [x] Share button [ref](https://developers.facebook.com/docs/messenger-platform/reference/buttons/share) 57 | - [x] URL button [ref](https://developers.facebook.com/docs/messenger-platform/reference/buttons/url) 58 | - [ ] Templates 59 | - [ ] Button template [ref](https://developers.facebook.com/docs/messenger-platform/reference/template/button) 60 | - [x] Generic template [ref](https://developers.facebook.com/docs/messenger-platform/reference/template/generic) 61 | - [x] List template [ref](https://developers.facebook.com/docs/messenger-platform/reference/template/list) 62 | - [x] Media template [ref](https://developers.facebook.com/docs/messenger-platform/reference/template/media) 63 | - [x] Open graph template [ref](https://developers.facebook.com/docs/messenger-platform/reference/template/open-graph) 64 | - [ ] Receipt template [ref](https://developers.facebook.com/docs/messenger-platform/reference/template/receipt) 65 | - [ ] Airline boarding pass template [ref](https://developers.facebook.com/docs/messenger-platform/reference/template/airline-boarding-pass) 66 | - [ ] Airline check-in reminder template [ref](https://developers.facebook.com/docs/messenger-platform/reference/template/airline-checkin) 67 | - [ ] Airline itinerary template [ref](https://developers.facebook.com/docs/messenger-platform/reference/template/airline-itinerary) 68 | - [ ] Airline flight update template [ref](https://developers.facebook.com/docs/messenger-platform/reference/template/airline-flight-update) 69 | -------------------------------------------------------------------------------- /src/components/Template.ts: -------------------------------------------------------------------------------- 1 | import { IAttachment, ITemplateElement, IIsTemplateElement, IIsFBMessage, IFBMessage, TemplateElementType, ButtonType, IIsButton, IButton, ITemplatePayload } from "../interfaces"; 2 | import { FBMessage } from "./Message"; 3 | 4 | export var TemplateTypes = { 5 | generic: "generic", 6 | list: "list", 7 | media: "media", 8 | open_graph: "open_graph", 9 | } 10 | 11 | export var ImageAspectRatioTypes = { 12 | horizontal: "horizontal", 13 | square: "square", 14 | } 15 | 16 | export var TopElementStyles = { 17 | compact: "compact", 18 | large: "large", 19 | } 20 | 21 | export class Template implements IIsFBMessage { 22 | protected data = {}; 23 | 24 | constructor() { 25 | this.data.type = "template"; 26 | this.data.payload = {}; 27 | } 28 | 29 | public templateType(text: string): this { 30 | if (text) { 31 | (this.data.payload).template_type = text; 32 | } 33 | return this; 34 | } 35 | 36 | public elements(list: TemplateElementType[]): this { 37 | if (list) { 38 | (this.data.payload).elements = []; 39 | for (let i = 0; i < list.length; i++) { 40 | let item = list[i]; 41 | (this.data.payload).elements.push((item).toElement ? (item).toElement() : item); 42 | } 43 | } else { 44 | delete (this.data.payload).elements; 45 | } 46 | return this; 47 | } 48 | 49 | public sharable(enabled: boolean): this { 50 | if (enabled != null) { 51 | (this.data.payload).sharable = enabled; 52 | } 53 | return this; 54 | } 55 | 56 | public imageAspectRatio(text: string): this { 57 | if (text) { 58 | (this.data.payload).image_aspect_ratio = text; 59 | } 60 | return this; 61 | } 62 | 63 | public topElementStyle(text: string): this { 64 | if (text) { 65 | (this.data.payload).top_element_style = text; 66 | } 67 | return this; 68 | } 69 | 70 | public buttons(list: ButtonType[]): this { 71 | if (list) { 72 | (this.data.payload).buttons = []; 73 | for (let i = 0; i < list.length; i++) { 74 | let item = list[i]; 75 | (this.data.payload).buttons.push((item).toButton ? (item).toButton() : item); 76 | } 77 | } else { 78 | delete (this.data.payload).buttons; 79 | } 80 | return this; 81 | } 82 | 83 | toMessage(): IFBMessage { 84 | return new FBMessage().attachment(this.data).toMessage(); 85 | } 86 | 87 | static generic(elements: TemplateElementType[], sharable?: boolean, imageAspectRatio?: string): Template { 88 | return new Template() 89 | .templateType(TemplateTypes.generic) 90 | .elements(elements) 91 | .imageAspectRatio(imageAspectRatio) 92 | .sharable(sharable) 93 | } 94 | 95 | static list(elements: TemplateElementType[], topElementStyle?: string, buttons?: ButtonType[]): Template { 96 | return new Template() 97 | .templateType(TemplateTypes.list) 98 | .elements(elements) 99 | .topElementStyle(topElementStyle) 100 | .buttons(buttons) 101 | } 102 | 103 | static media(elements: TemplateElementType[]): Template { 104 | return new Template() 105 | .templateType(TemplateTypes.media) 106 | .elements(elements) 107 | } 108 | 109 | static openGraph(elements: TemplateElementType[]): Template { 110 | return new Template() 111 | .templateType(TemplateTypes.open_graph) 112 | .elements(elements) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /lib/FacebookConnector.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var Bot = require("messenger-bot"); 4 | var botbuilder_1 = require("botbuilder"); 5 | var async = require("async"); 6 | var helpers_1 = require("./helpers"); 7 | var FacebookConnector = (function () { 8 | function FacebookConnector(bot, settings) { 9 | var _this = this; 10 | this.bot = bot; 11 | this.settings = settings; 12 | if (this.bot && this.settings) { 13 | throw new Error("Can't have both an instance of the bot and settings"); 14 | } 15 | if (this.settings) { 16 | this.bot = new Bot(settings); 17 | } 18 | this.bot.on("message", function (payload, reply, actions) { 19 | _this.dispatch(payload); 20 | }); 21 | this.bot.on("postback", function (payload, reply, actions) { 22 | _this.dispatch(payload); 23 | }); 24 | } 25 | FacebookConnector.prototype.listen = function () { 26 | return this.bot.middleware(); 27 | }; 28 | FacebookConnector.prototype.onEvent = function (handler) { 29 | this.onEventHandler = handler; 30 | }; 31 | FacebookConnector.prototype.send = function (messages, done) { 32 | var _this = this; 33 | var addresses = []; 34 | async.forEachOfSeries(messages, function (msg, idx, cb) { 35 | try { 36 | if (msg.type == 'delay') { 37 | setTimeout(cb, msg.value); 38 | } 39 | else if (msg.address) { 40 | _this.postMessage(msg, (idx == messages.length - 1), function (err, address) { 41 | addresses.push(address); 42 | cb(err); 43 | }); 44 | } 45 | else { 46 | console.error('FacebookConnector: send - message is missing address.'); 47 | cb(new Error('Message missing address.')); 48 | } 49 | } 50 | catch (e) { 51 | cb(e); 52 | } 53 | }, function (err) { 54 | done(err, !err ? addresses : null); 55 | }); 56 | }; 57 | FacebookConnector.prototype.startConversation = function (address, done) { 58 | console.log("startConversation"); 59 | if (address && address.user && address.bot) { 60 | done(null, address); 61 | } 62 | else { 63 | console.error('FacebookConnector: startConversation - address is invalid.'); 64 | done(new Error('Invalid address.')); 65 | } 66 | }; 67 | FacebookConnector.prototype.fromFacebookMessage = function (msg) { 68 | var sender = msg.sender.id; 69 | var recipient = msg.recipient.id; 70 | var m = new botbuilder_1.Message() 71 | .address({ 72 | channelId: "facebook", 73 | user: { id: sender }, 74 | bot: { id: recipient }, 75 | conversation: { isGroup: false, id: sender + "-" + recipient } 76 | }) 77 | .attachments(((msg.message ? msg.message.attachments : false) || []).map(function (a) { 78 | var attachment = { 79 | contentType: a.type 80 | }; 81 | if (a.title) { 82 | attachment.name = a.title; 83 | } 84 | if (a.url) { 85 | attachment.contentUrl = a.url; 86 | } 87 | if (a.payload) { 88 | attachment.content = a.payload; 89 | if (a.payload.url) { 90 | attachment.contentUrl = a.payload.url; 91 | } 92 | } 93 | return attachment; 94 | })) 95 | .timestamp() 96 | .sourceEvent(msg); 97 | if (msg.message) { 98 | m.text(msg.message.text); 99 | if (msg.message.quick_reply) { 100 | m.value(msg.message.quick_reply.payload); 101 | } 102 | } 103 | else if (msg.postback) { 104 | m.text(msg.postback.title); 105 | m.value(msg.postback.payload); 106 | } 107 | return m.toMessage(); 108 | }; 109 | FacebookConnector.prototype.postMessage = function (msg, lastMsg, cb) { 110 | console.info("FacebookConnector: sending message"); 111 | if (process.env.DEBUG) { 112 | console.info("Raw botbuilder message", JSON.stringify(msg, null, " ")); 113 | } 114 | if (msg.sourceEvent) { 115 | if (process.env.DEBUG) { 116 | console.info("Raw Facebook payload (sourceEvent)", JSON.stringify(msg.sourceEvent, null, " ")); 117 | } 118 | this.bot.sendMessage(msg.address.user.id, msg.sourceEvent, function (err, info) { 119 | if (err) { 120 | cb(new Error(err.message), null); 121 | } 122 | else { 123 | cb(null, msg.address); 124 | } 125 | }); 126 | return; 127 | } 128 | var messages = helpers_1.IMessageToFBMessage(msg); 129 | var bot = this.bot; 130 | async.forEachOfSeries(messages, function (message, idx, cb) { 131 | if (process.env.DEBUG) { 132 | console.info("Raw Facebook payload", JSON.stringify(message, null, " ")); 133 | } 134 | bot.sendMessage(msg.address.user.id, message, function (err, info) { 135 | cb(err); 136 | }); 137 | }, function (err) { 138 | if (err) { 139 | cb(new Error(err.message), null); 140 | } 141 | else { 142 | cb(null, msg.address); 143 | } 144 | }); 145 | }; 146 | FacebookConnector.prototype.dispatch = function (payload) { 147 | if (process.env.DEBUG) { 148 | console.log("FacebookConnector: message received", JSON.stringify(payload, null, " ")); 149 | } 150 | var msg = this.fromFacebookMessage(payload); 151 | if (process.env.DEBUG) { 152 | console.log("FacebookConnector: converted message", JSON.stringify(msg, null, " ")); 153 | } 154 | if (this.onEventHandler) { 155 | this.onEventHandler([msg]); 156 | } 157 | }; 158 | return FacebookConnector; 159 | }()); 160 | exports.FacebookConnector = FacebookConnector; 161 | -------------------------------------------------------------------------------- /src/FacebookConnector.ts: -------------------------------------------------------------------------------- 1 | import * as Bot from "messenger-bot"; 2 | import { IConnector, IEvent, IMessage, IAddress, Message, HeroCard, IThumbnailCard, ICardAction, Keyboard, IAttachment, MemoryBotStorage } from "botbuilder"; 3 | import * as async from "async"; 4 | import { IMessageToFBMessage } from "./helpers"; 5 | 6 | export interface IFacebookConnectorSettings { 7 | token?: string; 8 | verify?: string; 9 | } 10 | 11 | export class FacebookConnector implements IConnector { 12 | private onEventHandler: (events: IEvent[], cb?: (err: Error) => void) => void; 13 | 14 | constructor(protected bot?: any, protected settings?: IFacebookConnectorSettings) { 15 | if (this.bot && this.settings) { 16 | throw new Error("Can't have both an instance of the bot and settings"); 17 | } 18 | 19 | if (this.settings) { 20 | this.bot = new Bot(settings); 21 | } 22 | 23 | this.bot.on("message", (payload: any, reply: any, actions: any) => { 24 | this.dispatch(payload); 25 | }); 26 | 27 | this.bot.on("postback", (payload: any, reply: any, actions: any) => { 28 | this.dispatch(payload); 29 | }); 30 | } 31 | 32 | public listen() { 33 | return this.bot.middleware(); 34 | } 35 | 36 | onEvent(handler: (events: IEvent[], callback?: (err: Error) => void) => void): void { 37 | this.onEventHandler = handler; 38 | } 39 | 40 | send(messages: IMessage[], done: (err: Error, addresses?: IAddress[]) => void): void { 41 | let addresses: IAddress[] = []; 42 | async.forEachOfSeries(messages, (msg, idx, cb) => { 43 | try { 44 | if (msg.type == 'delay') { 45 | setTimeout(cb, (msg).value); 46 | } else if (msg.address) { 47 | this.postMessage(msg, (idx == messages.length - 1), (err, address) => { 48 | addresses.push(address); 49 | cb(err); 50 | }); 51 | } else { 52 | console.error('FacebookConnector: send - message is missing address.') 53 | cb(new Error('Message missing address.')); 54 | } 55 | } catch (e) { 56 | cb(e); 57 | } 58 | }, (err: Error) => { 59 | done(err, !err ? addresses : null) 60 | }); 61 | } 62 | 63 | startConversation(address: IAddress, done: (err: Error, address?: IAddress) => void): void { 64 | console.log("startConversation") 65 | if (address && address.user && address.bot) { 66 | done(null, address); 67 | } else { 68 | console.error('FacebookConnector: startConversation - address is invalid.') 69 | done(new Error('Invalid address.')) 70 | } 71 | } 72 | 73 | private fromFacebookMessage(msg: any): IMessage { 74 | const sender = msg.sender.id; 75 | const recipient = msg.recipient.id; 76 | 77 | let m = new Message() 78 | .address({ 79 | channelId: "facebook", 80 | // TODO: - replace this with the bot/user name 81 | user: { id: sender }, 82 | bot: { id: recipient }, 83 | conversation: { isGroup: false, id: `${sender}-${recipient}` } 84 | }) 85 | .attachments(((msg.message ? msg.message.attachments : false) || []).map((a: any) => { 86 | let attachment: IAttachment = { 87 | contentType: a.type 88 | }; 89 | if (a.title) { 90 | attachment.name = a.title; 91 | } 92 | if (a.url) { 93 | attachment.contentUrl = a.url; 94 | } 95 | if (a.payload) { 96 | attachment.content = a.payload; 97 | if (a.payload.url) { 98 | attachment.contentUrl = a.payload.url; 99 | } 100 | } 101 | return attachment; 102 | })) 103 | .timestamp() 104 | .sourceEvent(msg); 105 | 106 | if (msg.message) { 107 | m.text(msg.message.text); 108 | if (msg.message.quick_reply) { 109 | m.value(msg.message.quick_reply.payload); 110 | } 111 | } else if (msg.postback) { 112 | m.text(msg.postback.title); 113 | m.value(msg.postback.payload); 114 | } 115 | 116 | return m.toMessage(); 117 | } 118 | 119 | private postMessage(msg: IMessage, lastMsg: boolean, cb: (err: Error, address: IAddress) => void): void { 120 | console.info("FacebookConnector: sending message") 121 | if (process.env.DEBUG) { 122 | console.info("Raw botbuilder message", JSON.stringify(msg, null, " ")) 123 | } 124 | 125 | if (msg.sourceEvent) { 126 | if (process.env.DEBUG) { 127 | console.info("Raw Facebook payload (sourceEvent)", JSON.stringify(msg.sourceEvent, null, " ")) 128 | } 129 | this.bot.sendMessage(msg.address.user.id, msg.sourceEvent, function (err: any, info: any) { 130 | if (err) { 131 | cb(new Error(err.message), null); 132 | } else { 133 | cb(null, msg.address) 134 | } 135 | }); 136 | return; 137 | } 138 | 139 | let messages = IMessageToFBMessage(msg); 140 | 141 | let bot = this.bot; 142 | async.forEachOfSeries(messages, (message, idx, cb) => { 143 | if (process.env.DEBUG) { 144 | console.info("Raw Facebook payload", JSON.stringify(message, null, " ")) 145 | } 146 | bot.sendMessage(msg.address.user.id, message, function (err: any, info: any) { 147 | cb(err); 148 | }); 149 | }, (err: any) => { 150 | if (err) { 151 | cb(new Error(err.message), null); 152 | } else { 153 | cb(null, msg.address); 154 | } 155 | }); 156 | } 157 | 158 | private dispatch(payload: any) { 159 | if (process.env.DEBUG) { 160 | console.log("FacebookConnector: message received", JSON.stringify(payload, null, " ")); 161 | } 162 | 163 | let msg = this.fromFacebookMessage(payload); 164 | 165 | if (process.env.DEBUG) { 166 | console.log("FacebookConnector: converted message", JSON.stringify(msg, null, " ")); 167 | } 168 | 169 | if (this.onEventHandler) { 170 | this.onEventHandler([msg]); 171 | } 172 | } 173 | 174 | } 175 | -------------------------------------------------------------------------------- /lib/helpers.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var Button_1 = require("./components/Button"); 4 | var QuickReply_1 = require("./components/QuickReply"); 5 | var TemplateElement_1 = require("./components/TemplateElement"); 6 | var Message_1 = require("./components/Message"); 7 | var Template_1 = require("./components/Template"); 8 | var Attachment_1 = require("./components/Attachment"); 9 | function ActionToFBButton(msg) { 10 | switch (msg.type) { 11 | case "imBack": 12 | case "postBack": 13 | return Button_1.Button.postback(msg.title, msg.value).toButton(); 14 | case "openUrl": 15 | return Button_1.Button.webURL(msg.title, msg.value).toButton(); 16 | default: 17 | return undefined; 18 | } 19 | } 20 | exports.ActionToFBButton = ActionToFBButton; 21 | function ActionsToFBButtons(buttons) { 22 | return buttons.map(function (button) { 23 | return ActionToFBButton(button); 24 | }); 25 | } 26 | exports.ActionsToFBButtons = ActionsToFBButtons; 27 | function KeyboardToQuickReply(k) { 28 | var quick_replies = []; 29 | k.buttons.map(function (action) { 30 | switch (action.type) { 31 | case 'imBack': 32 | case 'postBack': 33 | quick_replies.push(new QuickReply_1.QuickReply() 34 | .contentType(QuickReply_1.QuickReplyTypes.text) 35 | .title(action.title) 36 | .payload(action.value) 37 | .toQuickReply()); 38 | break; 39 | default: 40 | console.warn("Invalid keyboard '%s' button sent to facebook.", action.type); 41 | break; 42 | } 43 | }); 44 | return quick_replies; 45 | } 46 | exports.KeyboardToQuickReply = KeyboardToQuickReply; 47 | function IMessageToFBMessage(msg) { 48 | var messages = []; 49 | switch (msg.type) { 50 | case "message": 51 | var hasQuickReplies = false; 52 | if (msg.attachments) { 53 | var genericElements = []; 54 | for (var i = 0; i < msg.attachments.length; i++) { 55 | var a = msg.attachments[i]; 56 | switch (a.contentType) { 57 | case 'application/vnd.microsoft.card.hero': 58 | case 'application/vnd.microsoft.card.thumbnail': { 59 | var tc = a.content; 60 | var el = new TemplateElement_1.TemplateElement() 61 | .title(tc.title) 62 | .subtitle(tc.subtitle); 63 | if (tc.images && tc.images.length) { 64 | el.imageURL(tc.images[0].url); 65 | } 66 | if (tc.buttons && tc.buttons.length) { 67 | var buttons = ActionsToFBButtons(tc.buttons); 68 | el.buttons(buttons); 69 | if (buttons.length == 1 && buttons[0].type == Button_1.ButtonTypes.web_url) { 70 | var defaultAction = ActionsToFBButtons(tc.buttons)[0]; 71 | delete defaultAction.title; 72 | el.defaultAction(defaultAction); 73 | } 74 | } 75 | if (tc.text) { 76 | } 77 | if (tc.tap) { 78 | el.defaultAction(Button_1.Button.webURL(null, tc.tap.value)); 79 | } 80 | genericElements.push(el.toElement()); 81 | break; 82 | } 83 | case 'application/vnd.microsoft.card.signin': { 84 | console.error("Not implemented"); 85 | break; 86 | } 87 | case 'application/vnd.microsoft.card.receipt': { 88 | console.error("Not implemented"); 89 | break; 90 | } 91 | case "application/vnd.microsoft.card.animation": 92 | case "application/vnd.microsoft.card.video": 93 | case "application/vnd.microsoft.card.audio": { 94 | var card = a.content; 95 | if (card.media && card.media.length) { 96 | for (var j = 0; j < card.media.length; j++) { 97 | switch (a.contentType) { 98 | case "application/vnd.microsoft.card.animation": 99 | messages.push(new Message_1.FBMessage().attachment(Attachment_1.Attachment.image(card.media[j].url, true)).toMessage()); 100 | break; 101 | case "application/vnd.microsoft.card.video": 102 | messages.push(new Message_1.FBMessage().attachment(Attachment_1.Attachment.video(card.media[j].url, true)).toMessage()); 103 | break; 104 | case "application/vnd.microsoft.card.audio": 105 | messages.push(new Message_1.FBMessage().attachment(Attachment_1.Attachment.audio(card.media[j].url, true)).toMessage()); 106 | break; 107 | } 108 | } 109 | } 110 | var hasProperties = false; 111 | var el = new TemplateElement_1.TemplateElement(); 112 | if (card.title) { 113 | el.title(card.title); 114 | hasProperties = true; 115 | } 116 | if (card.subtitle) { 117 | el.subtitle(card.title); 118 | hasProperties = true; 119 | } 120 | if (card.image && card.image.url) { 121 | el.imageURL(card.image.url); 122 | hasProperties = true; 123 | } 124 | if (card.buttons && card.buttons.length) { 125 | el.buttons(ActionsToFBButtons(card.buttons)); 126 | hasProperties = true; 127 | } 128 | if (hasProperties) { 129 | genericElements.push(el.toElement()); 130 | } 131 | break; 132 | } 133 | case "application/vnd.microsoft.keyboard": { 134 | if (msg.text) { 135 | var quickReplies = KeyboardToQuickReply(a.content); 136 | messages.push(new Message_1.FBMessage() 137 | .text(msg.text) 138 | .quickReplies(quickReplies) 139 | .toMessage()); 140 | hasQuickReplies = true; 141 | } 142 | break; 143 | } 144 | } 145 | } 146 | if (genericElements.length) { 147 | messages.push(Template_1.Template.generic(genericElements).toMessage()); 148 | } 149 | } 150 | if (msg.text && !hasQuickReplies) { 151 | messages.push(new Message_1.FBMessage() 152 | .text(msg.text) 153 | .toMessage()); 154 | } 155 | break; 156 | default: 157 | break; 158 | } 159 | return messages; 160 | } 161 | exports.IMessageToFBMessage = IMessageToFBMessage; 162 | -------------------------------------------------------------------------------- /src/helpers.ts: -------------------------------------------------------------------------------- 1 | import { ICardAction, IMessage, IThumbnailCard, IAnimationCard, IVideoCard, IAudioCard, IMediaCard, AnimationCard, VideoCard, AudioCard } from "botbuilder"; 2 | import { IButton, IQuickReply, IKeyboard, IFBMessage, ITemplateElement } from "./interfaces"; 3 | import { Button, ButtonTypes } from "./components/Button"; 4 | import { QuickReply, QuickReplyTypes } from "./components/QuickReply"; 5 | import { TemplateElement } from "./components/TemplateElement"; 6 | import { FBMessage } from "./components/Message"; 7 | import { Template } from "./components/Template"; 8 | import { Attachment } from "./components/Attachment"; 9 | 10 | export function ActionToFBButton(msg: ICardAction): IButton { 11 | switch (msg.type) { 12 | case "imBack": 13 | case "postBack": 14 | return Button.postback(msg.title, msg.value).toButton(); 15 | case "openUrl": 16 | return Button.webURL(msg.title, msg.value).toButton(); 17 | default: 18 | // TODO: - what other types can there be? 19 | return undefined; 20 | } 21 | } 22 | 23 | export function ActionsToFBButtons(buttons: ICardAction[]): IButton[] { 24 | return buttons.map((button) => { 25 | return ActionToFBButton(button); 26 | }) 27 | } 28 | 29 | export function KeyboardToQuickReply(k: IKeyboard) { 30 | let quick_replies: IQuickReply[] = []; 31 | 32 | k.buttons.map((action: ICardAction) => { 33 | switch (action.type) { 34 | case 'imBack': 35 | case 'postBack': 36 | quick_replies.push(new QuickReply() 37 | .contentType(QuickReplyTypes.text) 38 | .title(action.title) 39 | .payload(action.value) 40 | .toQuickReply()); 41 | break; 42 | default: 43 | console.warn("Invalid keyboard '%s' button sent to facebook.", action.type); 44 | break; 45 | } 46 | }); 47 | 48 | return quick_replies; 49 | } 50 | 51 | export function IMessageToFBMessage(msg: IMessage) { 52 | let messages: IFBMessage[] = []; 53 | 54 | switch (msg.type) { 55 | case "message": 56 | 57 | let hasQuickReplies = false; 58 | 59 | if (msg.attachments) { 60 | 61 | let genericElements: ITemplateElement[] = []; 62 | 63 | for (let i = 0; i < msg.attachments.length; i++) { 64 | let a = msg.attachments[i]; 65 | 66 | switch (a.contentType) { 67 | case 'application/vnd.microsoft.card.hero': 68 | case 'application/vnd.microsoft.card.thumbnail': { 69 | let tc: IThumbnailCard = a.content; 70 | 71 | let el = new TemplateElement() 72 | .title(tc.title) 73 | .subtitle(tc.subtitle) 74 | 75 | // TODO: - is this a good approach? 76 | if (tc.images && tc.images.length) { 77 | el.imageURL(tc.images[0].url); 78 | } 79 | 80 | if (tc.buttons && tc.buttons.length) { 81 | let buttons = ActionsToFBButtons(tc.buttons); 82 | el.buttons(buttons); 83 | 84 | // if there is only one button of type `web_url`, also set it as default action 85 | if (buttons.length == 1 && buttons[0].type == ButtonTypes.web_url) { 86 | let [defaultAction] = ActionsToFBButtons(tc.buttons); 87 | delete defaultAction.title; 88 | el.defaultAction(defaultAction) 89 | } 90 | } 91 | 92 | if (tc.text) { 93 | // not supported 94 | } 95 | 96 | // TODO: - I'm not sure if this is correct 97 | if (tc.tap) { 98 | el.defaultAction( 99 | Button.webURL(null, tc.tap.value) 100 | ) 101 | } 102 | 103 | genericElements.push(el.toElement()); 104 | 105 | break; 106 | } 107 | 108 | case 'application/vnd.microsoft.card.signin': { 109 | console.error("Not implemented"); 110 | break; 111 | } 112 | 113 | case 'application/vnd.microsoft.card.receipt': { 114 | console.error("Not implemented"); 115 | break; 116 | } 117 | 118 | case "application/vnd.microsoft.card.animation": 119 | case "application/vnd.microsoft.card.video": 120 | case "application/vnd.microsoft.card.audio": { 121 | 122 | let card: IMediaCard = a.content; 123 | 124 | if (card.media && card.media.length) { 125 | for (let j = 0; j < card.media.length; j++) { 126 | switch (a.contentType) { 127 | case "application/vnd.microsoft.card.animation": 128 | messages.push( 129 | new FBMessage().attachment( 130 | Attachment.image(card.media[j].url, true) 131 | ).toMessage() 132 | ) 133 | break; 134 | case "application/vnd.microsoft.card.video": 135 | messages.push( 136 | new FBMessage().attachment( 137 | Attachment.video(card.media[j].url, true) 138 | ).toMessage() 139 | ) 140 | break; 141 | case "application/vnd.microsoft.card.audio": 142 | messages.push( 143 | new FBMessage().attachment( 144 | Attachment.audio(card.media[j].url, true) 145 | ).toMessage() 146 | ) 147 | break; 148 | } 149 | } 150 | } 151 | 152 | let hasProperties = false; 153 | 154 | let el = new TemplateElement(); 155 | 156 | if (card.title) { 157 | el.title(card.title); 158 | hasProperties = true; 159 | } 160 | 161 | if (card.subtitle) { 162 | el.subtitle(card.title); 163 | hasProperties = true; 164 | } 165 | 166 | if (card.image && card.image.url) { 167 | el.imageURL(card.image.url) 168 | hasProperties = true; 169 | } 170 | 171 | if (card.buttons && card.buttons.length) { 172 | el.buttons(ActionsToFBButtons(card.buttons)); 173 | hasProperties = true; 174 | } 175 | 176 | if (hasProperties) { 177 | genericElements.push(el.toElement()); 178 | } 179 | 180 | break; 181 | } 182 | 183 | case "application/vnd.microsoft.keyboard": { 184 | if (msg.text) { 185 | let quickReplies = KeyboardToQuickReply(a.content); 186 | messages.push( 187 | new FBMessage() 188 | .text(msg.text) 189 | .quickReplies(quickReplies) 190 | .toMessage() 191 | ) 192 | hasQuickReplies = true; 193 | } 194 | 195 | break; 196 | } 197 | } 198 | } 199 | 200 | if (genericElements.length) { 201 | messages.push( 202 | Template.generic(genericElements).toMessage() 203 | ); 204 | } 205 | } 206 | 207 | if (msg.text && !hasQuickReplies) { 208 | messages.push( 209 | new FBMessage() 210 | .text(msg.text) 211 | .toMessage() 212 | ); 213 | } 214 | 215 | break; 216 | 217 | // TODO: - what other types are there? (check `delay`) 218 | default: 219 | break; 220 | } 221 | 222 | return messages; 223 | } 224 | --------------------------------------------------------------------------------