Skip to content

Instantly share code, notes, and snippets.

@luddilo
Last active May 19, 2020 13:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save luddilo/772c30c5fb607806e6680445b6557105 to your computer and use it in GitHub Desktop.
Save luddilo/772c30c5fb607806e6680445b6557105 to your computer and use it in GitHub Desktop.
A voximplant "scenario" supporting both Incomign and Outgoing calls with a Dialogflow agent built with Narratory
require(Modules.AI)
var debug = true
// Agent settings
var agentId = YOUR_DIALOGFLOW_AGENT_ID_IN_VOXIMPLANT // Should be a number, for example 2600. Check the Dialogflow Connector menu in Voximplant
var CALLER_ID = "YOUR_REAL_VOXIMPLANT_PHONE_NUMBER" // Cannot be a virtual number. Format should be for example 46701231212 for +46701231212
var lang = DialogflowLanguage.SWEDISH
// Other variables
var dialogflow, call, hangup
var singleResponse = false
var shortAnswers = []
// Inbound call processing - remove processing by commenting the code
VoxEngine.addEventListener(AppEvents.CallAlerting, (e) => {
log("Incoming call received")
call = e.call
call.addEventListener(CallEvents.Connected, onCallConnected)
call.addEventListener(CallEvents.Disconnected, VoxEngine.terminate)
call.answer()
})
function makeOutgoingCall(number) {
call = VoxEngine.callPSTN(number, CALLER_ID) // replace CALLER_ID with the number we bought in previous step (the real one, test number won't work)
call.addEventListener(CallEvents.Connected, onCallConnected)
call.addEventListener(CallEvents.Disconnected, VoxEngine.terminate)
call.addEventListener(CallEvents.Failed, VoxEngine.terminate)
}
function log(message, force) {
if (debug | force) {
Logger.write("#### " + message)
}
}
function getInputData() {
var data = {}
try {
data = JSON.parse(VoxEngine.customData())
log("Custom-data received: " + JSON.stringify(data))
} catch (e) {
log(
"Could not parse custom-data, so this is likely an incoming call. Message: " +
e.message,
true
)
return {}
}
return data
}
// Create outbound call as soon as StartScenarios HTTP API arrives
VoxEngine.addEventListener(AppEvents.Started, (e) => {
log("Got scenario start event")
let number = getInputData().number // We try to fetch the number, if we get one, we try to call the number. If not, it is likely an incoming call
if (number) {
log("Number received in payload, making call to " + number)
makeOutgoingCall(number)
}
})
function onCallConnected(e) {
// Get metadata from our input
let { sessionId, name } = getInputData()
// Create Dialogflow object
log("Initializing Dialogflow client")
dialogflow = AI.createDialogflow({
agentId: agentId,
lang: lang,
queryParameters: {
payload: {
platform: "voximplant",
},
},
singleUtterance: false,
sessionId: sessionId,
})
// Handler for when we get a response from Dialogflow
dialogflow.addEventListener(
AI.Events.DialogflowResponse,
onDialogflowResponse
)
// Sending WELCOME event to let the agent says a welcome message
log("Sending welcome message to Dialogflow")
dialogflow.sendQuery({ event: { name: "WELCOME", language_code: lang } })
// Playback marker used for better user experience
dialogflow.addMarker(-300)
// Start sending media from Dialogflow to the call
log("Starting streaming media from Dialogflow to call")
dialogflow.sendMediaTo(call)
dialogflow.addEventListener(AI.Events.DialogflowPlaybackStarted, (e) => {
// Dialogflow TTS playback started
log("Audio playback started")
})
dialogflow.addEventListener(
AI.Events.DialogflowPlaybackMarkerReached,
(e) => {
// Playback marker reached - start sending audio from the call to Dialogflow
log("Marker reached, so starting to send audio to Dialogflow")
call.sendMediaTo(dialogflow)
}
)
dialogflow.addEventListener(AI.Events.DialogflowPlaybackFinished, (e) => {
// Dialogflow TTS playback finished. Hangup the call if hangup flag was set to true
log("Audio playback finished")
if (hangup) {
call.hangup()
log("Hanging up")
}
})
}
// Handle Dialogflow responses
function onDialogflowResponse(e) {
// If we have an intermediary recognition result from Dialogflow
if (e.response.queryResult) {
let webhookPayload = e.response.queryResult.webhookPayload
if (
webhookPayload &&
webhookPayload.expectShortAnswer &&
webhookPayload.shortAnswers &&
webhookPayload.shortAnswers.length > 0
) {
log(
"Expecting a short answer, so starting check for intermediary speech recognition results"
)
singleResponse = true
shortAnswers = webhookPayload.shortAnswers
} else {
singleResponse = false
shortAnswers = []
}
}
if (e.response.recognitionResult && !e.response.queryResult) {
const transcript = e.response.recognitionResult.transcript
log("Caught an intermediery speech response")
if (transcript) {
if (e.response.recognitionResult.isFinal) {
log("Got a final response from Dialogflow so sending query")
call.stopMediaTo(dialogflow) // Stop sending media to Dialogflow
} else if (singleResponse) {
log("Checking for single-response match of intermediary response")
if (shortAnswers.includes(transcript.toLowerCase())) {
log(
"Intermediery speech response that matched one of the short-answers, sending query directly to DF"
)
singleResponse = false // Reset settings for shortAnswer
call.stopMediaTo(dialogflow) // Stop sending media to Dialogflow
} else {
log(
"Intermediery speech response didn't match single-response, continuing"
)
}
}
} else {
log("Intermediary response had no transcript, so doing nothing")
}
} else if (e.response.queryResult && e.response.responseId) {
// We get a final response from Dialogflow
handleReply(e)
} else {
// In case of response with queryResult but without responseId we can continue sending media to dialogflow
log("No responseId yet so continuing sending media to Dialogflow")
call.sendMediaTo(dialogflow)
}
}
function handleReply(e) {
if (
e.response.queryResult.webhookPayload &&
e.response.queryResult.webhookPayload.endOfConversation
) {
hangup = true
}
// Telephony messages arrive in fulfillmentMessages array
if (e.response.queryResult.fulfillmentMessages != undefined) {
log("Got an answer from Dialogflow")
}
}
// Process telephony messages from Dialogflow
function processTelephonyMessage(msg) {
log(
"Processing message, not really doing anything. " +
JSON.stringify(msg, null, 2)
)
// Transfer call to msg.telephonyTransferCall.phoneNumber
if (msg.telephonyTransferCall !== undefined) {
/**
* Example:
* dialogflow.stop()
* let newcall = VoxEngine.callPSTN(msg.telephonyTransferCall.phoneNumber, "put verified CALLER_ID here")
* VoxEngine.easyProcess(call, newcall)
*/
}
// Synthesize speech from msg.telephonySynthesizeSpeech.text
if (msg.telephonySynthesizeSpeech !== undefined) {
// See the list of available TTS languages at https://voximplant.com/docs/references/voxengine/language
// Example:
// if (msg.telephonySynthesizeSpeech.ssml !== undefined) call.say(msg.telephonySynthesizeSpeech.ssml, {"language": VoiceList.Amazon.en_US_Joanna})
// else call.say(msg.telephonySynthesizeSpeech.text, {"language": VoiceList.Amazon.en_US_Joanna})
}
// Play audio file located at msg.telephonyPlayAudio.audioUri
if (msg.telephonyPlayAudio !== undefined) {
// audioUri contains Google Storage URI (gs://), we need to transform it to URL (https://)
let url = msg.telephonyPlayAudio.audioUri.replace(
"gs://",
"https://storage.googleapis.com/"
)
// Example: call.startPlayback(url)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment