このページは大阪弁化フィルタによって翻訳生成されたんですわ。

翻訳前ページへ


Perl.com
The Wayback Machine - http://web.archive.org/web/20120304234034/http://www.perl.com/pub/

Thanks to Lars D?????? for coordinating entries into the Plat_Forms 2012 web development contest. This annual contest pits some of the best developers of frameworks and languages against each other to solve real problems. Perl developers have participated for several years. Teams using Catalyst, Dancer, and Mojolicious have all demonstrated their platform strengths and weaknesses.

No Perl team has won the contest—yet. With the rise of modern Perl 5, the extra energy from competition between all of the great platforms, and (of course) the power of the CPAN, we all expect great things. Consider entering and showing what we can do.

Perl makes writing Twilio telephony applications simple and enjoyable. My previous article, Automating Telephony with Perl and Twilio showed how to use WWW::Twilio::API to make a phone survey using Twilio's text-to-speech synthesizer as well as send an SMS message to a phone number of your choice.

This article builds an easy phone menu application using Twilio's TwiML application language, WWW::Twilio::TwiML, and Mojolicious.

Installation and Twilio account creation

To begin, install WWW::Twilio::TwiML and Mojolicious using your favorite method (see the previous article for more ideas). You'll need a Twilio account (it's free to create an account, plus Twilio gives new users US $30 for calls--plenty for several weeks of testing). The previous article covers installation and getting setup with a Twilio account in more detail. With WWW::Twilio::TwiML and Mojolicious installed and a Twilio account active, it's a great time to get a little more familiar with Twilio's Dashboard.

Twilio Dashboard

The Twilio Dashboard is where to find your Twilio sandbox information. The sandbox section of the dashboard is located below the fold of the dashboard page:

Twilio Dashboard

You should see the sandbox number and pin. You can set our inbound voice and inbound SMS handler URLs there (I'll explain those soon.) First, a primer on how Twilio works.

TwiML in the Twilio flow

Twilio's basic flow goes something like:

Handling an inbound call to Twilio
(This diagram is not an official Twilio diagram; it merely describes the author's mental model of how Twilio works and may differ wildly from Twilio's actual implementation.)

  1. The caller dials (or sends an SMS to) "555-867-5309" on their phone--this is your Twilio sandbox or purchased number. Twilio's inbound call dispatcher receives the call or text.

  2. Once the connection is made, the dispatcher makes an HTTP GET or POST to the Voice or SMS URL specified given for this number. Remember the Voice URL in the Sandbox App of the Twilio Dashboard shown above? That's the one. For purchased Twilio numbers, you set the voice and SMS URLs under the "Numbers" tab in the Dashboard.

  3. The Voice or SMS URL specified in the Sandbox App responds to the Twilio request with a TwiML document. Twilio's TwiML parser reads this document, then executes the "verbs" specified in the TwiML document. For example, if the TwiML document contained a <Say> verb, Twilio's text-to-speech synthesizer would "read" the text to the caller. If the document specified a <Dial> verb and number, Twilio would dial the number and connect the caller to it. <Redirect> verbs tell Twilio to fetch another TwiML document.

You may recall an example in the previous article which used Twilio's voicemail TwiML handler to conduct a brief phone survey. While you can sometimes manipulate third-party TwiML applications to do what you want, TwiML is so simple to use that you'll find it's often easier to write your own.

TwiML basics

TwiML is a subset of XML. Here is a TwiML document that when read by Twilio's parser, will say to the caller, "Foosball at 10 o'clock!" using the text-to-speech synthesizer:

<?xml version="1.0" encoding="UTF-8" ?>
<Response>
  <Say voice="man">Foosball at 10 o&apos;clock!</Say>
</Response>

Some TwiML verbs are nestable:

<?xml version="1.0" encoding="UTF-8" ?>
<Response>
  <Gather action="/menu.cgi" numDigits="1" method="GET">
    <Say voice="man">
      Ping pong at high noon!
      Hit 1 if you&apos;re with me.
      Hit 2 if you&apos;re a loser.
    </Say>
  </Gather>

  <Say>You must make a choice!</Say>
</Response>

The <Gather> verb tells Twilio to start gathering key presses. Meanwhile, Twilio will hand off the contents of the <Say> verb to the text-to-speech handler. Nesting the <Say> inside of the <Gather> lets the caller push a key anytime during the <Say> verb to interrupt it and process your choice. The final <Say> element only executes if the <Gather> fails (e.g., the caller doesn't press a key).

Twilio's excellent documentation details all of the available TwiML verbs. Armed with a little knowledge, you're dangerouly close to making something useful.

Simple phone menu

You've been asked to build a phone menu system for a young urban professional and his family. The application should accept an incoming call, prompt the caller with a numeric menu, and connect the caller with the number of their choice.

No problem. Start with a simple static TwiML document called menu.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<Response>
  <Gather method="GET" action="/menu_handler" numDigits="1">
    <Say>Press 1 for Ryan</Say>
    <Say>Press 2 for Liz</Say>
    <Say>Press 3 for Jason</Say>
    <Say>Press 4 for Erin</Say>
    <Say>Press 5 for Rachel</Say>
    <Say>Press 6 for Gilligan</Say>
    <Say>Press 7 for Potsie</Say>
  </Gather>
</Response>

You can put this file on the web and update the URL in the "Voice URL" field of the Sandbox section of the Dashboard. Anyone who dials the sandbox number, will hear a Twilio's text-to-speech voice reading the menu options. Anybody remember the "S.A.M." speech synthesizer from the mid 80's (Commodore 64 or Atari 800)? You've come a long way, baby!

While this static TwiML file would work, it does mean you'll have two files to update when a phone number changes. Fix that by consolidating the /menu and /menu_handler "routes" (the Mojolicious word for URL handlers) into a single program to generate TwiML dynamically.

WWW::Twilio::TwiML, briefly

WWW::Twilio::TwiML is a special-purpose XML generator and supports several programming styles. For example, the following two code snippets create this TwiML document:

<?xml version="1.0" encoding="UTF-8" ?>
<Response>
    <Say voice="man">Kilroy was here</Say>
</Response>

Snippet number 1:

my $say = WWW::Twilio::TwiML->new();
$say−>name('Say');
$say−>content("Kilroy was here");
$say−>attributes({voice => "man"});

my $resp = WWW::Twilio::TwiML->new();
$resp−>name('Response');
$resp−>content($say);

my $tw = WWW::Twilio::TwiML->new();
$tw−>content($resp);
print $tw−>to_string;

And snippet number 2:

my $tw = WWW::Twilio::TwiML->new();
$tw−>Response−>Say({voice => "man"}, "Kilroy was here");
print $tw−>to_string;

The second snippet uses a technique called "method chaining"; if you've used the jQuery module for Javascript, you may already know how powerful chaining object methods can be in certain contexts. WWW::Twilio::TwiML makes chaining possible because each TwiML verb method is a constructor of another WWW::Twilio::TwiML object.

When you chain TwiML objects like this:

$tw->Response->Say("Eat at Joe&apos;");

The Response object is created as a child of the top $tw object. The Say object is created as the child of the Response object. When the $tw object's to_string method is invoked, like this:

print $tw->to_string;

WWW:Twilio::TwiML crawls down $tw's list of children, recursively invoking to_string until the last child. The whole process creates this output:

<?xml version="1.0" encoding="UTF-8" ?>
<Response>
    <Say>Eat at Joe&apos;</Say>
</Response>

It's may look a little foreign, but it makes for concise and readable TwiML.

Getting your mojo on

Now that you have a feel for creating TwiML documents, you need a way to serve them from the web.

Mojolicious is an easy-to-use web application framework. Just a few lines and you have a sweet little web app to serve TwiML. Feel free to read a little of the Mojolicious documentation. This author highly recommends going through the Mojolicious::Lite documentation first: almost everything you learn in Mojolicious::Lite also applies to the larger Mojolicious application framework.

To write just enough Mojolicious to do what you've already done with the static TwiML document, you need only two modules for this entire application. (Neither has any dependencies--you're welcome.) Keep in mind that Mojolicious::Lite enables 'warnings' and 'strict' by default: no sloppy programming allowed!

This application implements a Mojolicious handler for the /menu route which is only called when the application receives an HTTP GET to the /menu URI.

#!/usr/bin/env perl

use Mojolicious::Lite;
use WWW::Twilio::TwiML;

get '/menu' => sub {
    my $self = shift;

    $self->render(format => 'xml',
                  text   => <<'_TWIML_');
<?xml version="1.0" encoding="UTF-8" ?>
<Response>
  <Gather method="GET" action="/menu_handler" numDigits="1">
    <Say>Press 1 for Ryan</Say>
    <Say>Press 2 for Liz</Say>
    <Say>Press 3 for Jason</Say>
    <Say>Press 4 for Erin</Say>
    <Say>Press 5 for Rachel</Say>
    <Say>Press 6 for Gilligan</Say>
    <Say>Press 7 for Potsie</Say>
  </Gather>
</Response>
_TWIML_
};

app->start;

That's all it takes for a Mojolicious application (source here). Mojolicious packs a full stack HTTP 1.1 web server, making tests easy. In one shell, start your Mojolicious program as a daemon (use Ctrl-c to stop when finished):

$ perl menu_part daemon
[Mon Nov 14 21:41:34 2011] [info] Server listening (http://*:3000)
Server available at http://127.0.0.1:3000.

In another shell, pretend you're Twilio's application server and fetch the TwiML:

$ curl http://localhost:3000/menu
<?xml version="1.0" encoding="UTF-8" ?>
<Response>
  <Gather method="GET" action="/menu_handler" numDigits="1">
    <Say>Press 1 for Ryan</Say>
    <Say>Press 2 for Liz</Say>
    <Say>Press 3 for Jason</Say>
    <Say>Press 4 for Erin</Say>
    <Say>Press 5 for Rachel</Say>
    <Say>Press 6 for Gilligan</Say>
    <Say>Press 7 for Potsie</Say>
  </Gather>
</Response>

Is this progress? This is same TwiML as in menu.xml, but now it's created with Mojolicious. At this point you can upload your app to a publicly accessible server, and, assuming Mojolicious is also installed there, you can start your application. Then go to the Twilio Dashboard and replace the existing Voice URL with:

http://your.server.org:3000/menu

Important note about Mojolicious and HTTP GET

Mojolicious's get method only responds to HTTP GET method requests. Make sure that when you set the Voice URL in the Twilio Dashboard, you also change the HTTP method to GET. Alternatively, you could use Mojolicious's post method, which only responds to HTTP POST, or the any method, which accepts both GET and POST (as well as PUT and DELETE), but making Twilio use HTTP GET seems more appropriate.

Putting it all together

Now it's time to improve the program because it still serves static TwiML. It also needs a programmatic way tell Twilio what to do when the caller presses a key.

Start over and make a hash for the phone menu. This could be put into a separate file--and probably should be--but it wouldn't be a proper tutorial without something left as an exercise.

#!/usr/bin/env perl

use Mojolicious::Lite;
use WWW::Twilio::TwiML;

my %list = ( 1 => { name => 'Ryan',
                    number => '+19165557720' },
             2 => { name => 'Liz',
                    number => '+19165551211' },
             3 => { name => 'Jason',
                    number => '+19285550122' },
             4 => { name => 'Erin',
                    number => '+19285551729' },
             5 => { name => 'Rachel',
                    number => '+18015553992' },
             6 => { name => 'Gilligan',
                    recording => 'http://www.televisiontunes.com/'
                              .     'themesongs/Gilligans%20Island.mp3' },
             7 => { name => 'Potsie',
                    recording => 'http://www.televisiontunes.com/'
                              .     'themesongs/Happy%20Days%20-%20Season%202.mp3' },
           );

get '/menu' => sub {
    my $self = shift;

This should looks mostly familiar so far. Now create $msg which holds all of the text you want Twilio to "Say" to the caller:

    my $msg = join '. ',
      map { "Press $_ for $list{$_}->{name}" }
        sort keys %list;

It's time to build the TwiML document, and this could use some error handling in case the caller doesn't press a key. Finally, print everything via Mojolicious's render() method:

    my $tw   = WWW::Twilio::TwiML->new();
    my $resp = $tw->Response;
    $resp->Gather({action    => $self->url_for('/menu_handler'),
                   method    => 'GET',
                   numDigits => 1})
         ->Say({voice => 'woman'}, $msg);

    $resp->Say("You need to make a choice or hang up.");
    $resp->Redirect("/menu");

    $self->render(format => 'xml',
                  text   => $tw->to_string);
};

Remember to start Mojolicious's event loop; this code should always go at the end of your application, as it never returns:

app->start;

(Source here). If declarative-style programming is new to you, don't fret: under the hood, Mojolicious simply creates a map for itself that says "when I receive an HTTP GET for '/menu', I should execute this subroutine". The app->start routine begins a loop that waits for said request, then handles it as you've defined (declared) it.

The menu handler route

Most of the hard work is done now. As specified in the Gather's action attribute above, when the caller presses a key, Twilio will perform an HTTP GET on /menu_handler. Add one more Mojolicious route:

get '/menu_handler' => sub {
    my $self   = shift;
    my $choice = $self->param('Digits') || 0;

    my $tw     = WWW::Twilio::TwiML->new();
    my $resp   = $tw->Response;

    unless( exists $list{$choice} ) {
        $resp->Say({voice => 'woman'},
                   "Sorry, that's not a valid option.");

        $resp->Redirect({method => 'GET'}, "/menu");

        $self->render(format => 'xml',
                      text   => $tw->to_string);
        return;
    }

Twilio always passes a Digits parameter to URLs it fetches as a result of a Gather action (see Twilio's Gather verb documentation); Digits contains the digit or digits pressed by the caller. HTTP GET (and POST) parameters are available to Mojolicious through the param() method.

The code then checks to see if the option the caller has selected exists. If it doesn't, it generates a TwiML response to tell Twilio to Say to the caller "Sorry, that's not a valid option." then sends a redirect back to the main menu.

It's polite to let the caller know what's going on, so add a status message:

$resp->Say({voice => 'woman'},
           "I'll try connecting you now.");

Remember that all this code does is build the TwiML response object; only when the complete, stringified TwiML object is sent to Twilio's application server does the document have any effect on the application flow.

The next step is to look up the caller's choice in the %list hash. If the caller's selection had a phone number associated with it, create a Dial, Play, or Say TwiML object, depending on whether %list specifies a number to call, a URL to an audio file to fetch and play, or neither (respectively):

    if( $list{$choice}->{number} ) {
        $resp->Dial($list{$choice}->{number});
    }

    elsif( $list{$choice}->{recording} ) {
        $resp->Play($list{$choice}->{recording});
    }

    else {
        $resp->Say({voice => 'woman'},
                   "Sorry, that option isn't working.");
        $resp->Redirect({method => 'GET'}, "/menu");
    }

Finally, invoke Mojolicious's render() method to create an XML Content-type header and send the stringified TwiML object to stdout (which Twilio's application server will read):

    $self->render(format => 'xml',
                  text   => $tw->to_string);
};

Here is the full source for your enjoyment. If you've already set Twilio's Voice URL in uour Sandbox, nothing further needs to be done on Twilio's side. Just upload the new application to the web server and start it (Mojolicious supports a variety of deployment options including Morbo, CGI, FastCGI, or Plack).

You've made a simple phone menu here, but you've only learned a few of Twilio's TwiML verbs. With WWW::Twilio::API, WWW::Twilio::TwiML, and Mojolicious you can also create conference rooms, make voice recordings, send and receive SMS messages, reject calls from unwanted numbers, and do other useful actions in just a few lines of code.

Perl can make your phone ring.

Twilio allows developers to write applications that can make and receive voice calls or SMS messages (though Twilio can do many other interesting telephony things). Twilio’s RESTful API, text-to-speech synthesizer, speech transcription services, and Javascript client make it easy to knock out a conference call application, an in-browser customer service voice application, a weather-by-SMS application, reminder by phone—anything, really—in minutes. This article shows how to make a couple of small applications, one to help you pronounce words correctly and the other to transcribe awkward condiment phone survey answers.

Twilio Setup

First, head over to Twilio.com and click the “Try Twilio Free” link. While inbound calls cost US $0.01 per minute and outbound calls cost US $0.02 per minute, Twilio has historically given new users a generous account balance to start with for free (currently US $30)—it’s plenty of credit to kick the tires and take it for a spin.

Go ahead and register (I’ll wait here). When you’ve finished, you’ll have an account SID (beginning with “AC”) and an auth token, available from your Twilio Dashboard. These are your Twilio API username and password; you’ll need them for any API application you write.

Twilio Basics

The Twilio website is full of well-organized documentation and sample applications. I recommend starting with “How It Works” (one of the main navigation links on the home page). Browse the documentation under “Docs” as well.

H. H. Munroe said, “A little inaccuracy sometimes saves tons of explanation.” Keeping that in mind, inbound calls (calls to a Twilio number) work like this:


Inbound calls to a Twilio number

1) the user calls “555-867-5309” on their phone

2) Twilio accepts the call, then makes an HTTP POST to http://example.com/jenny.xml

3) example.com responds with a “TwiML” document (TwiML is a simple XML language that describes how Twilio will interact with callers):

<?xml version="1.0" encoding="UTF-8" ?>
<Response>
  <Say voice="woman">This is Jenny!</Say>
</Response>

4) Twilio’s TwiML parser and text-to-speech synthesizer read this document and then says to the user (in a voice from the uncanny valley), “This is Jenny!”

When you setup a sandbox number, you tell Twilio to map a URL to that number. Twilio will GET/POST to that URL when receiving calls.

Outbound calls (calls from a Twilio number) work like this:


Outbound calls from a Twilio number

1) An application makes an HTTP POST to Twilio’s “Calls” API with the parameters:

From=+15558675309
To=+19991234567
Url=http://example.com/jenny.xml

2) Twilio places the call and waits for an answer

3) Once the user answers, Twilio retrieves the URL specified in the POST (which should return a TwiML document)

4) Twilio parses the TwiML document and passes it to the text-to-speech synthesizer

5) The synthesizer says to the user “This is Jenny!”

Twilio can also record voice input, transcribe it, send and receive SMS messages, make conference calls, and a few other useful things, all using the same familiar RESTful API and TwiML.

Twilio, meet Perl

CPAN makes writing Twilio applications easy, thanks to WWW::Twilio::API. My (current) favorite way to install CPAN modules comes from the Mojolicious project:

curl -L cpanmin.us | perl - WWW::Twilio::API

cpanmin.us returns a Perl program which handles all of the build dependencies for you. If you’re leery of running code from a website directly on the command line, install App::cpanminus and use its cpanm program instead.

If you’re like me and don’t want to mess up your clean development environment, tell cpanmin.us or cpanm to install things into a temporary location:

$ mkdir ~/perl-test
$ export PERL5LIB=~/perl-test/lib/perl5
$ curl -L cpanmin.us | perl - --local-lib=~/perl-test WWW::Twilio::API
# or
$ cpanm --local-lib=~/perl-test WWW::Twilio::API

See How to install CPAN modules for more information and options.

Getting all the necessary Twilio information

After you install WWW::Twilio::API, but before you make your first call, you need several pieces of information from your Twilio Dashboard:

  • AccountSid: this is a long string begining with “AC”

  • AuthToken: another long hex string, next to the AccountSid; you may have to click a lock icon to reveal it

  • Sandbox number: found on the bottom half of the Dashboard page under “Sandbox App”

That’s all! Now you’re ready to go.

Your first phone call

Fire up your favorite editor:

#!/usr/bin/env perl

use strict;
use warnings;
use WWW::Twilio::API;

my $twilio = WWW::Twilio::API->( AccountSid  => 'ACxxxxxxxxx',
                                 AuthToken   => 'xxxxxxxxxxx',
                                 API_VERSION => '2010-04-01' );

## A hollow voice says 'plugh'
my $response = $twilio->POST( 'Calls',
                              To   => '+15556667777', ## maybe your cell phone
                              From => '+12223334444', ## your Twilio sandbox
                              Url  => 'http://twimlets.com/message?'
                                    . 'Message%5B0%5D=plugh' );

print STDERR $response->{content};

That’s the entire application. Run it, and if all went well, you should see a long XML string returned which resembles:

<?xml version="1.0"?>
<TwilioResponse>
  <Call>
    <To>+15556667777</To>
    <From>+12223334444</From>
    <Status>queued</Status>
    <Direction>outbound-api</Direction>
  </Call>
</TwilioResponse>

… and then your phone should ring. I’ve always wondered how to pronounce “plugh”—now I know.

What could possibly go wrong?

Early on in Twilio development, you’re likely to experience a few little gotchas. For example, you might get this message:

LWP will support https URLs if the LWP::Protocol::https module
is installed.

That’s LWP telling you to install LWP::Protocol::https. (It will also install or update a few other modules, including Net::SSLeay).

If you see XML after running the script, your development environment is probably fine. You might instead see:

<TwilioResponse>
  <RestException>
    <Status>401</Status>
    <Message>Authenticate</Message>
    <Code>20003</Code>
    <MoreInfo>http://www.twilio.com/docs/errors/20003</MoreInfo>
  </RestException>
</TwilioResponse>

Notice the “401”? If you’re familiar with HTTP status codes, you might remember that 401 means “Unauthorized”. You either didn’t present any authorization information, or it was incorrect. In this case, it usually means the AccountSid or AuthToken are incorrect. Log into Twilio.com, go to the Dashboard and make sure your AccountSid and AuthToken are correct.

What else can we do?

Everything depends on what you put in for the Url parameter. You can browse some of the free applications at Twilio Labs, though most of those are for inbound calls.

Here’s a silly example of using the voicemail Twimlet to conduct a brief phone survey and have the callee’s response transcribed and emailed. Start by changing the Url line in the POST:

my $email    = 'you@example.com';  ## your email
my $msg      = 'Please+tell+us+what+you+think+of+Tabasco+sauce';
my $response = $twilio->POST( 'Calls',
                              To   => '+15556667777',
                              From => '+12223334444',
                              Url  => "http://twimlets.com/voicemail?"
                                    . "Email=$email&Message=$msg" );

Note that this Twimlet’s arguments are case-sensitive. ‘Email’ and ‘Message’ are not the same as ‘email’ and ‘message’. Make sure you use the correct case.

Also, be sure to substitute your phone number for the To parameter and your Twilio Sandbox phone number (also found on your Twilio Dashboard) for the From parameter. Twilio phone numbers always use the international calling prefix (e.g., United States numbers use “+1” followed by the three digit area code followed by the seven digit phone number).

When you run this, you’ll get a call from Twilio asking you to share your insights into Tabasco sauce. Please be honest. Once you’ve given your opinion, Twilio will then transcribe your message and email it to the email address you specified.

I’m sending out an SMS

SMS messages are even easier: no TwiML needed. Instead of the Calls API, use the SMS/Messages API:

my $response = $twilio->POST( 'SMS/Messages',
                              To   => '+15556667777',
                              From => '+12223334444',
                              Body => 'Rescue me before '
                                   .  'I fall into despair' );

That’s all you have to do to send an SMS message using Twilio (though the 160 character limit applies).

Conclusion

Twilio and Perl make a potent pair: so much is possible with so little code. The next installment will cover writing larger applications with TwiML.

Visit the home of the Perl programming language: Perl.org

Sponsored by

Monthly Archives

Powered by Movable Type 5.12