UNPKG

calamarcopollo

Version:
370 lines (324 loc) 15.7 kB
#!/usr/bin/env node 'use strict'; 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;