@tiledesk/tiledesk-voice-twilio-connector 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js ADDED
@@ -0,0 +1,1029 @@
1
+ "use strict";
2
+ const express = require("express");
3
+ const bodyParser = require("body-parser")
4
+ const router = express.Router();
5
+ const winston = require("./winston");
6
+ const fs = require("fs");
7
+ const path = require('path');
8
+ const pjson = require('./package.json');
9
+ const axios = require("axios").default;
10
+
11
+
12
+ router.use(bodyParser.json()); // support json encoded bodies
13
+ router.use(express.urlencoded({ extended: true })); // support encoded bodies
14
+ router.use(express.static(path.join(__dirname, 'template')));
15
+
16
+ //template
17
+ const manageApp = require('./routes/manageApp');
18
+ const manageRoute = manageApp.router;
19
+ router.use('/manage', manageRoute);
20
+
21
+ // tiledesk clients
22
+ const { TiledeskChannel } = require("./tiledesk/TiledeskChannel");
23
+ const { TiledeskTwilioTranslator } = require('./tiledesk/TiledeskTwilioTranslator');
24
+
25
+ //voice clients
26
+ const { VoiceChannel } = require("./tiledesk/VoiceChannel");
27
+ let voiceChannel = null;
28
+
29
+ // mongo
30
+ const { KVBaseMongo } = require('./tiledesk/KVBaseMongo');
31
+ const kvbase_collection = 'kvstore';
32
+ const db = new KVBaseMongo(kvbase_collection);
33
+
34
+ // redis
35
+ var redis = require('redis')
36
+ var redis_client;
37
+
38
+ //UTILS
39
+ const utils_message = require('./tiledesk/utils-message.js')
40
+ const CHANNEL_NAME = require('./tiledesk/constants').CHANNEL_NAME
41
+ const TYPE_MESSAGE = require('./tiledesk/constants').TYPE_MESSAGE
42
+ const CALL_STATUS = require('./tiledesk/constants').CALL_STATUS
43
+ const utils = require('./tiledesk/utils.js')
44
+
45
+ let API_URL = null;
46
+ let BASE_URL = null;
47
+ let REDIS_HOST = null;
48
+ let REDIS_PORT = null;
49
+ let REDIS_PASSWORD = null;
50
+ let BRAND_NAME = null;
51
+
52
+ let twilio = require('twilio');
53
+ const VoiceResponse = require('twilio').twiml.VoiceResponse;
54
+ const { MessagingResponse } = require('twilio').twiml;
55
+
56
+ router.get("/", async (req, res) => {
57
+ res.send("Welcome to Tiledesk-voice-twilio connector");
58
+ });
59
+
60
+
61
+ // TILEDESK WEBHOOK: message from tiledesk to user (chatbot/agent messagges)
62
+ router.post("/tiledesk", async (req, res) => {
63
+ let tiledeskMessage = req.body.payload
64
+ let project_id = tiledeskMessage.id_project
65
+
66
+ //const CONTENT_KEY = "vxml-" + project_id;
67
+ //let settings = await db.get(CONTENT_KEY)
68
+
69
+ const tdChannel = new TiledeskChannel({
70
+ API_URL: API_URL,
71
+ redis_client: redis_client
72
+ });
73
+ tdChannel.setProjectId(project_id)
74
+
75
+ await tdChannel.addMessageToQueue(tiledeskMessage)
76
+
77
+ res.send("(vxml) Message received from Voice VXML Proxy");
78
+
79
+
80
+ });
81
+
82
+ // TWILIO WEBHOOK : message from user to tiledesk
83
+ router.post('/webhook/:id_project', async (req, res) => {
84
+ winston.debug('(voice) called POST /webhook/:id_project ', req.params)
85
+
86
+ let project_id = req.params.id_project;
87
+ let callSid = req.body.CallSid;
88
+ let from = req.body.From;
89
+ let to = req.body.To;
90
+
91
+ if(!from || !to){
92
+ return res.status(404).send({error: "Error: Missing from/to parameters"})
93
+ }
94
+ from = utils.getNumber(from); //remove '+' from number
95
+ to = utils.getNumber(to); //remove '+' from number
96
+
97
+ const CONTENT_KEY = CHANNEL_NAME + "-" + project_id;
98
+ let settings = await db.get(CONTENT_KEY);
99
+ if(!settings){
100
+ return res.status(404).send({error: "VOICE Channel not already connected"})
101
+ }
102
+
103
+ let vxmlAttributes = {
104
+ voiceName: "Polly.Bianca",
105
+ voiceLanguage: "it-IT",
106
+ callSid: callSid
107
+ };
108
+
109
+ const tdChannel = new TiledeskChannel({
110
+ API_URL: API_URL,
111
+ redis_client: redis_client,
112
+ });
113
+ tdChannel.setProjectId(project_id)
114
+
115
+ const tdTranslator = new TiledeskTwilioTranslator({
116
+ BASE_URL: BASE_URL
117
+ });
118
+
119
+ let user = await tdChannel.signIn(from, settings);
120
+ if(!user){
121
+ res.status(401).send({message: "Cannot able to signIn with current caller phone :" + from});
122
+ return;
123
+ }
124
+
125
+ //let conversation_id = await tdChannel.getConversation(ani, callId, user.token);
126
+ let conversation_id = await tdChannel.generateConversation(from, callSid, user.token);
127
+ winston.debug("(voice) conversation returned:"+ conversation_id);
128
+
129
+ //save data to redis
130
+ let session_data = {
131
+ from: from,
132
+ to: to,
133
+ callSid: callSid,
134
+ project_id: project_id,
135
+ user: user,
136
+ conversation_id: conversation_id
137
+ }
138
+ if (!redis_client) {
139
+ return res.status(500).send({ message: "Redis not ready. Check redis connection..." })
140
+ }
141
+ //for (const [key, value] of Object.entries(redis_data)){
142
+ // await redis_client.hSet('tiledesk:vxml:'+callId, key, JSON.stringify(value))
143
+ //}
144
+ //await redis_client.expire('tiledesk:vxml:'+callId, 86400)
145
+
146
+ await redis_client.set('tiledesk:vxml:'+callSid+':session', JSON.stringify(session_data), {'EX': 86400});
147
+
148
+
149
+ let tiledeskMessage= {
150
+ text:'/start',
151
+ senderFullname: from,
152
+ type: 'text',
153
+ attributes: {
154
+ subtype: 'info',
155
+ payload: {
156
+ ... req.query //send all attributes back to chatbot
157
+ }
158
+ },
159
+ channel: { name: CHANNEL_NAME }
160
+ };
161
+
162
+ let message = await tdChannel.send(tiledeskMessage, user.token, conversation_id)
163
+ if(!message){
164
+ return res.status(503).send({ message: "Bad response: Quota exceeded" })
165
+ }
166
+
167
+
168
+ //generate Tiledesk wait message
169
+ let delayTime = await voiceChannel.getNextDelayTimeForCallId(callSid)
170
+ let waitTiledeskMessage = await tdChannel.generateWaitTdMessage(from, delayTime)
171
+ //update delayIndex for wait command message time
172
+ await voiceChannel.saveDelayIndexForCallId(from)
173
+
174
+ // send standard wait vxml message
175
+ let messageToVXML = await tdTranslator.toVXML(waitTiledeskMessage, callSid, vxmlAttributes)
176
+ winston.debug('(voice) messageVXML-->'+ messageToVXML)
177
+
178
+
179
+ // Render the response as XML in reply to the webhook request
180
+ res.set('Content-Type', 'text/xml');
181
+ res.status(200).send(messageToVXML);
182
+
183
+ });
184
+
185
+
186
+ router.post('/nextblock/:callSid/', async(req, res) => {
187
+ winston.info("(vxml) called POST /nextblock" , req.body);
188
+ winston.info("(vxml) called POST /nextblock query", req.query);
189
+
190
+ let usertext = req.body.SpeechResult;
191
+ let confidence = req.body.Confidence
192
+ let callSid = req.params.callSid;
193
+
194
+ let sessionInfo;
195
+ let project_id, conversation_id, user;
196
+ let from, to;
197
+
198
+ let redis_data = await redis_client.get('tiledesk:vxml:'+callSid+':session');
199
+ //let redis_data = await redis_client.hGetAll('tiledesk:vxml:'+callId);
200
+ if (!redis_data) {
201
+ return res.status(500).send({ success: "false", message: "Can't retrive data for callSid ->" + callSid });
202
+ }
203
+ sessionInfo = JSON.parse(redis_data)
204
+ project_id = sessionInfo.project_id;
205
+ from = sessionInfo.from;
206
+ to = sessionInfo.to;
207
+ conversation_id = sessionInfo.conversation_id;
208
+ user = sessionInfo.user;
209
+
210
+ let vxmlAttributes = {
211
+ voiceName: "Polly.Bianca",
212
+ voiceLanguage: "it-IT",
213
+ callSid: callSid,
214
+ };
215
+
216
+ const tdChannel = new TiledeskChannel({
217
+ API_URL: API_URL,
218
+ redis_client: redis_client
219
+ })
220
+ tdChannel.setProjectId(project_id);
221
+
222
+ const tdTranslator = new TiledeskTwilioTranslator({
223
+ BASE_URL: BASE_URL
224
+ });
225
+
226
+
227
+ //manage SPEECH FORM with <record>
228
+
229
+ //se coda vuota (esiste)
230
+ //manda vxml wait
231
+ //se usertext vuoto e coda ha almeno un elemento
232
+ // genera vxml su quel messaggio
233
+ //se usertet non vuoto e coda non esiste
234
+ //crea coda e manda messaggio su conversazione
235
+
236
+ let message = {}, queue = []
237
+ queue = await tdChannel.getMessagesFromQueue(conversation_id)
238
+ winston.debug('/NEXT controllo coda--> ', queue.length, usertext)
239
+ if(queue.length === 0 && (usertext === '' || !usertext)){
240
+ winston.debug('QUEUE is empty--> ',queue)
241
+
242
+ //CASE: queue is empty --> generate Tiledesk wait message and manage delayTime
243
+ let delayTime = await voiceChannel.getNextDelayTimeForCallId(callSid)
244
+ message = await tdChannel.generateWaitTdMessage(from, delayTime)
245
+ //update delayIndex for wait command message time
246
+ await voiceChannel.saveDelayIndexForCallId(from)
247
+
248
+ } else if(queue.length > 0 && (usertext === '' || !usertext)){
249
+
250
+ //CASE: queue has at least one message to reproduce --> get message from tiledesk queue and reset delayTime
251
+ message = queue[0]
252
+ winston.debug('QUEUE --> ',queue[0])
253
+ //remove message from queue
254
+ await tdChannel.removeMessageFromQueue(conversation_id, message._id)
255
+ //reset delayIndex for wait command message time
256
+ await voiceChannel.clearDelayTimeForCallId(callSid)
257
+
258
+ queue = await tdChannel.getMessagesFromQueue(conversation_id)
259
+ winston.debug('QUEUE after remove --> ',queue.length)
260
+ }else{
261
+ //CASE:usertext is not empty and queue is empty --> send message to tiledesk and manage delayTime
262
+ let tiledeskMessage= {
263
+ text:usertext,
264
+ senderFullname: from,
265
+ type: 'text',
266
+ channel: { name: CHANNEL_NAME }
267
+ };
268
+ let tdMessage = await tdChannel.send(tiledeskMessage, user.token, conversation_id);
269
+ winston.debug("message sent : ", tdMessage);
270
+ //generate Tiledesk wait message
271
+ let delayTime = await voiceChannel.getNextDelayTimeForCallId(callSid)
272
+ message = await tdChannel.generateWaitTdMessage(from, delayTime)
273
+ //update delayIndex for wait command message time
274
+ await voiceChannel.saveDelayIndexForCallId(callSid)
275
+ }
276
+
277
+ /*
278
+ if(usertext === '' || !usertext){
279
+ //get message from queue
280
+ queue = await tdChannel.getMessagesFromQueue(conversation_id)
281
+ winston.debug('QUEUE --> ',queue.length)
282
+ if(queue.length === 0){
283
+ //CASE: queue is empty --> generate Tiledesk wait message
284
+ message = await tdChannel.generateWaitTdMessage(ani, callId)
285
+ winston.debug('QUEUE is empty--> ',queue)
286
+ }else{
287
+ //CASE: queue has at least one message to reproduce --> get message from tiledesk queue
288
+ message = queue[0]
289
+ winston.debug('QUEUE --> ',queue[0])
290
+ //remove message from queue
291
+ await tdChannel.removeMessageFromQueue(conversation_id, message._id)
292
+
293
+ queue = await tdChannel.getMessagesFromQueue(conversation_id)
294
+ winston.debug('QUEUE after remove --> ',queue.length)
295
+ }
296
+ }else{
297
+ //send message to tiledesk
298
+ let tiledeskMessage= {
299
+ text:usertext,
300
+ senderFullname: ani,
301
+ type: 'text',
302
+ channel: { name: CHANNEL_NAME }
303
+ };
304
+ let tdMessage = await tdChannel.send(tiledeskMessage, user.token, conversation_id);
305
+ winston.debug("message sent : ", tdMessage);
306
+ //generate Tiledesk wait message
307
+ message = await tdChannel.generateWaitTdMessage(ani, callId)
308
+ }
309
+ */
310
+
311
+ // convert response to vxml
312
+ let messageToVXML = await tdTranslator.toVXML(message, callSid, vxmlAttributes)
313
+ winston.debug("(vxml) VXML to SEND: "+ messageToVXML);
314
+
315
+ // Render the response as XML in reply to the webhook request
316
+ res.set('Content-Type', 'text/xml');
317
+ res.status(200).send(messageToVXML);
318
+ })
319
+
320
+
321
+ router.post('/menublock/:callSid', async (req, res) => {
322
+ winston.info("(voice) called POST /menu", req.body);
323
+ winston.info("(voice) called POST /menu query" , req.query);
324
+
325
+ let message_text = '';
326
+ let attributes = {};
327
+ let button = {}
328
+
329
+ let callSid = req.params.callSid;
330
+ let buttons_menu = req.query.menu_options;
331
+ let buttonNoMatch = req.query.button_action;
332
+ let previousIntentName = req.query.intentName;
333
+
334
+ let menu_choice = req.body.Digits || '';
335
+
336
+ /** use case: DTMF MENU **/
337
+ if(buttons_menu){
338
+ buttons_menu.split(';').some((option)=> {
339
+ option = option.split(':')
340
+ if(option[0] === menu_choice){
341
+ button.value = option[0]
342
+ button.action = '#' + option[1]
343
+ return true;
344
+ }
345
+ })
346
+
347
+
348
+ /* case noMatch input: Digits is not in a valid menu option*/
349
+ if(Object.keys(button).length === 0){
350
+ button.value = menu_choice
351
+ button.action = '#' + buttonNoMatch
352
+ }
353
+
354
+ message_text = button.value.toString();
355
+ attributes = {
356
+ action: button.action
357
+ }
358
+
359
+ }else{
360
+ /** use case: DTMF Speech **/
361
+ message_text = menu_choice.toString(); //.replace(/(\d)/g, '$1 '); //convert number to string and then add a space after each number
362
+ }
363
+
364
+ winston.debug("(vxml) button menu: ", button);
365
+ winston.debug("(vxml) message_text menu: "+ message_text);
366
+
367
+
368
+ let sessionInfo;
369
+ let project_id, conversation_id, user;
370
+ let from, to;
371
+
372
+ let redis_data = await redis_client.get('tiledesk:vxml:'+callSid+':session');
373
+ if (!redis_data) {
374
+ return res.status(500).send({ success: "false", message: "Can't retrive data for callSid ->" + callSid });
375
+ }
376
+ sessionInfo = JSON.parse(redis_data)
377
+ project_id = sessionInfo.project_id;
378
+ from = sessionInfo.from;
379
+ to = sessionInfo.to;
380
+ conversation_id = sessionInfo.conversation_id;
381
+ user = sessionInfo.user;
382
+
383
+ let vxmlAttributes = {
384
+ voiceName: "Polly.Bianca",
385
+ voiceLanguage: "it-IT",
386
+ callSid: callSid,
387
+ };
388
+
389
+ const tdChannel = new TiledeskChannel({
390
+ API_URL: API_URL,
391
+ redis_client: redis_client
392
+ })
393
+ tdChannel.setProjectId(project_id);
394
+
395
+ const tdTranslator = new TiledeskTwilioTranslator({
396
+ BASE_URL: BASE_URL
397
+ });
398
+
399
+ //send message to tiledesk
400
+ let tiledeskMessage= {
401
+ text:message_text,
402
+ senderFullname: from,
403
+ type: 'text',
404
+ channel: { name: CHANNEL_NAME },
405
+ attributes: attributes
406
+ };
407
+ console.log('messssss', tiledeskMessage)
408
+ let tdMessage = await tdChannel.send(tiledeskMessage, user.token, conversation_id);
409
+
410
+ //generate Tiledesk wait message
411
+ let delayTime = await voiceChannel.getNextDelayTimeForCallId(callSid)
412
+ let message = await tdChannel.generateWaitTdMessage(from, delayTime)
413
+ //update delayIndex for wait command message time
414
+ await voiceChannel.saveDelayIndexForCallId(callSid)
415
+
416
+ // convert response to vxml
417
+ let messageToVXML = await tdTranslator.toVXML(message, callSid, vxmlAttributes)
418
+ winston.debug("(voice) VXML to SEND: "+ messageToVXML);
419
+
420
+ res.set('Content-Type', 'text/xml');
421
+ res.status(200).send(messageToVXML);
422
+
423
+ });
424
+
425
+ router.post('/handle/:callSid/:event', async (req, res) => {
426
+ winston.info("(voice) called POST /handle", req.body);
427
+ winston.info("(voice) called POST /handle query -->", req.query);
428
+ winston.info("(voice) called POST /handle params-->", req.params);
429
+
430
+ let event = req.params.event;
431
+ let callSid = req.params.callSid;
432
+ let button_action = '#' + req.query.button_action;
433
+ let previousIntentName = req.query.intentName;
434
+
435
+ let sessionInfo;
436
+ let project_id, conversation_id, user;
437
+ let from, to;
438
+
439
+ let redis_data = await redis_client.get('tiledesk:vxml:'+callSid+':session');
440
+ if (!redis_data) {
441
+ return res.status(500).send({ success: "false", message: "Can't retrive data for callSid ->" + callSid });
442
+ }
443
+ sessionInfo = JSON.parse(redis_data)
444
+ project_id = sessionInfo.project_id;
445
+ from = sessionInfo.from;
446
+ to = sessionInfo.to;
447
+ conversation_id = sessionInfo.conversation_id;
448
+ user = sessionInfo.user;
449
+
450
+ let vxmlAttributes = {
451
+ voiceName: "Polly.Bianca",
452
+ voiceLanguage: "it-IT",
453
+ callSid: callSid,
454
+ };
455
+
456
+ const tdChannel = new TiledeskChannel({
457
+ API_URL: API_URL,
458
+ redis_client: redis_client
459
+ })
460
+ tdChannel.setProjectId(project_id);
461
+
462
+ const tdTranslator = new TiledeskTwilioTranslator({
463
+ BASE_URL: BASE_URL
464
+ });
465
+
466
+
467
+ //send message to tiledesk
468
+ let tiledeskMessage= {
469
+ text: "/" + event,
470
+ senderFullname: from,
471
+ type: 'text',
472
+ channel: { name: CHANNEL_NAME },
473
+ attributes: {
474
+ type: 'info',
475
+ action: button_action
476
+ }
477
+ };
478
+ let tdMessage = await tdChannel.send(tiledeskMessage, user.token, conversation_id);
479
+
480
+ //generate Tiledesk wait message
481
+ let delayTime = await voiceChannel.getNextDelayTimeForCallId(callSid)
482
+ let message = await tdChannel.generateWaitTdMessage(callSid, delayTime)
483
+ ///update delayIndex for wait command message time
484
+ await voiceChannel.saveDelayIndexForCallId(callSid)
485
+
486
+ // convert response to vxml
487
+ let messageToVXML = await tdTranslator.toVXML(message, callSid, vxmlAttributes)
488
+ winston.debug("(vxml) VXML to SEND: "+ messageToVXML);
489
+
490
+ res.set('Content-Type', 'text/xml');
491
+ res.status(200).send(messageToVXML);
492
+
493
+ });
494
+
495
+
496
+ /* ----> catch Event block <----- */
497
+ router.post('/event/:callSid/:event', async(req, res)=> {
498
+ winston.info("(voice) called POST /event" , req.params);
499
+ winston.debug("(voice) called POST /event query" , req.query);
500
+ winston.debug("(voice) called POST /event body" , req.body);
501
+
502
+ let event = req.params.event;
503
+ let callSid = req.params.callSid;
504
+ let currentIntentName = req.query.intentName;
505
+ let currentIntentTimestamp = req.query.previousIntentTimestamp;
506
+
507
+
508
+ let sessionInfo;
509
+ let project_id, conversation_id, user;
510
+ let from, to;
511
+
512
+ let redis_data = await redis_client.get('tiledesk:vxml:'+callSid+':session');
513
+ if (!redis_data) {
514
+ return res.status(500).send({ success: "false", message: "Can't retrive data for callSid ->" + callSid });
515
+ }
516
+ sessionInfo = JSON.parse(redis_data)
517
+ project_id = sessionInfo.project_id;
518
+ from = sessionInfo.from;
519
+ to = sessionInfo.to;
520
+ conversation_id = sessionInfo.conversation_id;
521
+ user = sessionInfo.user;
522
+
523
+ let vxmlAttributes = {
524
+ voiceName: "Polly.Bianca",
525
+ voiceLanguage: "it-IT",
526
+ callSid: callSid,
527
+ };
528
+
529
+ const tdChannel = new TiledeskChannel({
530
+ API_URL: API_URL,
531
+ redis_client: redis_client
532
+ })
533
+ tdChannel.setProjectId(project_id);
534
+
535
+ const tdTranslator = new TiledeskTwilioTranslator({
536
+ BASE_URL: BASE_URL
537
+ });
538
+
539
+ let button_action = ''
540
+ if(event === 'transfer'){
541
+ let callStatus = req.body.CallStatus;
542
+ switch(callStatus){
543
+ case CALL_STATUS.COMPLETED:
544
+ button_action = '#' + req.query.button_success;
545
+ break;
546
+ case CALL_STATUS.FAILED:
547
+ button_action = '#' + req.query.button_failure;
548
+ break;
549
+ }
550
+
551
+ switch(callStatus){
552
+ case CALL_STATUS.COMPLETED:
553
+ case CALL_STATUS.FAILED: {
554
+ //send message to tiledesk
555
+ let tiledeskMessage= {
556
+ //text:'\\close',
557
+ text:'/'+event,
558
+ senderFullname: from,
559
+ type: 'text',
560
+ channel: { name: CHANNEL_NAME },
561
+ attributes: {
562
+ subtype: "info",
563
+ action: button_action,
564
+ payload: {
565
+ event: event,
566
+ lastBlock: currentIntentName,
567
+ lastTimestamp: currentIntentTimestamp
568
+ }
569
+ }
570
+ };
571
+ let tdMessage = await tdChannel.send(tiledeskMessage, user.token, conversation_id);
572
+ }
573
+ }
574
+
575
+ }
576
+
577
+
578
+ //generate Tiledesk wait message
579
+ let delayTime = await voiceChannel.getNextDelayTimeForCallId(callSid)
580
+ let message = await tdChannel.generateWaitTdMessage(callSid, delayTime)
581
+ ///update delayIndex for wait command message time
582
+ await voiceChannel.saveDelayIndexForCallId(callSid)
583
+
584
+ // convert response to vxml
585
+ let messageToVXML = await tdTranslator.toVXML(message, callSid, vxmlAttributes)
586
+ winston.debug("(vxml) VXML to SEND: "+ messageToVXML);
587
+
588
+ res.set('Content-Type', 'text/xml');
589
+ res.status(200).send(messageToVXML);
590
+ })
591
+
592
+
593
+ /* ----> catch Twilio Events <----- */
594
+ router.post('/twilio/status',async (req, res) => {
595
+ winston.debug('+++++++++++(voice) called POST twilio/status ', req.body);
596
+
597
+ let event = req.body.CallStatus;
598
+ let callSid = req.body.CallSid;
599
+
600
+ let sessionInfo;
601
+ let project_id, conversation_id, user;
602
+ let from, to;
603
+
604
+ let redis_data = await redis_client.get('tiledesk:vxml:'+callSid+':session');
605
+ if (!redis_data) {
606
+ return res.status(500).send({ success: "false", message: "Can't retrive data for callSid ->" + callSid });
607
+ }
608
+ sessionInfo = JSON.parse(redis_data)
609
+ project_id = sessionInfo.project_id;
610
+ from = sessionInfo.from;
611
+ to = sessionInfo.to;
612
+ conversation_id = sessionInfo.conversation_id;
613
+ user = sessionInfo.user;
614
+
615
+ let vxmlAttributes = {
616
+ voiceName: "Polly.Bianca",
617
+ voiceLanguage: "it-IT",
618
+ callSid: callSid,
619
+ };
620
+
621
+ const tdChannel = new TiledeskChannel({
622
+ API_URL: API_URL,
623
+ redis_client: redis_client
624
+ })
625
+ tdChannel.setProjectId(project_id)
626
+
627
+ const tdTranslator = new TiledeskTwilioTranslator({
628
+ BASE_URL: BASE_URL
629
+ });
630
+
631
+
632
+ switch(event){
633
+ case CALL_STATUS.COMPLETED:{
634
+ //send message to tiledesk
635
+ let tiledeskMessage= {
636
+ //text:'\\close',
637
+ text:'/close',
638
+ senderFullname: from,
639
+ type: 'text',
640
+ channel: { name: CHANNEL_NAME },
641
+ attributes: {
642
+ subtype: "info",
643
+ action: 'close'+JSON.stringify({event: event}),
644
+ payload: {
645
+ catchEvent: event
646
+ },
647
+ timestamp: 'xxxxxx'
648
+ }
649
+ };
650
+
651
+ let tdMessage = await tdChannel.send(tiledeskMessage, user.token, conversation_id);
652
+
653
+ //remove session data for current callId and relative queue data
654
+ await redis_client.del('tiledesk:vxml:'+callSid+':session');
655
+ await redis_client.del('tiledesk:vxml:'+callSid+':delayIndex');
656
+ await tdChannel.clearQueue(conversation_id);
657
+ break;
658
+ }
659
+ }
660
+
661
+ res.status(200).send();
662
+
663
+ })
664
+
665
+ router.post('/twilio/fail',async (req, res) => {
666
+ winston.debug('+++++++++++(voice) called POST twilio/fail ', req.params)
667
+ winston.debug('+++++++++++(voice) called POST twilio/fail ', req.body)
668
+
669
+ res.set('Content-Type', 'text/xml');
670
+ res.status(200).send('<Response></Response>');
671
+ })
672
+
673
+
674
+ /* ----> catch Twilio Events <----- */
675
+ router.post('/record/:callSid/',async (req, res) => {
676
+ winston.debug('+++++++++++(voice) called POST record/:callSid ', req.body);
677
+
678
+ let callSid = req.body.CallSid;
679
+
680
+ let sessionInfo;
681
+ let project_id, conversation_id, user;
682
+ let from, to;
683
+
684
+ let redis_data = await redis_client.get('tiledesk:vxml:'+callSid+':session');
685
+ if (!redis_data) {
686
+ return res.status(500).send({ success: "false", message: "Can't retrive data for callSid ->" + callSid });
687
+ }
688
+ sessionInfo = JSON.parse(redis_data)
689
+ project_id = sessionInfo.project_id;
690
+ from = sessionInfo.from;
691
+ to = sessionInfo.to;
692
+ conversation_id = sessionInfo.conversation_id;
693
+ user = sessionInfo.user;
694
+
695
+ let vxmlAttributes = {
696
+ voiceName: "Polly.Bianca",
697
+ voiceLanguage: "it-IT",
698
+ callSid: callSid,
699
+ };
700
+
701
+ const tdChannel = new TiledeskChannel({
702
+ API_URL: API_URL,
703
+ redis_client: redis_client
704
+ })
705
+ tdChannel.setProjectId(project_id)
706
+
707
+ const tdTranslator = new TiledeskTwilioTranslator({
708
+ BASE_URL: BASE_URL
709
+ });
710
+
711
+
712
+ let audioFileUrl = req.body.RecordingUrl
713
+ let textMessage = await tdChannel.speechToText(audioFileUrl, 'whisper-1')
714
+ winston.debug('(voice) Message captured after STT -->', textMessage)
715
+
716
+ if(!textMessage){
717
+ //case NO_INPUT
718
+ const queryString = utils.buildQueryString(req.query);
719
+ console.log('case no input.. redirect '+ queryString)
720
+
721
+ return await axios({
722
+ url: "http://localhost:3000/handle/" + callSid + '/no_input'+ queryString,
723
+ headers: req.headers,
724
+ data: req.body,
725
+ method: 'POST'
726
+ }).then((response) => {
727
+ winston.debug("[TiledeskChannel] speechToText response : ", response.data);
728
+ return res.status(response.status).send(response.data);
729
+ }).catch((err) => {
730
+ winston.error("[TiledeskChannel] speechToText error: ", err);
731
+ return res.status(500).send({ success: false, message: "Errore while redirect to /handle for callSid " + callSid});;
732
+ })
733
+ }
734
+
735
+
736
+ let tiledeskMessage= {
737
+ text:textMessage,
738
+ senderFullname: from,
739
+ type: 'text',
740
+ channel: { name: CHANNEL_NAME }
741
+ };
742
+ let tdMessage = await tdChannel.send(tiledeskMessage, user.token, conversation_id);
743
+ winston.debug("message sent : ", tdMessage);
744
+ //generate Tiledesk wait message
745
+ let delayTime = await voiceChannel.getNextDelayTimeForCallId(callSid)
746
+ let message = await tdChannel.generateWaitTdMessage(from, delayTime)
747
+ //update delayIndex for wait command message time
748
+ await voiceChannel.saveDelayIndexForCallId(callSid)
749
+
750
+
751
+
752
+ res.set('Content-Type', 'application/xml');
753
+ res.status(200).send(message);
754
+
755
+ })
756
+
757
+
758
+ /** --> only for test purpose <-- **/
759
+ router.get('/test', async (req, res) => {
760
+ winston.info("(vxml) called GET /test" , req.query);
761
+
762
+ let project_id = req.query.id_project;
763
+ let callSid = req.body.CallSid;
764
+ let from = req.body.From;
765
+ let to = req.body.To;
766
+
767
+ if(!from || !to){
768
+ return res.status(404).send({error: "Error: Missing from/to parameters"})
769
+ }
770
+ from = utils.getNumber(from); //remove '+' from number
771
+ to = utils.getNumber(to); //remove '+' from number
772
+
773
+ const tdChannel = new TiledeskChannel({
774
+ API_URL: API_URL,
775
+ redis_client: redis_client,
776
+ });
777
+ tdChannel.setProjectId(project_id)
778
+
779
+ const tdTranslator = new TiledeskTwilioTranslator({
780
+ BASE_URL: BASE_URL
781
+ });
782
+
783
+ //let result = await tdChannel.signIn(ani)
784
+
785
+ //let conversation_id = await tdChannel.getConversation(ani, callId, result.token);
786
+ //let message = await tdChannel.sendMessageAndWait(ani, result.chat21.user_data, conversation_id, '/start');
787
+ let delayTime = await voiceChannel.getNextDelayTimeForCallId(callSid);
788
+ let waitTiledeskMessage = await tdChannel.generateWaitTdMessage(from, delayTime);
789
+
790
+ //console.log("response message: ", message);
791
+ let message2= {
792
+ attributes: {
793
+ commands: [
794
+ {
795
+ type: 'wait',
796
+ time: 500
797
+ },
798
+ {
799
+ type: 'message',
800
+ message: {
801
+ text: 'ciao',
802
+ }
803
+ }
804
+ ]
805
+ }
806
+ }
807
+
808
+ let message_buttons = {
809
+ attributes: {
810
+ disableInputMessage: true,
811
+ commands: [
812
+ {
813
+ type: 'wait',
814
+ time: 500
815
+ },
816
+ {
817
+ type: 'message',
818
+ message: {
819
+ text: 'ciao',
820
+ type: 'text',
821
+ attributes: {
822
+ attachment: {
823
+ buttons: [
824
+ {value: 'button1', type: 'action', target: 'blank', action: '#action1'},
825
+ {value: 'button2', type: 'action', target: 'blank', action: '#action2'}
826
+ ]
827
+ }
828
+ }
829
+ }
830
+ },
831
+ {
832
+ type: 'wait',
833
+ time: 500
834
+ },
835
+ {
836
+ type: 'message',
837
+ message: {
838
+ text: 'ciao 2',
839
+ type: 'frame',
840
+ metadata: {
841
+ src:"https://your-audio-url.com"
842
+ }
843
+ }
844
+ },
845
+ {
846
+ type: 'wait',
847
+ time: 0
848
+ },
849
+ /*{
850
+ type: 'settings',
851
+ subType: 'blind_transfer',
852
+ settings: {
853
+ transferTo: "6040",
854
+ transferType: "consultation",
855
+ falseIntent: "#falseIntenttt",
856
+ trueIntent: "#trueIntennt"
857
+ }
858
+ }*/
859
+ /*{
860
+ type: 'settings',
861
+ subType: 'dtmf_form',
862
+ settings: {
863
+ minDigits: 5,
864
+ maxDigits: 5,
865
+ terminators: "#"
866
+ }
867
+ }*/
868
+ {
869
+ type: 'settings',
870
+ subType: 'speech_form',
871
+ settings: {
872
+ noInputIntent: "#a",
873
+ noInputTimeout: 2000,
874
+ incompleteSpeechTimeout: 2000
875
+ }
876
+ }
877
+ ]
878
+ }
879
+ }
880
+
881
+
882
+ let vxmlAttributes = {
883
+ voiceName: "Polly.Bianca",
884
+ voiceLanguage: "it-IT",
885
+ callSid: callSid
886
+ };
887
+
888
+
889
+
890
+ // convert response to vxml
891
+ let messageToVXML = await tdTranslator.toVXML(message_buttons, callSid, vxmlAttributes)
892
+ //let disconnect = await tdChannel.disconnect();
893
+
894
+
895
+ res.set('Content-Type', 'text/xml');
896
+ res.status(200).send(messageToVXML);
897
+
898
+ //res.status(200).send(message);
899
+
900
+ })
901
+
902
+
903
+ async function connectRedis() {
904
+ /*redis_client = await redis.createClient({
905
+ host: REDIS_HOST,
906
+ port: REDIS_PORT,
907
+ password: REDIS_PASSWORD
908
+ });*/
909
+
910
+ if(REDIS_PASSWORD){
911
+ redis_client = await redis.createClient({
912
+ socket: {
913
+ host: REDIS_HOST,
914
+ port: REDIS_PORT
915
+ },
916
+ password: REDIS_PASSWORD,
917
+ })
918
+ }else {
919
+ redis_client = await redis.createClient({
920
+ url: 'redis://'+REDIS_HOST+':'+REDIS_PORT
921
+ })
922
+ }
923
+
924
+
925
+ redis_client.on('error', err => {
926
+ winston.info('(voice) Connect Redis Error ' + err);
927
+ })
928
+ /*
929
+ redis_client.on('connect', () => {
930
+ winston.info('Redis Connected!'); // Connected!
931
+ });
932
+ */
933
+ redis_client.on('ready', () => {
934
+ winston.info("(voice) Redis ready!")
935
+ })
936
+ await redis_client.connect(); // only for v4
937
+ require('bluebird').promisifyAll(redis_client)
938
+
939
+ }
940
+
941
+ async function startApp(settings, callback) {
942
+ winston.info("(voice) Starting VOICE TWILIO App");
943
+
944
+ if (!settings.MONGODB_URI) {
945
+ winston.error("(voice) MONGODB_URI is mandatory. Exit...");
946
+ return callback('Missing parameter: MONGODB_URI');
947
+ }
948
+
949
+ if (!settings.API_URL) {
950
+ winston.error("(voice) API_URL is mandatory. Exit...");
951
+ return callback('Missing parameter: API_URL');
952
+ } else {
953
+ API_URL = settings.API_URL;
954
+ winston.info("(voice) API_URL: " + API_URL);
955
+ }
956
+
957
+ if (!settings.BASE_URL) {
958
+ winston.error("(voice) BASE_URL is mandatory. Exit...");
959
+ return callback('Missing parameter: BASE_URL');
960
+ } else {
961
+ BASE_URL = settings.BASE_URL;
962
+ winston.info("(voice) BASE_URL: " + BASE_URL);
963
+ }
964
+
965
+ if (settings.BRAND_NAME) {
966
+ BRAND_NAME = settings.BRAND_NAME
967
+ }
968
+
969
+ if (settings.REDIS_HOST && settings.REDIS_PORT) {
970
+ REDIS_HOST = settings.REDIS_HOST;
971
+ REDIS_PORT = settings.REDIS_PORT;
972
+ REDIS_PASSWORD = settings.REDIS_PASSWORD;
973
+ await connectRedis();
974
+ } else {
975
+ winston.error("(voice) Missing redis parameters --> REDIS_HOST and REDIS_PORT");
976
+ }
977
+
978
+ //init VOICE CHANNEL
979
+ voiceChannel = new VoiceChannel({
980
+ BASE_POOLING_DELAY: settings.BASE_POOLING_DELAY || 250,
981
+ redis_client: redis_client,
982
+ })
983
+
984
+ if (settings.dbconnection) {
985
+ db.reuseConnection(settings.dbconnection, () => {
986
+ winston.info("(voice) KVBaseMongo reused exsisting db connection");
987
+ if (callback) {
988
+ callback(null);
989
+ }
990
+ })
991
+ } else {
992
+ db.connect(settings.MONGODB_URI, () => {
993
+ winston.info("(voice) KVBaseMongo successfully connected.");
994
+
995
+ if (callback) {
996
+ callback(null);
997
+ }
998
+ });
999
+ }
1000
+
1001
+ manageApp.startApp({
1002
+ API_URL: API_URL,
1003
+ BASE_URL: BASE_URL,
1004
+ BRAND_NAME: BRAND_NAME,
1005
+ DB: db,
1006
+ redis_client: redis_client
1007
+ }, (err) => {
1008
+ if (!err) {
1009
+ winston.info("Manage route succesfully started.");
1010
+ } else {
1011
+ winston.info("(voice) Unable to start API route.")
1012
+ }
1013
+ })
1014
+
1015
+ }
1016
+
1017
+ function readHTMLFile(templateName, callback) {
1018
+ fs.readFile(__dirname + '/template' + templateName, { encoding: 'utf-8' },
1019
+ function(err, html) {
1020
+ if (err) {
1021
+ throw err;
1022
+ //callback(err);
1023
+ } else {
1024
+ callback(null, html)
1025
+ }
1026
+ })
1027
+ }
1028
+
1029
+ module.exports = { router: router, startApp: startApp };