wingbot 3.29.0 → 3.30.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wingbot",
3
- "version": "3.29.0",
3
+ "version": "3.30.2",
4
4
  "description": "Enterprise Messaging Bot Conversation Engine",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -36,33 +36,28 @@
36
36
  },
37
37
  "homepage": "https://github.com/wingbot.ai/wingbot#readme",
38
38
  "devDependencies": {
39
- "cpy-cli": "^3.1.1",
40
- "eslint": "^7.12.1",
41
- "eslint-config-airbnb": "^18.2.0",
42
- "eslint-mocha": "^0.7.3",
43
- "eslint-plugin-import": "^2.22.1",
44
- "eslint-plugin-jsdoc": "^30.7.7",
45
- "eslint-plugin-jsx-a11y": "^6.4.1",
46
- "eslint-plugin-mocha": "^8.0.0",
47
- "eslint-plugin-react": "^7.21.5",
48
- "gitbook-cli": "^2.3.2",
49
- "graphql-markdown": "^5.2.0",
50
- "graphql-tools": "^7.0.5",
51
- "jsdoc-to-markdown": "^6.0.1",
52
- "mocha": "^8.2.1",
53
- "move-cli": "^1.2.1",
39
+ "cpy-cli": "^4.1.0",
40
+ "eslint": "^8.11.0",
41
+ "eslint-config-airbnb": "^19.0.4",
42
+ "eslint-plugin-import": "^2.25.4",
43
+ "eslint-plugin-jsdoc": "^38.0.4",
44
+ "eslint-plugin-jsx-a11y": "^6.5.1",
45
+ "eslint-plugin-mocha": "^10.0.3",
46
+ "eslint-plugin-react": "^7.29.4",
47
+ "graphql-markdown": "^6.0.0",
48
+ "jsdoc-to-markdown": "^7.1.1",
49
+ "mocha": "^9.2.2",
54
50
  "nyc": "^15.1.0",
55
51
  "rimraf": "^3.0.2",
56
- "sinon": "^9.2.1",
52
+ "sinon": "^13.0.1",
57
53
  "tsd-jsdoc": "^2.5.0"
58
54
  },
59
55
  "dependencies": {
60
- "graphql": "^15.4.0",
61
- "graphql-mongodb-projection": "^1.1.1",
56
+ "graphql": "^15.8.0",
62
57
  "jsonwebtoken": "^8.5.1",
63
- "node-fetch": "^2.6.1",
58
+ "node-fetch": "^2.6.7",
64
59
  "path-to-regexp": "^6.2.0",
65
- "uuid": "^8.3.1",
60
+ "uuid": "^8.3.2",
66
61
  "webalize": "^0.1.0"
67
62
  },
68
63
  "optionalDependencies": {
@@ -71,4 +66,4 @@
71
66
  "request": "^2.88.0",
72
67
  "request-promise-native": "^1.0.7"
73
68
  }
74
- }
69
+ }
@@ -3,7 +3,7 @@
3
3
  * @param {import('../../src/Responder')} res
4
4
  * @param {Function} postback
5
5
  */
6
- module.exports = (req, res, postback) => {
6
+ module.exports = function (req, res, postback) {
7
7
  if (req.state.beforeLastInteraction
8
8
  && !req.state.beforeLastInteraction.match(/\/\*$/)
9
9
  && req.state.beforeLastInteraction !== res.currentAction()) {
@@ -3,7 +3,7 @@
3
3
  * @param {import('../../src/Responder')} res
4
4
  * @param {Function} postback
5
5
  */
6
- module.exports = (req, res, postback) => {
6
+ module.exports = function (req, res, postback) {
7
7
  let { breadcrumbs = [] } = req.state;
8
8
  let shift = req.state.breadcrumbAction === req.state._lastAction
9
9
  ? 2
@@ -3,7 +3,7 @@
3
3
  * @param {import('../../src/Responder')} res
4
4
  * @param {Function} postback
5
5
  */
6
- module.exports = (req, res, postback) => {
6
+ module.exports = function (req, res, postback) {
7
7
  let { jumpPluginStack = [] } = {
8
8
  ...req.state,
9
9
  ...res.newState
@@ -3,7 +3,7 @@
3
3
  * @param {import('../../src/Responder')} res
4
4
  * @param {Function} postback
5
5
  */
6
- module.exports = (req, res, postback) => {
6
+ module.exports = function (req, res, postback) {
7
7
  const {
8
8
  whereToJump,
9
9
  whereToJumpBack
@@ -2,4 +2,6 @@
2
2
  * @param {import('../../src/Request')} req
3
3
  * @param {import('../../src/Responder')} res
4
4
  */
5
- module.exports = (req, res) => null;
5
+ module.exports = function (req, res) {
6
+ return null;
7
+ };
package/src/Ai.js CHANGED
@@ -64,6 +64,20 @@ class Ai {
64
64
  */
65
65
  this.threshold = 0.3;
66
66
 
67
+ /**
68
+ * Upper limit for NLP resolving of STT alternatives
69
+ *
70
+ * @type {number}
71
+ */
72
+ this.sttMaxAlternatives = 3;
73
+
74
+ /**
75
+ * Minimal score to consider text as recognized well
76
+ *
77
+ * @type {number}
78
+ */
79
+ this.sttScoreThreshold = 0;
80
+
67
81
  /**
68
82
  * The logger (console by default)
69
83
  *
@@ -581,35 +595,61 @@ class Ai {
581
595
  }
582
596
  }
583
597
 
598
+ const texts = req.textAlternatives()
599
+ .filter((alt) => alt.score >= this.sttScoreThreshold)
600
+ .slice(0, this.sttMaxAlternatives);
601
+
602
+ const altMax = Math.max(0, ...texts.map((t) => t.score));
603
+ const altKoef = (1 - this.confidence);
604
+
584
605
  const results = await Promise.all(
585
- req.textAlternatives()
586
- .map(({ text, score }) => model
587
- .resolve(this.textFilter(text), req)
588
- .then((res) => ({
589
- ...res,
590
- _altScore: score
591
- })))
606
+ texts.map(({ text, score }) => model
607
+ .resolve(this.textFilter(text), req)
608
+ .then((res) => ({
609
+ ...res,
610
+ intents: res.intents
611
+ .map((i) => ({
612
+ ...i,
613
+ score: i.score - (altKoef * (altMax - score))
614
+ }))
615
+ .sort(({ score: a }, { score: z }) => z - a)
616
+ })))
592
617
  );
593
618
 
594
619
  results.sort(({
595
- intents: aIntents = [], _altScore: aAlt
620
+ intents: [aIntent = { score: 0 }]
596
621
  }, {
597
- intents: zIntents = [], _altScore: zAlt
598
- }) => {
599
- const [{ score: aScore = 0 } = {}] = aIntents;
600
- const [{ score: zScore = 0 } = {}] = zIntents;
622
+ intents: [zIntent = { score: 0 }]
623
+ }) => zIntent.score - aIntent.score);
601
624
 
602
- const a = aAlt * aScore;
603
- const z = zAlt * zScore;
625
+ const [winner = { intents: [], entities: [] }, ...others] = results;
604
626
 
605
- if (a === z) {
606
- return zAlt - aAlt;
607
- }
627
+ if (others.length === 0) {
628
+ return winner;
629
+ }
608
630
 
609
- return z - a;
631
+ const { intents } = winner;
632
+
633
+ const known = new Set(intents.map((i) => i.intent));
634
+
635
+ others.forEach((other) => {
636
+ intents.push(
637
+ ...other.intents.filter(({ score, intent }) => {
638
+ if (score >= this.confidence || known.has(intent)) {
639
+ return false;
640
+ }
641
+ known.add(intent);
642
+ return true;
643
+ })
644
+ );
610
645
  });
611
646
 
612
- return results[0];
647
+ intents.sort(({ score: a }, { score: z }) => z - a);
648
+
649
+ return {
650
+ ...winner,
651
+ intents
652
+ };
613
653
  }
614
654
 
615
655
  /**
package/src/AiMatching.js CHANGED
@@ -53,12 +53,16 @@ const COMPARE = {
53
53
  * @param {Entity[]} [entities]
54
54
  */
55
55
 
56
+ /**
57
+ * @typedef {string|number|Function} Comparable
58
+ */
59
+
56
60
  /**
57
61
  * @typedef {object} EntityExpression
58
62
  * @prop {string} entity - the requested entity
59
63
  * @prop {boolean} [optional] - the match is optional
60
64
  * @prop {Compare} [op] - comparison operation
61
- * @prop {(string|number|Function)[]} [compare] - value to compare
65
+ * @prop {Comparable[]} [compare] - value to compare
62
66
  */
63
67
 
64
68
  /**
@@ -578,10 +582,12 @@ class AiMatching {
578
582
  score: 1
579
583
  };
580
584
 
581
- matching = this
582
- ._entityIsMatching(
583
- wanted.op, wanted.compare, requestEntity.value, requestState
584
- );
585
+ matching = this._entityIsMatching(
586
+ wanted.op,
587
+ wanted.compare,
588
+ requestEntity.value,
589
+ requestState
590
+ );
585
591
  }
586
592
  } else if (!entityExists) {
587
593
  matching = this
package/src/BotApp.js CHANGED
@@ -87,14 +87,12 @@ class BotApp {
87
87
  this._senderLogger = chatLogStorage;
88
88
  this._verify = promisify(jwt.verify);
89
89
 
90
- this._processor = new Processor(
91
- bot, {
92
- ...processorOptions,
93
- secret,
94
- fetch: this._fetch,
95
- apiUrl: gqlApiUrl
96
- }
97
- );
90
+ this._processor = new Processor(bot, {
91
+ ...processorOptions,
92
+ secret,
93
+ fetch: this._fetch,
94
+ apiUrl: gqlApiUrl
95
+ });
98
96
 
99
97
  this._processor.plugin(BotApp.plugin());
100
98
  this._preferSynchronousResponse = preferSynchronousResponse;
@@ -191,7 +189,13 @@ class BotApp {
191
189
  }
192
190
 
193
191
  async _processIncommingMessage (
194
- message, senderId, pageId, appId, secret, sync = false, headers = {}
192
+ message,
193
+ senderId,
194
+ pageId,
195
+ appId,
196
+ secret,
197
+ sync = false,
198
+ headers = {}
195
199
  ) {
196
200
  const { mid = null } = message;
197
201
 
@@ -386,7 +390,13 @@ class BotApp {
386
390
 
387
391
  for (const message of messaging) {
388
392
  const res = await this._processIncommingMessage(
389
- message, senderId, pageId, appId, secret, sync, headers
393
+ message,
394
+ senderId,
395
+ pageId,
396
+ appId,
397
+ secret,
398
+ sync,
399
+ headers
390
400
  );
391
401
 
392
402
  responses.push(res);
@@ -305,9 +305,8 @@ class ConversationTester {
305
305
  return testCases
306
306
  // @ts-ignore
307
307
  .reduce(
308
- (map, testCase) => map.set(
309
- testCase.list, [...(map.get(testCase.list) || []), testCase]
310
- ),
308
+ (map, testCase) => map
309
+ .set(testCase.list, [...(map.get(testCase.list) || []), testCase]),
311
310
  new Map()
312
311
  );
313
312
  }
@@ -77,11 +77,9 @@ class OrchestratorClient {
77
77
  */
78
78
  async addConversationTokenToUrl (url, expirationInSeconds, tokenName = 'wbchtoken') {
79
79
  const { conversationToken } = await this.getConversationToken(expirationInSeconds);
80
- return withParams(
81
- url, {
82
- query: { [tokenName]: conversationToken }
83
- }
84
- );
80
+ return withParams(url, {
81
+ query: { [tokenName]: conversationToken }
82
+ });
85
83
  }
86
84
 
87
85
  /**
package/src/Processor.js CHANGED
@@ -100,7 +100,7 @@ const { mergeState } = require('./utils/stateVariables');
100
100
  * @prop {string|null} [meta.targetAction]
101
101
  */
102
102
 
103
- const NAME_FROM_STATE = (state) => {
103
+ function NAME_FROM_STATE (state) {
104
104
  if (state.user && state.user.firstName) {
105
105
  return `${state.user.firstName} ${state.user.lastName}`;
106
106
  }
@@ -108,7 +108,7 @@ const NAME_FROM_STATE = (state) => {
108
108
  return `${state.user.name}`;
109
109
  }
110
110
  return null;
111
- };
111
+ }
112
112
 
113
113
  /**
114
114
  * Messaging event processor
package/src/Request.js CHANGED
@@ -742,18 +742,36 @@ class Request {
742
742
  if (!this.message || typeof this.message.text !== 'string') {
743
743
  return [];
744
744
  }
745
- const { text, alternatives = [] } = this.message;
745
+ const { text: messageText, alternatives = [] } = this.message;
746
+
747
+ const unique = new Set();
748
+ let max = 0;
749
+
750
+ const sorted = alternatives
751
+ .slice()
752
+ .map(({ text, score = 1 }) => ({ text, score }))
753
+ .sort(({ score: a }, { score: z }) => z - a)
754
+ .filter((a) => {
755
+ const norm = tokenize(a.text);
756
+ if (unique.has(norm) || !a.text) {
757
+ return false;
758
+ }
759
+ max = Math.max(max, a.score);
760
+ unique.add(norm);
761
+ return true;
762
+ });
746
763
 
747
- if (alternatives.some((a) => a.text === text)) {
748
- return alternatives;
764
+ const normalized = tokenize(messageText);
765
+
766
+ if (unique.has(normalized)) {
767
+ return sorted;
749
768
  }
750
769
 
770
+ max = max ? Math.min(1, max + 0.1) : 1;
771
+
751
772
  return [
752
- {
753
- text: this.message.text,
754
- score: 1
755
- },
756
- ...alternatives
773
+ { text: messageText, score: max },
774
+ ...sorted
757
775
  ];
758
776
  }
759
777
 
@@ -1268,7 +1286,9 @@ class Request {
1268
1286
  if (this._quickReplyActions === null) {
1269
1287
  if (this.state._expectedKeywords) {
1270
1288
  this._quickReplyActions = quickReplyAction(
1271
- this.state._expectedKeywords, this, Ai.ai
1289
+ this.state._expectedKeywords,
1290
+ this,
1291
+ Ai.ai
1272
1292
  );
1273
1293
  } else {
1274
1294
  this._quickReplyActions = [];
@@ -13,6 +13,7 @@ type Tags {
13
13
  type Subscribtion {
14
14
  pageId: String
15
15
  senderId: String
16
+ meta: String
16
17
  }
17
18
 
18
19
  type Subscribtions {
@@ -103,6 +103,28 @@ const DEFAULT_CAMPAIGN_DATA = {
103
103
  * @prop {number} slideRound
104
104
  */
105
105
 
106
+ /**
107
+ * @typedef {object} Subscription
108
+ * @prop {string} senderId
109
+ * @prop {string} pageId
110
+ * @prop {string} [meta]
111
+ */
112
+
113
+ /**
114
+ * @callback PreprocessSubscriptions
115
+ * @param {Subscription[]} subscriptions
116
+ * @param {string} pageId
117
+ * @returns {Promise<Subscription[]>|Subscription[]}
118
+ */
119
+
120
+ /**
121
+ * @callback PreprocessSubscribers
122
+ * @param {string[]} senderIds
123
+ * @param {string} pageId
124
+ * @param {string} tag
125
+ * @returns {Promise<string[]>|string[]} list of senderIds
126
+ */
127
+
106
128
  /**
107
129
  * Experimental notifications service
108
130
  *
@@ -121,7 +143,8 @@ class Notifications extends EventEmitter {
121
143
  * @param {console} [options.log] - logger
122
144
  * @param {number} [options.default24Clearance] - use this clearance to ensure delivery in 24h
123
145
  * @param {string} [options.allAudienceTag] - tag to mark all users
124
- * @param {Function} [options.preprocessSubscribers] - to preprocess GQL api given senderIds
146
+ * @param {PreprocessSubscribers} [options.preprocessSubscribers] - preprocess senderIds import
147
+ * @param {PreprocessSubscriptions} [options.preprocessSubscriptions]
125
148
  */
126
149
  constructor (notificationStorage = new NotificationsStorage(), options = {}) {
127
150
  super();
@@ -138,6 +161,7 @@ class Notifications extends EventEmitter {
138
161
  this._lts = new Map();
139
162
 
140
163
  this._preprocessSubscribers = options.preprocessSubscribers;
164
+ this._preprocessSubscriptions = options.preprocessSubscriptions;
141
165
  }
142
166
 
143
167
  /**
@@ -150,7 +174,8 @@ class Notifications extends EventEmitter {
150
174
  */
151
175
  api (acl = null) {
152
176
  const options = {
153
- preprocessSubscribers: this._preprocessSubscribers
177
+ preprocessSubscribers: this._preprocessSubscribers,
178
+ preprocessSubscriptions: this._preprocessSubscriptions
154
179
  };
155
180
  return api(this._storage, this, acl, options);
156
181
  }
@@ -344,7 +369,10 @@ class Notifications extends EventEmitter {
344
369
 
345
370
  await Promise.all(tasks
346
371
  .map((task) => this._messageDeliveryByMid(
347
- task.campaignId, eventType, senderId, pageId
372
+ task.campaignId,
373
+ eventType,
374
+ senderId,
375
+ pageId
348
376
  )));
349
377
 
350
378
  return {
@@ -452,7 +452,7 @@ class NotificationsStorage {
452
452
  * @param {string} tag
453
453
  * @returns {Promise}
454
454
  */
455
- subscribe (senderId, pageId, tag) {
455
+ _subscribe (senderId, pageId, tag) {
456
456
  const key = `${senderId}|${pageId}`;
457
457
  let subscribtion = this._subscribtions.get(key);
458
458
  if (!subscribtion) {
@@ -467,6 +467,18 @@ class NotificationsStorage {
467
467
  }
468
468
 
469
469
  this._subscribtions.set(key, subscribtion);
470
+ }
471
+
472
+ /**
473
+ *
474
+ * @param {string|string[]} senderId
475
+ * @param {string} pageId
476
+ * @param {string} tag
477
+ * @returns {Promise}
478
+ */
479
+ subscribe (senderId, pageId, tag) {
480
+ const insert = Array.isArray(senderId) ? senderId : [senderId];
481
+ insert.forEach((sender) => this._subscribe(sender, pageId, tag));
470
482
  return Promise.resolve();
471
483
  }
472
484
 
@@ -3,9 +3,41 @@
3
3
  */
4
4
  'use strict';
5
5
 
6
- const { infoToProjection } = require('graphql-mongodb-projection');
7
6
  const apiAuthorizer = require('../../graphApi/apiAuthorizer');
8
7
 
8
+ /**
9
+ *
10
+ * @param {*} info
11
+ * @returns {object}
12
+ */
13
+
14
+ function getFields (info) {
15
+ if (info.fieldNodes) {
16
+ const fieldNodes = info.fieldNodes.filter((k) => k.kind === 'Field');
17
+
18
+ if (fieldNodes.length !== 1) {
19
+ return {};
20
+ }
21
+
22
+ return getFields(fieldNodes[0]);
23
+ }
24
+
25
+ const nodes = info.selectionSet ? info.selectionSet.selections : info.fieldNodes;
26
+
27
+ if (!info.selectionSet) {
28
+ return true;
29
+ }
30
+
31
+ return nodes.reduce((o, fieldInfo) => {
32
+ if (fieldInfo.kind !== 'Field' || !fieldInfo.name || !fieldInfo.name.value) {
33
+ return o;
34
+ }
35
+ return Object.assign(o, {
36
+ [fieldInfo.name.value]: getFields(fieldInfo)
37
+ });
38
+ }, {});
39
+ }
40
+
9
41
  function notificationsApiFactory (storage, notifications, acl, options = {}) {
10
42
  return {
11
43
  async campaigns (args, ctx) {
@@ -87,9 +119,8 @@ function notificationsApiFactory (storage, notifications, acl, options = {}) {
87
119
  };
88
120
  }
89
121
 
90
- const data = await storage.getUnsuccessfulSubscribersByCampaign(
91
- campaignId, sentWithoutReaction, pageId
92
- );
122
+ const data = await storage
123
+ .getUnsuccessfulSubscribersByCampaign(campaignId, sentWithoutReaction, pageId);
93
124
 
94
125
  return {
95
126
  data,
@@ -115,7 +146,7 @@ function notificationsApiFactory (storage, notifications, acl, options = {}) {
115
146
  return null;
116
147
  }
117
148
 
118
- const fields = infoToProjection(info);
149
+ const fields = getFields(info);
119
150
 
120
151
  const {
121
152
  include,
@@ -132,7 +163,15 @@ function notificationsApiFactory (storage, notifications, acl, options = {}) {
132
163
  }
133
164
 
134
165
  if (fields.data) {
135
- const res = storage.getSubscribtions(include, exclude, limit, pageId, lastKey);
166
+ const res = await storage
167
+ .getSubscribtions(include, exclude, limit, pageId, lastKey);
168
+
169
+ if (fields.data.meta && typeof options.preprocessSubscriptions === 'function') {
170
+ res.data = await Promise.resolve(
171
+ options.preprocessSubscriptions(res.data, pageId)
172
+ );
173
+ }
174
+
136
175
  return Object.assign(res, { count });
137
176
  }
138
177
 
@@ -159,8 +198,16 @@ function notificationsApiFactory (storage, notifications, acl, options = {}) {
159
198
  );
160
199
  }
161
200
 
162
- for (const senderId of senderIds) {
163
- await storage.subscribe(`${senderId}`, pageId, tag);
201
+ senderIds = senderIds.slice();
202
+
203
+ // make it little bit faster
204
+ while (senderIds.length > 0) {
205
+ if (storage.subscribe.length > 3) {
206
+ const insertSenders = senderIds.splice(0, 500);
207
+ await storage.subscribe(insertSenders, pageId, tag, true);
208
+ } else {
209
+ await storage.subscribe(senderIds.pop(), pageId, tag);
210
+ }
164
211
  }
165
212
 
166
213
  return true;
@@ -7,9 +7,11 @@ const Router = require('../Router');
7
7
  const getCondition = require('../utils/getCondition');
8
8
  const { stateData, cachedTranslatedCompilator } = require('./utils');
9
9
 
10
- function media (params,
10
+ function media (
11
+ params,
11
12
  // @ts-ignore
12
- { isLastIndex } = {}) {
13
+ { isLastIndex } = {}
14
+ ) {
13
15
  const { type, url } = params;
14
16
 
15
17
  const urlString = url || '';
@@ -9,7 +9,11 @@ const Request = require('../Request');
9
9
  const Router = require('../Router');
10
10
  const getCondition = require('../utils/getCondition');
11
11
  const {
12
- stateData, getLanguageText, getLanguageTextObjects, randomizedCompiler
12
+ stateData,
13
+ getLanguageText,
14
+ getLanguageTextObjects,
15
+ randomizedCompiler,
16
+ cachedTranslatedCompilator
13
17
  } = require('./utils');
14
18
  const {
15
19
  FEATURE_SSML, FEATURE_TEXT, FEATURE_VOICE
@@ -75,7 +79,7 @@ function parseReplies (replies, linksMap, allowForbiddenSnippetWords) {
75
79
  action,
76
80
  condition,
77
81
  title: reply.title
78
- ? randomizedCompiler(reply.title)
82
+ ? cachedTranslatedCompilator(reply.title)
79
83
  : null,
80
84
  data: {}
81
85
  };
@@ -202,18 +202,21 @@ function getText (text, state) {
202
202
  return renderer(state);
203
203
  }
204
204
 
205
+ // eslint-disable-next-line no-unused-vars
206
+ const DEFAULT_LINK_TRANSLATOR = (senderId, defaultText, urlText, isExtUrl, reqState) => urlText;
207
+
205
208
  function processButtons (
206
209
  buttons,
207
210
  state,
208
211
  elem,
209
212
  linksMap,
210
213
  senderId,
211
- // eslint-disable-next-line no-unused-vars
212
- linksTranslator = (sndr, defaultText, urlText, isExtUrl, reqState) => urlText,
214
+ linksTranslator,
213
215
  allowForbiddenSnippetWords,
214
216
  req,
215
217
  res
216
218
  ) {
219
+ const translateLinks = linksTranslator || DEFAULT_LINK_TRANSLATOR;
217
220
 
218
221
  buttons.forEach(({
219
222
  title: btnTitle,
@@ -251,7 +254,7 @@ function processButtons (
251
254
  case TYPE_URL_WITH_EXT: {
252
255
  const hasExtention = type === TYPE_URL_WITH_EXT;
253
256
  let urlText = getText(url, state);
254
- urlText = linksTranslator(senderId, defaultText, urlText, isExtUrl, state);
257
+ urlText = translateLinks(senderId, defaultText, urlText, isExtUrl, state);
255
258
  elem.urlButton(btnTitleText, urlText, hasExtention, webviewHeight);
256
259
  break;
257
260
  }
@@ -22,7 +22,7 @@ class MemoryBotConfigStorage {
22
22
  * @param {Function|string[]} [acl] - acl configuration
23
23
  * @returns {{updateBot:Function}}
24
24
  */
25
- api (onUpdate = () => Promise.resolve(), acl) {
25
+ api (onUpdate = () => Promise.resolve(), acl = []) {
26
26
  const storage = this;
27
27
  return {
28
28
  async updateBot (args, ctx) {
@@ -42,7 +42,7 @@ const isSimpleWord = (string) => {
42
42
  const isStringNumber = (string) => {
43
43
  if (typeof string !== 'string') return false;
44
44
  if (string.length === 0) return false;
45
- return !!new RegExp(/^([0-9]*[, ]{0,1})*[., ]{0,1}[0-9]*$/)
45
+ return !!/^([0-9]*[, ]{0,1})*[., ]{0,1}[0-9]*$/
46
46
  .test((string).replace(/\s+/g, ''));
47
47
  };
48
48
 
@@ -55,7 +55,7 @@ const stringToNumber = (string) => {
55
55
  if (typeof string !== 'string') return string;
56
56
  if (!isStringNumber(string)) throw new Error('String not a number');
57
57
  let trimmed;
58
- if (new RegExp(/^[^,.]+,[^,.]+$/).test(string)) {
58
+ if (/^[^,.]+,[^,.]+$/.test(string)) {
59
59
  trimmed = string.replace(',', '.');
60
60
  } else {
61
61
  trimmed = string.replace(/(\s|,)+/g, '').replace(',', '.');
@@ -88,8 +88,10 @@ function toArray (previousValue) {
88
88
  const ENTITY_HBS_REGEXP = /^\s*\{\{\[?@([^@[\]{}\s]+)(\])?\}\}\s*$/;
89
89
  const VARIABLE_HBS_REGEXP = /^\s*\{\{\[?([^@[\]{}\s]+)\]?\}\}\s*$/;
90
90
 
91
- function getSetState (setState = {}, req, res = null, useState = null) {
92
-
91
+ function getSetState (setState, req, res = null, useState = null) {
92
+ if (!setState) {
93
+ return {};
94
+ }
93
95
  const keys = Object.keys(setState)
94
96
  .filter((k) => k !== '_');
95
97
 
@@ -32,7 +32,12 @@ const { checkSetState } = require('./stateVariables');
32
32
  * @returns {ExpectedKeyword}
33
33
  */
34
34
  function makeExpectedKeyword (
35
- action, title, matcher = null, payloadData = {}, setState = null, aiTitle = null
35
+ action,
36
+ title,
37
+ matcher = null,
38
+ payloadData = {},
39
+ setState = null,
40
+ aiTitle = null
36
41
  ) {
37
42
  let match = null;
38
43
 
@@ -226,7 +231,12 @@ function makeQuickReplies (replies, path = '', translate = (w) => w, quickReplyC
226
231
  const translatedTitle = translate(title);
227
232
  const translatedAiTitle = typeof aiTitle === 'string' ? translate(aiTitle) : aiTitle;
228
233
  const expect = makeExpectedKeyword(
229
- absoluteAction, translatedTitle, match, data, setState, translatedAiTitle
234
+ absoluteAction,
235
+ translatedTitle,
236
+ match,
237
+ data,
238
+ setState,
239
+ translatedAiTitle
230
240
  );
231
241
  expectedKeywords.push(expect);
232
242