@tiledesk/tiledesk-voice-twilio-connector 0.1.28 → 0.2.0-rc3

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.
Files changed (50) hide show
  1. package/LICENSE +179 -0
  2. package/README.md +44 -0
  3. package/index.js +7 -1562
  4. package/package.json +23 -22
  5. package/src/app.js +146 -0
  6. package/src/config/index.js +32 -0
  7. package/src/controllers/VoiceController.js +488 -0
  8. package/src/controllers/VoiceController.original.js +811 -0
  9. package/src/middlewares/httpLogger.js +31 -0
  10. package/src/models/KeyValueStore.js +78 -0
  11. package/src/routes/manageApp.js +298 -0
  12. package/src/routes/voice.js +22 -0
  13. package/src/services/AiService.js +219 -0
  14. package/src/services/AiService.sdk.js +367 -0
  15. package/src/services/IntegrationService.js +74 -0
  16. package/src/services/MessageService.js +133 -0
  17. package/src/services/README_SDK.md +107 -0
  18. package/src/services/SessionService.js +143 -0
  19. package/src/services/SpeechService.js +134 -0
  20. package/src/services/TiledeskMessageBuilder.js +135 -0
  21. package/src/services/TwilioService.js +122 -0
  22. package/src/services/UploadService.js +78 -0
  23. package/src/services/channels/TiledeskChannel.js +269 -0
  24. package/{tiledesk → src/services/channels}/VoiceChannel.js +17 -56
  25. package/src/services/clients/TiledeskSubscriptionClient.js +78 -0
  26. package/src/services/index.js +45 -0
  27. package/src/services/translators/TiledeskTwilioTranslator.js +509 -0
  28. package/{tiledesk/TiledeskTwilioTranslator.js → src/services/translators/TiledeskTwilioTranslator.original.js} +119 -212
  29. package/src/utils/fileUtils.js +24 -0
  30. package/src/utils/logger.js +32 -0
  31. package/{tiledesk → src/utils}/utils-message.js +6 -21
  32. package/logs/app.log +0 -3082
  33. package/routes/manageApp.js +0 -419
  34. package/tiledesk/KVBaseMongo.js +0 -101
  35. package/tiledesk/TiledeskChannel.js +0 -363
  36. package/tiledesk/TiledeskSubscriptionClient.js +0 -135
  37. package/tiledesk/fileUtils.js +0 -55
  38. package/tiledesk/services/AiService.js +0 -230
  39. package/tiledesk/services/IntegrationService.js +0 -81
  40. package/tiledesk/services/UploadService.js +0 -88
  41. /package/{winston.js → src/config/logger.js} +0 -0
  42. /package/{tiledesk → src}/services/voiceEventEmitter.js +0 -0
  43. /package/{template → src/template}/configure.html +0 -0
  44. /package/{template → src/template}/css/configure.css +0 -0
  45. /package/{template → src/template}/css/error.css +0 -0
  46. /package/{template → src/template}/css/style.css +0 -0
  47. /package/{template → src/template}/error.html +0 -0
  48. /package/{tiledesk → src/utils}/constants.js +0 -0
  49. /package/{tiledesk → src/utils}/errors.js +0 -0
  50. /package/{tiledesk → src/utils}/utils.js +0 -0
@@ -1,26 +1,26 @@
1
1
  const { v4: uuidv4 } = require("uuid");
2
- const winston = require("../winston");
2
+ const winston = require("../../utils/logger");
3
3
  const xmlbuilder = require("xmlbuilder");
4
4
  const querystring = require("querystring");
5
5
 
6
- const utils = require("./utils.js");
7
- const utils_message = require("./utils-message.js");
8
- const MENU_CHOICE = require("./constants.js").MENU_CHOICE;
9
- const WAIT_MESSAGE = require("./constants.js").WAIT_MESSAGE;
10
- const TEXT_MESSAGE = require("./constants.js").TEXT_MESSAGE;
11
- const SETTING_MESSAGE = require('./constants').SETTING_MESSAGE
12
- const CHANNEL_NAME = require('./constants').CHANNEL_NAME
13
- const VOICE_PROVIDER = require('./constants').VOICE_PROVIDER;
14
- const OPENAI_SETTINGS = require('./constants').OPENAI_SETTINGS;
15
- const ELEVENLABS_SETTINGS = require('./constants').ELEVENLABS_SETTINGS;
6
+ const utils = require("../../utils/utils.js");
7
+ const utils_message = require("../../utils/utils-message.js");
8
+ const MENU_CHOICE = require("../../utils/constants.js").MENU_CHOICE;
9
+ const WAIT_MESSAGE = require("../../utils/constants.js").WAIT_MESSAGE;
10
+ const TEXT_MESSAGE = require("../../utils/constants.js").TEXT_MESSAGE;
11
+ const SETTING_MESSAGE = require('../../utils/constants').SETTING_MESSAGE
12
+ const CHANNEL_NAME = require('../../utils/constants').CHANNEL_NAME
13
+ const VOICE_PROVIDER = require('../../utils/constants').VOICE_PROVIDER;
14
+ const OPENAI_SETTINGS = require('../../utils/constants').OPENAI_SETTINGS;
15
+ const ELEVENLABS_SETTINGS = require('../../utils/constants').ELEVENLABS_SETTINGS;
16
16
 
17
- const TYPE_ACTION_VXML = require('./constants').TYPE_ACTION_VXML
18
- const TYPE_MESSAGE = require('./constants').TYPE_MESSAGE
19
- const INFO_MESSAGE_TYPE = require('./constants').INFO_MESSAGE_TYPE
17
+ const TYPE_ACTION_VXML = require('../../utils/constants').TYPE_ACTION_VXML
18
+ const TYPE_MESSAGE = require('../../utils/constants').TYPE_MESSAGE
19
+ const INFO_MESSAGE_TYPE = require('../../utils/constants').INFO_MESSAGE_TYPE
20
20
 
21
- const voiceEventEmitter = require('./services/voiceEventEmitter');
21
+ const voiceEventEmitter = require('../voiceEventEmitter');
22
22
 
23
- const { SttError } = require('./errors');
23
+ const { SttError } = require('../../utils/errors');
24
24
 
25
25
  class TiledeskTwilioTranslator {
26
26
  /**
@@ -52,7 +52,7 @@ class TiledeskTwilioTranslator {
52
52
  this.uploadService = config.uploadService
53
53
  }
54
54
 
55
-
55
+ this.lastCallSidVerb = {};
56
56
  this.log = false;
57
57
  }
58
58
 
@@ -115,8 +115,8 @@ class TiledeskTwilioTranslator {
115
115
  vxmlAttributes = this.manageVoiceAttributes(msg, vxmlAttributes)
116
116
 
117
117
 
118
- this.user = sessionInfo.user
119
- this.integrations = sessionInfo.integrations
118
+ // this.user = sessionInfo.user
119
+ // this.integrations = sessionInfo.integrations
120
120
 
121
121
 
122
122
  const xml = xmlbuilder.create("Response", {});
@@ -141,7 +141,7 @@ class TiledeskTwilioTranslator {
141
141
  const isWait = this.checkIfIsWait(msg);
142
142
  winston.debug("[TiledeskVXMLTranslator] toVXML: isWait:"+ isWait);
143
143
  if(isWait){
144
- const delayForm = await this.delayVXMLConverter(xml, msg, vxmlAttributes);
144
+ const delayForm = await this.delayVXMLConverter(xml, msg, vxmlAttributes, sessionInfo);
145
145
  return delayForm;
146
146
  }
147
147
 
@@ -149,7 +149,7 @@ class TiledeskTwilioTranslator {
149
149
  const isDtmfForm = this.checkIfIsDTMFForm(msg);
150
150
  winston.debug("[TiledeskVXMLTranslator] toVXML: isDtmfForm: "+ isDtmfForm);
151
151
  if(isDtmfForm){
152
- const DTMFForm = await this.dtmfFormVXMLConverter(xml, msg, vxmlAttributes);
152
+ const DTMFForm = await this.dtmfFormVXMLConverter(xml, msg, vxmlAttributes, sessionInfo);
153
153
  return DTMFForm;
154
154
  }
155
155
 
@@ -157,7 +157,7 @@ class TiledeskTwilioTranslator {
157
157
  const isBlindFransfer = this.checkIfIsBlindFransfer(msg);
158
158
  winston.debug("[TiledeskVXMLTranslator] toVXML: isBlindFransfer: "+ isBlindFransfer);
159
159
  if(isBlindFransfer){
160
- const blindTransfer = await this.blindTransferVXMLConverter(xml, msg, vxmlAttributes);
160
+ const blindTransfer = await this.blindTransferVXMLConverter(xml, msg, vxmlAttributes, sessionInfo);
161
161
  return blindTransfer;
162
162
  }
163
163
 
@@ -165,7 +165,7 @@ class TiledeskTwilioTranslator {
165
165
  const isMenu = this.checkIfIsDTMFMenuMessage(msg);
166
166
  winston.debug("[TiledeskVXMLTranslator] toVXML: isMenu: "+ isMenu);
167
167
  if(isMenu){
168
- const menu = await this.menuVXMLConverter(xml, msg, vxmlAttributes);
168
+ const menu = await this.menuVXMLConverter(xml, msg, vxmlAttributes, sessionInfo);
169
169
  return menu;
170
170
  }
171
171
 
@@ -173,71 +173,62 @@ class TiledeskTwilioTranslator {
173
173
  const isSpeechForm = this.checkIfIsSpeechFormMessage(msg);
174
174
  winston.debug("[TiledeskVXMLTranslator] toVXML: isSpeechForm: "+ isSpeechForm);
175
175
  if(isSpeechForm){
176
- const form = await this.speechFormVXMLConverter(xml, msg, vxmlAttributes);
176
+ const form = await this.speechFormVXMLConverter(xml, msg, vxmlAttributes, sessionInfo);
177
177
  return form;
178
178
  }
179
179
 
180
180
  /** check for FORM (PlayPrompt action) **/
181
181
  winston.debug("[TiledeskVXMLTranslator] toVXML: isPrompt: true");
182
- const prompt = await this.playPromptVXMLConverter(xml, msg, vxmlAttributes);
182
+ const prompt = await this.playPromptVXMLConverter(xml, msg, vxmlAttributes, sessionInfo);
183
183
  return prompt;
184
184
 
185
185
  }
186
186
 
187
187
  }
188
188
 
189
+ /**
190
+ * Generic helper to check if message has a specific VXML action subType
191
+ * @param {Object} msg - The message object
192
+ * @param {string} subType - The subType to check for
193
+ * @returns {boolean}
194
+ */
195
+ _hasSettingsSubType(msg, subType) {
196
+ const commands = msg.attributes?.commands;
197
+ if (!commands) return false;
198
+ const settingsElement = commands.find((command) => command.type === SETTING_MESSAGE);
199
+ return settingsElement?.subType === subType;
200
+ }
201
+
189
202
  checkIfIsDTMFMenuMessage(msg) {
190
- const commands = msg.attributes.commands;
191
- let dtmf_element = commands.filter((command) => command.type === SETTING_MESSAGE)
192
- if(dtmf_element && dtmf_element.length > 0 && dtmf_element[0].subType === TYPE_ACTION_VXML.DTMF_MENU){
193
- return true;
194
- }
195
- return false;
203
+ return this._hasSettingsSubType(msg, TYPE_ACTION_VXML.DTMF_MENU);
196
204
  }
197
205
 
198
206
  checkIfIsDTMFForm(msg){
199
- const commands = msg.attributes.commands;
200
- let dtmf_element = commands.filter((command) => command.type === SETTING_MESSAGE)
201
- if(dtmf_element && dtmf_element.length > 0 && dtmf_element[0].subType === TYPE_ACTION_VXML.DTMF_FORM){
202
- return true;
203
- }
204
- return false;
207
+ return this._hasSettingsSubType(msg, TYPE_ACTION_VXML.DTMF_FORM);
205
208
  }
206
209
 
207
210
  checkIfIsBlindFransfer(msg){
208
- const commands = msg.attributes.commands;
209
- let dtmf_element = commands.filter((command) => command.type === SETTING_MESSAGE)
210
- if(dtmf_element && dtmf_element.length > 0 && dtmf_element[0].subType === TYPE_ACTION_VXML.BLIND_TRANSFER ){
211
- return true;
212
- }
213
- return false;
211
+ return this._hasSettingsSubType(msg, TYPE_ACTION_VXML.BLIND_TRANSFER);
214
212
  }
215
213
 
216
214
  checkIfIsSpeechFormMessage(msg){
217
- const commands = msg.attributes.commands;
218
- let dtmf_element = commands.filter((command) => command.type === SETTING_MESSAGE)
219
- if(dtmf_element && dtmf_element.length > 0 && dtmf_element[0].subType === TYPE_ACTION_VXML.SPEECH_FORM ){
220
- return true;
221
- }
222
- return false;
215
+ return this._hasSettingsSubType(msg, TYPE_ACTION_VXML.SPEECH_FORM);
223
216
  }
224
217
 
225
218
  checkIfIsWait(msg){
226
- const commands = msg.attributes.commands;
227
- let wait_element = commands.filter((command) => command.type === WAIT_MESSAGE)
228
- let message_element = commands.filter((command) => command.type === TEXT_MESSAGE)
229
- if(wait_element && wait_element.length === 1 && message_element && message_element.length === 0){
230
- return true;
231
- }
232
- return false;
219
+ const commands = msg.attributes?.commands;
220
+ if (!commands) return false;
221
+ const hasWait = commands.some((command) => command.type === WAIT_MESSAGE);
222
+ const hasMessage = commands.some((command) => command.type === TEXT_MESSAGE);
223
+ return hasWait && !hasMessage;
233
224
  }
234
225
 
235
226
  setVXMLAttributes(commands, attributes){
236
227
  const settingsCommand = commands.slice(-1)[0];
237
- if(settingsCommand && settingsCommand.settings){
238
- Object.keys(settingsCommand.settings).forEach((key) => attributes[key]= settingsCommand.settings[key])
228
+ if(settingsCommand?.settings){
229
+ Object.assign(attributes, settingsCommand.settings);
239
230
  }
240
- return attributes
231
+ return attributes;
241
232
  }
242
233
 
243
234
  toTiledesk(vxmlMessage) {
@@ -245,20 +236,11 @@ class TiledeskTwilioTranslator {
245
236
  }
246
237
 
247
238
  getMessageFromTdMessage(msg) {
248
- let text = "";
249
- if (msg.attributes && msg.attributes.commands && msg.attributes.commands.length > 0 ) {
250
- /*msg.attributes.commands.forEach((command) => {
251
- if(command.type === 'message'){
252
- text= command.message.text
253
- }
254
- })*/
255
- let command = msg.attributes.commands[1];
256
- if (command.type === "message") {
257
- text = command.message.text;
258
- }
259
- }
260
-
261
- return text;
239
+ const commands = msg.attributes?.commands;
240
+ if (!commands || commands.length === 0) return "";
241
+
242
+ const command = commands[1];
243
+ return command?.type === "message" ? command.message.text : "";
262
244
  }
263
245
 
264
246
  getButtonsFromCommand(command) {
@@ -270,20 +252,16 @@ class TiledeskTwilioTranslator {
270
252
  }
271
253
  return buttons;
272
254
  }
255
+
273
256
  getButtonsFromTdMessage(msg) {
274
- let buttons = [];
275
- if (msg.attributes && msg.attributes.commands && msg.attributes.commands.length > 0 ) {
276
- /*msg.attributes.commands.forEach((command) => {
277
- if(command.type === 'message'){
278
- text= command.message.text
279
- }
280
- })*/
281
- let command = msg.attributes.commands[1];
282
- if (command.type === "message" && command.message.attributes && command.message.attributes.attachment && command.message.attributes.attachment.buttons ) {
283
- buttons = command.message.attributes.attachment.buttons.filter( (button) => (button.type === "action") );
284
- }
257
+ const commands = msg.attributes?.commands;
258
+ if (!commands || commands.length === 0) return [];
259
+
260
+ const command = commands[1];
261
+ if (command?.type === "message" && command.message?.attributes?.attachment?.buttons) {
262
+ return command.message.attributes.attachment.buttons.filter((button) => button.type === "action");
285
263
  }
286
- return buttons;
264
+ return [];
287
265
  }
288
266
 
289
267
  async hangupCall(rootEle){
@@ -293,10 +271,10 @@ class TiledeskTwilioTranslator {
293
271
  }
294
272
 
295
273
 
296
- async delayVXMLConverter(rootEle, message, xmlAttributes){
274
+ async delayVXMLConverter(rootEle, message, xmlAttributes, sessionInfo){
297
275
  const command = message.attributes.commands[0]
298
276
 
299
- const prompt = this.promptVXML(rootEle, message, xmlAttributes);
277
+ const prompt = this.promptVXML(rootEle, message, xmlAttributes, sessionInfo);
300
278
 
301
279
  rootEle.ele("Redirect", {method: "POST"}, this.BASE_URL + '/nextblock/' + xmlAttributes.callSid).up()
302
280
 
@@ -304,22 +282,12 @@ class TiledeskTwilioTranslator {
304
282
  }
305
283
 
306
284
 
307
- async playPromptVXMLConverter(rootEle, message, xmlAttributes){
285
+ async playPromptVXMLConverter(rootEle, message, xmlAttributes, sessionInfo){
308
286
 
309
- const gather = rootEle.ele("Gather", { input: "speech"})
310
-
311
- const queryUrl = '?intentName='+ querystring.encode(xmlAttributes.intentName) + '&previousIntentTimestamp='+Date.now();
312
- gather.att("action", this.BASE_URL + '/nextBlock/' + xmlAttributes.callSid + queryUrl)
313
- // gather.att("action", this.BASE_URL + '/speechresult/' + xmlAttributes.callSid + queryUrl)
314
- .att("method", "POST")
315
- .att("language", xmlAttributes.TTS_VOICE_LANGUAGE)
316
- .att('speechTimeout', "auto")
317
- .att("enhanced", "true") // enable enhanced recognition
318
-
319
- const prompt = await this.promptVXML(rootEle, message, xmlAttributes);
287
+ const prompt = await this.promptVXML(rootEle, message, xmlAttributes, sessionInfo);
320
288
 
321
- /** fallback se non parla --> redirige alla nextblock */
322
- rootEle.ele("Redirect", {method: "POST"}, this.BASE_URL + '/nextblock/' + xmlAttributes.callSid + queryUrl).up()
289
+ // const queryUrl = '?intentName='+ querystring.encode(xmlAttributes.intentName) + '&previousIntentTimestamp='+Date.now();
290
+ // rootEle.ele("Redirect", {method: "POST"}, this.BASE_URL + '/nextblock/' + xmlAttributes.callSid + queryUrl).up()
323
291
  //prompt.ele("submit", { fetchhint: "safe", expr: "proxyBaseUrl +'/nextblock/' + session.connection.calltoken", method: "post", namelist: "usertext session intentName previousIntentTimestamp" });
324
292
 
325
293
  return rootEle.end({ pretty: true });
@@ -327,7 +295,7 @@ class TiledeskTwilioTranslator {
327
295
  }
328
296
 
329
297
  /** DONE **/
330
- async speechFormVXMLConverter(rootEle, message, xmlAttributes) {
298
+ async speechFormVXMLConverter(rootEle, message, xmlAttributes, sessionInfo) {
331
299
 
332
300
  if(this.voiceProvider === VOICE_PROVIDER.TWILIO){
333
301
  const gather = rootEle.ele("Gather", { input: "speech"})
@@ -347,7 +315,7 @@ class TiledeskTwilioTranslator {
347
315
  gather.att("speechTimeout", Math.round(xmlAttributes.incompleteSpeechTimeout/1000) ).up();
348
316
  }
349
317
 
350
- const prompt = this.promptVXML(gather, message, xmlAttributes);
318
+ const prompt = this.promptVXML(gather, message, xmlAttributes, sessionInfo);
351
319
 
352
320
  const handleNoInputNoMatchQuery = await this.handleNoInputNoMatch(rootEle, message, xmlAttributes);
353
321
  if(handleNoInputNoMatchQuery && handleNoInputNoMatchQuery.queryNoInput){
@@ -356,7 +324,7 @@ class TiledeskTwilioTranslator {
356
324
 
357
325
  }else{
358
326
 
359
- const prompt = await this.promptVXML(rootEle, message, xmlAttributes);
327
+ const prompt = await this.promptVXML(rootEle, message, xmlAttributes, sessionInfo);
360
328
 
361
329
  const record = rootEle.ele("Record", { playBeep: "false"})
362
330
 
@@ -384,7 +352,7 @@ class TiledeskTwilioTranslator {
384
352
  }
385
353
 
386
354
  /** DONE **/
387
- async menuVXMLConverter(rootEle, message, xmlAttributes) {
355
+ async menuVXMLConverter(rootEle, message, xmlAttributes, sessionInfo) {
388
356
  const lastMessageCommand = message.attributes.commands.slice(-3)[0];
389
357
  const options = this.getButtonsFromCommand(lastMessageCommand);
390
358
 
@@ -407,7 +375,7 @@ class TiledeskTwilioTranslator {
407
375
  .att("method", "POST")
408
376
  .att("language", xmlAttributes.TTS_VOICE_LANGUAGE)
409
377
 
410
- const prompt = await this.promptVXML(gather, message, xmlAttributes);
378
+ const prompt = await this.promptVXML(gather, message, xmlAttributes, sessionInfo);
411
379
 
412
380
 
413
381
  if(handleNoInputNoMatchQuery && handleNoInputNoMatchQuery.queryNoInput){
@@ -418,7 +386,7 @@ class TiledeskTwilioTranslator {
418
386
  }
419
387
 
420
388
  /** DONE **/
421
- async dtmfFormVXMLConverter(rootEle, message, xmlAttributes) {
389
+ async dtmfFormVXMLConverter(rootEle, message, xmlAttributes, sessionInfo) {
422
390
 
423
391
  const gather = rootEle.ele("Gather", { input: "dtmf"})
424
392
 
@@ -429,7 +397,7 @@ class TiledeskTwilioTranslator {
429
397
  .att("language", xmlAttributes.TTS_VOICE_LANGUAGE)
430
398
  const settings = await this.optionsVXML(gather, message, xmlAttributes);
431
399
 
432
- const prompt = this.promptVXML(gather, message, xmlAttributes);
400
+ const prompt = await this.promptVXML(gather, message, xmlAttributes, sessionInfo);
433
401
 
434
402
  const handleNoInputNoMatchQuery = await this.handleNoInputNoMatch(rootEle, message, xmlAttributes);
435
403
  if(handleNoInputNoMatchQuery && handleNoInputNoMatchQuery.queryNoInput){
@@ -441,12 +409,12 @@ class TiledeskTwilioTranslator {
441
409
  }
442
410
 
443
411
 
444
- async blindTransferVXMLConverter(rootEle, message, xmlAttributes) {
412
+ async blindTransferVXMLConverter(rootEle, message, xmlAttributes, sessionInfo) {
445
413
  const lastMessageCommand = message.attributes.commands.slice(-3)[0];
446
414
  const options = this.getButtonsFromCommand(lastMessageCommand);
447
415
 
448
- const prompt = this.promptVXML(rootEle, message, xmlAttributes);
449
- const transfer = this.transferVXML(rootEle, message, xmlAttributes)
416
+ const prompt = await this.promptVXML(rootEle, message, xmlAttributes, sessionInfo);
417
+ const transfer = await this.transferVXML(rootEle, message, xmlAttributes)
450
418
 
451
419
 
452
420
  return rootEle.end({ pretty: true });
@@ -474,124 +442,63 @@ class TiledeskTwilioTranslator {
474
442
  rootEle.ele("catch", { event: "connection.disconnect.hangup" })
475
443
  .ele("submit", { fetchhint: "safe", expr: "proxyBaseUrl+'/event/' + session.connection.calltoken + '/hangup'", method: "post", namelist: "intentName previousIntentTimestamp"}).up();
476
444
  rootEle.ele("catch", { event: "error.noresource.asr" })
477
- //.ele("var", { name: "url", expr: "'https://tiledesk-vxml-connector.glitch.me/event" + "/no-resources?callId=" + attributes.callId + "'" }).up()
478
445
  .ele("submit", { fetchhint: "safe", expr: "proxyBaseUrl+'/event/' + session.connection.calltoken + '/no-resources?event=' + application._event", method: "post", namelist: "intentName previousIntentTimestamp" }).up();
479
446
  return rootEle;
480
447
  }
481
448
 
482
449
 
483
450
  async handleNoInputNoMatch(rootEle, msg, attributes){
484
- let intent_noInput = null, intent_noMatch = null
485
- let button_noIput = {}, button_noMatch = {};
486
- let queryNoInput = '', queryNoMatch = ''
487
-
488
- let dtmf_form_element = msg.attributes.commands.find((command) => command.type === SETTING_MESSAGE)
489
- if(!dtmf_form_element){
490
- return;
451
+ const dtmf_form_element = msg.attributes?.commands?.find((command) => command.type === SETTING_MESSAGE);
452
+ if (!dtmf_form_element?.settings) {
453
+ return null;
491
454
  }
492
455
 
456
+ const { noInputIntent, noMatchIntent } = dtmf_form_element.settings;
493
457
 
494
- if(dtmf_form_element.settings.noInputIntent){
495
- intent_noInput = dtmf_form_element.settings.noInputIntent
496
- button_noIput = {
497
- action: intent_noInput,
498
- value: 'no_input'
499
- }
500
-
501
-
502
- queryNoInput = 'button_action='+button_noIput.action.substring(1);
503
- //rootEle.ele("Redirect", {}, this.BASE_URL + '/handle/' + attributes.callSid + '/no_input'+ queryNoInput)
504
-
505
-
506
- /*element.ele("noinput")
507
- .ele("assign", { name: "menu_choice", expr: "'" + MENU_CHOICE.NO_INPUT + "'", }).up()
508
- .ele("assign", { name: "button", expr: "'" + JSON.stringify(button_noIput) + "'", }).up()
509
- .ele("goto", { next: "#noinput_form" }).up()
510
- .up()
511
-
512
- rootEle.ele("form", { id: "noinput_form" })
513
- .ele("block")
514
- .ele("submit", { fetchhint: "safe", expr: "proxyBaseUrl +'/handle/' + session.connection.calltoken + '/no_input'", method: "post", namelist: "intentName button previousIntentTimestamp"}).up()
515
- .up()
516
- .up();
517
- */
518
-
519
- }
520
-
521
-
522
- if(dtmf_form_element.settings.noMatchIntent){
523
- intent_noMatch = dtmf_form_element.settings.noMatchIntent
524
- button_noMatch = {
525
- action: intent_noMatch,
526
- value: 'no_match'
527
- }
528
-
529
- queryNoMatch = 'button_action='+button_noMatch.action.substring(1); //remove '#' from intentId because is not a valid char for XML lang
530
- //rootEle.ele("Redirect", {}, this.BASE_URL + '/handle/' + attributes.callSid + '/no_match'+ queryNoMatch)
531
-
532
- /*element.ele("nomatch")
533
- .ele("assign", { name: "menu_choice", expr: "'" + MENU_CHOICE.NO_MATCH + "'"}).up()
534
- .ele("assign", { name: "button", expr: "'" + JSON.stringify(button_noMatch) + "'", }).up()
535
- .ele("goto", { next: "#nomatch_form" }).up()
536
- .up();
537
-
538
- rootEle.ele("form", { id: "nomatch_form" })
539
- .ele("block")
540
- .ele("submit", { fetchhint: "safe", expr: "proxyBaseUrl +'/handle/' + session.connection.calltoken + '/no_match'", method: "post", namelist: "intentName button previousIntentTimestamp"}).up()
541
- .up()
542
- .up();
543
- */
544
-
545
- }
546
-
547
-
548
- return { queryNoInput: queryNoInput, queryNoMatch: queryNoMatch}
549
-
458
+ // Build query strings for noInput and noMatch intents
459
+ const queryNoInput = noInputIntent ? `button_action=${noInputIntent.substring(1)}` : '';
460
+ const queryNoMatch = noMatchIntent ? `button_action=${noMatchIntent.substring(1)}` : '';
550
461
 
462
+ return { queryNoInput, queryNoMatch };
551
463
  }
552
464
 
553
465
  /* create PROMPT section */
554
- async promptVXML(rootEle, msg, attributes) {
466
+ async promptVXML(rootEle, msg, attributes, sessionInfo) {
555
467
  //let promt = rootEle.ele("prompt", {bargein: attributes.bargein});
556
468
 
557
469
  if (msg.attributes && msg.attributes.commands && msg.attributes.commands.length > 0 ) {
558
470
  let commands = msg.attributes.commands;
559
- let i = 0;
560
- await new Promise((resolve, reject) => {
561
- const that = this
562
- async function execute(command) {
563
- if (command.type === "message") {
564
- //case type: TEXT
565
- if(command.message.type === 'text'){
566
- let text = utils.markdownToTwilioSpeech(command.message.text);
567
- if(that.voiceProvider !== VOICE_PROVIDER.TWILIO){
568
- let voiceMessageUrl = await that.generateTTS(text, attributes)
569
- rootEle.ele('Play', {}, voiceMessageUrl )
570
- }else{
571
- rootEle.ele("Say", { voice: attributes.TTS_VOICE_NAME, language: attributes.TTS_VOICE_LANGUAGE }, text);
572
- }
573
-
574
- }
575
- //case type: FRAME
576
- if(command.message.type === 'frame' && command.message.metadata.src !== ""){
577
- if(command.message.text != ''){
578
- rootEle.ele("Say", { voice: attributes.TTS_VOICE_NAME, language: attributes.TTS_VOICE_LANGUAGE }, text);
579
- }
580
- rootEle.ele('Play', {}, command.message.metadata.src )
471
+
472
+ for (const command of commands) {
473
+ if (command.type === "message") {
474
+ //case type: TEXT
475
+ if(command.message.type === 'text'){
476
+ let text = utils.markdownToTwilioSpeech(command.message.text);
477
+ if(this.voiceProvider !== VOICE_PROVIDER.TWILIO){
478
+ let voiceMessageUrl = await this.generateTTS(text, attributes, sessionInfo);
479
+ rootEle.ele('Play', { loop: 0}, voiceMessageUrl );
480
+ this.lastCallSidVerb[attributes.callSid] = "play";
481
+ }else{
482
+ rootEle.ele("Say", { voice: attributes.TTS_VOICE_NAME, language: attributes.TTS_VOICE_LANGUAGE }, text);
483
+ this.lastCallSidVerb[attributes.callSid] = "say";
581
484
  }
582
- } else if (command.type === "wait" && command.time !== 0) {
583
- rootEle.ele("Pause", { length: command.time/1000 }).up();
485
+
584
486
  }
585
-
586
- i += 1;
587
- if (i < commands.length) {
588
- execute(commands[i]);
589
- } else {
590
- resolve(true);
487
+ //case type: FRAME
488
+ if(command.message.type === 'frame' && command.message.metadata.src !== ""){
489
+ if(command.message.text != ''){
490
+ let text = utils.markdownToTwilioSpeech(command.message.text);
491
+ rootEle.ele("Say", { voice: attributes.TTS_VOICE_NAME, language: attributes.TTS_VOICE_LANGUAGE }, text);
492
+ }
493
+ rootEle.ele('Play', { loop: 0 }, command.message.metadata.src )
494
+ this.lastCallSidVerb[attributes.callSid] = "play";
495
+
591
496
  }
497
+ } else if (command.type === "wait" && command.time !== 0) {
498
+ rootEle.ele("Pause", { length: command.time/1000 }).up();
499
+ this.lastCallSidVerb[attributes.callSid] = "pause";
592
500
  }
593
- execute(commands[0]); //START render first message
594
- });
501
+ }
595
502
  }
596
503
  return rootEle;
597
504
  }
@@ -650,16 +557,16 @@ class TiledeskTwilioTranslator {
650
557
  return transfer.up()
651
558
  }
652
559
 
653
- async generateTTS(text, attributes){
560
+ async generateTTS(text, attributes, sessionInfo){
654
561
  let audioData = null;
655
562
  try {
656
563
  switch(this.voiceProvider){
657
564
  case VOICE_PROVIDER.OPENAI:
658
- let GPT_KEY = this.integrations.find((el => el.type === VOICE_PROVIDER.OPENAI))?.key
565
+ let GPT_KEY = sessionInfo.integrations.find((el => el.type === VOICE_PROVIDER.OPENAI))?.key
659
566
  audioData = await this.aiService.textToSpeech(text, attributes.TTS_VOICE_NAME, attributes.TTS_MODEL, GPT_KEY)
660
567
  break;
661
568
  case VOICE_PROVIDER.ELEVENLABS:
662
- let ELEVENLABS_APIKEY = this.integrations.find((el => el.type === VOICE_PROVIDER.ELEVENLABS))?.key
569
+ let ELEVENLABS_APIKEY = sessionInfo.integrations.find((el => el.type === VOICE_PROVIDER.ELEVENLABS))?.key
663
570
  audioData = await this.aiService.textToSpeechElevenLabs(text, attributes.TTS_VOICE_NAME, attributes.TTS_MODEL, attributes.TTS_VOICE_LANGUAGE, ELEVENLABS_APIKEY)
664
571
  break;
665
572
  default:
@@ -670,7 +577,7 @@ class TiledeskTwilioTranslator {
670
577
  throw new SttError('TTS_FAILED', 'TTS returned no audio data');
671
578
  }
672
579
 
673
- let fileUrl = await this.uploadService.upload(attributes.callSid, audioData, this.user)
580
+ let fileUrl = await this.uploadService.upload(attributes.callSid, audioData, sessionInfo.user)
674
581
  winston.debug('(voice) Audio Message url captured after TTS -->', fileUrl)
675
582
  return fileUrl
676
583
  } catch (error) {
@@ -0,0 +1,24 @@
1
+ const axios = require("axios").default;
2
+ const logger = require('./logger');
3
+
4
+ class FileUtils {
5
+
6
+
7
+ async downloadFromUrl(url) {
8
+ try {
9
+ const resbody = await axios({
10
+ url: url,
11
+ responseType: 'arraybuffer',
12
+ method: 'GET'
13
+ });
14
+ const buffer = Buffer.from(resbody.data, 'binary');
15
+ return buffer;
16
+ } catch (err) {
17
+ throw err;
18
+ }
19
+ }
20
+ }
21
+
22
+ var fileUtils = new FileUtils();
23
+
24
+ module.exports = fileUtils;
@@ -0,0 +1,32 @@
1
+ var appRoot = require('app-root-path');
2
+ var winston = require('winston');
3
+ var level = process.env.VOICE_TWILIO_LOG || 'info';
4
+
5
+ var options = {
6
+ file: {
7
+ level:level ,
8
+ filename: `${appRoot}/logs/app.log`,
9
+ handleExceptions: true,
10
+ json: false,
11
+ maxsize: 5242880, // 5MB
12
+ maxFiles: 5,
13
+ colorize: false,
14
+ format: winston.format.simple()
15
+ },
16
+ console: {
17
+ level: level,
18
+ handleExceptions: true,
19
+ json: true,
20
+ colorize: true,
21
+ format: winston.format.simple()
22
+ },
23
+ };
24
+
25
+ var logger = winston.createLogger({
26
+ transports: [
27
+ new winston.transports.Console(options.console)
28
+ ],
29
+ exitOnError: false, // do not exit on handled exceptions
30
+ });
31
+
32
+ module.exports = logger;
@@ -73,27 +73,12 @@ function messageType(msgType, message, user) {
73
73
  }
74
74
 
75
75
  function infoMessageType(msg){
76
- if(msg && msg.attributes.messagelabel && msg.attributes.messagelabel.key === INFO_MESSAGE_TYPE.MEMBER_JOINED_GROUP){
77
- return INFO_MESSAGE_TYPE.MEMBER_JOINED_GROUP
78
- }
79
- if(msg && msg.attributes.messagelabel && msg.attributes.messagelabel.key === INFO_MESSAGE_TYPE.CHAT_CLOSED){
80
- return INFO_MESSAGE_TYPE.CHAT_CLOSED
81
- }
82
- if(msg && msg.attributes.messagelabel && msg.attributes.messagelabel.key === INFO_MESSAGE_TYPE.CHAT_REOPENED){
83
- return INFO_MESSAGE_TYPE.CHAT_REOPENED
84
- }
85
- if(msg && msg.attributes.messagelabel && msg.attributes.messagelabel.key === INFO_MESSAGE_TYPE.TOUCHING_OPERATOR){
86
- return INFO_MESSAGE_TYPE.TOUCHING_OPERATOR
87
- }
88
- if(msg && msg.attributes.messagelabel && msg.attributes.messagelabel.key === INFO_MESSAGE_TYPE.LEAD_UPDATED){
89
- return INFO_MESSAGE_TYPE.LEAD_UPDATED
90
- }
91
- if(msg && msg.attributes.messagelabel && msg.attributes.messagelabel.key === INFO_MESSAGE_TYPE.MEMBER_LEFT_GROUP){
92
- return INFO_MESSAGE_TYPE.MEMBER_LEFT_GROUP
93
- }
94
- if(msg && msg.attributes.messagelabel && msg.attributes.messagelabel.key === INFO_MESSAGE_TYPE.LIVE_PAGE){
95
- return INFO_MESSAGE_TYPE.LIVE_PAGE
96
- }
76
+ const key = msg?.attributes?.messagelabel?.key;
77
+ if (!key) return null;
78
+
79
+ // Return the key directly if it's a valid INFO_MESSAGE_TYPE
80
+ const validTypes = Object.values(INFO_MESSAGE_TYPE);
81
+ return validTypes.includes(key) ? key : null;
97
82
  }
98
83
 
99
84
  module.exports = {isCarousel, isImage, isFile, isAudio, isInfo, isInfoSupport, messageType, infoMessageType}