wingbot 3.30.1 → 3.31.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/BotApp.js +16 -2
- package/src/BotAppSender.js +34 -0
- package/src/Responder.js +27 -0
- package/src/ReturnSender.js +41 -11
- package/src/resolvers/message.js +14 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wingbot",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.31.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/BotApp.js
CHANGED
|
@@ -23,6 +23,7 @@ const DEFAULT_API_URL = 'https://orchestrator-api.wingbot.ai';
|
|
|
23
23
|
|
|
24
24
|
/** @typedef {import('./CallbackAuditLog')} AuditLog */
|
|
25
25
|
/** @typedef {import('./BotAppSender').TlsOptions} TlsOptions */
|
|
26
|
+
/** @typedef {import('./ReturnSender').ReturnSenderOptions} ReturnSenderOptions */
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* @typedef {object} BotAppOptions
|
|
@@ -35,7 +36,7 @@ const DEFAULT_API_URL = 'https://orchestrator-api.wingbot.ai';
|
|
|
35
36
|
* @prop {AuditLog} [auditLog]
|
|
36
37
|
* @prop {TlsOptions} [tls]
|
|
37
38
|
*
|
|
38
|
-
* @typedef {ProcessorOptions & BotAppOptions} Options
|
|
39
|
+
* @typedef {ProcessorOptions & BotAppOptions & ReturnSenderOptions} Options
|
|
39
40
|
*/
|
|
40
41
|
|
|
41
42
|
/**
|
|
@@ -66,9 +67,20 @@ class BotApp {
|
|
|
66
67
|
preferSynchronousResponse = false,
|
|
67
68
|
auditLog = null,
|
|
68
69
|
tls = null,
|
|
70
|
+
|
|
71
|
+
textFilter,
|
|
72
|
+
logStandbyEvents,
|
|
73
|
+
confidentInputFilter,
|
|
74
|
+
|
|
69
75
|
...processorOptions
|
|
70
76
|
} = options;
|
|
71
77
|
|
|
78
|
+
this._returnSenderOptions = {
|
|
79
|
+
textFilter,
|
|
80
|
+
logStandbyEvents,
|
|
81
|
+
confidentInputFilter
|
|
82
|
+
};
|
|
83
|
+
|
|
72
84
|
this._secret = Promise.resolve(secret);
|
|
73
85
|
this._fetch = fetch; // mock
|
|
74
86
|
this._appId = appId;
|
|
@@ -200,7 +212,8 @@ class BotApp {
|
|
|
200
212
|
const { mid = null } = message;
|
|
201
213
|
|
|
202
214
|
if (sync || this._preferSynchronousResponse) {
|
|
203
|
-
const
|
|
215
|
+
const options = this._returnSenderOptions;
|
|
216
|
+
const sender = new ReturnSender(options, senderId, message, this._senderLogger);
|
|
204
217
|
sender.propagatesWaitEvent = true;
|
|
205
218
|
const res = await this._processor.processMessage(message, pageId, sender, { appId });
|
|
206
219
|
await this._processSenderResponses(sender, senderId, pageId, headers);
|
|
@@ -226,6 +239,7 @@ class BotApp {
|
|
|
226
239
|
}
|
|
227
240
|
|
|
228
241
|
const options = {
|
|
242
|
+
...this._returnSenderOptions,
|
|
229
243
|
apiUrl: this._apiUrl,
|
|
230
244
|
pageId,
|
|
231
245
|
appId,
|
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/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
|
*
|
|
@@ -190,9 +196,22 @@ class ReturnSender {
|
|
|
190
196
|
? this.confidentInputFilter
|
|
191
197
|
: this.textFilter;
|
|
192
198
|
|
|
199
|
+
let { message } = payload;
|
|
200
|
+
|
|
201
|
+
if (message && message.voice && message.voice.ssml) {
|
|
202
|
+
message = {
|
|
203
|
+
...message,
|
|
204
|
+
text: message.text ? message.text : message.voice.ssml,
|
|
205
|
+
voice: {
|
|
206
|
+
...message.voice,
|
|
207
|
+
ssml: filter(message.voice.ssml)
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
193
212
|
// text message
|
|
194
|
-
if (
|
|
195
|
-
let { text } =
|
|
213
|
+
if (message && message.text) {
|
|
214
|
+
let { text } = message;
|
|
196
215
|
|
|
197
216
|
if (req && req._anonymizedText) {
|
|
198
217
|
text = req._anonymizedText;
|
|
@@ -201,27 +220,27 @@ class ReturnSender {
|
|
|
201
220
|
return {
|
|
202
221
|
...payload,
|
|
203
222
|
message: {
|
|
204
|
-
...
|
|
223
|
+
...message,
|
|
205
224
|
text: filter(text)
|
|
206
225
|
}
|
|
207
226
|
};
|
|
208
227
|
}
|
|
209
228
|
|
|
210
229
|
// button message
|
|
211
|
-
if (
|
|
212
|
-
&&
|
|
213
|
-
&&
|
|
214
|
-
&&
|
|
230
|
+
if (message && message.attachment
|
|
231
|
+
&& message.attachment.type === 'template'
|
|
232
|
+
&& message.attachment.payload
|
|
233
|
+
&& message.attachment.payload.text) {
|
|
215
234
|
|
|
216
235
|
return {
|
|
217
236
|
...payload,
|
|
218
237
|
message: {
|
|
219
|
-
...
|
|
238
|
+
...message,
|
|
220
239
|
attachment: {
|
|
221
|
-
...
|
|
240
|
+
...message.attachment,
|
|
222
241
|
payload: {
|
|
223
|
-
...
|
|
224
|
-
text: filter(
|
|
242
|
+
...message.attachment.payload,
|
|
243
|
+
text: filter(message.attachment.payload.text)
|
|
225
244
|
}
|
|
226
245
|
}
|
|
227
246
|
}
|
|
@@ -319,6 +338,17 @@ class ReturnSender {
|
|
|
319
338
|
this._visitedInteractions.push(action);
|
|
320
339
|
}
|
|
321
340
|
|
|
341
|
+
/**
|
|
342
|
+
*
|
|
343
|
+
* @param {Buffer} data
|
|
344
|
+
* @param {string} contentType
|
|
345
|
+
* @param {string} fileName
|
|
346
|
+
* @returns {Promise<UploadResult>}
|
|
347
|
+
*/
|
|
348
|
+
async upload (data, contentType, fileName) { // eslint-disable-line no-unused-vars
|
|
349
|
+
throw new Error('#upload() not supported by this channel');
|
|
350
|
+
}
|
|
351
|
+
|
|
322
352
|
send (payload) {
|
|
323
353
|
if (this._finished) {
|
|
324
354
|
throw new Error('Cannot send message after sender is finished');
|
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'];
|
|
@@ -75,7 +82,7 @@ function parseReplies (replies, linksMap, allowForbiddenSnippetWords) {
|
|
|
75
82
|
action,
|
|
76
83
|
condition,
|
|
77
84
|
title: reply.title
|
|
78
|
-
?
|
|
85
|
+
? cachedTranslatedCompilator(reply.title)
|
|
79
86
|
: null,
|
|
80
87
|
data: {}
|
|
81
88
|
};
|
|
@@ -101,10 +108,10 @@ function parseReplies (replies, linksMap, allowForbiddenSnippetWords) {
|
|
|
101
108
|
}
|
|
102
109
|
|
|
103
110
|
/**
|
|
104
|
-
* @param {
|
|
111
|
+
* @param {Translations} text
|
|
105
112
|
* @param {string[]} features
|
|
106
113
|
* @param {string} lang
|
|
107
|
-
* @returns {{translations:
|
|
114
|
+
* @returns {{translations:TextObject[],ssmlAlternatives:string[] | null}}
|
|
108
115
|
*/
|
|
109
116
|
function findSupportedMessages (text, features, lang = null) {
|
|
110
117
|
let translations = getLanguageTextObjects(text, lang);
|
|
@@ -180,7 +187,6 @@ function message (params, {
|
|
|
180
187
|
const data = stateData(req, res);
|
|
181
188
|
|
|
182
189
|
// filter supported messages
|
|
183
|
-
/** @type {{translations:import('./utils').TextObject[],ssmlAlternatives:string[]}} */
|
|
184
190
|
const supportedText = findSupportedMessages(
|
|
185
191
|
params.text,
|
|
186
192
|
req.features,
|
|
@@ -231,7 +237,7 @@ function message (params, {
|
|
|
231
237
|
}
|
|
232
238
|
|
|
233
239
|
// generate voice control
|
|
234
|
-
let voiceControl =
|
|
240
|
+
let voiceControl = getVoiceControlFromParams(params, data.lang);
|
|
235
241
|
if (supportedText.ssmlAlternatives) {
|
|
236
242
|
// find SSML alternative
|
|
237
243
|
const ssmlAlternativeTemplate = randomizedCompiler([{
|