This repository has been archived by the owner on Mar 7, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 119
/
dialogflow.js
157 lines (146 loc) · 5.4 KB
/
dialogflow.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
const Srf = require('drachtio-srf');
const srf = new Srf();
const Mrf = require('drachtio-fsmrf');
const mrf = new Mrf(srf);
const config = require('config');
const projectId = config.get('dialogflow.project');
const lang = config.get('dialogflow.lang');
const startEvent = config.get('dialogflow.event');
srf.connect(config.get('drachtio'))
.on('connect', (err, hp) => console.log(`connected to sip on ${hp}`))
.on('error', (err) => console.log(err, 'Error connecting'));
mrf.connect(config.get('freeswitch'))
.then((ms) => run(ms));
function run(ms) {
srf.invite((req, res) => {
ms.connectCaller(req, res)
.then(({endpoint, dialog}) => {
dialog.on('destroy', () => endpoint.destroy());
setHandlers(endpoint, dialog);
dialog.locals = {
callingNumber: req.callingNumber,
source_address: req.source_address
};
endpoint.api('dialogflow_start', `${endpoint.uuid} ${projectId} ${lang} 30 ${startEvent}`);
})
.catch((err) => {
console.log(err, 'Error connecting call to freeswitch');
});
});
}
function setHandlers(ep, dlg) {
ep.addCustomEventListener('dialogflow::intent', onIntent.bind(null, ep, dlg) );
ep.addCustomEventListener('dialogflow::transcription', onTranscription);
ep.addCustomEventListener('dialogflow::audio_provided', onAudioProvided.bind(null, ep, dlg));
ep.addCustomEventListener('dialogflow::end_of_utterance', onEndOfUtterance);
ep.addCustomEventListener('dialogflow::error', onError);
}
// event handler: we just received an intent
// action: if 'end_interaction' is true, end the dialog after playing the final prompt
// (or in 1 second if there is no final prompt)
function onIntent(ep, dlg, evt) {
const responseId = evt.response_id;
console.log(`got intent ${responseId}: ${JSON.stringify(evt)}`);
if (responseId.length === 0) {
console.log('no intent was detected, reprompt...');
ep.api('dialogflow_start', `${ep.uuid} ${projectId} ${lang} 30 actions_intent_NO_INPUT`);
return;
}
const transferTo = checkIntentForCallTransfer(evt);
if (transferTo) {
console.log(`transfering call to ${transferTo} after prompt completes`);
ep.transferTo = transferTo;
}
if (evt.query_result.intent.end_interaction || transferTo) {
ep.hangupAfterPlayDone = !transferTo;
ep.waitingForPlayStart = true;
setTimeout(() => {
if (ep.waitingForPlayStart) dlg.destroy();
}, 1000);
}
}
// event handler: we just received a transcription
// action: nothing, just log the transcription if this was a final transcription
function onTranscription(transcription) {
if (transcription.recognition_result.is_final) {
console.log(`got transcription: ${JSON.stringify(transcription)}`);
}
}
// event handler: we just got an audio clip we can play
// action: play the clip, and when it ends send another DialogIntentRequest
async function onAudioProvided(ep, dlg, evt) {
console.log(`got audio file to play: ${evt.path}`);
ep.waitingForPlayStart = false;
await ep.play(evt.path);
if (ep.hangupAfterPlayDone) {
console.log('hanging up since intent was marked end interaction');
dlg.destroy();
}
else if (ep.transferTo) {
const doRefer = config.has('transferMethod') && config.get('transferMethod') === 'REFER';
console.log(`transfering call to ${ep.transferTo} using ${doRefer ? 'REFER' : 'INVITE'}`);
if (doRefer) {
dlg.request({
method: 'REFER',
headers: {
'Refer-To': `<sip:${ep.transferTo}@${dlg.locals.source_address}>`,
'Referred-By': `<sip:${dlg.locals.callingNumber}@${dlg.locals.source_address}>`,
'Contact': '<sip:localhost>'
}
});
dlg.on('notify', (req, res) => {
res.send(200);
logger.info(`received NOTIFY with ${req.body}`);
if (req.get('Subscription-State').match(/terminated/)) {
logger.info('hanging up after transfer completes');
dlg.destroy();
ep.destroy();
}
});
}
else {
const srf = dlg.srf;
try {
const dlgB = await srf.createUAC(
`sip:${ep.transferTo}@${dlg.locals.source_address}`,
{
localSdp: dlg.remote.sdp,
callingNumber: dlg.locals.callingNumber
}
);
dlg.removeAllListeners('destroy');
ep.destroy();
dlg.other = dlgB;
dlgB.other = dlg;
[dlg, dlgB].forEach((d) => {
d.on('destroy', () => d.other.destroy());
});
}
catch (err) {
console.log(err, `Call transfer outdial failed with ${err.status}`);
}
}
}
else ep.api('dialogflow_start', `${ep.uuid} ${projectId} ${lang} 30`);
}
// event handler: speaker just completed saying something
// action: nothing, just log the event
function onEndOfUtterance(evt) {
console.log(`got end of utterance: ${JSON.stringify(evt)}`);
}
// event handler: dialog flow error of some kind
// action: just log it
function onError(evt) {
console.log(`got error: ${JSON.stringify(evt)}`);
}
function checkIntentForCallTransfer(intent) {
if (!intent.query_result || !intent.query_result.fulfillment_messages) return;
const telephonyPlatform = intent.query_result.fulfillment_messages.find((f) =>{
return f.platform === 'TELEPHONY' &&
f.telephony_transfer_call &&
f.telephony_transfer_call.phone_number;
});
if (telephonyPlatform) {
return telephonyPlatform.telephony_transfer_call.phone_number;
}
}