botnaut
Version:
Facebook Messenger Chatbot Framework
175 lines (148 loc) • 4.98 kB
JavaScript
/*
* @author David Menger
*/
;
const request = require('request-promise-native');
const RES_HANDLER = (res, nextData) => nextData;
const DEFAULT_URI = 'https://graph.facebook.com/v2.8/me';
function wait (ms) {
return new Promise(res => setTimeout(res, ms));
}
function createDefaultSender (baseUrl = DEFAULT_URI, attachmentStorage = null) {
return function (data, token) {
let uri = baseUrl;
if (data.target_app_id) {
uri += '/pass_thread_control';
} else {
uri += '/messages';
}
let promise = Promise.resolve(data);
const attachmentPayload = data.message
&& data.message.attachment
&& data.message.attachment.payload;
const attachmentUrl = attachmentStorage
&& attachmentPayload
&& attachmentPayload.is_reusable
&& attachmentPayload.url;
if (attachmentUrl) {
promise = attachmentStorage
.findAttachmentByUrl(attachmentUrl)
.then((attachmentId) => {
if (!attachmentId) {
return data;
}
return Object.assign({}, data, {
message: Object.assign({}, data.message, {
attachment: Object.assign({}, data.message.attachment, {
payload: {
attachment_id: attachmentId
}
})
})
});
});
}
return promise
.then(body => request({
uri,
qs: { access_token: token },
method: 'POST',
body,
json: true
}))
.then((res) => {
if (!attachmentUrl || !res.attachment_id) {
return res;
}
return attachmentStorage.saveAttachmentId(attachmentUrl, res.attachment_id)
.then(() => res);
});
};
}
function sendData (senderFn, token, data, queue, sent = [], handler = RES_HANDLER, res = null) {
const next = handler(res, data);
if (!next) {
return sent;
}
let promise;
if (next.wait) {
promise = wait(next.wait);
} else {
sent.push(next);
promise = senderFn(next, token);
}
return promise
.then(result =>
sendData(senderFn, token, queue.shift(), queue, sent, handler, result));
}
function getDisconnectedError (e) {
if (!e.response || !e.response.body || !e.response.body.error) {
return null;
}
if (e.response.statusCode !== 403 || e.response.body.error.code !== 200) {
return null;
}
const err = new Error(e.response.body.error.message);
err.code = 403;
return err;
}
function senderFactory (
token,
logger = console,
onSenderError = () => {},
sender = null,
attachmentCache = null
) {
let senderFn;
if (typeof sender === 'string') {
senderFn = createDefaultSender(sender, attachmentCache);
} else if (typeof sender === 'function') {
senderFn = sender;
} else {
senderFn = createDefaultSender(DEFAULT_URI, attachmentCache);
}
const factoryFn = function factory (userId, incommingMessage, pageId, handler = RES_HANDLER) {
const queue = [];
let promise = null;
let working = false;
return function send (payload = null) {
if (payload === null) {
return promise;
}
if (working) {
// store in queue
queue.push(payload);
return promise;
}
working = true;
const sent = [];
const responses = [];
const handlerOverride = (res, nextData) => {
if (res !== null) {
responses.push(res);
}
return handler(res, nextData);
};
promise = sendData(senderFn, token, payload, queue, sent, handlerOverride)
.then(() => {
working = false;
logger.log(userId, sent, incommingMessage);
return { status: 200, responses };
})
.catch((e) => {
// detect disconnected users
const err = getDisconnectedError(e);
if (onSenderError(err || e, incommingMessage) !== true) {
logger.error(e, userId, sent, incommingMessage);
}
return { status: err ? 403 : 500, responses };
});
return promise;
};
};
return factoryFn;
}
module.exports = {
senderFactory,
sender: createDefaultSender()
};