wingbot 3.27.1 → 3.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/BotApp.js +2 -1
- package/src/Request.js +24 -2
- package/src/Tester.js +22 -4
- package/src/resolvers/media.js +10 -1
- package/src/resolvers/message.js +127 -17
- package/src/resolvers/utils.js +87 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wingbot",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.29.0",
|
|
4
4
|
"description": "Enterprise Messaging Bot Conversation Engine",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -71,4 +71,4 @@
|
|
|
71
71
|
"request": "^2.88.0",
|
|
72
72
|
"request-promise-native": "^1.0.7"
|
|
73
73
|
}
|
|
74
|
-
}
|
|
74
|
+
}
|
package/src/BotApp.js
CHANGED
package/src/Request.js
CHANGED
|
@@ -1411,7 +1411,8 @@ It looks like the bot isn't connected to class BotApp or the Processor is used w
|
|
|
1411
1411
|
data = {},
|
|
1412
1412
|
refAction = null,
|
|
1413
1413
|
refData = {},
|
|
1414
|
-
timestamp = makeTimestamp()
|
|
1414
|
+
timestamp = makeTimestamp(),
|
|
1415
|
+
features = [FEATURE_TEXT]
|
|
1415
1416
|
) {
|
|
1416
1417
|
const postback = {
|
|
1417
1418
|
payload: {
|
|
@@ -1429,7 +1430,8 @@ It looks like the bot isn't connected to class BotApp or the Processor is used w
|
|
|
1429
1430
|
sender: {
|
|
1430
1431
|
id: senderId
|
|
1431
1432
|
},
|
|
1432
|
-
postback
|
|
1433
|
+
postback,
|
|
1434
|
+
features
|
|
1433
1435
|
};
|
|
1434
1436
|
}
|
|
1435
1437
|
|
|
@@ -1725,4 +1727,24 @@ It looks like the bot isn't connected to class BotApp or the Processor is used w
|
|
|
1725
1727
|
|
|
1726
1728
|
}
|
|
1727
1729
|
|
|
1730
|
+
/**
|
|
1731
|
+
* @constant {string} FEATURE_VOICE channel supports voice messages
|
|
1732
|
+
*/
|
|
1733
|
+
Request.FEATURE_VOICE = FEATURE_VOICE;
|
|
1734
|
+
|
|
1735
|
+
/**
|
|
1736
|
+
* @constant {string} FEATURE_SSML channel supports SSML voice messages
|
|
1737
|
+
*/
|
|
1738
|
+
Request.FEATURE_SSML = FEATURE_SSML;
|
|
1739
|
+
|
|
1740
|
+
/**
|
|
1741
|
+
* @constant {string} FEATURE_PHRASES channel supports expected phrases messages
|
|
1742
|
+
*/
|
|
1743
|
+
Request.FEATURE_PHRASES = FEATURE_PHRASES;
|
|
1744
|
+
|
|
1745
|
+
/**
|
|
1746
|
+
* @constant {string} FEATURE_TEXT channel supports text communication
|
|
1747
|
+
*/
|
|
1748
|
+
Request.FEATURE_TEXT = FEATURE_TEXT;
|
|
1749
|
+
|
|
1728
1750
|
module.exports = Request;
|
package/src/Tester.js
CHANGED
|
@@ -14,6 +14,8 @@ const AnyResponseAssert = require('./testTools/AnyResponseAssert');
|
|
|
14
14
|
const ResponseAssert = require('./testTools/ResponseAssert');
|
|
15
15
|
|
|
16
16
|
const Router = require('./Router'); // eslint-disable-line no-unused-vars
|
|
17
|
+
const ReducerWrapper = require('./ReducerWrapper'); // eslint-disable-line no-unused-vars
|
|
18
|
+
const { FEATURE_TEXT } = require('./features');
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
21
|
* Utility for testing requests
|
|
@@ -118,6 +120,11 @@ class Tester {
|
|
|
118
120
|
* @prop {console} use own loggger
|
|
119
121
|
*/
|
|
120
122
|
this.senderLogger = undefined;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @prop {string[]}
|
|
126
|
+
*/
|
|
127
|
+
this.features = null;
|
|
121
128
|
}
|
|
122
129
|
|
|
123
130
|
dealloc () {
|
|
@@ -148,6 +155,15 @@ class Tester {
|
|
|
148
155
|
this._responsesMock = [];
|
|
149
156
|
}
|
|
150
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Set features for all messages
|
|
160
|
+
*
|
|
161
|
+
* @param {string[]} [features]
|
|
162
|
+
*/
|
|
163
|
+
setFeatures (features = [FEATURE_TEXT]) {
|
|
164
|
+
this.features = features;
|
|
165
|
+
}
|
|
166
|
+
|
|
151
167
|
/**
|
|
152
168
|
* Use tester as a connector :)
|
|
153
169
|
*
|
|
@@ -164,6 +180,10 @@ class Tester {
|
|
|
164
180
|
});
|
|
165
181
|
}
|
|
166
182
|
|
|
183
|
+
if (this.features) {
|
|
184
|
+
Object.assign(message, { features: this.features });
|
|
185
|
+
}
|
|
186
|
+
|
|
167
187
|
const messageSender = new ReturnSender({}, senderId, message, this.senderLogger);
|
|
168
188
|
messageSender.simulatesOptIn = true;
|
|
169
189
|
|
|
@@ -259,7 +279,7 @@ class Tester {
|
|
|
259
279
|
}
|
|
260
280
|
|
|
261
281
|
/**
|
|
262
|
-
* Checks, that a plugin used a block as a
|
|
282
|
+
* Checks, that a plugin used a block as a response
|
|
263
283
|
*
|
|
264
284
|
* @param {string} blockName
|
|
265
285
|
* @returns {this}
|
|
@@ -308,7 +328,6 @@ class Tester {
|
|
|
308
328
|
*
|
|
309
329
|
* @param {string} text
|
|
310
330
|
* @returns {Promise}
|
|
311
|
-
*
|
|
312
331
|
* @memberOf Tester
|
|
313
332
|
*/
|
|
314
333
|
text (text) {
|
|
@@ -430,12 +449,11 @@ class Tester {
|
|
|
430
449
|
* @param {string} [refAction=null] - referred action
|
|
431
450
|
* @param {object} [refData={}] - referred action data
|
|
432
451
|
* @returns {Promise}
|
|
433
|
-
*
|
|
434
452
|
* @memberOf Tester
|
|
435
453
|
*/
|
|
436
454
|
postBack (action, data = {}, refAction = null, refData = {}) {
|
|
437
455
|
return this.processMessage(Request
|
|
438
|
-
.postBack(this.senderId, action, data, refAction, refData));
|
|
456
|
+
.postBack(this.senderId, action, data, refAction, refData, null));
|
|
439
457
|
}
|
|
440
458
|
|
|
441
459
|
}
|
package/src/resolvers/media.js
CHANGED
|
@@ -4,9 +4,12 @@
|
|
|
4
4
|
'use strict';
|
|
5
5
|
|
|
6
6
|
const Router = require('../Router');
|
|
7
|
+
const getCondition = require('../utils/getCondition');
|
|
7
8
|
const { stateData, cachedTranslatedCompilator } = require('./utils');
|
|
8
9
|
|
|
9
|
-
function media (params,
|
|
10
|
+
function media (params,
|
|
11
|
+
// @ts-ignore
|
|
12
|
+
{ isLastIndex } = {}) {
|
|
10
13
|
const { type, url } = params;
|
|
11
14
|
|
|
12
15
|
const urlString = url || '';
|
|
@@ -19,7 +22,13 @@ function media (params, { isLastIndex }) {
|
|
|
19
22
|
throw new Error(`Unsupported media type: ${type}`);
|
|
20
23
|
}
|
|
21
24
|
|
|
25
|
+
const condition = getCondition(params, 'Media condition');
|
|
26
|
+
|
|
22
27
|
return (req, res) => {
|
|
28
|
+
if (condition && !condition(req, res)) {
|
|
29
|
+
return ret;
|
|
30
|
+
}
|
|
31
|
+
|
|
23
32
|
const data = stateData(req, res);
|
|
24
33
|
const sendUrl = urlTemplate(data);
|
|
25
34
|
|
package/src/resolvers/message.js
CHANGED
|
@@ -2,10 +2,40 @@
|
|
|
2
2
|
* @author David Menger
|
|
3
3
|
*/
|
|
4
4
|
'use strict';
|
|
5
|
-
|
|
5
|
+
// eslint-disable-next-line no-unused-vars
|
|
6
|
+
const Responder = require('../Responder');
|
|
7
|
+
// eslint-disable-next-line no-unused-vars
|
|
8
|
+
const Request = require('../Request');
|
|
6
9
|
const Router = require('../Router');
|
|
7
10
|
const getCondition = require('../utils/getCondition');
|
|
8
|
-
const {
|
|
11
|
+
const {
|
|
12
|
+
stateData, getLanguageText, getLanguageTextObjects, randomizedCompiler
|
|
13
|
+
} = require('./utils');
|
|
14
|
+
const {
|
|
15
|
+
FEATURE_SSML, FEATURE_TEXT, FEATURE_VOICE
|
|
16
|
+
} = require('../features');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Returns voice control props from params
|
|
20
|
+
*
|
|
21
|
+
* @param {any} params
|
|
22
|
+
* @param {string} lang
|
|
23
|
+
* @returns {null | import('../Responder').VoiceControl}
|
|
24
|
+
*/
|
|
25
|
+
function getVoiceControl (params, lang = null) {
|
|
26
|
+
const voiceControl = {};
|
|
27
|
+
|
|
28
|
+
const voiceControlProps = ['speed', 'pitch', 'volume', 'voice', 'style', 'language'];
|
|
29
|
+
|
|
30
|
+
voiceControlProps.forEach((prop) => {
|
|
31
|
+
if (params[prop]) {
|
|
32
|
+
voiceControl[prop] = getLanguageText(params[prop], lang);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// if voiceControl is empty, return null
|
|
37
|
+
return Object.keys(voiceControl).length > 0 ? voiceControl : null;
|
|
38
|
+
}
|
|
9
39
|
|
|
10
40
|
function parseReplies (replies, linksMap, allowForbiddenSnippetWords) {
|
|
11
41
|
return replies.map((reply) => {
|
|
@@ -45,7 +75,7 @@ function parseReplies (replies, linksMap, allowForbiddenSnippetWords) {
|
|
|
45
75
|
action,
|
|
46
76
|
condition,
|
|
47
77
|
title: reply.title
|
|
48
|
-
?
|
|
78
|
+
? randomizedCompiler(reply.title)
|
|
49
79
|
: null,
|
|
50
80
|
data: {}
|
|
51
81
|
};
|
|
@@ -70,43 +100,108 @@ function parseReplies (replies, linksMap, allowForbiddenSnippetWords) {
|
|
|
70
100
|
});
|
|
71
101
|
}
|
|
72
102
|
|
|
103
|
+
/**
|
|
104
|
+
* @param {import('./utils').Translations} text
|
|
105
|
+
* @param {string[]} features
|
|
106
|
+
* @param {string} lang
|
|
107
|
+
* @returns {{translations: import('./utils').TextObject[],ssmlAlternatives:string[] | null}}
|
|
108
|
+
*/
|
|
109
|
+
function findSupportedMessages (text, features, lang = null) {
|
|
110
|
+
let translations = getLanguageTextObjects(text, lang);
|
|
111
|
+
|
|
112
|
+
const useSSML = features.includes(FEATURE_SSML);
|
|
113
|
+
const useText = features.includes(FEATURE_TEXT);
|
|
114
|
+
const useVoice = features.includes(FEATURE_VOICE);
|
|
115
|
+
|
|
116
|
+
// filter out SSML alternatives - they will be used in voice control
|
|
117
|
+
let ssmlAlternatives = translations.filter((t) => t.p === 's').map((t) => t.t);
|
|
118
|
+
if (ssmlAlternatives.length === 0 || !useSSML) {
|
|
119
|
+
ssmlAlternatives = null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
translations = translations.filter((t) => t.p !== 's');
|
|
123
|
+
|
|
124
|
+
// find supported text alternatives
|
|
125
|
+
translations = translations.filter((translation) => {
|
|
126
|
+
// always use text+voice
|
|
127
|
+
if (!translation.p) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// text only
|
|
132
|
+
if (useText && translation.p === 't') {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// voice (& SSML) only
|
|
137
|
+
if (useVoice && translation.p === 'v') {
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return false;
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
translations,
|
|
146
|
+
ssmlAlternatives
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
73
150
|
function message (params, {
|
|
151
|
+
// @ts-ignore
|
|
74
152
|
isLastIndex, isLastMessage, linksMap, allowForbiddenSnippetWords
|
|
75
|
-
}) {
|
|
153
|
+
} = {}) {
|
|
154
|
+
|
|
76
155
|
if (typeof params.text !== 'string' && !Array.isArray(params.text)) {
|
|
77
156
|
throw new Error('Message should be a text!');
|
|
78
157
|
}
|
|
79
158
|
|
|
80
|
-
|
|
81
|
-
|
|
159
|
+
// parse quick replies
|
|
82
160
|
let quickReplies = null;
|
|
83
|
-
|
|
84
161
|
if (params.replies && !Array.isArray(params.replies)) {
|
|
85
162
|
throw new Error('Replies should be an array');
|
|
86
163
|
} else if (params.replies && params.replies.length > 0) {
|
|
87
164
|
quickReplies = parseReplies(params.replies, linksMap, allowForbiddenSnippetWords);
|
|
88
165
|
}
|
|
89
166
|
|
|
167
|
+
// compile condition
|
|
90
168
|
const condition = getCondition(params, 'Message condition', allowForbiddenSnippetWords);
|
|
91
169
|
|
|
92
170
|
const ret = isLastIndex ? Router.END : Router.CONTINUE;
|
|
93
171
|
|
|
172
|
+
/**
|
|
173
|
+
* @param {Request} req
|
|
174
|
+
* @param {Responder} res
|
|
175
|
+
*/
|
|
94
176
|
return (req, res) => {
|
|
95
|
-
if (condition
|
|
96
|
-
|
|
97
|
-
return ret;
|
|
98
|
-
}
|
|
177
|
+
if (condition && !condition(req, res)) {
|
|
178
|
+
return ret;
|
|
99
179
|
}
|
|
100
|
-
|
|
101
180
|
const data = stateData(req, res);
|
|
181
|
+
|
|
182
|
+
// filter supported messages
|
|
183
|
+
/** @type {{translations:import('./utils').TextObject[],ssmlAlternatives:string[]}} */
|
|
184
|
+
const supportedText = findSupportedMessages(
|
|
185
|
+
params.text,
|
|
186
|
+
req.features,
|
|
187
|
+
data.lang
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// find random alternative
|
|
191
|
+
const textTemplate = randomizedCompiler([{
|
|
192
|
+
l: data.lang,
|
|
193
|
+
t: supportedText.translations.map((x) => x.t)
|
|
194
|
+
}]);
|
|
195
|
+
|
|
102
196
|
const text = textTemplate(data)
|
|
103
197
|
.trim();
|
|
104
198
|
|
|
199
|
+
let sendReplies;
|
|
105
200
|
if (quickReplies) {
|
|
106
201
|
const okQuickReplies = quickReplies
|
|
107
202
|
.filter((reply) => reply.condition(req, res));
|
|
108
203
|
|
|
109
|
-
|
|
204
|
+
sendReplies = okQuickReplies
|
|
110
205
|
.filter((reply) => reply.title
|
|
111
206
|
|| reply.isLocation
|
|
112
207
|
|| reply.isEmail
|
|
@@ -123,8 +218,6 @@ function message (params, {
|
|
|
123
218
|
return rep;
|
|
124
219
|
});
|
|
125
220
|
|
|
126
|
-
res.text(text, sendReplies);
|
|
127
|
-
|
|
128
221
|
okQuickReplies
|
|
129
222
|
.filter((reply) => !reply.title && reply.match)
|
|
130
223
|
.forEach(({
|
|
@@ -134,10 +227,27 @@ function message (params, {
|
|
|
134
227
|
});
|
|
135
228
|
} else {
|
|
136
229
|
// replies on last index will be present, so the addQuickReply will be working
|
|
137
|
-
|
|
138
|
-
res.text(text, sendReplies);
|
|
230
|
+
sendReplies = isLastMessage ? [] : undefined;
|
|
139
231
|
}
|
|
140
232
|
|
|
233
|
+
// generate voice control
|
|
234
|
+
let voiceControl = getVoiceControl(params, data.lang);
|
|
235
|
+
if (supportedText.ssmlAlternatives) {
|
|
236
|
+
// find SSML alternative
|
|
237
|
+
const ssmlAlternativeTemplate = randomizedCompiler([{
|
|
238
|
+
l: data.lang,
|
|
239
|
+
t: supportedText.ssmlAlternatives
|
|
240
|
+
}]);
|
|
241
|
+
|
|
242
|
+
voiceControl = {
|
|
243
|
+
...voiceControl,
|
|
244
|
+
ssml: ssmlAlternativeTemplate(data)
|
|
245
|
+
.trim()
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
res.text(text, sendReplies, voiceControl);
|
|
250
|
+
|
|
141
251
|
return ret;
|
|
142
252
|
};
|
|
143
253
|
}
|
package/src/resolvers/utils.js
CHANGED
|
@@ -19,6 +19,28 @@ const WEBVIEW_FULL = 'full';
|
|
|
19
19
|
const WEBVIEW_TALL = 'tall';
|
|
20
20
|
const WEBVIEW_COMPACT = 'compact';
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {Translation[] | Translation | string[] | string} Translations
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {object} TextObject
|
|
28
|
+
* @prop {string} t - text
|
|
29
|
+
* @prop {string} l - lang
|
|
30
|
+
* @prop {string|null} p - purpose
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @typedef Translation
|
|
35
|
+
* @property {string|string[]} t - text alternatives
|
|
36
|
+
* @property {string | null} l - language
|
|
37
|
+
* @property {string|string[]} [p] - purposes
|
|
38
|
+
* null = default + voice
|
|
39
|
+
* t = text
|
|
40
|
+
* v = voice
|
|
41
|
+
* s = ssml
|
|
42
|
+
*/
|
|
43
|
+
|
|
22
44
|
function isArrayOfObjects (translations) {
|
|
23
45
|
return Array.isArray(translations)
|
|
24
46
|
&& typeof translations[0] === 'object'
|
|
@@ -36,18 +58,20 @@ function isTextObjectEmpty (text) {
|
|
|
36
58
|
}
|
|
37
59
|
|
|
38
60
|
/**
|
|
39
|
-
*
|
|
40
|
-
* @param {{t:string,l:string}[]|string} translations
|
|
61
|
+
* @param {Translations} translations
|
|
41
62
|
* @param {string} [lang]
|
|
42
63
|
* @returns {null|string}
|
|
43
64
|
*/
|
|
44
65
|
function getLanguageText (translations, lang = null) {
|
|
45
66
|
let foundText;
|
|
67
|
+
|
|
46
68
|
if (isArrayOfObjects(translations)) {
|
|
47
69
|
if (lang) {
|
|
70
|
+
// @ts-ignore
|
|
48
71
|
foundText = translations.find((t) => t.l === lang);
|
|
49
72
|
}
|
|
50
73
|
if (isTextObjectEmpty(foundText)) {
|
|
74
|
+
// @ts-ignore
|
|
51
75
|
foundText = translations.find((t) => !isTextObjectEmpty(t));
|
|
52
76
|
}
|
|
53
77
|
foundText = foundText ? foundText.t : null;
|
|
@@ -63,6 +87,58 @@ function getLanguageText (translations, lang = null) {
|
|
|
63
87
|
}
|
|
64
88
|
return foundText || '';
|
|
65
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
*
|
|
92
|
+
* @param {Translations} translations
|
|
93
|
+
* @param {string} [lang]
|
|
94
|
+
* @returns {TextObject[]}
|
|
95
|
+
*/
|
|
96
|
+
function getLanguageTextObjects (translations, lang = null) {
|
|
97
|
+
/** @type {{t:string|string[],l?:string,p?:string|string[]}[]} */
|
|
98
|
+
let foundTexts;
|
|
99
|
+
|
|
100
|
+
if (!Array.isArray(translations) || typeof translations[0] === 'string') {
|
|
101
|
+
// @ts-ignore
|
|
102
|
+
foundTexts = [{ t: translations, l: lang }];
|
|
103
|
+
} else { // is array of objects
|
|
104
|
+
foundTexts = translations
|
|
105
|
+
// @ts-ignore
|
|
106
|
+
.filter(({ l = null, ...to }) => l === lang && !isTextObjectEmpty(to));
|
|
107
|
+
|
|
108
|
+
if (foundTexts.length === 0) {
|
|
109
|
+
const { l: firstNonEmptyLang = null } = translations
|
|
110
|
+
// @ts-ignore
|
|
111
|
+
.find((to) => !isTextObjectEmpty(to)) || {};
|
|
112
|
+
|
|
113
|
+
foundTexts = translations
|
|
114
|
+
// @ts-ignore
|
|
115
|
+
.filter(({ l = null, ...to }) => l === firstNonEmptyLang && !isTextObjectEmpty(to));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return foundTexts
|
|
120
|
+
.reduce((a, to) => {
|
|
121
|
+
if (Array.isArray(to.t)) {
|
|
122
|
+
const purposes = Array.isArray(to.p)
|
|
123
|
+
? to.p
|
|
124
|
+
: [to.p];
|
|
125
|
+
a.push(
|
|
126
|
+
...to.t.map((t, i) => ({
|
|
127
|
+
...to, t, p: purposes[i] || null
|
|
128
|
+
}))
|
|
129
|
+
);
|
|
130
|
+
} else {
|
|
131
|
+
a.push(to);
|
|
132
|
+
}
|
|
133
|
+
return a;
|
|
134
|
+
}, [])
|
|
135
|
+
.filter((to) => !!to.t)
|
|
136
|
+
.map(({
|
|
137
|
+
t, l = null, p = null, ...rest
|
|
138
|
+
}) => ({
|
|
139
|
+
t, l, p, ...rest
|
|
140
|
+
}));
|
|
141
|
+
}
|
|
66
142
|
|
|
67
143
|
function randomizedCompiler (text, lang) {
|
|
68
144
|
const texts = getLanguageText(text, lang);
|
|
@@ -100,6 +176,13 @@ function randomizedCompiler (text, lang) {
|
|
|
100
176
|
};
|
|
101
177
|
}
|
|
102
178
|
|
|
179
|
+
/**
|
|
180
|
+
* l - language
|
|
181
|
+
* t - alternatives
|
|
182
|
+
*
|
|
183
|
+
* @param {{l:string,t:string[]}[] | string} text
|
|
184
|
+
* @returns {(state:any)=>string}
|
|
185
|
+
*/
|
|
103
186
|
function cachedTranslatedCompilator (text) {
|
|
104
187
|
const cache = new Map();
|
|
105
188
|
|
|
@@ -194,7 +277,9 @@ function processButtons (
|
|
|
194
277
|
|
|
195
278
|
module.exports = {
|
|
196
279
|
getLanguageText,
|
|
280
|
+
getLanguageTextObjects,
|
|
197
281
|
cachedTranslatedCompilator,
|
|
282
|
+
randomizedCompiler,
|
|
198
283
|
getText,
|
|
199
284
|
stateData,
|
|
200
285
|
|