├── .npmignore ├── .babelrc ├── .gitignore ├── .travis.yml ├── lib ├── elements │ ├── messenger-address.js │ ├── messenger-bubble.js │ ├── messenger-summary.js │ ├── messenger-text.js │ ├── messenger-adjustment.js │ ├── messenger-image.js │ └── messenger-button.js ├── templates │ ├── messenger-generic-template.js │ ├── messenger-button-template.js │ └── messenger-receipt-template.js ├── messenger-client.js └── messenger-wrapper.js ├── test ├── elements │ ├── messenger-text.js │ ├── messenger-adjustment.js │ ├── messenger-image.js │ ├── messenger-summary.js │ ├── messenger-address.js │ ├── messenger-bubble.js │ └── messenger-button.js ├── messenger-wrapper.js └── templates │ ├── messenger-button-template.js │ ├── messenger-generic-template.js │ └── messenger-receipt-template.js ├── package.json ├── example └── express-example.js └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | messenger-wrapper.js 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | /node_modules 3 | .env* 4 | .nyc* 5 | distribution 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '5' 4 | install: 5 | - npm install -g babel-cli 6 | - npm install 7 | after_success: npm run travis-coveralls 8 | -------------------------------------------------------------------------------- /lib/elements/messenger-address.js: -------------------------------------------------------------------------------- 1 | class MessengerAddress { 2 | constructor(attrs) { 3 | return attrs; 4 | } 5 | } 6 | 7 | export default MessengerAddress; 8 | -------------------------------------------------------------------------------- /lib/elements/messenger-bubble.js: -------------------------------------------------------------------------------- 1 | class MessengerBubble { 2 | constructor(attrs) { 3 | return attrs; 4 | } 5 | } 6 | 7 | export default MessengerBubble; 8 | -------------------------------------------------------------------------------- /lib/elements/messenger-summary.js: -------------------------------------------------------------------------------- 1 | class MessengerSummary { 2 | constructor(attrs) { 3 | return attrs; 4 | } 5 | } 6 | 7 | export default MessengerSummary; 8 | -------------------------------------------------------------------------------- /lib/elements/messenger-text.js: -------------------------------------------------------------------------------- 1 | class MessengerText { 2 | constructor(text) { 3 | return { 4 | text: text 5 | }; 6 | } 7 | } 8 | 9 | export default MessengerText; 10 | -------------------------------------------------------------------------------- /lib/elements/messenger-adjustment.js: -------------------------------------------------------------------------------- 1 | class MessengerAdjustment { 2 | constructor(name, amount) { 3 | return { 4 | name: name, 5 | amount: amount 6 | }; 7 | } 8 | } 9 | 10 | export default MessengerAdjustment; 11 | -------------------------------------------------------------------------------- /lib/elements/messenger-image.js: -------------------------------------------------------------------------------- 1 | class MessengerImage { 2 | constructor(url) { 3 | return { 4 | attachment: { 5 | type: 'image', 6 | payload: { 7 | url: url 8 | } 9 | } 10 | }; 11 | } 12 | } 13 | 14 | export default MessengerImage; 15 | -------------------------------------------------------------------------------- /lib/templates/messenger-generic-template.js: -------------------------------------------------------------------------------- 1 | class MessengerGenericTemplate { 2 | constructor(elements) { 3 | return { 4 | attachment: { 5 | type: 'template', 6 | payload: { 7 | template_type: 'generic', 8 | elements: elements 9 | } 10 | } 11 | }; 12 | } 13 | } 14 | 15 | export default MessengerGenericTemplate; 16 | -------------------------------------------------------------------------------- /lib/templates/messenger-button-template.js: -------------------------------------------------------------------------------- 1 | class MessengerButtonTemplate { 2 | constructor(text, buttons) { 3 | return { 4 | attachment: { 5 | type: 'template', 6 | payload: { 7 | template_type: 'button', 8 | text: text, 9 | buttons: buttons 10 | } 11 | } 12 | }; 13 | } 14 | } 15 | 16 | export default MessengerButtonTemplate; 17 | -------------------------------------------------------------------------------- /test/elements/messenger-text.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import MessengerText from '../../lib/elements/messenger-text'; 4 | 5 | describe('MessengerText', () => { 6 | describe('new', () => { 7 | it('returns proper object', () => { 8 | const text = new MessengerText('Hello user!'); 9 | 10 | expect(text).to.deep.equal({ text: 'Hello user!' }); 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /test/elements/messenger-adjustment.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import MessengerAdjustment from '../../lib/elements/messenger-adjustment'; 4 | 5 | describe('MessengerAdjustment', () => { 6 | describe('new', () => { 7 | it('returns proper object', () => { 8 | const adjustment = new MessengerAdjustment('Adjustment', 20); 9 | 10 | expect(adjustment).to.deep.equal({ 11 | name: 'Adjustment', 12 | amount: 20 13 | }); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/elements/messenger-image.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import MessengerImage from '../../lib/elements/messenger-image'; 4 | 5 | describe('MessengerImage', () => { 6 | describe('new', () => { 7 | it('returns proper object', () => { 8 | const image = new MessengerImage('http://coolimage.com'); 9 | 10 | expect(image).to.deep.equal({ 11 | attachment: { 12 | type: 'image', 13 | payload: { 14 | url: 'http://coolimage.com' 15 | } 16 | } 17 | }); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/elements/messenger-summary.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import MessengerSummary from '../../lib/elements/messenger-summary'; 4 | 5 | describe('MessengerSummary', () => { 6 | describe('new', () => { 7 | it('returns proper object', () => { 8 | const summary = new MessengerSummary({ 9 | subtotal: 75.00, 10 | shipping_cost: 4.95, 11 | total_tax: 6.19, 12 | total_cost: 56.14 13 | }); 14 | 15 | expect(summary).to.deep.equal({ 16 | subtotal: 75.00, 17 | shipping_cost: 4.95, 18 | total_tax: 6.19, 19 | total_cost: 56.14 20 | }); 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /lib/templates/messenger-receipt-template.js: -------------------------------------------------------------------------------- 1 | class MessengerReceiptTemplate { 2 | constructor(attrs) { 3 | return { 4 | attachment: { 5 | type: 'template', 6 | payload: { 7 | template_type: 'receipt', 8 | recipient_name: attrs.recipient_name, 9 | order_number: attrs.order_number, 10 | currency: attrs.currency, 11 | payment_method: attrs.payment_method, 12 | order_url: attrs.order_url, 13 | timestamp: attrs.timestamp, 14 | elements: attrs.elements, 15 | address: attrs.address, 16 | summary: attrs.summary, 17 | adjustments: attrs.adjustments 18 | } 19 | } 20 | }; 21 | } 22 | } 23 | 24 | export default MessengerReceiptTemplate; 25 | -------------------------------------------------------------------------------- /test/elements/messenger-address.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import MessengerAddress from '../../lib/elements/messenger-address'; 4 | 5 | describe('MessengerAddress', () => { 6 | describe('new', () => { 7 | it('returns proper object', () => { 8 | const address = new MessengerAddress({ 9 | street_1: '1 Hacker Way', 10 | street_2: '', 11 | city: 'Menlo Park', 12 | postal_code: '94025', 13 | state: 'CA', 14 | country: 'US' 15 | }); 16 | 17 | expect(address).to.deep.equal({ 18 | street_1: '1 Hacker Way', 19 | street_2: '', 20 | city: 'Menlo Park', 21 | postal_code: '94025', 22 | state: 'CA', 23 | country: 'US' 24 | }); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /test/messenger-wrapper.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import { MessengerWrapper } from '../lib/messenger-wrapper'; 4 | 5 | describe('MessengerWrapper', () => { 6 | describe('new', () => { 7 | describe('with all attributes', () => { 8 | it('initializes correctly', () => { 9 | const wrapper = new MessengerWrapper({ 10 | verifyToken: 'verify_token', 11 | pageAccessToken: 'page_access_token' 12 | }); 13 | 14 | expect(wrapper).to.be.ok; 15 | }); 16 | }); 17 | 18 | describe('with one attribute missing', () => { 19 | it('throws an error', () => { 20 | expect(() => { 21 | new MessengerWrapper({ verifyToken: 'verify_token' }) 22 | }).to.throw(Error, 'VERIFY_TOKEN or PAGE_ACCESS_TOKEN are missing.'); 23 | }); 24 | }); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /lib/elements/messenger-button.js: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | 3 | class MessengerButton { 4 | constructor(attrs) { 5 | return this.constructObject(attrs); 6 | } 7 | 8 | constructObject(attrs) { 9 | const type = this.determineType(attrs); 10 | 11 | if (type === 'web_url') { 12 | return { 13 | type: 'web_url', 14 | url: attrs.url, 15 | title: attrs.title 16 | }; 17 | } 18 | 19 | if (type === 'postback') { 20 | return { 21 | type: 'postback', 22 | title: attrs.title, 23 | payload: attrs.payload 24 | }; 25 | } 26 | } 27 | 28 | determineType(attrs) { 29 | if (_.has(attrs, 'url') && _.has(attrs, 'title')) { 30 | return 'web_url'; 31 | } else if (_.has(attrs, 'title') && _.has(attrs, 'payload')) { 32 | return 'postback'; 33 | } 34 | }; 35 | } 36 | 37 | export default MessengerButton; 38 | -------------------------------------------------------------------------------- /test/templates/messenger-button-template.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import MessengerButton from '../../lib/elements/messenger-button'; 4 | import MessengerButtonTemplate from '../../lib/templates/messenger-button-template'; 5 | 6 | describe('MessengerButtonTemplate', () => { 7 | describe('new', () => { 8 | it('returns proper object', () => { 9 | const template = new MessengerButtonTemplate( 10 | 'Hello user!', 11 | [ 12 | new MessengerButton({ title: 'Button', url: 'http://www.example.com' }) 13 | ] 14 | ); 15 | 16 | expect(template).to.deep.equal({ 17 | attachment: { 18 | type: 'template', 19 | payload: { 20 | template_type: 'button', 21 | text: 'Hello user!', 22 | buttons: [ 23 | { 24 | type: 'web_url', 25 | title: 'Button', 26 | url: 'http://www.example.com' 27 | } 28 | ] 29 | } 30 | } 31 | }); 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /test/elements/messenger-bubble.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import MessengerButton from '../../lib/elements/messenger-button'; 4 | import MessengerBubble from '../../lib/elements/messenger-bubble'; 5 | 6 | describe('MessengerButton', () => { 7 | describe('new', () => { 8 | it('returns proper object', () => { 9 | const bubble = new MessengerBubble({ 10 | title: 'Title', 11 | item_url: 'http://www.example.com', 12 | image_url: 'http://www.example.com', 13 | subtitle: 'Subtitle', 14 | buttons: [ 15 | new MessengerButton({ title: 'Button', url: 'http://www.example.com' }) 16 | ] 17 | }); 18 | 19 | expect(bubble).to.deep.equal({ 20 | title: 'Title', 21 | item_url: 'http://www.example.com', 22 | image_url: 'http://www.example.com', 23 | subtitle: 'Subtitle', 24 | buttons: [ 25 | { 26 | type: 'web_url', 27 | title: 'Button', 28 | url: 'http://www.example.com' 29 | } 30 | ] 31 | }); 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /test/elements/messenger-button.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import MessengerButton from '../../lib/elements/messenger-button'; 4 | 5 | describe('MessengerButton', () => { 6 | describe('new', () => { 7 | describe('with type of web_url', () => { 8 | it('returns proper object', () => { 9 | const button = new MessengerButton({ 10 | url: 'http://www.example.com', 11 | title: 'Example Title' 12 | }); 13 | 14 | expect(button).to.deep.equal({ 15 | type: 'web_url', 16 | url: 'http://www.example.com', 17 | title: 'Example Title' 18 | }); 19 | }); 20 | }); 21 | 22 | describe('with type of postback', () => { 23 | it('returns proper object', () => { 24 | const button = new MessengerButton({ 25 | title: 'Example Title', 26 | payload: 'EXAMPLE' 27 | }); 28 | 29 | expect(button).to.deep.equal({ 30 | type: 'postback', 31 | title: 'Example Title', 32 | payload: 'EXAMPLE' 33 | }); 34 | }); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /lib/messenger-client.js: -------------------------------------------------------------------------------- 1 | import request from 'request'; 2 | import Promise from 'bluebird'; 3 | 4 | Promise.promisifyAll(request); 5 | 6 | class MessengerClient { 7 | constructor(context) { 8 | this.context = context; 9 | } 10 | 11 | getUserData(entry) { 12 | return request.getAsync({ 13 | uri: `https://graph.facebook.com/v2.6/${entry.sender.id}`, 14 | qs: { 15 | fields: 'first_name,last_name,profile_pic', 16 | access_token: this.context.pageAccessToken 17 | }, 18 | json: true 19 | }).then((response) => { 20 | return response.body; 21 | }); 22 | } 23 | 24 | sendData(payload, entry) { 25 | return request.postAsync({ 26 | uri: 'https://graph.facebook.com/v2.6/me/messages', 27 | qs: { 28 | access_token: this.context.pageAccessToken 29 | }, 30 | json: { 31 | recipient: { id: entry.sender.id }, 32 | message: payload 33 | } 34 | }); 35 | } 36 | 37 | subscribeAppToPage() { 38 | return request.postAsync({ 39 | uri: `https://graph.facebook.com/v2.6/me/subscribed_apps`, 40 | qs: { 41 | access_token: this.context.pageAccessToken 42 | }, 43 | json: true 44 | }); 45 | } 46 | } 47 | 48 | export default MessengerClient; 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "messenger-wrapper", 3 | "version": "2.2.3", 4 | "description": "A simple wrapper for Facebook Messenger Bots", 5 | "main": "./distribution/messenger-wrapper.js", 6 | "scripts": { 7 | "build": "babel lib --presets babel-preset-es2015 --out-dir distribution", 8 | "prepublish": "npm run build", 9 | "start": "babel-node ./lib/messenger-wrapper.js", 10 | "start:server": "nodemon --exec \"babel-node ./example/express-example.js\" --watch ./", 11 | "test": "mocha --compilers js:babel-core/register \"test/**/*@(.js)\"", 12 | "travis-coveralls": "nyc npm test && nyc report --reporter=text-lcov | coveralls" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/justynjozwiak/messenger-wrapper.git" 17 | }, 18 | "author": "Justyn Jóźwiak", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/justynjozwiak/messenger-wrapper/issues" 22 | }, 23 | "homepage": "https://github.com/justynjozwiak/messenger-wrapper#readme", 24 | "devDependencies": { 25 | "babel-core": "^6.7.7", 26 | "babel-preset-es2015": "^6.6.0", 27 | "chai": "^3.5.0", 28 | "coveralls": "^2.11.9", 29 | "mocha": "^2.4.5", 30 | "mocha-lcov-reporter": "^1.2.0", 31 | "nodemon": "^1.9.1", 32 | "nyc": "^6.4.0" 33 | }, 34 | "dependencies": { 35 | "bluebird": "^3.3.5", 36 | "lodash": "^4.11.1", 37 | "request": "^2.72.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/templates/messenger-generic-template.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import MessengerBubble from '../../lib/elements/messenger-bubble'; 4 | import MessengerButton from '../../lib/elements/messenger-button'; 5 | import MessengerGenericTemplate from '../../lib/templates/messenger-generic-template'; 6 | 7 | describe('MessengerGenericTemplate', () => { 8 | describe('new', () => { 9 | it('returns proper object', () => { 10 | const template = new MessengerGenericTemplate([ 11 | new MessengerBubble({ 12 | title: 'Title', 13 | item_url: 'http://www.example.com', 14 | image_url: 'http://www.example.com', 15 | subtitle: 'Subtitle', 16 | buttons: [ 17 | new MessengerButton({ title: 'Button', url: 'http://www.example.com' }) 18 | ] 19 | }) 20 | ]); 21 | 22 | expect(template).to.deep.equal({ 23 | attachment: { 24 | type: 'template', 25 | payload: { 26 | template_type: 'generic', 27 | elements: [ 28 | { 29 | title: 'Title', 30 | item_url: 'http://www.example.com', 31 | image_url: 'http://www.example.com', 32 | subtitle: 'Subtitle', 33 | buttons: [ 34 | { 35 | type: 'web_url', 36 | title: 'Button', 37 | url: 'http://www.example.com' 38 | } 39 | ] 40 | } 41 | ] 42 | } 43 | } 44 | }) 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /example/express-example.js: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv'; 2 | import http from 'http'; 3 | import express from 'express'; 4 | import bodyParser from 'body-parser'; 5 | 6 | import { 7 | MessengerWrapper, 8 | MessengerText, 9 | MessengerImage, 10 | MessengerButton, 11 | MessengerBubble, 12 | MessengerAddress, 13 | MessengerSummary, 14 | MessengerAdjustment, 15 | MessengerButtonTemplate, 16 | MessengerGenericTemplate, 17 | MessengerReceiptTemplate 18 | } from '../lib/messenger-wrapper'; 19 | 20 | dotenv.config(); 21 | 22 | let app = express(); 23 | 24 | app.use(bodyParser.json()); 25 | app.use(bodyParser.urlencoded({ extended: true })); 26 | 27 | let messenger = new MessengerWrapper({ 28 | verifyToken: process.env.VERIFY_TOKEN, 29 | pageAccessToken: process.env.PAGE_ACCESS_TOKEN 30 | }); 31 | 32 | messenger.on('message', () => { 33 | messenger.send(new MessengerGenericTemplate( 34 | [ 35 | new MessengerBubble({ 36 | title: 'Title', 37 | item_url: 'http://www.example.com', 38 | image_url: 'http://www.example.com', 39 | subtitle: 'Subtitle', 40 | buttons: [ 41 | new MessengerButton({ title: 'Button', url: 'http://www.example.com' }) 42 | ] 43 | }) 44 | ] 45 | )); 46 | }); 47 | 48 | messenger.on('delivery', () => { 49 | // console.log(wrapper.lastEntry); 50 | }); 51 | 52 | messenger.on('postback', () => { 53 | // console.log(wrapper.lastEntry); 54 | }); 55 | 56 | app.get('/webhook', (req, res) => { 57 | messenger.verify(req, res); 58 | }); 59 | 60 | app.get('/subscribe', (req, res) => { 61 | messenger.subscribe().then((response) => { 62 | res.send(response.body); 63 | }); 64 | }); 65 | 66 | app.post('/webhook', (req, res) => { 67 | res.sendStatus(200); 68 | messenger.handle(req); 69 | }); 70 | 71 | http.createServer(app).listen(3000); 72 | -------------------------------------------------------------------------------- /lib/messenger-wrapper.js: -------------------------------------------------------------------------------- 1 | import EventEmitter from 'events'; 2 | 3 | import MessengerClient from './messenger-client'; 4 | 5 | import MessengerText from './elements/messenger-text'; 6 | import MessengerImage from './elements/messenger-image'; 7 | import MessengerButton from './elements/messenger-button'; 8 | import MessengerBubble from './elements/messenger-bubble'; 9 | import MessengerAddress from './elements/messenger-address'; 10 | import MessengerSummary from './elements/messenger-summary'; 11 | import MessengerAdjustment from './elements/messenger-adjustment'; 12 | 13 | import MessengerButtonTemplate from './templates/messenger-button-template'; 14 | import MessengerGenericTemplate from './templates/messenger-generic-template'; 15 | import MessengerReceiptTemplate from './templates/messenger-receipt-template'; 16 | 17 | class MessengerWrapper extends EventEmitter { 18 | constructor(opts) { 19 | super(); 20 | 21 | opts = opts || {}; 22 | 23 | if (!opts.verifyToken || !opts.pageAccessToken) { 24 | throw new Error('VERIFY_TOKEN or PAGE_ACCESS_TOKEN are missing.'); 25 | } 26 | 27 | this.verifyToken = opts.verifyToken; 28 | this.pageAccessToken = opts.pageAccessToken; 29 | this.messengerClient = new MessengerClient(this); 30 | this.lastEntry = {}; 31 | } 32 | 33 | verify(req, res) { 34 | if (req.query['hub.verify_token'] === this.verifyToken) { 35 | return res.send(req.query['hub.challenge']); 36 | } else { 37 | return res.send('VERIFY_TOKEN does not match.'); 38 | } 39 | } 40 | 41 | handle(req) { 42 | let entries = req.body.entry; 43 | 44 | entries.forEach((entry) => { 45 | entry.messaging.forEach((event) => { 46 | if (event.message) { 47 | this.handleEvent('message', event); 48 | } 49 | else if (event.delivery) { 50 | this.handleEvent('delivery', event); 51 | } 52 | else if (event.postback) { 53 | this.handleEvent('postback', event); 54 | } 55 | else if (event.optin) { 56 | this.handleEvent('optin', event); 57 | } 58 | }); 59 | }); 60 | } 61 | 62 | handleEvent(action, event) { 63 | this.lastEntry = event; 64 | this.emit(action, event); 65 | } 66 | 67 | getUser() { 68 | return this.messengerClient.getUserData(this.lastEntry); 69 | } 70 | 71 | send(payload) { 72 | return this.messengerClient.sendData(payload, this.lastEntry); 73 | } 74 | 75 | getUserId() { 76 | return this.lastEntry.sender.id; 77 | } 78 | 79 | subscribe() { 80 | return this.messengerClient.subscribeAppToPage(); 81 | } 82 | } 83 | 84 | export { 85 | MessengerWrapper, 86 | MessengerText, 87 | MessengerImage, 88 | MessengerButton, 89 | MessengerBubble, 90 | MessengerAddress, 91 | MessengerSummary, 92 | MessengerAdjustment, 93 | MessengerButtonTemplate, 94 | MessengerGenericTemplate, 95 | MessengerReceiptTemplate 96 | } 97 | -------------------------------------------------------------------------------- /test/templates/messenger-receipt-template.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import MessengerBubble from '../../lib/elements/messenger-bubble'; 4 | import MessengerButton from '../../lib/elements/messenger-button'; 5 | import MessengerAddress from '../../lib/elements/messenger-address'; 6 | import MessengerSummary from '../../lib/elements/messenger-summary'; 7 | import MessengerAdjustment from '../../lib/elements/messenger-adjustment'; 8 | import MessengerReceiptTemplate from '../../lib/templates/messenger-receipt-template'; 9 | 10 | describe('MessengerReceiptTemplate', () => { 11 | describe('new', () => { 12 | it('returns proper object', () => { 13 | const template = new MessengerReceiptTemplate({ 14 | recipient_name: 'Name', 15 | order_number: '123', 16 | currency: 'USD', 17 | payment_method: 'Visa', 18 | order_url: 'http://www.example.com', 19 | timestamp: '123123123', 20 | elements: [ 21 | new MessengerBubble({ 22 | title: 'Title', 23 | item_url: 'http://www.example.com', 24 | image_url: 'http://www.example.com', 25 | subtitle: 'Subtitle', 26 | buttons: [ 27 | new MessengerButton({ title: 'Button', url: 'http://www.example.com' }) 28 | ] 29 | }) 30 | ], 31 | address: new MessengerAddress({ 32 | street_1: '1 Hacker Way', 33 | street_2: '', 34 | city: 'Menlo Park', 35 | postal_code: '94025', 36 | state: 'CA', 37 | country: 'US' 38 | }), 39 | summary: new MessengerSummary({ 40 | subtotal: 75.00, 41 | shipping_cost: 4.95, 42 | total_tax: 6.19, 43 | total_cost: 56.14 44 | }), 45 | adjustments: [ 46 | new MessengerAdjustment('Adjustment', 20) 47 | ] 48 | }); 49 | 50 | expect(template).to.deep.equal({ 51 | attachment: { 52 | type: 'template', 53 | payload: { 54 | template_type: 'receipt', 55 | recipient_name: 'Name', 56 | order_number: '123', 57 | currency: 'USD', 58 | payment_method: 'Visa', 59 | order_url: 'http://www.example.com', 60 | timestamp: '123123123', 61 | elements: [ 62 | { 63 | title: 'Title', 64 | item_url: 'http://www.example.com', 65 | image_url: 'http://www.example.com', 66 | subtitle: 'Subtitle', 67 | buttons: [ 68 | { 69 | type: 'web_url', 70 | title: 'Button', 71 | url: 'http://www.example.com' 72 | } 73 | ] 74 | } 75 | ], 76 | address: { 77 | street_1: '1 Hacker Way', 78 | street_2: '', 79 | city: 'Menlo Park', 80 | postal_code: '94025', 81 | state: 'CA', 82 | country: 'US' 83 | }, 84 | summary: { 85 | subtotal: 75.00, 86 | shipping_cost: 4.95, 87 | total_tax: 6.19, 88 | total_cost: 56.14 89 | }, 90 | adjustments: [ 91 | { 92 | name: 'Adjustment', 93 | amount: 20 94 | } 95 | ] 96 | } 97 | } 98 | }); 99 | }); 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # messenger-wrapper 2 | [![Build Status](https://travis-ci.org/justynjozwiak/messenger-wrapper.svg?branch=master)](https://travis-ci.org/justynjozwiak/messenger-wrapper) 3 | [![CoverageStatus](https://coveralls.io/repos/github/justynjozwiak/messenger-wrapper/badge.svg?branch=master)](https://coveralls.io/github/justynjozwiak/messenger-wrapper?branch=master) 4 | [![npm version](https://img.shields.io/npm/v/messenger-wrapper.svg?style=flat)](https://www.npmjs.com/package/messenger-wrapper) 5 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/) 6 | [![Dependency Status](https://www.versioneye.com/user/projects/571a18b3fcd19a00415b21bc/badge.svg)](https://www.versioneye.com/user/projects/571a18b3fcd19a00415b21bc) 7 | [![Github Issues](http://githubbadges.herokuapp.com/justynjozwiak/messenger-wrapper/issues.svg)](https://github.com/justynjozwiak/messenger-wrapper/issues) 8 | [![License](http://img.shields.io/:license-MIT-blue.svg)](http://badges.mit-license.org) 9 | 10 | A simple library for handling [Facebook Messenger Bots](https://developers.facebook.com/docs/messenger-platform). 11 | 12 | ## Installation 13 | 14 | Execute this line in your app directory: 15 | 16 | ``` 17 | npm install --save messenger-wrapper 18 | ``` 19 | 20 | Import library into your app: 21 | 22 | ```javascript 23 | import { MessengerWrapper } from 'messenger-wrapper'; 24 | ``` 25 | 26 | Initialize it: 27 | 28 | ```javascript 29 | const messenger = new MessengerWrapper({ 30 | verifyToken: '', 31 | pageAccessToken: '' 32 | }); 33 | ``` 34 | 35 | and you are ready to go. 36 | 37 | ## Configuration (facebook's side) 38 | 39 | First of all visit this official [tutorial](https://developers.facebook.com/docs/messenger-platform/quickstart#steps]) and 40 | make sure you complete these 3 steps: 41 | 42 | Subscribe the App to a Page - remember that instead of making subscription call manually: 43 | 44 | ``` 45 | curl -X POST "https://graph.facebook.com/v2.6/me/subscribed_apps?access_token=" 46 | ``` 47 | 48 | You can always run you express app with all necessary routes defined and go under `/subscribe`. See the first example for more details. 49 | 50 | Steps: 51 | 52 | * [Create page on Facebook](https://www.facebook.com/pages/create/) or use existing one if you already have it 53 | 54 | * [create app on Facebook](https://developers.facebook.com/quickstarts/?platform=web) or use existing one if you already have it 55 | 56 | * visit [your developer account](https://developers.facebook.com/apps/) and get `PAGE_ACCESS_TOKEN` to initialize wrapper 57 | 58 | ## Express.js (example usage) 59 | 60 | This is sample usage within express.js application. For full example look [here](https://github.com/justynjozwiak/messenger-wrapper/blob/master/example/express-example.js). 61 | 62 | ```javascript 63 | import { MessengerWrapper } from 'messenger-wrapper'; 64 | 65 | //let's initialize our wrapper here 66 | const messenger = new MessengerWrapper({ 67 | verifyToken: '', 68 | pageAccessToken: '' 69 | }); 70 | 71 | //here we define 4 available listeners: 'message', 'delivery', 'postback' and 'optin': 72 | messenger.on('message', (event) => { 73 | //put your logic here 74 | }); 75 | 76 | messenger.on('delivery', (event) => { 77 | //put your logic here 78 | }); 79 | 80 | messenger.on('postback', (event) => { 81 | //put your logic here 82 | }); 83 | 84 | messenger.on('optin', (event) => { 85 | //put your logic here 86 | }); 87 | 88 | //this route is needed for facebook messenger verification 89 | app.get('/webhook', (req, res) => { 90 | messenger.verify(req, res); 91 | }); 92 | 93 | //according to documentation https://developers.facebook.com/docs/messenger-platform/implementation 94 | //instead of sending this request manually -> curl -X POST "https://graph.facebook.com/v2.6/me/subscribed_apps?access_token=" 95 | //you can just run your express app and go under /subscribe in your web browser 96 | app.get('/subscribe', (req, res) => { 97 | messenger.subscribe().then((response) => { 98 | res.send(response.body); 99 | }); 100 | }); 101 | 102 | //here we handle messenger data, you've got nothing to do here, just define that route 103 | app.post('/webhook', (req, res) => { 104 | res.sendStatus(200); 105 | messenger.handle(req); 106 | }); 107 | ``` 108 | 109 | ## Documentation 110 | 111 | ### Events 112 | 113 | Basically we have 3 types of events according to Facebook documentation: 114 | 115 | ATTENTION: 116 | 117 | `(event)` param here is optional, you can omit it, and it's only purpose is to show you incoming data. According to Facebook documentation 118 | each incoming data can containt multiple `entries`, that's why this library supports iterating over them in background and emits proper 119 | actions, so you don't have to worry about losing any data. To get latest entry you should use `messenger.lastEntry` object or use dedicated methods like `send()` or `getUserId()` (and more `soon`) that operate on `messenger.lastEntry` object. Go through 120 | documentation to see examples. 121 | 122 | #### `messenger.on('message', (event))` 123 | 124 | Event triggered when the bot receives message from the user. 125 | 126 | `event` - object with payload received from messenger user 127 | 128 | Example usage: 129 | 130 | ```javascript 131 | messenger.on('message', (event) => { 132 | messenger.send({ text: 'Welcome!' }); 133 | }); 134 | ``` 135 | 136 | #### `messenger.on('delivery', (event))` 137 | 138 | Event triggered when the message has been successfully delivered to the user. 139 | 140 | `event` - object with payload received from messenger user 141 | 142 | Example usage: 143 | 144 | ```javascript 145 | messenger.on('delivery', (event) => { 146 | messenger.send({ text: 'Message has been delivered!' }); 147 | }); 148 | ``` 149 | 150 | #### `messenger.on('postback', (event))` 151 | 152 | Event triggered when the postback action is triggered by the user. 153 | 154 | `event` - object with payload received from messenger user 155 | 156 | Example usage: 157 | 158 | ```javascript 159 | messenger.on('postback', (event) => { 160 | messenger.send({ text: 'Postback event!' }); 161 | }); 162 | ``` 163 | 164 | #### `messenger.on('optin', (event))` 165 | 166 | Event triggered when the optin action is triggered by the user. 167 | 168 | `event` - object with payload received from messenger user 169 | 170 | Example usage: 171 | 172 | ```javascript 173 | messenger.on('optin', (event) => { 174 | messenger.send({ text: 'Optin event!' }); 175 | }); 176 | ``` 177 | 178 | ### Functions 179 | 180 | #### `messenger.send(payload)` 181 | 182 | `payload` - object with data that will be send to the user, see [docs](https://developers.facebook.com/docs/messenger-platform/send-api-reference#request) for format specification 183 | 184 | Example usage: 185 | 186 | ```javascript 187 | messenger.on('message', () => { 188 | messenger.send({ 189 | "attachment":{ 190 | "type":"template", 191 | "payload":{ 192 | "template_type":"button", 193 | "text":"What do you want to do next?", 194 | "buttons":[ 195 | { 196 | "type":"web_url", 197 | "url":"https://petersapparel.parseapp.com", 198 | "title":"Show Website" 199 | }, 200 | { 201 | "type":"postback", 202 | "title":"Start Chatting", 203 | "payload":"USER_DEFINED_PAYLOAD" 204 | } 205 | ] 206 | } 207 | } 208 | }); 209 | }); 210 | ``` 211 | 212 | #### `messenger.getUser()` 213 | 214 | Returns object with user data: 215 | 216 | * `first_name` 217 | * `last_name` 218 | * `profile_pic` 219 | 220 | Example usage: 221 | 222 | ```javascript 223 | messenger.on('message', () => { 224 | messenger.getUser().then((user) => { 225 | messenger.send({ text: `Hey ${user.first_name} ${user.last_name}` }); 226 | }); 227 | }); 228 | ``` 229 | 230 | #### `messenger.getUserId()` 231 | 232 | Returns ID of the user who sent the message. 233 | 234 | Example usage: 235 | 236 | ```javascript 237 | messenger.on('postback', () => { 238 | console.log(messenger.getUserId()); 239 | }); 240 | ``` 241 | 242 | ### Elements 243 | 244 | #### `MessengerText(text)` 245 | 246 | `text` - your text message to the user 247 | 248 | This element can be sent separately. 249 | 250 | Returns proper text hash: 251 | 252 | ```javascript 253 | { text: 'text attribute' } 254 | ``` 255 | 256 | Example usage: 257 | 258 | ```javascript 259 | import { MessengerText } from 'messenger-wrapper'; 260 | 261 | messenger.on('message', () => { 262 | messenger.send(new MessengerText('Hello user!')); 263 | }); 264 | ``` 265 | 266 | #### `MessengerImage(url)` 267 | 268 | `url` - url to the image 269 | 270 | This element can be sent separately. 271 | 272 | Returns proper image hash: 273 | 274 | ```javascript 275 | attachment: { 276 | type: 'image', 277 | payload: { 278 | url: 'http://yoururl.com/image' 279 | } 280 | } 281 | ``` 282 | 283 | Example usage: 284 | 285 | ```javascript 286 | import { MessengerImage } from 'messenger-wrapper'; 287 | 288 | messenger.on('message', () => { 289 | messenger.send(new MessengerImage('http://lorempixel.com/400/400/sports/1/')); 290 | }); 291 | ``` 292 | 293 | #### `MessengerButton(attrs)` 294 | 295 | `attrs` - object containing two attributes: 296 | 297 | `{ url: 'url', title: 'title' }` or `{ title: 'title', payload: 'payload' }` 298 | 299 | This element CANNOT be sent separately. Use it with Button, Generic or Receipt templates. 300 | 301 | Returns proper button hash depending on attributes set: 302 | 303 | First: 304 | 305 | ```javascript 306 | { 307 | type: 'web_url', 308 | url: 'url', 309 | title: 'title' 310 | } 311 | ``` 312 | 313 | Second: 314 | 315 | ```javascript 316 | { 317 | type: 'postback', 318 | title: 'title', 319 | payload: 'payload' 320 | } 321 | ``` 322 | 323 | Example usage (with MessengerButtonTemplate): 324 | 325 | ```javascript 326 | import { 327 | MessengerButton, 328 | MessengerButtonTemplate 329 | } from 'messenger-wrapper'; 330 | 331 | messenger.on('message', () => { 332 | messenger.send(new MessengerButtonTemplate( 333 | 'Hey user! Watch these buttons:', 334 | [ 335 | new MessengerButton({ title: 'Web Url Button', url: 'http://www.example.com' }), 336 | new MessengerButton({ title: 'Postback Button', payload: 'POSTBACK_INFO' }) 337 | ] 338 | )); 339 | }); 340 | ``` 341 | 342 | ### `MessengerBubble(attrs)` 343 | 344 | `attrs` - hash attributes defined in Facebook documentation 345 | 346 | This element CANNOT be sent separately. Use it with Generic or Receipt templates. 347 | 348 | Returns `attrs` object: 349 | 350 | ```javascript 351 | { 352 | title: 'Title', 353 | item_url: 'http://www.example.com', 354 | image_url: 'http://www.example.com', 355 | subtitle: 'Subtitle', 356 | buttons: [ 357 | { 358 | type: 'web_url', 359 | title: 'Button', 360 | url: 'http://www.example.com' 361 | } 362 | ] 363 | } 364 | ``` 365 | 366 | Example usage: 367 | 368 | ```javascript 369 | import { 370 | MessengerButton, 371 | MessengerBubble, 372 | } from 'messenger-wrapper'; 373 | 374 | ... 375 | new MessengerBubble({ 376 | itle: 'Title', 377 | item_url: 'http://www.example.com', 378 | image_url: 'http://www.example.com', 379 | subtitle: 'Subtitle', 380 | buttons: [ 381 | new MessengerButton({ title: 'Web Url Button', url: 'http://www.example.com' }), 382 | new MessengerButton({ title: 'Postback Button', payload: 'POSTBACK_INFO' }) 383 | ] 384 | }); 385 | ... 386 | ``` 387 | 388 | ### `MessengerAddress(attrs)` 389 | 390 | `attrs` - hash attributes defined in Facebook documentation 391 | 392 | This element CANNOT be sent separately. Use it with Receipt template. 393 | 394 | Returns `attrs` object: 395 | 396 | ```javascript 397 | { 398 | street_1: '1 Hacker Way', 399 | street_2: '', 400 | city: 'Menlo Park', 401 | postal_code: '94025', 402 | state: 'CA', 403 | country: 'US' 404 | } 405 | ``` 406 | 407 | Example usage: 408 | 409 | ```javascript 410 | import { 411 | MessengerAddress 412 | } from 'messenger-wrapper'; 413 | 414 | ... 415 | new MessengerAddress({ 416 | street_1: '1 Hacker Way', 417 | street_2: '', 418 | city: 'Menlo Park', 419 | postal_code: '94025', 420 | state: 'CA', 421 | country: 'US' 422 | }); 423 | ... 424 | ``` 425 | 426 | ### `MessengerSummary(attrs)` 427 | 428 | `attrs` - hash attributes defined in Facebook documentation 429 | 430 | This element CANNOT be sent separately. Use it with Receipt template. 431 | 432 | Returns `attrs` object: 433 | 434 | ```javascript 435 | { 436 | subtotal: 75.00, 437 | shipping_cost: 4.95, 438 | total_tax: 6.19, 439 | total_cost: 56.14 440 | } 441 | ``` 442 | 443 | Example usage: 444 | 445 | ```javascript 446 | import { 447 | MessengerSummary 448 | } from 'messenger-wrapper'; 449 | 450 | ... 451 | new MessengerSummary({ 452 | subtotal: 75.00, 453 | shipping_cost: 4.95, 454 | total_tax: 6.19, 455 | total_cost: 56.14 456 | }); 457 | ... 458 | ``` 459 | 460 | ### `MessengerAdjustment(text, amount)` 461 | 462 | `text` - text attribute according to Facebook documentation 463 | 464 | `amount` - amount attribute according to Facebook documentation 465 | 466 | This element CANNOT be sent separately. Use it with Receipt template. 467 | 468 | Returns `attrs` object: 469 | 470 | ```javascript 471 | { 472 | name: 'Adjustment', 473 | amount: 20 474 | } 475 | ``` 476 | 477 | Example usage: 478 | 479 | ```javascript 480 | import { 481 | MessengerAdjustment 482 | } from 'messenger-wrapper'; 483 | 484 | ... 485 | new MessengerAdjustment({ 486 | name: 'Adjustment', 487 | amount: 20 488 | }); 489 | ... 490 | ``` 491 | 492 | ### Templates 493 | 494 | #### `MessengerButtonTemplate(text, buttons)` 495 | 496 | `text` - text attribute 497 | `buttons` - array with buttons 498 | 499 | Returns proper template object: 500 | 501 | ```javascript 502 | { 503 | attachment: { 504 | type: 'template', 505 | payload: { 506 | template_type: 'button', 507 | text: 'Hello user!', 508 | buttons: [ 509 | { 510 | type: 'web_url', 511 | title: 'Button', 512 | url: 'http://www.example.com' 513 | } 514 | ] 515 | } 516 | } 517 | } 518 | ``` 519 | 520 | Example usage: 521 | 522 | ```javascript 523 | import { 524 | MessengerButton, 525 | MessengerButtonTemplate 526 | } from 'messenger-wrapper'; 527 | 528 | messenger.on('message', () => { 529 | messenger.send(new MessengerButtonTemplate( 530 | 'Hey user! Watch these buttons:', 531 | [ 532 | new MessengerButton({ title: 'Web Url Button', url: 'http://www.example.com' }), 533 | new MessengerButton({ title: 'Postback Button', payload: 'POSTBACK_INFO' }) 534 | ] 535 | )); 536 | }); 537 | ``` 538 | 539 | #### `MessengerGenericTemplate(bubbles)` 540 | 541 | `bubbles` - array with bubbles 542 | 543 | Returns proper generic template object: 544 | 545 | ```javascript 546 | { 547 | attachment: { 548 | type: 'template', 549 | payload: { 550 | template_type: 'generic', 551 | elements: [ 552 | { 553 | title: 'Title', 554 | item_url: 'http://www.example.com', 555 | image_url: 'http://www.example.com', 556 | subtitle: 'Subtitle', 557 | buttons: [ 558 | { 559 | type: 'web_url', 560 | title: 'Button', 561 | url: 'http://www.example.com' 562 | } 563 | ] 564 | } 565 | ] 566 | } 567 | } 568 | } 569 | ``` 570 | 571 | Example usage: 572 | 573 | ```javascript 574 | import { 575 | MessengerButton, 576 | MessengerBubble 577 | MessengerGenericTemplate 578 | } from 'messenger-wrapper'; 579 | 580 | messenger.on('message', () => { 581 | messenger.send(new MessengerGenericTemplate( 582 | [ 583 | new MessengerBubble({ 584 | title: 'Title', 585 | item_url: 'http://www.example.com', 586 | image_url: 'http://www.example.com', 587 | subtitle: 'Subtitle', 588 | buttons: [ 589 | new MessengerButton({ title: 'Button', url: 'http://www.example.com' }) 590 | ] 591 | }), 592 | ... 593 | ] 594 | )); 595 | }); 596 | ``` 597 | 598 | #### `MessengerReceiptTemplate(attrs)` 599 | 600 | `attrs` - attributes hash according to Facebook documentation 601 | 602 | Returns proper receipt template object: 603 | 604 | ```javascript 605 | { 606 | attachment: { 607 | type: 'template', 608 | payload: { 609 | template_type: 'receipt', 610 | recipient_name: 'Name', 611 | order_number: '123', 612 | currency: 'USD', 613 | payment_method: 'Visa', 614 | order_url: 'http://www.example.com', 615 | timestamp: '123123123', 616 | elements: [ 617 | { 618 | title: 'Title', 619 | item_url: 'http://www.example.com', 620 | image_url: 'http://www.example.com', 621 | subtitle: 'Subtitle', 622 | buttons: [ 623 | { 624 | type: 'web_url', 625 | title: 'Button', 626 | url: 'http://www.example.com' 627 | } 628 | ] 629 | } 630 | ], 631 | address: { 632 | street_1: '1 Hacker Way', 633 | street_2: '', 634 | city: 'Menlo Park', 635 | postal_code: '94025', 636 | state: 'CA', 637 | country: 'US' 638 | }, 639 | summary: { 640 | subtotal: 75.00, 641 | shipping_cost: 4.95, 642 | total_tax: 6.19, 643 | total_cost: 56.14 644 | }, 645 | adjustments: [ 646 | { 647 | name: 'Adjustment', 648 | amount: 20 649 | } 650 | ] 651 | } 652 | } 653 | } 654 | ``` 655 | 656 | Example usage: 657 | 658 | ```javascript 659 | import { 660 | MessengerButton, 661 | MessengerBubble, 662 | MessengerAddress, 663 | MessengerSummary, 664 | MessengerAdjustment, 665 | MessengerReceiptTemplate 666 | } from 'messenger-wrapper'; 667 | 668 | messenger.on('message', () => { 669 | messenger.send(new MessengerReceiptTemplate({ 670 | recipient_name: 'Name', 671 | order_number: '123', 672 | currency: 'USD', 673 | payment_method: 'Visa', 674 | order_url: 'http://www.example.com', 675 | timestamp: '123123123', 676 | elements: [ 677 | new MessengerBubble({ 678 | title: 'Title', 679 | item_url: 'http://www.example.com', 680 | image_url: 'http://www.example.com', 681 | subtitle: 'Subtitle', 682 | buttons: [ 683 | new MessengerButton({ title: 'Button', url: 'http://www.example.com' }) 684 | ] 685 | }) 686 | ], 687 | address: new MessengerAddress({ 688 | street_1: '1 Hacker Way', 689 | street_2: '', 690 | city: 'Menlo Park', 691 | postal_code: '94025', 692 | state: 'CA', 693 | country: 'US' 694 | }), 695 | summary: new MessengerSummary({ 696 | subtotal: 75.00, 697 | shipping_cost: 4.95, 698 | total_tax: 6.19, 699 | total_cost: 56.14 700 | }), 701 | adjustments: [ 702 | new MessengerAdjustment('Adjustment', 20) 703 | ] 704 | }); 705 | }); 706 | ``` 707 | --------------------------------------------------------------------------------