UNPKG

botpress

Version:

The world's first CMS for bots. Easily create, manage and extend chatbots.

212 lines (170 loc) 6.92 kB
'use strict'; var _mustache = require('mustache'); var _mustache2 = _interopRequireDefault(_mustache); var _ms = require('ms'); var _ms2 = _interopRequireDefault(_ms); var _lodash = require('lodash'); var _lodash2 = _interopRequireDefault(_lodash); 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"); }); }; } // TODO: can use typed-error that handles stack capturing class ParsingError extends Error { constructor(bloc, instructionIndex, error) { super(`Error parsing bloc '${bloc}' at instruction ${instructionIndex + 1}: ${error}`); this.bloc = bloc; this.instructionIndex = instructionIndex; this.internalMessage = error; Error.captureStackTrace(this, ParsingError); } } const premapInstruction = ({ currentPlatform }) => ({ instruction, index, instructions, detectedPlatforms, blocName }) => { if (typeof instruction === 'string' || _lodash2.default.isArray(instruction)) { return [{ text: instruction }]; } // Parsing conditionals const evaluate = (val, exp) => { if (typeof exp === 'boolean') { return val === exp; } if (_lodash2.default.isArrayLike(exp)) { return val ? !_lodash2.default.isEmpty(exp) : _lodash2.default.isEmpty(exp); } else { return val ? !!exp : !exp; } }; if (!_lodash2.default.isNil(instruction.if) && !_lodash2.default.isNil(instruction.unless)) { throw new ParsingError(blocName, index, "Message can't be both 'if' and 'else'."); } if (!_lodash2.default.isNil(instruction.unless) && !evaluate(false, instruction.unless)) { return []; } if (!_lodash2.default.isNil(instruction.if) && !evaluate(true, instruction.if)) { return []; } // Parsing ".on" let i = Object.assign({}, instruction); if (instruction.on) { if (typeof instruction.on === 'string') { const platforms = instruction.on.toLowerCase().split('+').map(_lodash2.default.trim); if (!_lodash2.default.includes(platforms, currentPlatform.toLowerCase())) { return []; } else { i['__platformSpecific'] = true; } } else if (_lodash2.default.isPlainObject(instruction.on)) { const on = _lodash2.default.mapKeys(instruction.on, (__, key) => key.toLowerCase()); // This allows multiple platforms to be specified // e.g. "messenger+slack+web" _lodash2.default.keys(on).forEach(key => { if (key.indexOf('+') >= 0) { _lodash2.default.split(key, '+').forEach(alias => { const trimmed = _lodash2.default.trim(alias); on[trimmed] = _lodash2.default.merge({}, on[trimmed] || {}, on[key]); }); } }); i = Object.assign(i, on[currentPlatform.toLowerCase()], { on: currentPlatform }); } else { throw new ParsingError(blocName, index, '"on" must be a string or a plain object but was a ' + typeof instruction.on); } } return [i]; }; const mapInstruction = ({ currentPlatform, processors, incomingEvent }) => ({ instruction, messages, blocName }) => { const ret = []; if (!_lodash2.default.isNil(instruction.wait)) { ret.push({ __internal: true, type: 'wait', wait: _lodash2.default.isString(instruction.wait) ? (0, _ms2.default)(instruction.wait || 1000) : parseInt(instruction.wait) || 1000 }); } if (!_lodash2.default.isNil(instruction.typing)) { instruction.typing = _lodash2.default.isString(instruction.typing) ? (0, _ms2.default)(instruction.typing || 1000) : parseInt(instruction.typing) || 1000; } const raw = _lodash2.default.omit(instruction, ['unless', 'if', 'on', 'wait']); if (!_lodash2.default.keys(raw).length) { return ret; } if (_lodash2.default.isArray(instruction.text)) { instruction.text = _lodash2.default.sample(instruction.text); } const processor = currentPlatform && processors[currentPlatform]; if (processor) { const msg = processor({ instruction, messages, blocName, event: incomingEvent }); if (msg) { ret.push(msg); } return ret; } throw new Error('Unsupported platform: ' + currentPlatform); }; const mapBloc = (bloc, blocName, options, processors, incomingEvent) => { const { currentPlatform, throwIfNoPlatform = false } = options; if (!currentPlatform && throwIfNoPlatform) { throw new Error('You need to supply `currentplatform`'); } const _premapInstruction = premapInstruction({ currentPlatform }); const _mapInstruction = mapInstruction({ currentPlatform, processors, incomingEvent }); if (!Array.isArray(bloc)) { bloc = [bloc]; } const messages = []; const detectedPlatforms = []; const instructions = []; // Premapping allows for modifications, drop and addition of instructions _lodash2.default.forEach(bloc, (instruction, index) => { const add = _premapInstruction({ instruction, index, instructions: bloc, detectedPlatforms, blocName }); add && _lodash2.default.forEach(add, i => instructions.push(i)); }); _lodash2.default.forEach(instructions, instruction => { const m = _mapInstruction({ instruction, messages, blocName }); if (m != null) { // Messages can be null when the instruction only modified existing messages m.forEach(message => messages.push(message)); } }); return messages; }; const renderMustache = (template, context) => { _mustache2.default.parse(template); return _mustache2.default.render(template, context); }; const hydrateMustache = (obj, context) => { if (_lodash2.default.isString(obj)) { return renderMustache(obj, context); } if (Array.isArray(obj)) { return obj.map(v => hydrateMustache(v, context)); } if (_lodash2.default.isObject(obj)) { return _lodash2.default.mapValues(obj, v => hydrateMustache(v, context)); } return obj; }; module.exports = (() => { var _ref = _asyncToGenerator(function* ({ rendererName, rendererFn, context, options, processors, incomingEvent }) { // We're running it a second time to do second-level variable replacement // This happens often when there are variables used in the Content Manager const rawBloc = hydrateMustache(hydrateMustache((yield rendererFn(context)), context), context); return mapBloc(rawBloc, rendererName, options, processors, incomingEvent); }); return function (_x) { return _ref.apply(this, arguments); }; })(); //# sourceMappingURL=engine.js.map