UNPKG

comet-messenger

Version:

Yet another way to interact with Facebook Messenger in Node-JS

121 lines (102 loc) 5.36 kB
const crypto = require('crypto'); const xhub = require('express-x-hub'); function tryRequire(module) { try { return require(module); } catch (err) { return null; } } const bodyParser = tryRequire('body-parser'); const express = tryRequire('express'); module.exports = function createExpressRouter({ pages, queue, app_secret, verify_token, logger }) { if (!express) throw new Error('Module "express" not found - did you forget to install it?'); /** * Sad fatals for missing config */ if (!Array.isArray(pages)) throw new Error('Missing array of Facebook pages for expressRouter'); if (!verify_token) throw new Error('Missing verify_token for expressRouter'); if (!queue) throw new Error('Missing queue for expressRouter'); /** * If there's no app_secret, throw a warning because whilst we can accept requests we can't authenticate them. */ if (!app_secret) { console.warn('A comet router was created, but no app_secret was passed to the config'); // eslint-disable-line no-console console.warn('Facebook Messenger Verification will be skipped, technically allowing anyone to trigger messages'); // eslint-disable-line no-console console.warn('Add your app\'s secret to comet.createExpressRouter to suppress this message'); // eslint-disable-line no-console console.warn('To ignore this & continue, install body-parser (required to parse the JSON-body)'); // eslint-disable-line no-console if (!bodyParser) throw new Error('Module "body-parser" not found'); } const log_error = logger && logger.error ? (...args) => logger.error(...args) : (...args) => console.error(...args); // eslint-disable-line no-console const page_ids = pages.filter(({ id }) => Boolean(id)).map(({ id }) => `${id}`); const status = pages.map(({ id, name }) => `${name || id} - ${crypto.randomBytes(16).toString('hex')}`).join('\n'); const router = express.Router(); /** * Configure the GET request, so Facebook can verify this API. */ router.get('/', (req, res) => { res.status(200).set('Content-Type', 'text/plain'); if (req.query['hub.verify_token'] === verify_token) res.send(req.query['hub.challenge']); else res.send(status); }); /** * Configure a Status route, for autoscaling etc. */ router.get('/status', (req, res) => res.status(200).set('Content-Type', 'text/plain').send(status)); /** * Configure the POST request, so Facebook can send messages to this API. * All this does is flatten the messages into a single array, and append them to the queue. */ router.post('/', [ app_secret ? xhub({ algorithm: 'sha1', limit: '1mb', secret: app_secret }) : bodyParser.json({ limit: '1mb' }), (req, res) => { res.status(200).set('Content-Type', 'text/plain'); if (app_secret && (!req.isXHub || !req.isXHubValid())) { return res.set('X-Rejected', 'x-hub').send('These violent delights have violent ends'); } const payloads = []; // For each page if (Array.isArray(req.body.entry)) req.body.entry.forEach(entry => { // If the page ID is missing from this request, or it's not for this platform, drop it if (!entry || !entry.id || page_ids.indexOf(`${entry.id}`) < 0) return; // For each message if (Array.isArray(entry.messaging)) entry.messaging.forEach(ev => { // eslint-disable-line max-statements const payload = { page_id: `${entry.id}`, user_id: `${ev.sender.id}` }; if (ev.postback && ev.postback.payload) { payload.type = 'postback'; // Queue message with the postback try { payload.postback = JSON.parse(ev.postback.payload); } catch (err) { payload.postback = null; } if (!payload.postback) return; } else if (ev.message) { if (ev.message.is_echo) { // If this is an echo with an app_id, then it's a message that the bot has sent // Otherwise, a human is talking to the recipient, so we need to silence the bot for a period of time if (!ev.message.app_id) { payload.type = 'silence'; } } else { if (Array.isArray(ev.message.attachments) && ev.message.attachments.length) { payload.type = 'input'; payload.attachments = ev.message.attachments.map(function (attachment) { return Object.assign({ type: attachment.type }, attachment.payload || {}); }); } else if (ev.message.text) { payload.type = 'input'; payload.text = ev.message.text.trim(); if (ev.message.quick_reply && ev.message.quick_reply.payload) { try { payload.quick_reply = JSON.parse(ev.message.quick_reply.payload); } // eslint-disable-line max-depth catch (err) { payload.quick_reply = null; } } } } } if (payload.type) payloads.push(payload); }); }); if (payloads.length) queue(payloads).catch(err => log_error(err)); return res.set('X-Payloads-Queued', payloads.length).send('Thank you 😎'); } ]); router.use((req, res) => res.status(200).set('Content-Type', 'text/plain').send('Not found')); return router; };