wingbot 3.30.1 → 3.32.0-alpha.1
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 -1
- package/src/Ai.js +2 -2
- package/src/BotAppSender.js +34 -0
- package/src/BuildRouter.js +319 -32
- package/src/Processor.js +7 -1
- package/src/Request.js +3 -0
- package/src/Responder.js +27 -0
- package/src/ReturnSender.js +17 -0
- package/src/Router.js +21 -7
- package/src/notifications/Notifications.js +1 -1
- package/src/resolvers/bounce.js +6 -3
- package/src/resolvers/button.js +16 -11
- package/src/resolvers/carousel.js +8 -10
- package/src/resolvers/media.js +13 -7
- package/src/resolvers/message.js +38 -18
- package/src/resolvers/postback.js +13 -3
- package/src/resolvers/setState.js +12 -3
- package/src/resolvers/subscribtions.js +13 -3
- package/src/resolvers/utils.js +6 -6
- package/src/utils/customCondition.js +3 -2
- package/src/utils/getCondition.js +15 -2
- package/src/utils/getUpdate.js +3 -3
- package/src/utils/stateData.js +5 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wingbot",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.32.0-alpha.1",
|
|
4
4
|
"description": "Enterprise Messaging Bot Conversation Engine",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"tsd-jsdoc": "^2.5.0"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
+
"form-data": "^4.0.0",
|
|
56
57
|
"graphql": "^15.8.0",
|
|
57
58
|
"jsonwebtoken": "^8.5.1",
|
|
58
59
|
"node-fetch": "^2.6.7",
|
package/src/Ai.js
CHANGED
|
@@ -229,7 +229,7 @@ class Ai {
|
|
|
229
229
|
*
|
|
230
230
|
* @param {string} path
|
|
231
231
|
* @param {IntentRule|IntentRule[]} intents
|
|
232
|
-
* @param {string} [title] - disambiguation title
|
|
232
|
+
* @param {string|Function} [title] - disambiguation title
|
|
233
233
|
* @param {object} [meta] - metadata for multibot environments
|
|
234
234
|
* @param {object} [meta.targetAppId] - target application id
|
|
235
235
|
* @param {object} [meta.targetAction] - target action
|
|
@@ -276,7 +276,7 @@ class Ai {
|
|
|
276
276
|
*
|
|
277
277
|
* @param {string} path
|
|
278
278
|
* @param {IntentRule|IntentRule[]} intents
|
|
279
|
-
* @param {string} [title] - disambiguation title
|
|
279
|
+
* @param {string|Function} [title] - disambiguation title
|
|
280
280
|
* @returns {object} - the middleware
|
|
281
281
|
* @memberOf Ai
|
|
282
282
|
* @example
|
package/src/BotAppSender.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
const jwt = require('jsonwebtoken');
|
|
7
7
|
const { default: fetch, Headers } = require('node-fetch');
|
|
8
|
+
const FormData = require('form-data');
|
|
8
9
|
const crypto = require('crypto');
|
|
9
10
|
const { Agent } = require('https');
|
|
10
11
|
const { promisify } = require('util');
|
|
@@ -12,6 +13,7 @@ const ReturnSender = require('./ReturnSender');
|
|
|
12
13
|
|
|
13
14
|
/** @typedef {import('./ReturnSender').ReturnSenderOptions} ReturnSenderOptions */
|
|
14
15
|
/** @typedef {import('./ReturnSender').ChatLogStorage} ChatLogStorage */
|
|
16
|
+
/** @typedef {import('./ReturnSender').UploadResult} UploadResult */
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
* @typedef {object} TlsOptions
|
|
@@ -91,6 +93,38 @@ class BotAppSender extends ReturnSender {
|
|
|
91
93
|
return this._agent;
|
|
92
94
|
}
|
|
93
95
|
|
|
96
|
+
/**
|
|
97
|
+
*
|
|
98
|
+
* @param {Buffer} data
|
|
99
|
+
* @param {string} contentType
|
|
100
|
+
* @param {string} fileName
|
|
101
|
+
* @returns {Promise<UploadResult>}
|
|
102
|
+
*/
|
|
103
|
+
async upload (data, contentType, fileName) {
|
|
104
|
+
const formData = new FormData();
|
|
105
|
+
|
|
106
|
+
const nonce = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(36).padEnd(11, '0');
|
|
107
|
+
|
|
108
|
+
formData.append('nonce', nonce);
|
|
109
|
+
formData.append('f0', data, { filename: fileName, contentType });
|
|
110
|
+
|
|
111
|
+
const [token, agent] = await Promise.all([
|
|
112
|
+
BotAppSender.signBody(nonce, this._secret, this._appId),
|
|
113
|
+
this._getAgent()
|
|
114
|
+
]);
|
|
115
|
+
|
|
116
|
+
const headers = new Headers();
|
|
117
|
+
|
|
118
|
+
headers.set('Authorization', token);
|
|
119
|
+
|
|
120
|
+
const response = await this._fetch(this._apiUrl, {
|
|
121
|
+
headers, body: formData, agent, method: 'POST'
|
|
122
|
+
})
|
|
123
|
+
.then((r) => r.json());
|
|
124
|
+
|
|
125
|
+
return response;
|
|
126
|
+
}
|
|
127
|
+
|
|
94
128
|
async _send (payload) {
|
|
95
129
|
// attach sender
|
|
96
130
|
if (typeof payload.sender === 'undefined') {
|
package/src/BuildRouter.js
CHANGED
|
@@ -17,6 +17,67 @@ const { shouldExecuteResolver } = require('./resolvers/resolverTags');
|
|
|
17
17
|
|
|
18
18
|
const MESSAGE_RESOLVER_NAME = 'botbuild.message';
|
|
19
19
|
|
|
20
|
+
/** @typedef {import('./Router').Middleware} Middleware */
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {object} Resolver
|
|
24
|
+
* @prop {string} type
|
|
25
|
+
* @prop {object} params
|
|
26
|
+
* @prop {string} [params.staticBlockId]
|
|
27
|
+
* @prop {string} [tag]
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/** @typedef {import('./resolvers/bounce').BounceAllow} BounceAllow */
|
|
31
|
+
/** @typedef {import('./resolvers/bounce').BounceReturn} BounceReturn */
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @typedef {object} Route
|
|
35
|
+
* @prop {number} id
|
|
36
|
+
* @prop {string|null} path
|
|
37
|
+
* @prop {Resolver[]} resolvers
|
|
38
|
+
* @prop {boolean} [isFallback]
|
|
39
|
+
* @prop {string[]} [aiTags]
|
|
40
|
+
* @prop {boolean} [isResponder]
|
|
41
|
+
* @prop {number} [respondsToRouteId]
|
|
42
|
+
* @prop {string|any[]} [aiTitle]
|
|
43
|
+
* @prop {boolean} [aiGlobal]
|
|
44
|
+
* @prop {BounceAllow} [bounceAllowedTo]
|
|
45
|
+
* @prop {BounceReturn} [bounceReturn]
|
|
46
|
+
* @prop {boolean} [isEntryPoint]
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @typedef {object} RouteTransformation
|
|
51
|
+
* @prop {string} [expectedPath]
|
|
52
|
+
*
|
|
53
|
+
* @typedef {RouteTransformation & Route} TransformedRoute
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @typedef {Map<string|number,string>} LinksMap
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
/** @type {TransformedRoute} */
|
|
61
|
+
const DUMMY_ROUTE = { id: 0, path: null, resolvers: [] };
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @typedef {object} Block
|
|
65
|
+
* @prop {string} staticBlockId
|
|
66
|
+
* @prop {Route[]} routes
|
|
67
|
+
* @prop {boolean} [isRoot]
|
|
68
|
+
* @prop {boolean} [disabled]
|
|
69
|
+
* @prop {string} [blockName]
|
|
70
|
+
* @prop {string} [blockType]
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @typedef {object} BotConfig
|
|
75
|
+
* @prop {string} botId - the ID of bot
|
|
76
|
+
* @prop {string} snapshot - snapshot stage of bot
|
|
77
|
+
* @prop {string|Promise<string>} token - authorization token for bot
|
|
78
|
+
* @prop {string} [url] - specify alternative configuration resource
|
|
79
|
+
*/
|
|
80
|
+
|
|
20
81
|
/**
|
|
21
82
|
* @typedef {object} ConfigStorage
|
|
22
83
|
* @prop {{():Promise}} invalidateConfig
|
|
@@ -25,6 +86,61 @@ const MESSAGE_RESOLVER_NAME = 'botbuild.message';
|
|
|
25
86
|
* @prop {{():Promise<Object>}} getConfig
|
|
26
87
|
*/
|
|
27
88
|
|
|
89
|
+
/**
|
|
90
|
+
* @typedef {object} RouteConfig
|
|
91
|
+
* @prop {string} path
|
|
92
|
+
* @prop {boolean} enabled
|
|
93
|
+
* @prop {object} configuration
|
|
94
|
+
*/
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @callback LinkTranslator
|
|
98
|
+
* @param {string} senderId
|
|
99
|
+
* @param {string} textLabel
|
|
100
|
+
* @param {string} urlText
|
|
101
|
+
* @param {boolean} [isExtUrl]
|
|
102
|
+
* @param {object} [state]
|
|
103
|
+
* @returns {string}
|
|
104
|
+
*/
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @typedef {object} BotContext
|
|
108
|
+
* @prop {LinkTranslator} [linksTranslator] - function, that translates links globally
|
|
109
|
+
* @prop {ConfigStorage} [configStorage] - function, that translates links globally
|
|
110
|
+
* @prop {boolean} [allowForbiddenSnippetWords] - disable security rule
|
|
111
|
+
* @prop {RouteConfig[]} [routeConfigs] - list of disabled routes
|
|
112
|
+
* @prop {object} [configuration] - context data
|
|
113
|
+
* @prop {LinksMap} [linksMap]
|
|
114
|
+
* @prop {boolean} [isLastIndex]
|
|
115
|
+
* @prop {boolean} [isLastMessage]
|
|
116
|
+
* @prop {BuildRouter} [router]
|
|
117
|
+
* @prop {string} [path]
|
|
118
|
+
* @prop {boolean} [isFallback]
|
|
119
|
+
* @prop {string} [expectedPath]
|
|
120
|
+
* @prop {boolean} [isResponder]
|
|
121
|
+
* @prop {string|number} [routeId]
|
|
122
|
+
* @prop {string} [blockName]
|
|
123
|
+
* @prop {string} [blockType]
|
|
124
|
+
* @prop {boolean} [isRoot]
|
|
125
|
+
* @prop {string} [staticBlockId]
|
|
126
|
+
* @prop {Block[]} [blocks]
|
|
127
|
+
* @prop {object} [BuildRouter]
|
|
128
|
+
*/
|
|
129
|
+
|
|
130
|
+
function deepFreeze (object) {
|
|
131
|
+
const propNames = Object.getOwnPropertyNames(object);
|
|
132
|
+
|
|
133
|
+
for (const name of propNames) {
|
|
134
|
+
const value = object[name];
|
|
135
|
+
|
|
136
|
+
if (value && typeof value === 'object') {
|
|
137
|
+
deepFreeze(value);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return Object.freeze(object);
|
|
142
|
+
}
|
|
143
|
+
|
|
28
144
|
/**
|
|
29
145
|
* Build bot from Wingbot configuration file or snapshot url
|
|
30
146
|
*
|
|
@@ -36,16 +152,15 @@ class BuildRouter extends Router {
|
|
|
36
152
|
* Create new router from configuration
|
|
37
153
|
*
|
|
38
154
|
* @constructor
|
|
39
|
-
* @param {
|
|
40
|
-
* @param {string} [block.botId] - the ID of bot
|
|
41
|
-
* @param {string} [block.snapshot] - snapshot stage of bot
|
|
42
|
-
* @param {string|Promise<string>} [block.token] - authorization token for bot
|
|
43
|
-
* @param {string} [block.url] - specify alternative configuration resource
|
|
155
|
+
* @param {BotConfig|Block} block
|
|
44
156
|
* @param {Plugins} plugins - custom code blocks resource
|
|
45
157
|
* @param {object} context - the building context
|
|
46
158
|
* @param {object} [context.linksTranslator] - function, that translates links globally
|
|
47
159
|
* @param {ConfigStorage} [context.configStorage] - function, that translates links globally
|
|
48
160
|
* @param {boolean} [context.allowForbiddenSnippetWords] - disable security rule
|
|
161
|
+
* @param {RouteConfig[]} [context.routeConfigs] - list of disabled routes
|
|
162
|
+
* @param {object} [context.config] - context data
|
|
163
|
+
* @param {object} [context.configuration]
|
|
49
164
|
* @param {fetch} [fetchFn] - override a request function
|
|
50
165
|
* @example
|
|
51
166
|
*
|
|
@@ -76,8 +191,10 @@ class BuildRouter extends Router {
|
|
|
76
191
|
|
|
77
192
|
this._plugins = plugins;
|
|
78
193
|
|
|
194
|
+
/** @type {BotContext} */
|
|
79
195
|
this._context = context;
|
|
80
196
|
|
|
197
|
+
/** @type {LinksMap} */
|
|
81
198
|
this._linksMap = new Map();
|
|
82
199
|
|
|
83
200
|
this._loadBotUrl = null;
|
|
@@ -94,7 +211,7 @@ class BuildRouter extends Router {
|
|
|
94
211
|
|
|
95
212
|
this.resources = defaultResourceMap();
|
|
96
213
|
|
|
97
|
-
this._loadBotAuthorization = block.token
|
|
214
|
+
this._loadBotAuthorization = 'token' in block ? block.token : null;
|
|
98
215
|
|
|
99
216
|
this._configStorage = context.configStorage;
|
|
100
217
|
|
|
@@ -112,7 +229,7 @@ class BuildRouter extends Router {
|
|
|
112
229
|
this._snapshot = null;
|
|
113
230
|
this._botId = null;
|
|
114
231
|
|
|
115
|
-
if (
|
|
232
|
+
if ('routes' in block) {
|
|
116
233
|
this._buildBot(block);
|
|
117
234
|
} else if (typeof block.url === 'string') {
|
|
118
235
|
this._loadBotUrl = block.url;
|
|
@@ -314,6 +431,12 @@ class BuildRouter extends Router {
|
|
|
314
431
|
}
|
|
315
432
|
}
|
|
316
433
|
|
|
434
|
+
/**
|
|
435
|
+
*
|
|
436
|
+
* @param {Block} block
|
|
437
|
+
* @param {number} setConfigTimestamp
|
|
438
|
+
* @param {string} lastmod
|
|
439
|
+
*/
|
|
317
440
|
_buildBot (block, setConfigTimestamp = Number.MAX_SAFE_INTEGER, lastmod = '-') {
|
|
318
441
|
try {
|
|
319
442
|
if (setConfigTimestamp !== Number.MAX_SAFE_INTEGER) {
|
|
@@ -388,7 +511,13 @@ class BuildRouter extends Router {
|
|
|
388
511
|
});
|
|
389
512
|
}
|
|
390
513
|
|
|
514
|
+
/**
|
|
515
|
+
*
|
|
516
|
+
* @param {Block} block
|
|
517
|
+
* @returns {LinksMap}
|
|
518
|
+
*/
|
|
391
519
|
_createLinksMap (block) {
|
|
520
|
+
/** @type {LinksMap} */
|
|
392
521
|
const linksMap = new Map();
|
|
393
522
|
|
|
394
523
|
block.routes
|
|
@@ -400,35 +529,140 @@ class BuildRouter extends Router {
|
|
|
400
529
|
if (prevLinksMap) {
|
|
401
530
|
for (const [from, to] of prevLinksMap.entries()) {
|
|
402
531
|
if (!linksMap.has(from)) {
|
|
403
|
-
|
|
404
|
-
(path.posix || path).join('..', to);
|
|
405
|
-
|
|
406
|
-
linksMap.set(from, (path.posix || path).join('..', to));
|
|
532
|
+
linksMap.set(from, this._joinPaths('..', to));
|
|
407
533
|
}
|
|
408
534
|
}
|
|
409
535
|
}
|
|
410
536
|
|
|
411
537
|
block.routes.forEach((route) => {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
538
|
+
const enabledNestedBlock = this._getBlockById(this._getIncludedBlockId(route));
|
|
539
|
+
if (!enabledNestedBlock) {
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
const routeConfig = this._getRouteConfig(route);
|
|
543
|
+
if (this._enabledByRouteConfig(routeConfig)) {
|
|
544
|
+
this._findEntryPointsInResolver(linksMap, enabledNestedBlock, route);
|
|
418
545
|
}
|
|
419
546
|
});
|
|
420
547
|
|
|
421
548
|
return linksMap;
|
|
422
549
|
}
|
|
423
550
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
551
|
+
/**
|
|
552
|
+
*
|
|
553
|
+
* @param {RouteConfig} routeConfig
|
|
554
|
+
*/
|
|
555
|
+
_enabledByRouteConfig (routeConfig) {
|
|
556
|
+
return !this._context.routeConfigs || (routeConfig && routeConfig.enabled);
|
|
557
|
+
}
|
|
427
558
|
|
|
428
|
-
|
|
429
|
-
|
|
559
|
+
_joinPaths (...args) {
|
|
560
|
+
return (path.posix || path).join(...args);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
_normalizeConfigPath (routePath, ctxPath = null) {
|
|
564
|
+
const joined = ctxPath
|
|
565
|
+
? this._joinPaths(ctxPath, routePath)
|
|
566
|
+
: routePath;
|
|
567
|
+
|
|
568
|
+
return joined
|
|
569
|
+
.replace(/^\/?|(_responder)?\/?$/g, '/');
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
*
|
|
574
|
+
* @param {Route} route
|
|
575
|
+
* @returns {string|null}
|
|
576
|
+
*/
|
|
577
|
+
_getIncludedBlockId (route) {
|
|
578
|
+
const includeResolver = route.resolvers.find((r) => r.type === 'botbuild.include');
|
|
579
|
+
|
|
580
|
+
return includeResolver
|
|
581
|
+
? includeResolver.params.staticBlockId
|
|
582
|
+
: null;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
*
|
|
587
|
+
* @param {string} staticBlockId
|
|
588
|
+
* @returns {Block|null}
|
|
589
|
+
*/
|
|
590
|
+
_getBlockById (staticBlockId) {
|
|
591
|
+
if (!staticBlockId) {
|
|
592
|
+
return null;
|
|
593
|
+
}
|
|
594
|
+
const nestedBlock = (this._context.blocks || [])
|
|
595
|
+
.find((b) => b.staticBlockId === staticBlockId);
|
|
596
|
+
|
|
597
|
+
if (!nestedBlock || nestedBlock.disabled) {
|
|
598
|
+
return null;
|
|
599
|
+
}
|
|
600
|
+
return nestedBlock;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
*
|
|
605
|
+
* @param {TransformedRoute} route
|
|
606
|
+
* @returns {RouteConfig}
|
|
607
|
+
*/
|
|
608
|
+
_getRouteConfig (route) {
|
|
609
|
+
const { path: ctxPath, routeConfigs } = this._context;
|
|
610
|
+
if (!routeConfigs || !route.path || route.isFallback) {
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
let rPath;
|
|
615
|
+
if (!route.isResponder) {
|
|
616
|
+
rPath = route.path;
|
|
617
|
+
} else if (route.expectedPath) {
|
|
618
|
+
rPath = route.expectedPath;
|
|
619
|
+
} else if (this._linksMap.has(route.respondsToRouteId)) {
|
|
620
|
+
rPath = this._linksMap.get(route.respondsToRouteId);
|
|
621
|
+
} else {
|
|
622
|
+
throw new Error('Illegal state');
|
|
430
623
|
}
|
|
431
624
|
|
|
625
|
+
const routePath = this._normalizeConfigPath(rPath, ctxPath);
|
|
626
|
+
return routeConfigs.find((config) => {
|
|
627
|
+
const configPath = this._normalizeConfigPath(config.path);
|
|
628
|
+
|
|
629
|
+
return configPath === routePath;
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
*
|
|
635
|
+
* @param {string} routePath
|
|
636
|
+
* @returns {object}
|
|
637
|
+
*/
|
|
638
|
+
getConfiguration (routePath) {
|
|
639
|
+
const { path: ctxPath, routeConfigs, configuration = {} } = this._context;
|
|
640
|
+
if (!routeConfigs || !routePath) {
|
|
641
|
+
return deepFreeze(configuration);
|
|
642
|
+
}
|
|
643
|
+
const normalized = this._normalizeConfigPath(routePath, ctxPath);
|
|
644
|
+
|
|
645
|
+
const configs = routeConfigs
|
|
646
|
+
.filter((config) => {
|
|
647
|
+
const configPath = this._normalizeConfigPath(config.path);
|
|
648
|
+
return normalized.startsWith(configPath);
|
|
649
|
+
})
|
|
650
|
+
.sort((a, z) => a.path.length - z.path.length);
|
|
651
|
+
|
|
652
|
+
return deepFreeze(configs.reduce((cfg, config) => ({
|
|
653
|
+
...cfg,
|
|
654
|
+
...config.configuration
|
|
655
|
+
}), configuration));
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
*
|
|
660
|
+
* @param {LinksMap} linksMap
|
|
661
|
+
* @param {Block} includedBlock
|
|
662
|
+
* @param {TransformedRoute} route
|
|
663
|
+
* @returns {void}
|
|
664
|
+
*/
|
|
665
|
+
_findEntryPointsInResolver (linksMap, includedBlock, route) {
|
|
432
666
|
let basePath = `${route.path}/`;
|
|
433
667
|
|
|
434
668
|
if (route.isFallback) {
|
|
@@ -436,14 +670,20 @@ class BuildRouter extends Router {
|
|
|
436
670
|
}
|
|
437
671
|
|
|
438
672
|
includedBlock.routes.forEach((blockRoute) => {
|
|
439
|
-
if (!blockRoute.isEntryPoint
|
|
673
|
+
if (!blockRoute.isEntryPoint) {
|
|
440
674
|
return;
|
|
441
675
|
}
|
|
442
676
|
|
|
443
|
-
linksMap.set(
|
|
677
|
+
linksMap.set(blockRoute.id, `${basePath}${blockRoute.path}`);
|
|
444
678
|
});
|
|
445
679
|
}
|
|
446
680
|
|
|
681
|
+
/**
|
|
682
|
+
*
|
|
683
|
+
* @param {TransformedRoute} route
|
|
684
|
+
* @param {boolean} nextRouteIsSameResponder
|
|
685
|
+
* @returns {Middleware[]}
|
|
686
|
+
*/
|
|
447
687
|
_buildRouteHead (route, nextRouteIsSameResponder) {
|
|
448
688
|
const resolvers = [];
|
|
449
689
|
|
|
@@ -451,10 +691,10 @@ class BuildRouter extends Router {
|
|
|
451
691
|
let aiResolver = null;
|
|
452
692
|
|
|
453
693
|
if (route.aiTags && route.aiTags.length) {
|
|
454
|
-
let
|
|
694
|
+
let aiTitle = null;
|
|
455
695
|
|
|
456
|
-
if (aiTitle) {
|
|
457
|
-
aiTitle = cachedTranslatedCompilator(aiTitle);
|
|
696
|
+
if (route.aiTitle) {
|
|
697
|
+
aiTitle = cachedTranslatedCompilator(route.aiTitle);
|
|
458
698
|
}
|
|
459
699
|
|
|
460
700
|
if (route.aiGlobal) {
|
|
@@ -486,8 +726,25 @@ class BuildRouter extends Router {
|
|
|
486
726
|
return resolvers;
|
|
487
727
|
}
|
|
488
728
|
|
|
729
|
+
/**
|
|
730
|
+
*
|
|
731
|
+
* @param {TransformedRoute[]} routes
|
|
732
|
+
*/
|
|
489
733
|
_buildRoutes (routes) {
|
|
490
734
|
routes.forEach((route, i) => {
|
|
735
|
+
const routeConfig = this._getRouteConfig(route);
|
|
736
|
+
|
|
737
|
+
if (routeConfig && !routeConfig.enabled) {
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
const includedBlockId = this._getIncludedBlockId(route);
|
|
742
|
+
const nestedBlock = this._getBlockById(includedBlockId);
|
|
743
|
+
|
|
744
|
+
if (includedBlockId && (!nestedBlock || !this._enabledByRouteConfig(routeConfig))) {
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
|
|
491
748
|
const nextRoute = routes.length > (i + 1)
|
|
492
749
|
? routes[i + 1]
|
|
493
750
|
: null;
|
|
@@ -515,6 +772,11 @@ class BuildRouter extends Router {
|
|
|
515
772
|
});
|
|
516
773
|
}
|
|
517
774
|
|
|
775
|
+
/**
|
|
776
|
+
*
|
|
777
|
+
* @param {Resolver[]} resolvers
|
|
778
|
+
* @returns {number}
|
|
779
|
+
*/
|
|
518
780
|
_lastMessageIndex (resolvers) {
|
|
519
781
|
for (let i = resolvers.length - 1; i >= 0; i--) {
|
|
520
782
|
if (resolvers[i].type === MESSAGE_RESOLVER_NAME) {
|
|
@@ -524,15 +786,30 @@ class BuildRouter extends Router {
|
|
|
524
786
|
return -1;
|
|
525
787
|
}
|
|
526
788
|
|
|
527
|
-
|
|
789
|
+
/**
|
|
790
|
+
*
|
|
791
|
+
* @param {Resolver[]} resolvers
|
|
792
|
+
* @param {TransformedRoute} route
|
|
793
|
+
* @param {*} expectedToAddResolver
|
|
794
|
+
* @returns {Middleware[]}
|
|
795
|
+
*/
|
|
796
|
+
buildResolvers (resolvers, route = DUMMY_ROUTE, expectedToAddResolver = false) {
|
|
528
797
|
const {
|
|
529
798
|
path: ctxPath, isFallback, isResponder, expectedPath, id
|
|
530
799
|
} = route;
|
|
531
800
|
|
|
801
|
+
const routeConfig = this._getRouteConfig(route);
|
|
802
|
+
const routeConfigData = routeConfig && routeConfig.configuration;
|
|
803
|
+
|
|
532
804
|
const lastMessageIndex = this._lastMessageIndex(resolvers);
|
|
533
805
|
const lastIndex = resolvers.length - 1;
|
|
534
806
|
|
|
535
807
|
return resolvers.map((resolver, i) => {
|
|
808
|
+
const configuration = deepFreeze({
|
|
809
|
+
...this._context.configuration,
|
|
810
|
+
...routeConfigData
|
|
811
|
+
});
|
|
812
|
+
|
|
536
813
|
const context = {
|
|
537
814
|
...this._context,
|
|
538
815
|
isLastIndex: lastIndex === i && !expectedToAddResolver,
|
|
@@ -543,13 +820,23 @@ class BuildRouter extends Router {
|
|
|
543
820
|
isFallback,
|
|
544
821
|
isResponder,
|
|
545
822
|
expectedPath,
|
|
546
|
-
routeId: id
|
|
823
|
+
routeId: id,
|
|
824
|
+
configuration
|
|
547
825
|
};
|
|
548
826
|
|
|
549
|
-
|
|
827
|
+
const resFn = this._resolverFactory(resolver, context);
|
|
828
|
+
// @ts-ignore
|
|
829
|
+
resFn.configuration = configuration;
|
|
830
|
+
return resFn;
|
|
550
831
|
});
|
|
551
832
|
}
|
|
552
833
|
|
|
834
|
+
/**
|
|
835
|
+
*
|
|
836
|
+
* @param {Resolver} resolver
|
|
837
|
+
* @param {BotContext} context
|
|
838
|
+
* @returns {Middleware}
|
|
839
|
+
*/
|
|
553
840
|
_resolverFactory (resolver, context) {
|
|
554
841
|
const { type } = resolver;
|
|
555
842
|
|
|
@@ -602,9 +889,9 @@ class BuildRouter extends Router {
|
|
|
602
889
|
}
|
|
603
890
|
|
|
604
891
|
/**
|
|
605
|
-
* @param {
|
|
892
|
+
* @param {Block[]} blocks - blocks list
|
|
606
893
|
* @param {Plugins} [plugins]
|
|
607
|
-
* @param {
|
|
894
|
+
* @param {BotContext} [context]
|
|
608
895
|
*/
|
|
609
896
|
BuildRouter.fromData = function fromData (blocks, plugins = new Plugins(), context = {}) {
|
|
610
897
|
|
package/src/Processor.js
CHANGED
|
@@ -14,6 +14,7 @@ const { mergeState } = require('./utils/stateVariables');
|
|
|
14
14
|
/** @typedef {import('./wingbot/CustomEntityDetectionModel').Intent} Intent */
|
|
15
15
|
/** @typedef {import('./ReducerWrapper')} ReducerWrapper */
|
|
16
16
|
/** @typedef {import('./Router')} Router */
|
|
17
|
+
/** @typedef {import('./BuildRouter')} BuildRouter */
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* @typedef {object} AutoTypingConfig
|
|
@@ -121,7 +122,7 @@ class Processor extends EventEmitter {
|
|
|
121
122
|
/**
|
|
122
123
|
* Creates an instance of Processor
|
|
123
124
|
*
|
|
124
|
-
* @param {ReducerWrapper|Router} reducer
|
|
125
|
+
* @param {ReducerWrapper|Router|BuildRouter} reducer
|
|
125
126
|
* @param {ProcessorOptions} [options] - processor options
|
|
126
127
|
*
|
|
127
128
|
* @memberOf Processor
|
|
@@ -548,6 +549,11 @@ class Processor extends EventEmitter {
|
|
|
548
549
|
res.setBookmark(aByAi);
|
|
549
550
|
}
|
|
550
551
|
|
|
552
|
+
if ('getConfiguration' in this.reducer) {
|
|
553
|
+
const configuration = this.reducer.getConfiguration(state._lastAction);
|
|
554
|
+
req.configuration = Object.freeze(configuration);
|
|
555
|
+
}
|
|
556
|
+
|
|
551
557
|
// process setState
|
|
552
558
|
const setState = req.getSetState(req.AI_SETSTATE.EXCLUDE_WITH_SET_ENTITIES);
|
|
553
559
|
await Ai.ai.processSetStateEntities(req, setState);
|
package/src/Request.js
CHANGED
package/src/Responder.js
CHANGED
|
@@ -60,6 +60,8 @@ Object.freeze(ExpectedInput);
|
|
|
60
60
|
* @prop {string} [voice]
|
|
61
61
|
*/
|
|
62
62
|
|
|
63
|
+
/** @typedef {import('./ReturnSender').UploadResult} UploadResult */
|
|
64
|
+
|
|
63
65
|
/**
|
|
64
66
|
* Instance of responder is passed as second parameter of handler (res)
|
|
65
67
|
*
|
|
@@ -537,6 +539,7 @@ class Responder {
|
|
|
537
539
|
});
|
|
538
540
|
} else {
|
|
539
541
|
this._quickReplyCollector.push({
|
|
542
|
+
// @ts-ignore
|
|
540
543
|
action: this.toAbsoluteAction(action),
|
|
541
544
|
title,
|
|
542
545
|
data,
|
|
@@ -749,6 +752,30 @@ class Responder {
|
|
|
749
752
|
return ret;
|
|
750
753
|
}
|
|
751
754
|
|
|
755
|
+
/**
|
|
756
|
+
*
|
|
757
|
+
* @param {Buffer} data
|
|
758
|
+
* @param {string} contentType
|
|
759
|
+
* @param {string} fileName
|
|
760
|
+
* @returns {Promise<UploadResult>}
|
|
761
|
+
*/
|
|
762
|
+
async upload (data, contentType, fileName) {
|
|
763
|
+
const result = await this._messageSender.upload(data, contentType, fileName);
|
|
764
|
+
|
|
765
|
+
if (!result.url) {
|
|
766
|
+
throw new Error(`Got no url on file upload ${fileName}. Probably a compatibility issue.`);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
let [type] = contentType.split('/');
|
|
770
|
+
|
|
771
|
+
if (!['image', 'video', 'audio'].includes(type)) {
|
|
772
|
+
type = 'file';
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
this._attachment(result.url, type, true);
|
|
776
|
+
return result;
|
|
777
|
+
}
|
|
778
|
+
|
|
752
779
|
/**
|
|
753
780
|
* Sends image as response. Requires appUrl option to send images from server
|
|
754
781
|
*
|
package/src/ReturnSender.js
CHANGED
|
@@ -23,6 +23,12 @@ const { FLAG_DO_NOT_LOG } = require('./flags');
|
|
|
23
23
|
* @prop {textFilter} [confidentInputFilter] - filter for confident input (@CONFIDENT)
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {object} UploadResult
|
|
28
|
+
* @prop {string} [url]
|
|
29
|
+
* @prop {string|number} [attachmentId]
|
|
30
|
+
*/
|
|
31
|
+
|
|
26
32
|
/**
|
|
27
33
|
* Text filter function
|
|
28
34
|
*
|
|
@@ -319,6 +325,17 @@ class ReturnSender {
|
|
|
319
325
|
this._visitedInteractions.push(action);
|
|
320
326
|
}
|
|
321
327
|
|
|
328
|
+
/**
|
|
329
|
+
*
|
|
330
|
+
* @param {Buffer} data
|
|
331
|
+
* @param {string} contentType
|
|
332
|
+
* @param {string} fileName
|
|
333
|
+
* @returns {Promise<UploadResult>}
|
|
334
|
+
*/
|
|
335
|
+
async upload (data, contentType, fileName) { // eslint-disable-line no-unused-vars
|
|
336
|
+
throw new Error('#upload() not supported by this channel');
|
|
337
|
+
}
|
|
338
|
+
|
|
322
339
|
send (payload) {
|
|
323
340
|
if (this._finished) {
|
|
324
341
|
throw new Error('Cannot send message after sender is finished');
|
package/src/Router.js
CHANGED
|
@@ -140,7 +140,12 @@ class Router extends ReducerWrapper {
|
|
|
140
140
|
|
|
141
141
|
const reducersArray = reducer.map((re) => {
|
|
142
142
|
const {
|
|
143
|
-
resolverPath,
|
|
143
|
+
resolverPath,
|
|
144
|
+
reduce,
|
|
145
|
+
isReducer,
|
|
146
|
+
globalIntents: gis,
|
|
147
|
+
globalIntentsMeta,
|
|
148
|
+
configuration
|
|
144
149
|
} = this._createReducer(
|
|
145
150
|
re,
|
|
146
151
|
pathContext.path
|
|
@@ -149,16 +154,19 @@ class Router extends ReducerWrapper {
|
|
|
149
154
|
Object.assign(pathContext, { path: resolverPath });
|
|
150
155
|
Object.assign(pathContext.globalIntentsMeta, globalIntentsMeta);
|
|
151
156
|
isAnyReducer = isAnyReducer || isReducer;
|
|
152
|
-
return { reduce, isReducer };
|
|
157
|
+
return { reduce, isReducer, configuration };
|
|
153
158
|
});
|
|
154
159
|
|
|
155
160
|
return {
|
|
156
|
-
reducers: reducersArray,
|
|
161
|
+
reducers: reducersArray,
|
|
162
|
+
isReducer: isAnyReducer,
|
|
163
|
+
isOr: true,
|
|
164
|
+
globalIntents
|
|
157
165
|
};
|
|
158
166
|
}
|
|
159
167
|
|
|
160
168
|
const {
|
|
161
|
-
resolverPath, reduce, isReducer, globalIntents, globalIntentsMeta
|
|
169
|
+
resolverPath, reduce, isReducer, globalIntents, globalIntentsMeta, configuration
|
|
162
170
|
} = this._createReducer(
|
|
163
171
|
reducer,
|
|
164
172
|
pathContext.path
|
|
@@ -167,7 +175,9 @@ class Router extends ReducerWrapper {
|
|
|
167
175
|
Object.assign(pathContext, { path: resolverPath });
|
|
168
176
|
Object.assign(pathContext.globalIntentsMeta, globalIntentsMeta);
|
|
169
177
|
|
|
170
|
-
return {
|
|
178
|
+
return {
|
|
179
|
+
reduce, isReducer, globalIntents, configuration
|
|
180
|
+
};
|
|
171
181
|
});
|
|
172
182
|
}
|
|
173
183
|
|
|
@@ -178,7 +188,8 @@ class Router extends ReducerWrapper {
|
|
|
178
188
|
const {
|
|
179
189
|
globalIntents = new Map(),
|
|
180
190
|
path,
|
|
181
|
-
globalIntentsMeta = {}
|
|
191
|
+
globalIntentsMeta = {},
|
|
192
|
+
configuration = null
|
|
182
193
|
} = reducer;
|
|
183
194
|
|
|
184
195
|
if (typeof reducer === 'string' || path) {
|
|
@@ -219,7 +230,7 @@ class Router extends ReducerWrapper {
|
|
|
219
230
|
}
|
|
220
231
|
|
|
221
232
|
return {
|
|
222
|
-
resolverPath, isReducer, reduce, globalIntents, globalIntentsMeta
|
|
233
|
+
resolverPath, isReducer, reduce, globalIntents, globalIntentsMeta, configuration
|
|
223
234
|
};
|
|
224
235
|
}
|
|
225
236
|
|
|
@@ -291,6 +302,9 @@ class Router extends ReducerWrapper {
|
|
|
291
302
|
|
|
292
303
|
let pathContext = `${path === '/' ? '' : path}${route.path.replace(/\/\*/, '')}`;
|
|
293
304
|
res.setPath(path, route.path);
|
|
305
|
+
if (reducer.configuration) {
|
|
306
|
+
req.configuration = reducer.configuration;
|
|
307
|
+
}
|
|
294
308
|
|
|
295
309
|
let result;
|
|
296
310
|
|
|
@@ -545,7 +545,7 @@ class Notifications extends EventEmitter {
|
|
|
545
545
|
if (!campaign.hasEditableCondition) {
|
|
546
546
|
fn = customFn(campaign.condition, `Campaign "${campaign.name}" condition`);
|
|
547
547
|
} else {
|
|
548
|
-
fn = customCondition(campaign.editableCondition, `Campaign "${campaign.name}" condition`);
|
|
548
|
+
fn = customCondition(campaign.editableCondition, req.configuration, `Campaign "${campaign.name}" condition`);
|
|
549
549
|
}
|
|
550
550
|
|
|
551
551
|
const fnRes = fn(req, res);
|
package/src/resolvers/bounce.js
CHANGED
|
@@ -22,12 +22,15 @@ const BOUNCE_RETURN = {
|
|
|
22
22
|
IF_POSSIBLE: 'ifpos'
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
+
/** @typedef {import('../BuildRouter').Route} Route */
|
|
26
|
+
|
|
27
|
+
/** @typedef {BOUNCE_RETURN} BounceReturn */
|
|
28
|
+
/** @typedef {BOUNCE_ALLOW} BounceAllow */
|
|
29
|
+
|
|
25
30
|
/**
|
|
26
31
|
*
|
|
27
32
|
*
|
|
28
|
-
* @param {
|
|
29
|
-
* @param {BOUNCE_ALLOW} [route.bounceAllowedTo]
|
|
30
|
-
* @param {BOUNCE_RETURN} [route.bounceReturn]
|
|
33
|
+
* @param {Route} route
|
|
31
34
|
* @param {boolean} nextRouteIsSameResponder
|
|
32
35
|
* @param {string} [referredRoutePath]
|
|
33
36
|
* @returns {Function|null}
|
package/src/resolvers/button.js
CHANGED
|
@@ -11,19 +11,26 @@ const {
|
|
|
11
11
|
processButtons
|
|
12
12
|
} = require('./utils');
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
14
|
+
/** @typedef {import('../BuildRouter').BotContext} BotContext */
|
|
15
|
+
/** @typedef {import('../Router').Resolver} Resolver */
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
*
|
|
19
|
+
* @param {object} params
|
|
20
|
+
* @param {BotContext} context
|
|
21
|
+
* @returns {Resolver}
|
|
22
|
+
*/
|
|
23
|
+
function button (params, context) {
|
|
24
|
+
const {
|
|
25
|
+
isLastIndex
|
|
26
|
+
} = context;
|
|
20
27
|
const {
|
|
21
28
|
buttons = [],
|
|
22
29
|
text = null
|
|
23
30
|
} = params;
|
|
24
31
|
const compiledText = cachedTranslatedCompilator(text);
|
|
25
32
|
|
|
26
|
-
const condition = getCondition(params, ''
|
|
33
|
+
const condition = getCondition(params, context, 'button');
|
|
27
34
|
|
|
28
35
|
const ret = isLastIndex ? Router.END : Router.CONTINUE;
|
|
29
36
|
|
|
@@ -37,17 +44,15 @@ function button (params, {
|
|
|
37
44
|
}
|
|
38
45
|
}
|
|
39
46
|
|
|
40
|
-
const state = stateData(req, res);
|
|
47
|
+
const state = stateData(req, res, context.configuration);
|
|
41
48
|
const tpl = res.button(compiledText(state));
|
|
42
49
|
|
|
43
50
|
processButtons(
|
|
44
51
|
buttons,
|
|
45
52
|
state,
|
|
46
53
|
tpl,
|
|
47
|
-
linksMap,
|
|
48
54
|
req.senderId,
|
|
49
|
-
|
|
50
|
-
allowForbiddenSnippetWords,
|
|
55
|
+
context,
|
|
51
56
|
req,
|
|
52
57
|
res
|
|
53
58
|
);
|
|
@@ -17,12 +17,12 @@ const {
|
|
|
17
17
|
TYPE_URL_WITH_EXT
|
|
18
18
|
} = require('./utils');
|
|
19
19
|
|
|
20
|
-
function carousel (params, {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
20
|
+
function carousel (params, context) {
|
|
21
|
+
const {
|
|
22
|
+
isLastIndex,
|
|
23
|
+
linksMap,
|
|
24
|
+
linksTranslator = (a, b, c) => c
|
|
25
|
+
} = context;
|
|
26
26
|
const {
|
|
27
27
|
items = [],
|
|
28
28
|
shareable = false,
|
|
@@ -36,7 +36,7 @@ function carousel (params, {
|
|
|
36
36
|
return ret;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
const state = stateData(req, res);
|
|
39
|
+
const state = stateData(req, res, context.configuration);
|
|
40
40
|
const isSquare = imageAspect === ASPECT_SQUARE;
|
|
41
41
|
const tpl = res.genericTemplate(shareable, isSquare);
|
|
42
42
|
|
|
@@ -99,10 +99,8 @@ function carousel (params, {
|
|
|
99
99
|
buttons,
|
|
100
100
|
state,
|
|
101
101
|
elem,
|
|
102
|
-
linksMap,
|
|
103
102
|
senderId,
|
|
104
|
-
|
|
105
|
-
allowForbiddenSnippetWords,
|
|
103
|
+
context,
|
|
106
104
|
req,
|
|
107
105
|
res
|
|
108
106
|
);
|
package/src/resolvers/media.js
CHANGED
|
@@ -7,11 +7,17 @@ const Router = require('../Router');
|
|
|
7
7
|
const getCondition = require('../utils/getCondition');
|
|
8
8
|
const { stateData, cachedTranslatedCompilator } = require('./utils');
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
/** @typedef {import('../BuildRouter').BotContext} BotContext */
|
|
11
|
+
/** @typedef {import('../Router').Resolver} Resolver */
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
*
|
|
15
|
+
* @param {object} params
|
|
16
|
+
* @param {BotContext} context
|
|
17
|
+
* @returns {Resolver}
|
|
18
|
+
*/
|
|
19
|
+
function media (params, context = {}) {
|
|
20
|
+
const { isLastIndex } = context;
|
|
15
21
|
const { type, url } = params;
|
|
16
22
|
|
|
17
23
|
const urlString = url || '';
|
|
@@ -24,14 +30,14 @@ function media (
|
|
|
24
30
|
throw new Error(`Unsupported media type: ${type}`);
|
|
25
31
|
}
|
|
26
32
|
|
|
27
|
-
const condition = getCondition(params, 'Media condition');
|
|
33
|
+
const condition = getCondition(params, context, 'Media condition');
|
|
28
34
|
|
|
29
35
|
return (req, res) => {
|
|
30
36
|
if (condition && !condition(req, res)) {
|
|
31
37
|
return ret;
|
|
32
38
|
}
|
|
33
39
|
|
|
34
|
-
const data = stateData(req, res);
|
|
40
|
+
const data = stateData(req, res, context.configuration);
|
|
35
41
|
const sendUrl = urlTemplate(data);
|
|
36
42
|
|
|
37
43
|
res[type](sendUrl, true);
|
package/src/resolvers/message.js
CHANGED
|
@@ -9,20 +9,27 @@ const Request = require('../Request');
|
|
|
9
9
|
const Router = require('../Router');
|
|
10
10
|
const getCondition = require('../utils/getCondition');
|
|
11
11
|
const {
|
|
12
|
-
stateData,
|
|
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
|
|
16
20
|
} = require('../features');
|
|
17
21
|
|
|
22
|
+
/** @typedef {import('../Responder').VoiceControl} VoiceControl */
|
|
23
|
+
/** @typedef {import('./utils').Translations} Translations */
|
|
24
|
+
/** @typedef {import('./utils').TextObject} TextObject */
|
|
18
25
|
/**
|
|
19
26
|
* Returns voice control props from params
|
|
20
27
|
*
|
|
21
28
|
* @param {any} params
|
|
22
29
|
* @param {string} lang
|
|
23
|
-
* @returns {null |
|
|
30
|
+
* @returns {null | VoiceControl}
|
|
24
31
|
*/
|
|
25
|
-
function
|
|
32
|
+
function getVoiceControlFromParams (params, lang = null) {
|
|
26
33
|
const voiceControl = {};
|
|
27
34
|
|
|
28
35
|
const voiceControlProps = ['speed', 'pitch', 'volume', 'voice', 'style', 'language'];
|
|
@@ -37,10 +44,10 @@ function getVoiceControl (params, lang = null) {
|
|
|
37
44
|
return Object.keys(voiceControl).length > 0 ? voiceControl : null;
|
|
38
45
|
}
|
|
39
46
|
|
|
40
|
-
function parseReplies (replies, linksMap,
|
|
47
|
+
function parseReplies (replies, linksMap, context) {
|
|
41
48
|
return replies.map((reply) => {
|
|
42
49
|
|
|
43
|
-
const condition = getCondition(reply, 'Quick reply condition'
|
|
50
|
+
const condition = getCondition(reply, context, 'Quick reply condition');
|
|
44
51
|
|
|
45
52
|
if (reply.isLocation) {
|
|
46
53
|
return {
|
|
@@ -71,11 +78,15 @@ function parseReplies (replies, linksMap, allowForbiddenSnippetWords) {
|
|
|
71
78
|
}
|
|
72
79
|
}
|
|
73
80
|
|
|
81
|
+
if (!action) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
74
85
|
const ret = {
|
|
75
86
|
action,
|
|
76
87
|
condition,
|
|
77
88
|
title: reply.title
|
|
78
|
-
?
|
|
89
|
+
? cachedTranslatedCompilator(reply.title)
|
|
79
90
|
: null,
|
|
80
91
|
data: {}
|
|
81
92
|
};
|
|
@@ -97,14 +108,15 @@ function parseReplies (replies, linksMap, allowForbiddenSnippetWords) {
|
|
|
97
108
|
}
|
|
98
109
|
|
|
99
110
|
return ret;
|
|
100
|
-
})
|
|
111
|
+
})
|
|
112
|
+
.filter((r) => r !== null);
|
|
101
113
|
}
|
|
102
114
|
|
|
103
115
|
/**
|
|
104
|
-
* @param {
|
|
116
|
+
* @param {Translations} text
|
|
105
117
|
* @param {string[]} features
|
|
106
118
|
* @param {string} lang
|
|
107
|
-
* @returns {{translations:
|
|
119
|
+
* @returns {{translations:TextObject[],ssmlAlternatives:string[] | null}}
|
|
108
120
|
*/
|
|
109
121
|
function findSupportedMessages (text, features, lang = null) {
|
|
110
122
|
let translations = getLanguageTextObjects(text, lang);
|
|
@@ -147,11 +159,20 @@ function findSupportedMessages (text, features, lang = null) {
|
|
|
147
159
|
};
|
|
148
160
|
}
|
|
149
161
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
isLastIndex, isLastMessage, linksMap, allowForbiddenSnippetWords
|
|
153
|
-
} = {}) {
|
|
162
|
+
/** @typedef {import('../BuildRouter').BotContext} BotContext */
|
|
163
|
+
/** @typedef {import('../Router').Resolver} Resolver */
|
|
154
164
|
|
|
165
|
+
/**
|
|
166
|
+
*
|
|
167
|
+
* @param {object} params
|
|
168
|
+
* @param {BotContext} context
|
|
169
|
+
* @returns {Resolver}
|
|
170
|
+
*/
|
|
171
|
+
function message (params, context = {}) {
|
|
172
|
+
const {
|
|
173
|
+
// @ts-ignore
|
|
174
|
+
isLastIndex, isLastMessage, linksMap, configuration
|
|
175
|
+
} = context;
|
|
155
176
|
if (typeof params.text !== 'string' && !Array.isArray(params.text)) {
|
|
156
177
|
throw new Error('Message should be a text!');
|
|
157
178
|
}
|
|
@@ -161,11 +182,11 @@ function message (params, {
|
|
|
161
182
|
if (params.replies && !Array.isArray(params.replies)) {
|
|
162
183
|
throw new Error('Replies should be an array');
|
|
163
184
|
} else if (params.replies && params.replies.length > 0) {
|
|
164
|
-
quickReplies = parseReplies(params.replies, linksMap,
|
|
185
|
+
quickReplies = parseReplies(params.replies, linksMap, context);
|
|
165
186
|
}
|
|
166
187
|
|
|
167
188
|
// compile condition
|
|
168
|
-
const condition = getCondition(params, 'Message condition'
|
|
189
|
+
const condition = getCondition(params, context, 'Message condition');
|
|
169
190
|
|
|
170
191
|
const ret = isLastIndex ? Router.END : Router.CONTINUE;
|
|
171
192
|
|
|
@@ -177,10 +198,9 @@ function message (params, {
|
|
|
177
198
|
if (condition && !condition(req, res)) {
|
|
178
199
|
return ret;
|
|
179
200
|
}
|
|
180
|
-
const data = stateData(req, res);
|
|
201
|
+
const data = stateData(req, res, configuration);
|
|
181
202
|
|
|
182
203
|
// filter supported messages
|
|
183
|
-
/** @type {{translations:import('./utils').TextObject[],ssmlAlternatives:string[]}} */
|
|
184
204
|
const supportedText = findSupportedMessages(
|
|
185
205
|
params.text,
|
|
186
206
|
req.features,
|
|
@@ -231,7 +251,7 @@ function message (params, {
|
|
|
231
251
|
}
|
|
232
252
|
|
|
233
253
|
// generate voice control
|
|
234
|
-
let voiceControl =
|
|
254
|
+
let voiceControl = getVoiceControlFromParams(params, data.lang);
|
|
235
255
|
if (supportedText.ssmlAlternatives) {
|
|
236
256
|
// find SSML alternative
|
|
237
257
|
const ssmlAlternativeTemplate = randomizedCompiler([{
|
|
@@ -7,7 +7,17 @@ const Router = require('../Router');
|
|
|
7
7
|
const getCondition = require('../utils/getCondition');
|
|
8
8
|
const { shouldExecuteResolver } = require('./resolverTags');
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
/** @typedef {import('../BuildRouter').BotContext} BotContext */
|
|
11
|
+
/** @typedef {import('../Router').Resolver} Resolver */
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
*
|
|
15
|
+
* @param {object} params
|
|
16
|
+
* @param {BotContext} context
|
|
17
|
+
* @returns {Resolver}
|
|
18
|
+
*/
|
|
19
|
+
function postback (params, context) {
|
|
20
|
+
const { linksMap, isLastIndex } = context;
|
|
11
21
|
const {
|
|
12
22
|
routeId,
|
|
13
23
|
postBack: staticAction
|
|
@@ -22,12 +32,12 @@ function postback (params, { linksMap, isLastIndex, allowForbiddenSnippetWords }
|
|
|
22
32
|
}
|
|
23
33
|
}
|
|
24
34
|
|
|
25
|
-
const condition = getCondition(params, ''
|
|
35
|
+
const condition = getCondition(params, context, 'postback');
|
|
26
36
|
|
|
27
37
|
const ret = isLastIndex ? Router.END : Router.CONTINUE;
|
|
28
38
|
|
|
29
39
|
return (req, res, postBack) => {
|
|
30
|
-
if (!shouldExecuteResolver(req, params)) {
|
|
40
|
+
if (!action || !shouldExecuteResolver(req, params)) {
|
|
31
41
|
return ret;
|
|
32
42
|
}
|
|
33
43
|
|
|
@@ -8,11 +8,20 @@ const Ai = require('../Ai');
|
|
|
8
8
|
const { getSetState } = require('../utils/getUpdate');
|
|
9
9
|
const getCondition = require('../utils/getCondition');
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
/** @typedef {import('../BuildRouter').BotContext} BotContext */
|
|
12
|
+
/** @typedef {import('../Router').Resolver} Resolver */
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
*
|
|
16
|
+
* @param {object} params
|
|
17
|
+
* @param {BotContext} context
|
|
18
|
+
* @returns {Resolver}
|
|
19
|
+
*/
|
|
20
|
+
function setState (params, context) {
|
|
12
21
|
|
|
13
|
-
const condition = getCondition(params, ''
|
|
22
|
+
const condition = getCondition(params, context, 'setState');
|
|
14
23
|
|
|
15
|
-
const ret = isLastIndex ? Router.END : Router.CONTINUE;
|
|
24
|
+
const ret = context.isLastIndex ? Router.END : Router.CONTINUE;
|
|
16
25
|
|
|
17
26
|
return async (req, res) => {
|
|
18
27
|
if (condition !== null) {
|
|
@@ -6,15 +6,24 @@
|
|
|
6
6
|
const Router = require('../Router');
|
|
7
7
|
const getCondition = require('../utils/getCondition');
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
/** @typedef {import('../BuildRouter').BotContext} BotContext */
|
|
10
|
+
/** @typedef {import('../Router').Resolver} Resolver */
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
*
|
|
14
|
+
* @param {object} params
|
|
15
|
+
* @param {BotContext} context
|
|
16
|
+
* @returns {Resolver}
|
|
17
|
+
*/
|
|
18
|
+
function subscribtions (params, context) {
|
|
10
19
|
const {
|
|
11
20
|
tags = [],
|
|
12
21
|
unsetTag = false
|
|
13
22
|
} = params;
|
|
14
23
|
|
|
15
|
-
const condition = getCondition(params, ''
|
|
24
|
+
const condition = getCondition(params, context, 'subscribtions');
|
|
16
25
|
|
|
17
|
-
const ret = isLastIndex ? Router.END : Router.CONTINUE;
|
|
26
|
+
const ret = context.isLastIndex ? Router.END : Router.CONTINUE;
|
|
18
27
|
const method = unsetTag ? 'unsubscribe' : 'subscribe';
|
|
19
28
|
|
|
20
29
|
return async (req, res) => {
|
|
@@ -35,6 +44,7 @@ function subscribtions (params, { isLastIndex, allowForbiddenSnippetWords }) {
|
|
|
35
44
|
}
|
|
36
45
|
|
|
37
46
|
if (tags.length === 0 && unsetTag) {
|
|
47
|
+
// @ts-ignore
|
|
38
48
|
res.unsubscribe();
|
|
39
49
|
}
|
|
40
50
|
|
package/src/resolvers/utils.js
CHANGED
|
@@ -205,18 +205,18 @@ function getText (text, state) {
|
|
|
205
205
|
// eslint-disable-next-line no-unused-vars
|
|
206
206
|
const DEFAULT_LINK_TRANSLATOR = (senderId, defaultText, urlText, isExtUrl, reqState) => urlText;
|
|
207
207
|
|
|
208
|
+
/** @typedef {import('../BuildRouter').BotContext} BotContext */
|
|
209
|
+
|
|
208
210
|
function processButtons (
|
|
209
211
|
buttons,
|
|
210
212
|
state,
|
|
211
213
|
elem,
|
|
212
|
-
linksMap,
|
|
213
214
|
senderId,
|
|
214
|
-
|
|
215
|
-
allowForbiddenSnippetWords,
|
|
215
|
+
context,
|
|
216
216
|
req,
|
|
217
217
|
res
|
|
218
218
|
) {
|
|
219
|
-
const translateLinks = linksTranslator || DEFAULT_LINK_TRANSLATOR;
|
|
219
|
+
const translateLinks = context.linksTranslator || DEFAULT_LINK_TRANSLATOR;
|
|
220
220
|
|
|
221
221
|
buttons.forEach(({
|
|
222
222
|
title: btnTitle,
|
|
@@ -230,7 +230,7 @@ function processButtons (
|
|
|
230
230
|
if (hasCondition) {
|
|
231
231
|
const condition = getCondition({
|
|
232
232
|
hasCondition, conditionFn, hasEditableCondition, editableCondition
|
|
233
|
-
}, 'Quick reply condition'
|
|
233
|
+
}, context, 'Quick reply condition');
|
|
234
234
|
|
|
235
235
|
if (!condition(req, res)) {
|
|
236
236
|
return;
|
|
@@ -259,7 +259,7 @@ function processButtons (
|
|
|
259
259
|
break;
|
|
260
260
|
}
|
|
261
261
|
case TYPE_POSTBACK: {
|
|
262
|
-
let postbackAction = linksMap.get(targetRouteId) || action;
|
|
262
|
+
let postbackAction = context.linksMap.get(targetRouteId) || action;
|
|
263
263
|
|
|
264
264
|
if (postbackAction === '/') {
|
|
265
265
|
postbackAction = './';
|
|
@@ -148,15 +148,16 @@ const compare = (variable, operator, value = undefined) => {
|
|
|
148
148
|
/**
|
|
149
149
|
*
|
|
150
150
|
* @param {{value:string, operator:string, variable:string}[][]} condition
|
|
151
|
+
* @param {object} configuration
|
|
151
152
|
* @param {string} description
|
|
152
153
|
*/
|
|
153
|
-
function customCondition (condition, description = '') {
|
|
154
|
+
function customCondition (condition, configuration, description = '') {
|
|
154
155
|
if (typeof condition !== 'object' || !Array.isArray(condition)) {
|
|
155
156
|
throw new Error(`Invalid condition (${description}) type`);
|
|
156
157
|
}
|
|
157
158
|
|
|
158
159
|
const resolver = (req, res) => condition.some((condList) => condList.every((cond) => {
|
|
159
|
-
const data = stateData(req, res);
|
|
160
|
+
const data = stateData(req, res, configuration);
|
|
160
161
|
const variableValue = getValue(cond.variable, data);
|
|
161
162
|
const isRegExp = [
|
|
162
163
|
ConditionOperators['matches regexp'],
|
|
@@ -6,7 +6,20 @@
|
|
|
6
6
|
const customCondition = require('./customCondition');
|
|
7
7
|
const customFn = require('./customFn');
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
/** @typedef {import('../BuildRouter').BotContext} BotContext */
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
* @param {object} params
|
|
14
|
+
* @param {BotContext} context
|
|
15
|
+
* @param {string} description
|
|
16
|
+
* @returns {Function}
|
|
17
|
+
*/
|
|
18
|
+
module.exports = function getCondition (params, context, description = '') {
|
|
19
|
+
const {
|
|
20
|
+
allowForbiddenSnippetWords = false,
|
|
21
|
+
configuration
|
|
22
|
+
} = context;
|
|
10
23
|
const {
|
|
11
24
|
hasCondition = false,
|
|
12
25
|
conditionFn = '() => true',
|
|
@@ -18,7 +31,7 @@ module.exports = (params, description = '', allowForbiddenSnippetWords = false)
|
|
|
18
31
|
|
|
19
32
|
if (hasCondition) {
|
|
20
33
|
if (hasEditableCondition) {
|
|
21
|
-
condition = customCondition(editableCondition);
|
|
34
|
+
condition = customCondition(editableCondition, configuration, description);
|
|
22
35
|
} else {
|
|
23
36
|
condition = customFn(conditionFn, description, allowForbiddenSnippetWords);
|
|
24
37
|
}
|
package/src/utils/getUpdate.js
CHANGED
|
@@ -88,7 +88,7 @@ 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) {
|
|
91
|
+
function getSetState (setState, req, res = null, useState = null, configuration = null) {
|
|
92
92
|
if (!setState) {
|
|
93
93
|
return {};
|
|
94
94
|
}
|
|
@@ -179,7 +179,7 @@ function getSetState (setState, req, res = null, useState = null) {
|
|
|
179
179
|
values = [];
|
|
180
180
|
} else {
|
|
181
181
|
const useValue = typeof value === 'string'
|
|
182
|
-
? handlebars.compile(value)(stateData(req, res))
|
|
182
|
+
? handlebars.compile(value)(stateData(req, res, configuration))
|
|
183
183
|
.split(/(?<!\\),/g)
|
|
184
184
|
.map((v) => v.replace(/\\,/g, ',').trim())
|
|
185
185
|
: value;
|
|
@@ -205,7 +205,7 @@ function getSetState (setState, req, res = null, useState = null) {
|
|
|
205
205
|
set = val;
|
|
206
206
|
}
|
|
207
207
|
} else if (typeof val === 'string') {
|
|
208
|
-
set = handlebars.compile(val)(stateData(req, res));
|
|
208
|
+
set = handlebars.compile(val)(stateData(req, res, configuration));
|
|
209
209
|
} else if (val === null
|
|
210
210
|
|| SCALAR_TYPES.includes(typeof val)) {
|
|
211
211
|
set = val;
|
package/src/utils/stateData.js
CHANGED
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
*/
|
|
4
4
|
'use strict';
|
|
5
5
|
|
|
6
|
-
module.exports = function stateData (req, res = null) {
|
|
6
|
+
module.exports = function stateData (req, res = null, configuration = null) {
|
|
7
|
+
const c = configuration || req.configuration;
|
|
7
8
|
return {
|
|
8
9
|
...req.state,
|
|
9
10
|
...(res ? res.newState : {}),
|
|
10
11
|
...req.actionData(),
|
|
11
|
-
...(res ? res.data : {})
|
|
12
|
+
...(res ? res.data : {}),
|
|
13
|
+
c,
|
|
14
|
+
configuration: c
|
|
12
15
|
};
|
|
13
16
|
};
|