botpress
Version:
The world's first CMS for bots. Easily create, manage and extend chatbots.
212 lines (170 loc) • 6.92 kB
JavaScript
;
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