calamarcopollo
Version:
Save the chicken foundation
370 lines (324 loc) • 15.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.setupUpdateListener = undefined;
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _calamars = require('calamars');
var _tgBot = require('./tgBot');
var _tgBot2 = _interopRequireDefault(_tgBot);
var _stringHelpers = require('./stringHelpers');
var _store = require('./store');
var _actionCreators = require('./actionCreators');
var _wit = require('./wit');
var _wit2 = _interopRequireDefault(_wit);
var _router = require('./router');
var _router2 = _interopRequireDefault(_router);
var _replies = require('../replies');
var _menu = require('../menu');
var _moment = require('moment');
var _moment2 = _interopRequireDefault(_moment);
var _requestPromise = require('request-promise');
var _requestPromise2 = _interopRequireDefault(_requestPromise);
var _googleUrl = require('google-url');
var _googleUrl2 = _interopRequireDefault(_googleUrl);
var _tripDialog = require('./tripDialog');
var _fs = require('fs');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
const googleUrl = new _googleUrl2.default({ key: process.env.GOOGLE_API_KEY });
const CLICKBUS_URL = process.env.CLICKBUS_URL;
const CLICKBUS_API_KEY = process.env.CLICKBUS_API_KEY;
const CLICKBUS_WEB_URL = process.env.CLICKBUS_WEB_URL;
const CLICKBUS_UTM_PARAMS = process.env.CLICKBUS_UTM_PARAMS || '';
const CLICKBUS_WEB_URL_DATE_PARAM = process.env.CLICKBUS_WEB_URL_DATE_PARAM;
const DEBUG_TO_LOGFILE = process.env.DEBUG_TO_LOGFILE;
const POLLO_PATH = process.env.POLLO_PATH;
const store = (0, _store.createStore)();
const updateListenerConfig = {
googleUrl
};
const getSession = chatId => {
const currentChat = store.getState().chats.find(item => item.id === chatId);
return currentChat.session;
};
const setupUpdateListener = config => (() => {
var _ref = _asyncToGenerator(function* ({ bot, update }) {
const message = (0, _calamars.calamarMessageFormat)(update);
if (!message || !message.text || message.isEcho) {
// console.log(`Update: ${JSON.stringify(update, ' ', 2)}`);
return null;
}
const { chatId, senderId } = message;
const messageText = message.text;
const date = message.timestamp;
const text = (0, _stringHelpers.polloSanitize)(messageText);
console.log(`
Message: ${ messageText }
${ text }`);
store.dispatch((0, _actionCreators.updateExpression)({ text }));
const chat = {
id: chatId
};
store.dispatch((0, _actionCreators.updateChatSession)({ chat, date }));
const botType = message.platform;
let sendMessageOptions;
let context = getSession(chatId);
let from;
if (botType === 'facebookMessenger') {
sendMessageOptions = { userId: senderId };
if (!context.user) {
from = yield bot.getUserInfo(senderId).catch(function (e) {
console.log('getUserInfo error:', e.message);
});
}
} else {
sendMessageOptions = { disable_web_page_preview: 'true', chat_id: chatId };
if (!context.user) {
from = update.message.from;
}
}
store.dispatch((0, _actionCreators.updateChatSession)({
chat: _extends({}, chat, { session: _extends({}, context, { user: from }) })
}));
const witResult = yield _wit2.default.query(text, true);
/* eslint-disable no-underscore-dangle */
const { _text, outcomes } = witResult;
const outcome = outcomes[0] ? { text: _text, entities: outcomes[0].entities } : {};
/* eslint-enable no-underscore-dangle */
console.log('outcome', JSON.stringify(outcome));
console.log('chat, from, date', chat, from, date);
const reply = (0, _router2.default)(outcome, { store, chat, from, date });
// router has side-effects and modifies context,
// that's why we get it again here
context = getSession(chatId);
store.dispatch((0, _actionCreators.updateOutcome)(outcome));
if (typeof reply === 'string') {
console.log('reply', reply);
console.log('context', context);
bot.sendMessage(_extends({}, sendMessageOptions, {
text: reply
})).catch(function (e) {
console.log('sendMessage error:', e.message);
});
// @TODO remove unknown place from context if the bot replied
// with the noSlug answer
if (!context.destinationMeta || !context.originMeta) {
const nextContext = _extends({}, context, {
destination: !context.destinationMeta ? undefined : context.destination,
origin: !context.originMeta ? undefined : context.origin
});
console.log('## nextContext ##', nextContext);
store.dispatch((0, _actionCreators.updateChatSession)({
chat: _extends({}, chat, { session: nextContext })
}));
}
return reply;
}
if (reply && reply.url) {
const timeFilterFrom = context.timeFilter && context.timeFilter.from ? (0, _moment2.default)(context.timeFilter.from.value) : null;
const timeFilterFromHour = context.timeFilter && context.timeFilter.from && context.timeFilter.from.grain !== 'day' ? timeFilterFrom : null;
const day = timeFilterFrom || (0, _moment2.default)();
const timeFilterTo = context.timeFilter && context.timeFilter.to ? (0, _moment2.default)(context.timeFilter.to.value) : null;
const hasBustypeFilters = context.busTypeFilters && context.busTypeFilters.length;
const busTypeFilters = hasBustypeFilters ? context.busTypeFilters.reduce(function (prev, curr) {
return prev.indexOf(curr.value) === -1 ? prev.concat(curr.value) : prev;
}, []) : null;
const priceFilter = context.priceFilter;
console.log('busTypeFilters', busTypeFilters);
const replyText = _replies.replies.trip.requestingWithFilters(context.origin, context.destination, {
day,
timeFilterFrom: timeFilterFromHour,
timeFilterTo,
busTypeFilters,
priceFilter
});
bot.sendMessage(_extends({}, sendMessageOptions, {
text: replyText
}));
console.log(`requesting ${ reply.url }`);
const apiTripRequestOptions = {
uri: reply.url,
headers: {
'X-API-KEY': CLICKBUS_API_KEY
}
};
const apiSessionRequestOptions = _extends({}, apiTripRequestOptions, {
uri: `${ CLICKBUS_URL }/session`
});
let responseBody;
let sessionBody;
try {
responseBody = yield (0, _requestPromise2.default)(apiTripRequestOptions);
sessionBody = yield (0, _requestPromise2.default)(apiSessionRequestOptions);
} catch (err) {
console.log('___err___', err);
const { statusCode } = err;
const nextContext = Object.assign({}, context, { apiError: statusCode });
const errorReply = (0, _tripDialog.tripDialogReply)(nextContext);
return bot.sendMessage(_extends({}, sendMessageOptions, {
text: errorReply
}));
}
console.log('reply arrived');
const sessionCookie = JSON.parse(sessionBody).content;
const apiResult = JSON.parse(responseBody);
const rawTrips = apiResult.items;
console.log(`${ rawTrips.length } trips`);
// console.log(JSON.stringify(rawTrips, ' ', 2));
const trips = rawTrips.filter(function (trip) {
return trip.parts && trip.parts[0] && trip.parts[0].availableSeats > 0;
}).map(function (trip) {
const firstPart = trip.parts[0];
const {
departure,
arrival,
busCompany,
bus,
availableSeats
} = firstPart;
const price = departure.waypoint.prices[0].price;
const beginTime = departure.waypoint.schedule;
const scheduleId = beginTime.id;
const endTime = arrival.waypoint.schedule;
const departurePlace = departure.waypoint.place.city;
const departureTime = (0, _moment2.default)(`${ beginTime.date } ${ beginTime.time }`);
const arrivalPlace = arrival.waypoint.place.city;
const arrivalTime = (0, _moment2.default)(`${ endTime.date } ${ endTime.time }`);
const duration = arrivalTime.diff(departureTime, 'minutes');
const busCompanyName = busCompany.name;
const busCompanyLogo = busCompany.logo;
const busType = bus.serviceClass;
const busTypeId = bus.id;
console.log(`${ beginTime.date } ${ beginTime.time } - ${ endTime.date } ${ endTime.time } - ${ duration } - ${ availableSeats }`);
return {
price,
departurePlace,
arrivalPlace,
departureTime,
arrivalTime,
duration,
busCompanyName,
busCompanyLogo,
busType,
busTypeId,
availableSeats,
scheduleId
};
});
// console.log('trips', trips);
// console.log(`trips[0]: ${JSON.stringify(trips[0])}`);
const srcSlug = context.originMeta ? context.originMeta.slugs[0] : null;
const destSlug = context.destinationMeta ? context.destinationMeta.slugs[0] : null;
const webUrl = `${ CLICKBUS_WEB_URL }/${ srcSlug }/${ destSlug }/?${ CLICKBUS_UTM_PARAMS }`;
const url = `${ webUrl }&${ CLICKBUS_WEB_URL_DATE_PARAM }=${ reply.departureDay }`;
console.log(`shortening the web url ${ url }…`);
const secondReply = yield new Promise(function (resolve) {
config.googleUrl.shorten(url, function (err, shortUrl) {
if (err) {
console.error('URL shortening failed');
}
console.log('URL shortened', shortUrl);
const nextContext = Object.assign({}, context, {
trips,
shortUrl: err ? url : shortUrl,
sessionCookie
});
return resolve((0, _tripDialog.tripDialogReply)(nextContext));
});
});
// bot already have the responses, clear busTypeFilters and priceFilter
const nextContext = _extends({}, context, {
busTypeFilters: undefined,
priceFilter: undefined
});
store.dispatch((0, _actionCreators.updateChatSession)({
chat: _extends({}, chat, { session: nextContext })
}));
// send query results to user
if (botType === 'facebookMessenger' && secondReply.structuredRely && secondReply.structuredRely.length > 0) {
return bot.sendMessage(_extends({}, sendMessageOptions, {
text: secondReply.textReply.header
})).then(function () {
bot.sendMessage(_extends({}, sendMessageOptions, {
attachment: {
type: 'template',
payload: {
template_type: 'generic',
elements: secondReply.structuredRely.slice(0, 10)
}
}
}));
});
}
if (secondReply.textReply) {
return bot.sendMessage(_extends({}, sendMessageOptions, {
text: [secondReply.textReply.header, secondReply.textReply.body, '\n\n', secondReply.textReply.footer].join('')
}));
}
return bot.sendMessage(_extends({}, sendMessageOptions, {
text: secondReply
}));
}
console.log('what is this?', reply);
const debugContext = JSON.stringify(context);
const debugOutcome = JSON.stringify(outcome);
if (DEBUG_TO_LOGFILE) {
const logLine = `${ new Date().toString() }, ${ text }, ${ debugOutcome }, ${ debugContext }\n`;
(0, _fs.appendFile)(DEBUG_TO_LOGFILE, logLine, function (err) {
return console.error(err);
});
}
return bot.sendMessage(_extends({}, sendMessageOptions, {
text: _replies.replies.unknown(`context: ${ debugContext }
outcome: ${ debugOutcome }`)
}));
});
return function (_x) {
return _ref.apply(this, arguments);
};
})();
const onPostback = ({ update, bot }) => {
const postbackPayload = update.postback.payload;
return onUpdate({
bot,
update: _extends({}, update, {
message: {
text: postbackPayload
}
})
});
};
const onUpdate = setupUpdateListener(updateListenerConfig);
const port = process.env.PORT;
const callbackPath = process.env.FB_CALLBACK_PATH;
const listeners = {
onUpdate,
onPostback
};
const staticFiles = [{ path: process.env.POST_TO_CLICKBUS_HACK_PATH, file: `${ POLLO_PATH }/src/autopost.html` }];
const fbBot = new _calamars.FacebookMessengerBot({ port, callbackPath, listeners, staticFiles });
console.log((0, _moment2.default)().format());
//
fbBot.start().then(serverStatus => {
console.log('serverStatus', serverStatus, port);
// Get Started button reply
fbBot.setWelcomeMessage({
text: _replies.replies.start()
}).then(welcomeMsgSetResult => console.log('welcomeMsgSetResult', welcomeMsgSetResult)).catch(e => {
console.log('\nFailed to setup Get Started Button');
console.error(e.message);
});
// Persistent Menu
fbBot.setThreadSettings({
type: 'call_to_actions',
state: 'existing_thread',
cta: _menu.menu
}).catch(e => {
console.log('\nFailed to setup Persistent Menu');
console.error(e.message);
});
});
_tgBot2.default.on('update', update => onUpdate({ bot: _tgBot2.default, update }));
exports.setupUpdateListener = setupUpdateListener;