Whisper Funlet
eric-brechemier opened this issue · comments
Migrate the undocumented Whisper Twimlet to an equivalent Twilio Function Template.
Stage 1
Gather a single digit, requested:
- by playing a sound file from the URL provided in
Message
, if it starts withhttp
- by saying the message provided in
Message
, if any, using an English voice - or by saying a default message in English stating the number of the caller and ending with
Press any key to accept.
IF the HumanCheck
flag is set
AND no digit has been entered
(gather times out after waiting for 5 seconds more after the end of the message)
THEN hang up
Stage 2
WHEN a digit has been entered (Digits
parameter is set and non-empty)
THEN accept the call (return an empty document)
Annotated Source Code
based on a snapshot of Whisper TwiML source code (whisper.php)
sent to me by Twilio support by email on 2019-07-08
This is PHP code.
<?php
It is shared by Twilio under the MIT license.
/*
Copyright (c) 2012 Twilio, Inc.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/
It uses the (deprecated) class Twilio\Twiml of the twilio-php helper library to generate TwiML.
use Twilio\Twiml;
Start a new TwiML document.
// initiate response library
$response = new Twiml();
Stage 2
// if we have a Digits= parameter, then this is the 2nd loop of this script
if (isset($_REQUEST['Digits'])) {
// if a digit was pressed, then let us drop through
if (!strlen($_REQUEST['Digits'])) {
This part seems unreachable: <Gather/>
falls through to the rest of the document without triggering the action
URL when no digits have been pressed:
However, if the caller did not enter any digits or speech, call flow would continue in the original TwiML document.
— https://www.twilio.com/docs/voice/twiml/gather#action
// no digit was pressed, so just hangup
$response->hangup();
}
// otherwise, we'll just return an empty document, which will bridge the calls
Stage 1
} else {
// no digits submitted, so this is the first run of the whisper file
// grab the caller's phone number
$from = strlen($_REQUEST['From']) ? $_REQUEST['From'] : $_REQUEST['Caller'];
// add a Gather to get the digits when pressed
$gather = $response->gather(array(
'numDigits' => 1,
));
// figure out the message
// first, check to see if we have an http URL (simple check)
if (strtolower(substr(trim($_GET['Message']), 0, 4)) == 'http') {
$gather->play($_GET['Message']);
}
// check if we have any message, if so, read it back
elseif (strlen(trim($_GET['Message']))) {
$gather->say(stripslashes($_GET['Message']));
}
// no message, just use a default
else {
$gather->say('You are receiving a call from ' . preg_replace('/([^\s])/', '$1. ', $from) . '. Press any key to accept.');
}
// if we're screening to check for a person answering, hangup the call if gather falls through
if (isset($_REQUEST['HumanCheck'])) {
$response->hangup();
}
}
Send the XML content-type header and the TwiML body.
// send response
if (!headers_sent()) {
header('Content-type: text/xml');
}
echo $response;
Test Cases
[WHISPER-1-1] Recorded Message
Input
Parameter | Value |
---|---|
Message |
https://example.com/recorded-message.mp3 |
Output
$ curl -s 'https://twimlets.com/whisper?Message=https%3A%2F%2Fexample.com%2Frecorded-message.mp3' \
| xmllint --format -
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Gather numDigits="1">
<Play>https://example.com/recorded-message.mp3</Play>
</Gather>
</Response>
[WHISPER-1-2] Text Message
Input
Parameter | Value |
---|---|
Message |
Text message |
Output
$ curl -s 'https://twimlets.com/whisper?Message=Text%20message' \
| xmllint --format -
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Gather numDigits="1">
<Say>Text message</Say>
</Gather>
</Response>
[WHISPER-1-3] Default Message
Input
Parameter | Value |
---|---|
Message |
(omitted or empty) |
From |
+1-916-555-0123 |
Output
$ curl -s 'https://twimlets.com/whisper?Message=&From=%2B19165550123' \
| xmllint --format -
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Gather numDigits="1">
<Say>You are receiving a call from +. 1. 9. 1. 6. 5. 5. 5. 0. 1. 2. 3. . Press any key to accept.</Say>
</Gather>
</Response>
[WHISPER-1-4] Human Check
Input
Parameter | Value |
---|---|
Message |
Text message |
HumanCheck |
true |
Output
$ curl -s 'https://twimlets.com/whisper?Message=Text%20message&HumanCheck=true' \
| xmllint --format -
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Gather numDigits="1">
<Say>Text message</Say>
</Gather>
<Hangup/>
</Response>
[WHISPER-2-1] A Digit was Pressed
Input
Parameter | Value |
---|---|
Digits |
5 |
Output
$ curl -s 'https://twimlets.com/whisper?Digits=5' \
| xmllint --format -
<?xml version="1.0" encoding="UTF-8"?>
<Response/>
[WHISPER-2-2] No Digits were Pressed
Input
Parameter | Value |
---|---|
Digits |
(empty) |
Output
$ curl -s 'https://twimlets.com/whisper?Digits=' \
| xmllint --format -
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Hangup/>
</Response>
Unit Tests
PASS funlet-whisper/funlet-whisper.test.js
✓ [WHISPER-INPUT-MESSAGE-1] Read Message from Event (3ms)
✓ [WHISPER-INPUT-MESSAGE-2] Read Message from Environment
✓ [WHISPER-INPUT-MESSAGE-3] Read Default Message from Script (with number in From parameter) (1ms)
✓ [WHISPER-INPUT-MESSAGE-4] Read Default Message from Script (with number in Caller parameter)
✓ [WHISPER-INPUT-LANGUAGE-1] Read Language from Event
✓ [WHISPER-INPUT-LANGUAGE-2] Read Language from Environment
✓ [WHISPER-INPUT-LANGUAGE-3] Read Default Language from Script (1ms)
✓ [WHISPER-INPUT-VOICE-1] Read Voice from Event
✓ [WHISPER-INPUT-VOICE-2] Read Voice from Environment
✓ [WHISPER-INPUT-VOICE-3] Read Default Voice from Script
✓ [WHISPER-INPUT-HUMAN-CHECK-0] Read Human Check "1" from Event (1ms)
✓ [WHISPER-INPUT-HUMAN-CHECK-1] Read Human Check from Event
✓ [WHISPER-INPUT-HUMAN-CHECK-2] Read Human Check from Environment
✓ [WHISPER-INPUT-HUMAN-CHECK-3] Read Default Human Check from Script (1ms)
✓ [WHISPER-INPUT-DIGITS-0] Read No Digits from Event
✓ [WHISPER-INPUT-DIGITS-1] Read Empty Digits from Event
✓ [WHISPER-INPUT-DIGITS-2] Read Non-Empty Digits from Event
✓ [WHISPER-OUTPUT-SPELL-1] Spell a phone number digit by digit
✓ [WHISPER-OUTPUT-WHISPER-1-2] Recorded Message (3ms)
✓ [WHISPER-OUTPUT-WHISPER-1-4] Human Check (1ms)
✓ [WHISPER-OUTPUT-WHISPER-2-0] No Digits Provided
✓ [WHISPER-OUTPUT-WHISPER-2-1] A Digit was Pressed
✓ [WHISPER-OUTPUT-WHISPER-2-2] No Digits were Pressed, Empty Digits Set (1ms)
✓ [WHISPER-1-1] Full Response: Recorded Message
✓ [WHISPER-1-4] Full Response: Human Check (1ms)
✓ [WHISPER-2-1] Full Response: A Digit was Pressed (2ms)
Integration Tests
With https://$DOMAIN.twil.io/whisper
the URL of a copy of the Whisper Funlet deployed in my Twilio account with checks for signed requests disabled, we get:
$ ./test-whisper.sh "https://$DOMAIN.twil.io/whisper"
[WHISPER-1-1] Recorded Message
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Gather numDigits="1">
<Play>https://example.com/recorded-message.mp3</Play>
</Gather>
</Response>
[WHISPER-1-2] Text Message
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Gather numDigits="1">
<Say language="en" voice="alice">Text message</Say>
</Gather>
</Response>
[WHISPER-1-3] Default Message
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Gather numDigits="1">
<Say language="en" voice="alice">You are receiving a call from +. 1. 9. 1. 6. 5. 5. 5. 0. 1. 2. 3. . Press any key to accept.</Say>
</Gather>
</Response>
[WHISPER-1-4] Human Check
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Gather numDigits="1">
<Say language="en" voice="alice">Text message</Say>
</Gather>
<Hangup/>
</Response>
[WHISPER-2-1] A Digit was Pressed
<?xml version="1.0" encoding="UTF-8"?>
<Response/>
[WHISPER-2-2] No Digits were Pressed
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Hangup/>
</Response>
And the limited differences with the Twimlet output look alright:
$ ./test-whisper.sh "https://$DOMAIN.twil.io/whisper" | diff twimlet-whisper.log -
13c13
< <Say>Text message</Say>
---
> <Say language="en" voice="alice">Text message</Say>
21c21
< <Say>You are receiving a call from +. 1. 9. 1. 6. 5. 5. 5. 0. 1. 2. 3. . Press any key to accept.</Say>
---
> <Say language="en" voice="alice">You are receiving a call from +. 1. 9. 1. 6. 5. 5. 5. 0. 1. 2. 3. . Press any key to accept.</Say>
29c29
< <Say>Text message</Say>
---
> <Say language="en" voice="alice">Text message</Say>